
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993-1996 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: TSCROLL.C
*
* Scrolling and selection routines.
*
* Functions:
*
* gtab_msg_vscroll()
* gtab_msg_hscroll()
* gtab_dovscroll()
* gtab_dohscroll()
* gtab_linetorow()
* gtab_rowtoline()
* gtab_select()
* gtab_ytoline()
* gtab_xtocol()
* gtab_isborder()
* gtab_enter()
* gtab_trackcol()
* gtab_press()
* gtab_release()
* gtab_move()
* gtab_dblclick()
* gtab_showsel()
* gtab_showsel_middle()
* gtab_changesel()
* gtab_selhome()
* gtab_key()
*
* Comments:
*
* This implementation currently only supports TM_SINGLE, not TM_MANY
* modes of selection.
*
****************************************************************************/

#include <windows.h>
#include <commdlg.h>

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

/***************************************************************************
 * Function: gtab_msg_vscroll
 *
 * Purpose:
 *
 * Handle a vscroll message 
 */
void
gtab_msg_vscroll(HWND hwnd, lpTable ptab, int opcode, int pos)
{
        long change;

        switch(opcode) {
        case SB_THUMBPOSITION:
                change = (pos * ptab->scrollscale) - ptab->toprow;
                break;

        case SB_LINEUP:
                change = -1;
                break;

        case SB_LINEDOWN:
                change = 1;
                break;

        case SB_PAGEUP:
                change = - (ptab->nlines - 3);
                break;

        case SB_PAGEDOWN:
                change = (ptab->nlines - 3);
                break;

        default:
                return;
        }
        gtab_dovscroll(hwnd, ptab, change);
}

/***************************************************************************
 * Function: gtab_msg_hscroll
 *
 * Purpose:
 *
 * Handle a hscroll message 
 */
void
gtab_msg_hscroll(HWND hwnd, lpTable ptab, int opcode, int pos)
{
        int change;

        switch(opcode) {
        case SB_THUMBPOSITION:
                change = pos - ptab->scroll_dx;
                break;

        case SB_LINEUP:
                change = -(ptab->avewidth);
                break;

        case SB_LINEDOWN:
                change = ptab->avewidth;
                break;

        case SB_PAGEUP:
                change = - (ptab->winwidth * 2 / 3);
                break;

        case SB_PAGEDOWN:
                change = (ptab->winwidth * 2 / 3);
                break;

        default:
                return;
        }
        gtab_dohscroll(hwnd, ptab, change);
}



/***************************************************************************
 * Function: gtab_dovscroll
 *
 * Purpose:
 *
 * Set new vertical scroll pos,
 * adjust linedata array
 * set line win-relative start posns & clip top/bottom posns
 * revise display.
 */
void
gtab_dovscroll(HWND hwnd, lpTable ptab, long change)
{
        int cury, i;
        long ncopy;
        lpCellPos cp;
        LineData ldtemp;
        RECT rc, rcpaint;
        long range;
        long newtop;
        int newpos;


        range = ptab->hdr.nrows - (ptab->nlines - 1);
        newtop = ptab->toprow + change;
        if (range < 0) {
                range = 0;
        }
        if (newtop > range) {
                change = range - ptab->toprow;
        } else if (newtop < 0) {
                change = -(ptab->toprow);
        }
        ptab->toprow += change;

        newpos = (int) (newtop / ptab->scrollscale);
        SetScrollPos(hwnd, SB_VERT, newpos, TRUE);

        if (ptab->hdr.sendscroll) {
                gtab_sendtq(hwnd, TQ_SCROLL, ptab->toprow);
        }

        /* adjust data ptrs rather than invalidate, to retain the
         * data we know is still valid
         */
        if (abs(change) >= ptab->nlines) {
                gtab_invallines(hwnd, ptab, ptab->hdr.fixedrows,
                        ptab->nlines - ptab->hdr.fixedrows);
                InvalidateRect(hwnd, NULL, TRUE);
                change = 0;
        } else if (change < 0) {
                /* copy data down */
                ncopy = (ptab->nlines - ptab->hdr.fixedrows) - abs(change);
                for (i =  ptab->nlines - 1;
                  i >= (ptab->hdr.fixedrows + abs(change)); i--) {
                        ldtemp = ptab->pdata[i - abs(change)];
                        ptab->pdata[i - abs(change)] = ptab->pdata[i];
                        ptab->pdata[i] = ldtemp;
                }
                gtab_invallines(hwnd, ptab,
                        ptab->hdr.fixedrows, (int) abs(change));
        } else if (change > 0) {
                ncopy = (ptab->nlines - ptab->hdr.fixedrows) - change;
                for (i = ptab->hdr.fixedrows;
                  i < (ncopy + ptab->hdr.fixedrows); i++) {
                        ldtemp = ptab->pdata[i + change];
                        ptab->pdata[i + change] = ptab->pdata[i];
                        ptab->pdata[i] = ldtemp;
                }
                gtab_invallines(hwnd, ptab,
                        (int) ncopy + ptab->hdr.fixedrows, (int) change);
        }

        /* scroll window */
        GetClientRect(hwnd, &rc);
        rcpaint = rc;
        if (change > 0) {
                rc.top += (int) (change + ptab->hdr.fixedrows) * ptab->rowheight;
                rcpaint.top = (ptab->hdr.fixedrows * ptab->rowheight);
                rcpaint.top += rc.bottom - rc.top;
        } else if (change < 0) {
                rc.top += (ptab->hdr.fixedrows * ptab->rowheight);
                rc.bottom -= (int) (change * ptab->rowheight);
                rcpaint.bottom -= rc.bottom - rc.top;
        }

        /* loop through each line setting relative posn and clipping */

        /* set up all rows  - the fixed/moveable difference for
         * rows is made at fetch-time during painting, when we remember
         * which absolute row nr to ask for, for a given screen line
         */
        cury = 0;
        for (i = 0; i < ptab->nlines; i++) {
                cp = &ptab->pdata[i].linepos;
                cp->start = cury;
                cp->clipstart = cury;
                cp->clipend = cury + cp->size;
                cury += cp->size;
        }

        /* now move and repaint the window */
        if (change != 0) {
                if (rc.top < rc.bottom) {
                        ScrollWindow(hwnd, 0, (int) -(change * ptab->rowheight),
                                &rc, NULL);

                }

                /* force repaint now, not just post message for later,
                 * since we want to repaint that line before the next
                 * scroll down occurs
                 */
                RedrawWindow(hwnd, &rcpaint, NULL,
                        RDW_ERASE | RDW_INVALIDATE|RDW_UPDATENOW);
        }
}

/***************************************************************************
 * Function: gtab_dohscroll
 *
 * Purpose:
 *
 * Set new horizontal scroll pos,
 * set col win-relative start posns & clip left/right posns
 * revise display.
 */
void
gtab_dohscroll(HWND hwnd, lpTable ptab, long change)
{
        int curx, i;
        int moveable;
        lpCellPos cp;
        int newdx, range;


        /* check that the new scroll pos is still within the valid range */
        range = ptab->rowwidth - ptab->winwidth;
        newdx = ptab->scroll_dx + (int) change;
        if (range < 0) {
                range = 0;
        }
        if (newdx > range) {
                change = range - ptab->scroll_dx;
        } else if (newdx < 0) {
                change = -(ptab->scroll_dx);
        }
        ptab->scroll_dx += (int) change;

        SetScrollPos(hwnd, SB_HORZ, ptab->scroll_dx, TRUE);
        InvalidateRect(hwnd, NULL, TRUE);
        
        /* loop through each col setting relative posn and clipping */
        /* clip off 1 pixel left and right (we added 2 on to size for this) */

        /* first set up fixed columns */
        curx = 0;
        for (i = 0; i < ptab->hdr.fixedcols; i++) {
                cp = &ptab->pcellpos[i];
                cp->start = curx + 1;
                cp->clipstart = cp->start;
                cp->clipend = cp->start + cp->size - 2;
                curx += cp->size;
        }

        /* now moveable columns. remember start of moveable cols */
        moveable = curx;
        curx = - ptab->scroll_dx;       /* rel. pos of col */
        for (i = ptab->hdr.fixedcols; i < ptab->hdr.ncols; i++) {
                cp = &ptab->pcellpos[i];
                cp->start = curx + moveable + 1;
                cp->clipstart = max(moveable+1, cp->start);
                cp->clipend = cp->start + cp->size - 2;
                curx += cp->size;
        }
}

/***************************************************************************
 * Function: gtab_linetorow
 *
 * Purpose:
 *
 * Convert screen line nr to table row nr
 */
long
gtab_linetorow(HWND hwnd, lpTable ptab, int line)
{
        if (line < ptab->hdr.fixedrows) {
                return(line);
        }

        return (line + ptab->toprow);
}

/***************************************************************************
 * Function: gtab_rowtoline
 *
 * Purpose:
 *
 * Convert table row nr to screen line nr or -1 if not on screen
 */
int
gtab_rowtoline(HWND hwnd, lpTable ptab, long row)
{
        if (row < ptab->hdr.fixedrows) {
                return( (int) row);
        }

        row -= ptab->toprow;
        if ((row >= ptab->hdr.fixedrows) && (row < ptab->nlines)) {
                return ( (int) row);
        }
        return(-1);
}

/***************************************************************************
 * Function: gtab_select
 *
 * Purpose:
 *
 * Replace old selection with new. Notify owner if bNotify. Change
 * display to reflect new display.
 */
void
gtab_select(
        HWND hwnd,
        lpTable ptab,
        long row,
        long col,
        long nrows,
        long ncells,
        BOOL bNotify)
{
        int line;

        /* if in ROW mode, force col and ncells to reflect the entire row. */
        if (ptab->hdr.selectmode & TM_ROW) {
                col = 0;
                ncells = ptab->hdr.ncols;
        }

        /* clear existing sel if valid and visible */
        if ((ptab->select.nrows > 0) && (ptab->selvisible == TRUE)) {

                /* only clear sel if it is different from the new one */
                if ((ptab->select.startrow != row) ||
                    (ptab->select.startcell != col) ||
                    (ptab->select.nrows != nrows) ||
                    (ptab->select.ncells != ncells)) {
                        line = gtab_rowtoline(hwnd, ptab,
                                ptab->select.startrow);
                        if (line >= 0) {
                                gtab_invertsel(hwnd, ptab, NULL);
                        }
                        ptab->selvisible = FALSE;
                }
        }

        /* set select fields and send TQ_SELECT */
        if (row < ptab->hdr.nrows) {
                ptab->select.startrow = row;
                ptab->select.startcell = col;
                ptab->select.nrows = nrows;
                ptab->select.ncells = ncells;
        } else {
                ptab->select.nrows = 0;
                ptab->select.startrow = 0;
                ptab->select.startcell = 0;
                ptab->select.ncells = 0;
        }

        if (bNotify) {
                gtab_sendtq(hwnd, TQ_SELECT, (long) (LPSTR) &ptab->select);
        }

        /* paint in selection */
        if (nrows > 0) {
                if (!ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                        ptab->selvisible = TRUE;
                }
        } else {
                if (ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                        ptab->selvisible = FALSE;
                }
                ptab->selvisible = FALSE;
        }
}

/***************************************************************************
 * Function: gtab_ytoline
 *
 * Purpose:
 *
 * Convert window y co-ord to a line nr
 */
int
gtab_ytoline(HWND hwnd, lpTable ptab, int y)
{
        return(y / ptab->rowheight);
}

/***************************************************************************
 * Function: gtab_xtocol
 *
 * Purpose:
 *
 * Convert window x co-ord to a cell nr
 */
int
gtab_xtocol(HWND hwnd, lpTable ptab, int x)
{
        int i;
        lpCellPos ppos;

        for (i = 0; i < ptab->hdr.ncols; i++) {
                ppos = &ptab->pcellpos[i];
                if (ppos->clipstart < ppos->clipend) {
                        if ( (x >= ppos->clipstart) && (x < ppos->clipend)) {
                                return(i);
                        }
                }
        }
        return(-1);
}


/***************************************************************************
 * Function: gtab_isborder
 *
 * Purpose:
 *
 * Check if x co-ord is 'near' (+- 2 pixels) the right border of given cell
 */
BOOL
gtab_isborder(HWND hwnd, lpTable ptab, int x, int col)
{
        
        if (abs(ptab->pcellpos[col].clipend - x) < 2) {
                return(TRUE);
        } else {
                return(FALSE);
        }
}


/***************************************************************************
 * Function: gtab_enter
 *
 * Purpose:
 *
 * Set selection and send 'TQ_ENTER' event to owner
 */
void
gtab_enter(HWND hwnd, lpTable ptab, long row, long col, long nrows,
        long ncells)
{
        int line;

        /* clear existing sel if valid and visible */
        if ((ptab->select.nrows > 0) && (ptab->selvisible == TRUE)) {

                /* only clear sel if it is different from the new one */
                if ((ptab->select.startrow != row) ||
                    (ptab->select.startcell != col) ||
                    (ptab->select.nrows != nrows) ||
                    (ptab->select.ncells != ncells)) {
                        line = gtab_rowtoline(hwnd, ptab,
                                ptab->select.startrow);
                        if (line >= 0) {
                                gtab_invertsel(hwnd, ptab, NULL);
                        }
                        ptab->selvisible = FALSE;
                }
        }

        /* set select fields and send TQ_SELECT */
        if (row < ptab->hdr.nrows) {
                ptab->select.startrow = row;
                ptab->select.startcell = col;
                ptab->select.nrows = nrows;
                ptab->select.ncells = ncells;
        } else {
                ptab->select.nrows = 0;
                ptab->select.startrow = 0;
                ptab->select.startcell = 0;
                ptab->select.ncells = 0;
        }

        /* paint in selection */
        if (nrows > 0) {
                if (!ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                        ptab->selvisible = TRUE;
                }
                /* do this at end because it could cause a layout-change */
                gtab_sendtq(hwnd, TQ_ENTER, (long) (LPSTR) &ptab->select);
        } else {
                if (ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                }
                ptab->selvisible = FALSE;
        }
}


/***************************************************************************
 * Function: gtab_trackcol
 *
 * Purpose:
 *
 * Start re-sizing a column
 */
void
gtab_trackcol(HWND hwnd, lpTable ptab, int col, int x)
{

        /* ensure we see the mouse-up */
        SetCapture(hwnd);
        ptab->trackmode = TRACK_COLUMN;
        ptab->tracknr = col;
        ptab->trackline1 = x;

        /* if line at other side of cell is visible, draw that too */
        if (ptab->pcellpos[col].start >= ptab->pcellpos[col].clipstart) {
                ptab->trackline2 = ptab->pcellpos[col].start;
        } else {
                ptab->trackline2 = -1;
        }
        gtab_drawvertline(hwnd, ptab);
}



/***************************************************************************
 * Function: gtab_press
 *
 * Purpose:
 *
 * Called on mouse-down events. decide what to start tracking.
 */
void
gtab_press(HWND hwnd, lpTable ptab, int x, int y)
{
        int cell;
        long row;

        if (ptab->trackmode != TRACK_NONE) {
                return;
        }

        /* has he grabbed a cell-edge to resize ? */
        cell = gtab_xtocol(hwnd, ptab, x);
        if (cell == -1) {
                return;
        }
        if (gtab_isborder(hwnd, ptab, x, cell)) {
                gtab_trackcol(hwnd, ptab, cell, x);
                return;
        }
        if ( (cell > 0) && gtab_isborder(hwnd, ptab, x, cell-1)) {
                gtab_trackcol(hwnd, ptab, cell, x);
                return;
        }

        /* find which line he selected */
        row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));

        /* is he selecting a disabled fixed area ? */
        if ( (row < ptab->hdr.fixedrows) || (cell < ptab->hdr.fixedcols)) {
                if (ptab->hdr.fixedselectable == FALSE) {
                        return;
                }
        }

        /* ok, start cell selection */
        ptab->trackmode = TRACK_CELL;
        SetCapture(hwnd);

        /* record and paint new selection */
        if (ptab->hdr.selectmode & TM_ROW) {
                gtab_select(hwnd, ptab, row, 0, 1, ptab->hdr.ncols, FALSE);
        } else {
                gtab_select(hwnd, ptab, row, cell, 1, 1, FALSE);
        }
        return;
}

/***************************************************************************
 * Function: gtab_release
 *
 * Purpose:
 *
 * Called on mouse-up. complete any tracking that was happening
 */
void
gtab_release(HWND hwnd, lpTable ptab, int x, int y)
{
        lpCellPos ppos;
        lpProps pprop;
        long row;
        int cx;

        switch(ptab->trackmode) {

        case TRACK_NONE:
                return;
        
        case TRACK_COLUMN:
                /* erase marker lines */
                gtab_drawvertline(hwnd, ptab);
                ReleaseCapture();
                ptab->trackmode = TRACK_NONE;

                /* adjust cell width */
                ppos = &ptab->pcellpos[ptab->tracknr];
                cx = ptab->trackline1 - ppos->start;
                pprop = &ptab->pcolhdr[ptab->tracknr].props;
                pprop->valid |= P_WIDTH;
                pprop->width = cx;
                gtab_calcwidths(hwnd, ptab);
                gtab_setsize(hwnd, ptab);
                InvalidateRect(hwnd, NULL, TRUE);
                return;

        case TRACK_CELL:
                row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));
                ReleaseCapture();
                ptab->trackmode = TRACK_NONE;

                /* keep the same selection. if the mouse is still
                 * in the box, select it, otherwise de-select it
                 */
                if ((row == ptab->select.startrow) &&
                  ( (ptab->hdr.selectmode & TM_ROW) ||
                    (ptab->select.startcell == gtab_xtocol(hwnd, ptab, x))) ) {

                        gtab_select(hwnd, ptab, ptab->select.startrow,
                                ptab->select.startcell,
                                ptab->select.nrows, ptab->select.ncells, TRUE);
                } else {
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                }
                return;
        }
}


/***************************************************************************
 * Function: gtab_move
 *
 * Purpose:
 *
 * Called on mouse-move. if tracking - adjust position, if not,
 * set correct cursor
 */
void
gtab_move(HWND hwnd, lpTable ptab, int x, int y)
{
        BOOL fOK;
        long row;
        int col;
        lpCellPos ppos;

        switch(ptab->trackmode) {

        case TRACK_NONE:
                col = gtab_xtocol(hwnd, ptab, x);
                if (col == -1) {
                        SetCursor(hNormCurs);
                        return;
                }
                if (gtab_isborder(hwnd, ptab, x, col)) {
                        SetCursor(hVertCurs);
                        return;
                }
                if ( (col > 0) && gtab_isborder(hwnd, ptab, x, col-1)) {
                        SetCursor(hVertCurs);
                        return;
                }
                SetCursor(hNormCurs);
                return;

        case TRACK_CELL:
                row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));

                /* keep the same selection. if the mouse is still
                 * in the box, select it, otherwise de-select it
                 */
                if ((row == ptab->select.startrow) &&
                  ( (ptab->hdr.selectmode & TM_ROW) ||
                    (ptab->select.startcell == gtab_xtocol(hwnd, ptab, x))) ) {

                        if (!ptab->selvisible) {
                                gtab_invertsel(hwnd, ptab, NULL);
                                ptab->selvisible = TRUE;
                        }
                } else {
                        if (ptab->selvisible) {
                                gtab_invertsel(hwnd, ptab, NULL);
                                ptab->selvisible = FALSE;
                        }
                }
                return;
        
        case TRACK_COLUMN:
                /* check that new x is still visible/valid */
                ppos = &ptab->pcellpos[ptab->tracknr];
                fOK = FALSE;

                if (ptab->tracknr < ptab->hdr.fixedcols)  {
                        if ((x > ppos->start) && (x < ptab->winwidth)) {
                                fOK = TRUE;
                        }
                } else {
                        if ((x > ppos->clipstart) && (x < ptab->winwidth)) {
                                fOK = TRUE;
                        }
                }
                if (fOK == TRUE) {
                        gtab_drawvertline(hwnd, ptab);
                        ptab->trackline1 = x;
                        gtab_drawvertline(hwnd, ptab);
                }
                return;
        }
}

/***************************************************************************
 * Function: gtab_dblclick
 *
 * Purpose:
 *
 * dbl-click - send an TQ_ENTER event to the owner (if valid) 
 */
void
gtab_dblclick(HWND hwnd, lpTable ptab, int x, int y)
{
        int cell, line;
        long row;

        line = gtab_ytoline(hwnd, ptab, y);
        cell = gtab_xtocol(hwnd, ptab, x);
        if ( (line < ptab->hdr.fixedrows) || (cell < ptab->hdr.fixedcols) ) {
                if (!ptab->hdr.fixedselectable) {
                        return;
                }
        }
        row = gtab_linetorow(hwnd, ptab, line);

        if (ptab->hdr.selectmode & TM_ROW) {
                gtab_enter(hwnd, ptab, row, 0, 1, ptab->hdr.ncols);
        } else {
                gtab_enter(hwnd, ptab, row, cell, 1, 1);
        }
}

/***************************************************************************
 * Function: gtab_showsel
 *
 * Purpose:
 *
 * Move selection area to visible part of window. Argument bToBottom
 * indicates whether to move the line onto the bottom or the top of the
 * window if not visible - this affects the smoothness of scrolling
 * line-by-line.
 */
void
gtab_showsel(HWND hwnd, lpTable ptab, BOOL bToBottom)
{
        int line;
        long change;

        line = gtab_rowtoline(hwnd, ptab, ptab->select.startrow);

        /* move up if last line or not at all visible */
        if ( (line < 0) || line == (ptab->nlines - 1)) {
                change = ptab->select.startrow - ptab->toprow;
                if (bToBottom) {
                        /* change to bottom of window. subtract 2 not 1
                         * since nlines includes one line that is only
                         * partly visible
                         */
                        change -= (ptab->nlines - 2);
                }
                change -= ptab->hdr.fixedrows;
                gtab_dovscroll(hwnd, ptab, change);
        }
        /* add support for TM_CELL here! */
}

/***************************************************************************
 * Function: gtab_showsel_middle
 *
 * Purpose:
 *
 * Scroll the window so that if possible, the selected row is in the
 * middle 60% of the screen so that context around it is visible.
 */
void
gtab_showsel_middle(HWND hwnd, lpTable ptab)
{
        int line;
        long change;
        int mid_top, mid_end;

        line = gtab_rowtoline(hwnd, ptab, ptab->select.startrow);


        /* is this within the middle 60 % ?  */
        mid_top = ptab->nlines * 20 / 100;
        mid_end = ptab->nlines * 80 / 100;
        if ((line < mid_top) || (line > mid_end)) {

                /* no - scroll so that selected line is at
                 * the 20% mark
                 */
                change = (ptab->select.startrow - mid_top) - ptab->toprow;              
                change -= ptab->hdr.fixedrows;
                gtab_dovscroll(hwnd, ptab, change);
        }
        /* again - need code here for TM_CELL mode to ensure that
         * active cell is horizontally scrolled correctly
         */
}



/***************************************************************************
 * Function: gtab_changesel
 *
 * Purpose:
 *
 * Move the selection a specified nr of rows or cells
 * if no selection, select first visible unit
 */
VOID
gtab_changesel(HWND hwnd, lpTable ptab, long rowincr, int cellincr, BOOL bToBottom)
{
        long row, col, nrows, ncols;

        /* is there a selection ? */
        if (ptab->select.nrows < 1) {

                /* no selection - force a selection
                 * at the first visible unit
                 */
                if (ptab->hdr.fixedselectable) {
                        row = 0;
                        col = 0;
                } else {
                        row = gtab_linetorow(hwnd, ptab, ptab->hdr.fixedrows);
                        /* should really check for first visible cell */
                        col = ptab->hdr.fixedcols;
                }
                nrows = 1;
                ncols = 1;
                if (ptab->hdr.selectmode & TM_ROW) {
                        col = 0;
                        ncols = ptab->hdr.ncols;
                }
        } else {
                row = ptab->select.startrow + rowincr;
                col = ptab->select.startcell + cellincr;
                while (col >= ptab->hdr.ncols) {
                        col -= ptab->hdr.ncols;
                        row++;
                }
                while (col < 0) {
                        col += ptab->hdr.ncols;
                        row--;
                }
                if (row < 0) {
                        row = 0;
                }
                if (row >= ptab->hdr.nrows) {
                        row = ptab->hdr.nrows-1;
                }
                /* check we haven't moved into non-selectable region */
                if ((row < ptab->hdr.fixedrows) &&
                        (!ptab->hdr.fixedselectable)) {
                                row = ptab->hdr.fixedrows;
                }
                nrows = ptab->select.nrows;
                ncols = ptab->select.ncells;
        }
        gtab_select(hwnd, ptab, row, col, nrows, ncols, TRUE);
        /* ensure selection visible */
        gtab_showsel(hwnd, ptab, bToBottom);
}

/***************************************************************************
 * Function: gtab_selhome
 *
 * Purpose:
 *
 * Set the topmost selectable unit in window as the selection 
 */
void
gtab_selhome(HWND hwnd, lpTable ptab)
{
        long row;

        if (ptab->hdr.fixedselectable) {
                row = gtab_linetorow(hwnd, ptab, 0);
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row, 0, 1, 1, TRUE);
                }
        } else {
                row = gtab_linetorow(hwnd, ptab, ptab->hdr.fixedrows);
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row,
                                ptab->hdr.fixedcols, 1, 1, TRUE);
                }
        }
}


/***************************************************************************
 * Function: gtab_key
 *
 * Purpose:
 *
 * Handle key-down events - scroll windows and/or move selection 
 */
int
gtab_key(HWND hwnd, lpTable ptab, int vkey)
{
        long row;
        BOOL bControl = FALSE;

        if (GetKeyState(VK_CONTROL) & 0x8000) {
                bControl = TRUE;
        }

        switch(vkey) {

        case VK_UP:
                if (bControl) {
                        /* control-uparrow scrolls window without selection.
                         * the selection is de-selected (to avoid surprises
                         * moving back to it).
                         */
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                        gtab_dovscroll(hwnd, ptab, -1);
                } else {
                        /* uparrow moves selection up one line */
                        gtab_changesel(hwnd, ptab, -1, 0, FALSE);
                }
                return(0);

        case VK_DOWN:
                if (bControl) {
                        /* control downarrow scrolls window without
                         * a selection.
                         */
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                        gtab_dovscroll(hwnd, ptab, 1);
                } else {
                        /* the normal gtab_changesel behaviour is
                         * that if the selected line is not visible now,
                         * we scroll it to the top of the window. This is fine
                         * in most cases but causes unacceptable jumps when
                         * repeatedly scrolling down with the down key.
                         *
                         * Thus we now have an argument to changesel to say
                         * that in this case, if you need to move the line onto
                         * the window, move it to the bottom and not the top
                         */
                        gtab_changesel(hwnd, ptab, 1, 0, TRUE);
                }
                return(0);

        case VK_LEFT:
                /* if cell-selection mode, move left one cell.
                 * otherwise the whole row is selected - scroll
                 * the line left a little
                 */

                if (ptab->hdr.selectmode & TM_ROW) {
                        if (bControl) {
                                /* ctrl-left moves to start of line */
                                gtab_dohscroll(hwnd, ptab, -(ptab->scroll_dx));
                        } else {
                                gtab_dohscroll(hwnd, ptab, -(ptab->avewidth));
                        }
                } else {
                        gtab_changesel(hwnd, ptab, 0, -1, FALSE);
                }
                return(0);

        case VK_RIGHT:
                /* if cell-selection mode, move right one cell.
                 * otherwise the whole row is selected - scroll
                 * the line right a little
                 */
                if (ptab->hdr.selectmode & TM_ROW) {
                        if (bControl) {
                                /* control-right moves to right end of line */
                                gtab_dohscroll(hwnd, ptab, ptab->rowwidth -
                                                ptab->winwidth);
                        } else {
                                gtab_dohscroll(hwnd, ptab, ptab->avewidth);
                        }
                } else {
                        gtab_changesel(hwnd, ptab, 0, 1, TRUE);
                }
                return(0);

        case VK_HOME:
                if (bControl) {
                        /* control-home == top of file */
                        gtab_dovscroll(hwnd, ptab, -(ptab->toprow));
                }
                /* top of window */
                gtab_selhome(hwnd, ptab);
                gtab_showsel(hwnd, ptab, FALSE);
                
                return(0);

        case VK_END:
                if (bControl) {
                        /* control-end -> end of file */
                        row = ptab->hdr.nrows-1;
                } else {
                        row = gtab_linetorow(hwnd, ptab, ptab->nlines - 1);
                        if (row >= ptab->hdr.nrows) {
                                row = ptab->hdr.nrows-1;
                        }
                }
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row,
                                ptab->hdr.ncols-1, 1, 1, TRUE);
                }
                /* we have selected the bottom line. We don't want to
                 * move it up into the window, since the intended
                 * effect is to select the lowest line. This doesn't
                 * apply to the ctrl-end behaviour (move to bottom of
                 * buffer.
                 */
                if (bControl) {
                        /* move the selection to make it visible - but move it
                         * to the bottom and not to the top of the window
                         */
                        gtab_showsel(hwnd, ptab, TRUE);
                }
                return(0);

        case VK_RETURN:
                if (ptab->select.nrows > 0) {
                        gtab_showsel(hwnd, ptab, FALSE);
                        gtab_enter(hwnd, ptab, ptab->select.startrow,
                                ptab->select.startcell,
                                ptab->select.nrows, ptab->select.ncells);
                }
                return(0);

        case VK_SPACE:
                /* toggle the selection */
                if (ptab->select.nrows < 1) {
                        /* no selection - make one */
                        gtab_changesel(hwnd, ptab, 0, 0, TRUE);
                } else {
                        /* there is a selection - deselect it */
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                }
                return(0);

        case VK_PRIOR:          /* page up */

                gtab_dovscroll(hwnd, ptab, -(ptab->nlines - 3));
                gtab_selhome(hwnd, ptab);
                return(0);

        case VK_NEXT:           /* page down */

                /* scroll down one page */
                gtab_dovscroll(hwnd, ptab, (ptab->nlines - 3));

                /* select new bottom line */
                row = gtab_linetorow(hwnd, ptab, ptab->nlines - 1);
                if (row >= ptab->hdr.nrows) {
                        row = ptab->hdr.nrows-1;
                }
                /* select bottom line, but don't call showsel
                 * since we don't want to adjust it's position - we
                 * want it to remain at the bottom of the window
                 */
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row,
                                ptab->hdr.ncols-1, 1, 1, TRUE);
                }

                return(0);

        default:
                return(1);
        }
}

