/*
 * Pager control
 *
 * Copyright 1998, 1999 Eric Kohl
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * NOTES
 *    Tested primarily with the controlspy Pager application.
 *       Susan Farley (susan@codeweavers.com)
 *
 * TODO:
 *    Implement repetitive button press.
 *    Adjust arrow size relative to size of button.
 *    Allow border size changes.
 *    Implement drag and drop style.
 */

#include <string.h>
#include "winbase.h"
#include "commctrl.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(pager);

typedef struct
{
    HWND   hwndChild;  /* handle of the contained wnd */
    BOOL   bNoResize;  /* set when created with CCS_NORESIZE */
    COLORREF clrBk;    /* background color */
    INT    nBorder;    /* border size for the control */
    INT    nButtonSize;/* size of the pager btns */
    INT    nPos;       /* scroll position */
    INT    nWidth;     /* from child wnd's response to PGN_CALCSIZE */
    INT    nHeight;    /* from child wnd's response to PGN_CALCSIZE */
    BOOL   bForward;   /* forward WM_MOUSEMOVE msgs to the contained wnd */
    BOOL   bCapture;   /* we have captured the mouse  */
    INT    TLbtnState; /* state of top or left btn */
    INT    BRbtnState; /* state of bottom or right btn */
    INT    direction;  /* direction of the scroll, (e.g. PGF_SCROLLUP) */
} PAGER_INFO;

#define PAGER_GetInfoPtr(hwnd) ((PAGER_INFO *)GetWindowLongA(hwnd, 0))
#define PAGER_IsHorizontal(hwnd) ((GetWindowLongA (hwnd, GWL_STYLE) & PGS_HORZ))

#define MIN_ARROW_WIDTH  8
#define MIN_ARROW_HEIGHT 5

#define TIMERID1         1
#define TIMERID2         2
#define INITIAL_DELAY    500
#define REPEAT_DELAY     50

/* the horizontal arrows are:
 *
 * 01234    01234
 * 1  *      *
 * 2 **      **
 * 3***      ***
 * 4***      ***
 * 5 **      **
 * 6  *      *
 * 7
 *
 */
static void
PAGER_DrawHorzArrow (HDC hdc, RECT r, INT colorRef, BOOL left)
{
    INT x, y, w, h;
    HPEN hPen, hOldPen;

    w = r.right - r.left + 1;
    h = r.bottom - r.top + 1;
    if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
        return;  /* refuse to draw partial arrow */

    if (!(hPen = CreatePen( PS_SOLID, 1, GetSysColor( colorRef )))) return;
    hOldPen = SelectObject ( hdc, hPen );
    if (left)
    {
        x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 3;
        y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x--, y+5); y++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x--, y+3); y++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x, y+1);
    }
    else
    {
        x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
        y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x++, y+5); y++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x++, y+3); y++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x, y+1);
    }

    SelectObject( hdc, hOldPen );
    DeleteObject( hPen );
}

/* the vertical arrows are:
 *
 * 01234567    01234567
 * 1******        **
 * 2 ****        ****
 * 3  **        ******
 * 4
 *
 */
static void
PAGER_DrawVertArrow (HDC hdc, RECT r, INT colorRef, BOOL up)
{
    INT x, y, w, h;
    HPEN hPen, hOldPen;

    w = r.right - r.left + 1;
    h = r.bottom - r.top + 1;
    if ((h < MIN_ARROW_WIDTH) || (w < MIN_ARROW_HEIGHT))
        return;  /* refuse to draw partial arrow */

    if (!(hPen = CreatePen( PS_SOLID, 1, GetSysColor( colorRef )))) return;
    hOldPen = SelectObject ( hdc, hPen );
    if (up)
    {
        x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
        y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 3;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x+5, y--); x++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x+3, y--); x++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x+1, y);
    }
    else
    {
        x = r.left + ((w - MIN_ARROW_HEIGHT) / 2) + 1;
        y = r.top + ((h - MIN_ARROW_WIDTH) / 2) + 1;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x+5, y++); x++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x+3, y++); x++;
        MoveToEx (hdc, x, y, NULL);
        LineTo (hdc, x+1, y);
    }

    SelectObject( hdc, hOldPen );
    DeleteObject( hPen );
}

static void
PAGER_DrawButton(HDC hdc, COLORREF clrBk, RECT arrowRect,
                 BOOL horz, BOOL topLeft, INT btnState)
{
    HBRUSH   hBrush, hOldBrush;
    RECT     rc = arrowRect;

    if (!btnState) /* PGF_INVISIBLE */
        return;

    if ((rc.right - rc.left <= 0) || (rc.bottom - rc.top <= 0))
        return;

    hBrush = CreateSolidBrush(clrBk);
    hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);

    FillRect(hdc, &rc, hBrush);

    if (btnState == PGF_HOT)
    {
       DrawEdge( hdc, &rc, BDR_RAISEDINNER, BF_RECT);
       if (horz)
           PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
       else
           PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
    }
    else if (btnState == PGF_NORMAL)
    {
       DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
       if (horz)
           PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
       else
           PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
    }
    else if (btnState == PGF_DEPRESSED)
    {
       DrawEdge( hdc, &rc, BDR_SUNKENOUTER, BF_RECT);
       if (horz)
           PAGER_DrawHorzArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
       else
           PAGER_DrawVertArrow(hdc, rc, COLOR_WINDOWFRAME, topLeft);
    }
    else if (btnState == PGF_GRAYED)
    {
       DrawEdge (hdc, &rc, BDR_OUTER, BF_FLAT);
       if (horz)
       {
           PAGER_DrawHorzArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
           rc.left++, rc.top++; rc.right++, rc.bottom++;
           PAGER_DrawHorzArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
       }
       else
       {
           PAGER_DrawVertArrow(hdc, rc, COLOR_3DHIGHLIGHT, topLeft);
           rc.left++, rc.top++; rc.right++, rc.bottom++;
           PAGER_DrawVertArrow(hdc, rc, COLOR_3DSHADOW, topLeft);
       }
    }

    SelectObject( hdc, hOldBrush );
    DeleteObject(hBrush);
}

static void PAGER_CaptureandTrack(PAGER_INFO *infoPtr, HWND hwnd)
{
    TRACKMOUSEEVENT trackinfo;

    TRACE("[%08x] SetCapture\n", hwnd);
    SetCapture(hwnd);
    infoPtr->bCapture = TRUE;

    trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
    trackinfo.dwFlags = TME_QUERY;
    trackinfo.hwndTrack = hwnd;
    trackinfo.dwHoverTime = HOVER_DEFAULT;

    /* call _TrackMouseEvent to see if we are currently tracking for this hwnd */
    _TrackMouseEvent(&trackinfo);

    /* Make sure tracking is enabled so we receive a WM_MOUSELEAVE message */
    if(!(trackinfo.dwFlags & TME_LEAVE)) {
	trackinfo.dwFlags = TME_LEAVE; /* notify upon leaving */

	/* call TRACKMOUSEEVENT so we receive a WM_MOUSELEAVE message */
	/* and can properly deactivate the hot button */
	_TrackMouseEvent(&trackinfo);
    }
}


/* << PAGER_GetDropTarget >> */

static inline LRESULT
PAGER_ForwardMouse (HWND hwnd, WPARAM wParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    TRACE("[%08x]\n", hwnd);

    infoPtr->bForward = (BOOL)wParam;

    return 0;
}

static inline LRESULT
PAGER_GetButtonState (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    LRESULT btnState = PGF_INVISIBLE;
    INT btn = (INT)lParam;
    TRACE("[%08x]\n", hwnd);

    if (btn == PGB_TOPORLEFT)
        btnState = infoPtr->TLbtnState;
    else if (btn == PGB_BOTTOMORRIGHT)
        btnState = infoPtr->BRbtnState;

    return btnState;
}


static inline LRESULT
PAGER_GetPos(HWND hwnd)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    TRACE("[%08x] returns %d\n", hwnd, infoPtr->nPos);
    return (LRESULT)infoPtr->nPos;
}

static inline LRESULT
PAGER_GetButtonSize(HWND hwnd)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    TRACE("[%08x] returns %d\n", hwnd, infoPtr->nButtonSize);
    return (LRESULT)infoPtr->nButtonSize;
}

static inline LRESULT
PAGER_GetBorder(HWND hwnd)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    TRACE("[%08x] returns %d\n", hwnd, infoPtr->nBorder);
    return (LRESULT)infoPtr->nBorder;
}

static inline LRESULT
PAGER_GetBkColor(HWND hwnd)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    TRACE("[%08x] returns %06lx\n", hwnd, infoPtr->clrBk);
    return (LRESULT)infoPtr->clrBk;
}

static void
PAGER_CalcSize (HWND hwnd, INT* size, BOOL getWidth)
{
    NMPGCALCSIZE nmpgcs;
    ZeroMemory (&nmpgcs, sizeof (NMPGCALCSIZE));
    nmpgcs.hdr.hwndFrom = hwnd;
    nmpgcs.hdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
    nmpgcs.hdr.code = PGN_CALCSIZE;
    nmpgcs.dwFlag = getWidth ? PGF_CALCWIDTH : PGF_CALCHEIGHT;
    nmpgcs.iWidth = getWidth ? *size : 0;
    nmpgcs.iHeight = getWidth ? 0 : *size;
    SendMessageA (GetParent (hwnd), WM_NOTIFY,
                  (WPARAM)nmpgcs.hdr.idFrom, (LPARAM)&nmpgcs);

    *size = getWidth ? nmpgcs.iWidth : nmpgcs.iHeight;

    TRACE("[%08x] PGN_CALCSIZE returns %s=%d\n", hwnd,
                  getWidth ? "width" : "height", *size);
}

static void
PAGER_PositionChildWnd(HWND hwnd, PAGER_INFO* infoPtr)
{
    if (infoPtr->hwndChild)
    {
        RECT rcClient;
        int nPos = infoPtr->nPos;

        /* compensate for a grayed btn, which will soon become invisible */
        if (infoPtr->TLbtnState == PGF_GRAYED)
            nPos += infoPtr->nButtonSize;

        GetClientRect(hwnd, &rcClient);

        if (PAGER_IsHorizontal(hwnd))
        {
            int wndSize = max(0, rcClient.right - rcClient.left);
            if (infoPtr->nWidth < wndSize)
                infoPtr->nWidth = wndSize;

            TRACE("[%08x] SWP %dx%d at (%d,%d)\n", hwnd,
                         infoPtr->nWidth, infoPtr->nHeight,
                         -nPos, 0);
            SetWindowPos(infoPtr->hwndChild, 0,
                         -nPos, 0,
                         infoPtr->nWidth, infoPtr->nHeight,
                         SWP_NOZORDER);
        }
        else
        {
            int wndSize = max(0, rcClient.bottom - rcClient.top);
            if (infoPtr->nHeight < wndSize)
                infoPtr->nHeight = wndSize;

            TRACE("[%08x] SWP %dx%d at (%d,%d)\n", hwnd,
                         infoPtr->nWidth, infoPtr->nHeight,
                         0, -nPos);
            SetWindowPos(infoPtr->hwndChild, 0,
                         0, -nPos,
                         infoPtr->nWidth, infoPtr->nHeight,
                         SWP_NOZORDER);
        }

        InvalidateRect(infoPtr->hwndChild, NULL, TRUE);
    }
}

static INT
PAGER_GetScrollRange(HWND hwnd, PAGER_INFO* infoPtr)
{
    INT scrollRange = 0;

    if (infoPtr->hwndChild)
    {
        INT wndSize, childSize;
        RECT wndRect;
        GetWindowRect(hwnd, &wndRect);

        if (PAGER_IsHorizontal(hwnd))
        {
            wndSize = wndRect.right - wndRect.left;
            PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);
            childSize = infoPtr->nWidth;
        }
        else
        {
            wndSize = wndRect.bottom - wndRect.top;
            PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);
            childSize = infoPtr->nHeight;
        }

        TRACE("childSize = %d,  wndSize = %d\n", childSize, wndSize);
        if (childSize > wndSize)
            scrollRange = childSize - wndSize + infoPtr->nButtonSize;
    }

    TRACE("[%08x] returns %d\n", hwnd, scrollRange);
    return scrollRange;
}

static void
PAGER_GrayAndRestoreBtns(PAGER_INFO* infoPtr, INT scrollRange,
                         BOOL* needsResize, BOOL* needsRepaint)
{
    if (infoPtr->nPos > 0)
    {
        *needsResize |= !infoPtr->TLbtnState; /* PGF_INVISIBLE */
        if (infoPtr->TLbtnState != PGF_DEPRESSED)
            infoPtr->TLbtnState = PGF_NORMAL;
    }
    else
    {
        *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
        infoPtr->TLbtnState = PGF_GRAYED;
    }

    if (scrollRange <= 0)
    {
        *needsRepaint |= (infoPtr->TLbtnState != PGF_GRAYED);
        infoPtr->TLbtnState = PGF_GRAYED;
        *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
        infoPtr->BRbtnState = PGF_GRAYED;
    }
    else if (infoPtr->nPos < scrollRange)
    {
        *needsResize |= !infoPtr->BRbtnState; /* PGF_INVISIBLE */
        if (infoPtr->BRbtnState != PGF_DEPRESSED)
            infoPtr->BRbtnState = PGF_NORMAL;
    }
    else
    {
        *needsRepaint |= (infoPtr->BRbtnState != PGF_GRAYED);
        infoPtr->BRbtnState = PGF_GRAYED;
    }
}


static void
PAGER_NormalizeBtns(PAGER_INFO* infoPtr, BOOL* needsRepaint)
{
    if (infoPtr->TLbtnState & (PGF_HOT | PGF_DEPRESSED))
    {
        infoPtr->TLbtnState = PGF_NORMAL;
        *needsRepaint = TRUE;
    }

    if (infoPtr->BRbtnState & (PGF_HOT | PGF_DEPRESSED))
    {
        infoPtr->BRbtnState = PGF_NORMAL;
        *needsRepaint = TRUE;
    }
}

static void
PAGER_HideGrayBtns(PAGER_INFO* infoPtr, BOOL* needsResize)
{
    if (infoPtr->TLbtnState == PGF_GRAYED)
    {
        infoPtr->TLbtnState = PGF_INVISIBLE;
        *needsResize = TRUE;
    }

    if (infoPtr->BRbtnState == PGF_GRAYED)
    {
        infoPtr->BRbtnState = PGF_INVISIBLE;
        *needsResize = TRUE;
    }
}

static void
PAGER_UpdateBtns(HWND hwnd, PAGER_INFO *infoPtr,
                 INT scrollRange, BOOL hideGrayBtns)
{
    BOOL resizeClient = FALSE;
    BOOL repaintBtns = FALSE;

    if (scrollRange < 0)
        PAGER_NormalizeBtns(infoPtr, &repaintBtns);
    else
        PAGER_GrayAndRestoreBtns(infoPtr, scrollRange, &resizeClient, &repaintBtns);

    if (hideGrayBtns)
        PAGER_HideGrayBtns(infoPtr, &resizeClient);

    if (resizeClient) /* initiate NCCalcSize to resize client wnd */ {
        SetWindowPos(hwnd, 0,0,0,0,0,
                     SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
                     SWP_NOZORDER | SWP_NOACTIVATE);
    }

    if (repaintBtns)
        SendMessageA(hwnd, WM_NCPAINT, 0, 0);
}

static LRESULT
PAGER_SetPos(HWND hwnd, INT newPos, BOOL fromBtnPress)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);
    INT oldPos = infoPtr->nPos;

    if ((scrollRange <= 0) || (newPos < 0))
        infoPtr->nPos = 0;
    else if (newPos > scrollRange)
        infoPtr->nPos = scrollRange;
    else
        infoPtr->nPos = newPos;

    TRACE("[%08x] pos=%d, oldpos=%d\n", hwnd, infoPtr->nPos, oldPos);

    if (infoPtr->nPos != oldPos)
    {
        /* gray and restore btns, and if from WM_SETPOS, hide the gray btns */
        PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, !fromBtnPress);
        PAGER_PositionChildWnd(hwnd, infoPtr);
    }

    return 0;
}

static LRESULT
PAGER_HandleWindowPosChanging(HWND hwnd, WPARAM wParam, WINDOWPOS *winpos)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);

    if (infoPtr->bNoResize && !(winpos->flags & SWP_NOSIZE))
    {
        /* don't let the app resize the nonscrollable dimension of a control
         * that was created with CCS_NORESIZE style
         * (i.e. height for a horizontal pager, or width for a vertical one) */

	/* except if the current dimension is 0 and app is setting for
	 * first time, then save amount as dimension. - GA 8/01 */

        if (PAGER_IsHorizontal(hwnd))
	    if (!infoPtr->nHeight && winpos->cy)
		infoPtr->nHeight = winpos->cy;
	    else
		winpos->cy = infoPtr->nHeight;
        else
	    if (!infoPtr->nWidth && winpos->cx)
		infoPtr->nWidth = winpos->cx;
	    else
		winpos->cx = infoPtr->nWidth;
	return 0;
    }

    DefWindowProcA (hwnd, WM_WINDOWPOSCHANGING, wParam, (LPARAM)winpos);

    return 1;
}

static INT
PAGER_SetFixedWidth(HWND hwnd, PAGER_INFO* infoPtr)
{
  /* Must set the non-scrollable dimension to be less than the full height/width
   * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
   * size, and experimentation shows that affect is almost right. */

    RECT wndRect;
    INT delta, h;
    GetWindowRect(hwnd, &wndRect);

    /* see what the app says for btn width */
    PAGER_CalcSize(hwnd, &infoPtr->nWidth, TRUE);

    if (infoPtr->bNoResize)
    {
        delta = wndRect.right - wndRect.left - infoPtr->nWidth;
        if (delta > infoPtr->nButtonSize)
            infoPtr->nWidth += 4 * infoPtr->nButtonSize / 3;
        else if (delta > 0)
            infoPtr->nWidth +=  infoPtr->nButtonSize / 3;
    }

    h = wndRect.bottom - wndRect.top + infoPtr->nButtonSize;

    TRACE("[%08x] infoPtr->nWidth set to %d\n",
	       hwnd, infoPtr->nWidth);

    return h;
}

static INT
PAGER_SetFixedHeight(HWND hwnd, PAGER_INFO* infoPtr)
{
  /* Must set the non-scrollable dimension to be less than the full height/width
   * so that NCCalcSize is called.  The Msoft docs mention 3/4 factor for button
   * size, and experimentation shows that affect is almost right. */

    RECT wndRect;
    INT delta, w;
    GetWindowRect(hwnd, &wndRect);

    /* see what the app says for btn height */
    PAGER_CalcSize(hwnd, &infoPtr->nHeight, FALSE);

    if (infoPtr->bNoResize)
    {
        delta = wndRect.bottom - wndRect.top - infoPtr->nHeight;
        if (delta > infoPtr->nButtonSize)
            infoPtr->nHeight += infoPtr->nButtonSize;
        else if (delta > 0)
            infoPtr->nHeight +=  infoPtr->nButtonSize / 3;
    }

    w = wndRect.right - wndRect.left + infoPtr->nButtonSize;

    TRACE("[%08x] infoPtr->nHeight set to %d\n",
	       hwnd, infoPtr->nHeight);

    return w;
}

/******************************************************************
 * For the PGM_RECALCSIZE message (but not the other uses in      *
 * this module), the native control does only the following:      *
 *                                                                *
 *    if (some condition)                                         *
 *          PostMessageA(hwnd, EM_FMTLINES, 0, 0);                *
 *    return DefWindowProcA(hwnd, PGM_RECALCSIZE, 0, 0);          *
 *                                                                *
 * When we figure out what the "some condition" is we will        *
 * implement that for the message processing.                     *
 ******************************************************************/

static LRESULT
PAGER_RecalcSize(HWND hwnd)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);

    TRACE("[%08x]\n", hwnd);

    if (infoPtr->hwndChild)
    {
        INT scrollRange = PAGER_GetScrollRange(hwnd, infoPtr);

        if (scrollRange <= 0)
        {
            infoPtr->nPos = -1;
            PAGER_SetPos(hwnd, 0, FALSE);
        }
        else
        {
            PAGER_UpdateBtns(hwnd, infoPtr, scrollRange, TRUE);
            PAGER_PositionChildWnd(hwnd, infoPtr);
        }
    }

    return 1;
}


static LRESULT
PAGER_SetBkColor (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    COLORREF clrTemp = infoPtr->clrBk;

    infoPtr->clrBk = (COLORREF)lParam;
    TRACE("[%08x] %06lx\n", hwnd, infoPtr->clrBk);

    /* the native control seems to do things this way */
    SetWindowPos(hwnd, 0,0,0,0,0,
		 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
		 SWP_NOZORDER | SWP_NOACTIVATE);

    RedrawWindow(hwnd, 0, 0, RDW_ERASE | RDW_INVALIDATE);

    return (LRESULT)clrTemp;
}


static LRESULT
PAGER_SetBorder (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    INT nTemp = infoPtr->nBorder;

    infoPtr->nBorder = (INT)lParam;
    TRACE("[%08x] %d\n", hwnd, infoPtr->nBorder);

    PAGER_RecalcSize(hwnd);

    return (LRESULT)nTemp;
}


static LRESULT
PAGER_SetButtonSize (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    INT nTemp = infoPtr->nButtonSize;

    infoPtr->nButtonSize = (INT)lParam;
    TRACE("[%08x] %d\n", hwnd, infoPtr->nButtonSize);

    PAGER_RecalcSize(hwnd);

    return (LRESULT)nTemp;
}


static LRESULT
PAGER_SetChild (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    INT hw;

    infoPtr->hwndChild = IsWindow ((HWND)lParam) ? (HWND)lParam : 0;

    if (infoPtr->hwndChild)
    {
        TRACE("[%08x] hwndChild=%08x\n", hwnd, infoPtr->hwndChild);

        if (PAGER_IsHorizontal(hwnd)) {
            hw = PAGER_SetFixedHeight(hwnd, infoPtr);
	    /* adjust non-scrollable dimension to fit the child */
	    SetWindowPos(hwnd, 0, 0,0, hw, infoPtr->nHeight,
			 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER |
			 SWP_NOSIZE | SWP_NOACTIVATE);
	}
        else {
            hw = PAGER_SetFixedWidth(hwnd, infoPtr);
	    /* adjust non-scrollable dimension to fit the child */
	    SetWindowPos(hwnd, 0, 0,0, infoPtr->nWidth, hw,
			 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOZORDER |
			 SWP_NOSIZE | SWP_NOACTIVATE);
	}

        /* position child within the page scroller */
        SetWindowPos(infoPtr->hwndChild, HWND_TOP,
                     0,0,0,0,
                     SWP_SHOWWINDOW | SWP_NOSIZE);  /* native is 0 */

        infoPtr->nPos = -1;
        PAGER_SetPos(hwnd, 0, FALSE);
    }

    return 0;
}

static void
PAGER_Scroll(HWND hwnd, INT dir)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    NMPGSCROLL nmpgScroll;
    RECT rcWnd;

    if (infoPtr->hwndChild)
    {
        ZeroMemory (&nmpgScroll, sizeof (NMPGSCROLL));
        nmpgScroll.hdr.hwndFrom = hwnd;
        nmpgScroll.hdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
        nmpgScroll.hdr.code = PGN_SCROLL;

        GetWindowRect(hwnd, &rcWnd);
        GetClientRect(hwnd, &nmpgScroll.rcParent);
        nmpgScroll.iXpos = nmpgScroll.iYpos = 0;
        nmpgScroll.iDir = dir;

        if (PAGER_IsHorizontal(hwnd))
        {
            nmpgScroll.iScroll = rcWnd.right - rcWnd.left;
            nmpgScroll.iXpos = infoPtr->nPos;
        }
        else
        {
            nmpgScroll.iScroll = rcWnd.bottom - rcWnd.top;
            nmpgScroll.iYpos = infoPtr->nPos;
        }
        nmpgScroll.iScroll -= 2*infoPtr->nButtonSize;

        SendMessageA (GetParent(hwnd), WM_NOTIFY,
                    (WPARAM)nmpgScroll.hdr.idFrom, (LPARAM)&nmpgScroll);

        TRACE("[%08x] PGN_SCROLL returns iScroll=%d\n", hwnd, nmpgScroll.iScroll);

        if (nmpgScroll.iScroll > 0)
        {
            infoPtr->direction = dir;

            if (dir == PGF_SCROLLLEFT || dir == PGF_SCROLLUP)
                PAGER_SetPos(hwnd, infoPtr->nPos - nmpgScroll.iScroll, TRUE);
            else
                PAGER_SetPos(hwnd, infoPtr->nPos + nmpgScroll.iScroll, TRUE);
        }
        else
            infoPtr->direction = -1;
    }
}

static LRESULT
PAGER_FmtLines(HWND hwnd)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);

    /* initiate NCCalcSize to resize client wnd and get size */
    SetWindowPos(hwnd, 0, 0,0,0,0,
		 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
		 SWP_NOZORDER | SWP_NOACTIVATE);

    SetWindowPos(infoPtr->hwndChild, 0,
		 0,0,infoPtr->nWidth,infoPtr->nHeight,
		 0);

    return DefWindowProcA (hwnd, EM_FMTLINES, 0, 0);
}

static LRESULT
PAGER_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr;
    DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);

    /* allocate memory for info structure */
    infoPtr = (PAGER_INFO *)COMCTL32_Alloc (sizeof(PAGER_INFO));
    SetWindowLongA (hwnd, 0, (DWORD)infoPtr);

    /* set default settings */
    infoPtr->hwndChild = (HWND)NULL;
    infoPtr->bNoResize = dwStyle & CCS_NORESIZE;
    infoPtr->clrBk = GetSysColor(COLOR_BTNFACE);
    infoPtr->nBorder = 0;
    infoPtr->nButtonSize = 12;
    infoPtr->nPos = 0;
    infoPtr->nWidth = 0;
    infoPtr->nHeight = 0;
    infoPtr->bForward = FALSE;
    infoPtr->bCapture = FALSE;
    infoPtr->TLbtnState = PGF_INVISIBLE;
    infoPtr->BRbtnState = PGF_INVISIBLE;
    infoPtr->direction = -1;

    if (dwStyle & PGS_DRAGNDROP)
        FIXME("[%08x] Drag and Drop style is not implemented yet.\n", hwnd);
    /*
	 * If neither horizontal nor vertical style specified, default to vertical.
	 * This is probably not necessary, since the style may be set later on as
	 * the control is initialized, but just in case it isn't, set it here.
	 */
    if (!(dwStyle & PGS_HORZ) && !(dwStyle & PGS_VERT))
    {
        dwStyle |= PGS_VERT;
        SetWindowLongA(hwnd, GWL_STYLE, dwStyle);
    }

    return 0;
}


static LRESULT
PAGER_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    /* free pager info data */
    COMCTL32_Free (infoPtr);
    SetWindowLongA (hwnd, 0, 0);
    return 0;
}

static LRESULT
PAGER_NCCalcSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    LPRECT lpRect = (LPRECT)lParam;
    RECT rcChildw, rcmyw, wnrc, ltrc, rbrc;
    POINT cursor;
    BOOL resizeClient = FALSE;
    BOOL repaintBtns = FALSE;
    INT scrollRange;

    /*
     * lParam points to a RECT struct.  On entry, the struct
     * contains the proposed wnd rectangle for the window.
     * On exit, the struct should contain the screen
     * coordinates of the corresponding window's client area.
     */

    DefWindowProcA (hwnd, WM_NCCALCSIZE, wParam, lParam);

    TRACE("orig rect=(%d,%d)-(%d,%d)\n",
	  lpRect->left, lpRect->top, lpRect->right, lpRect->bottom);

    if (PAGER_IsHorizontal(hwnd))
    {
	infoPtr->nWidth = lpRect->right - lpRect->left;
	PAGER_CalcSize (hwnd, &infoPtr->nWidth, TRUE);
	GetWindowRect (infoPtr->hwndChild, &rcChildw);
	MapWindowPoints (0, hwnd, (LPPOINT)&rcChildw, 2);
	GetCursorPos (&cursor);
	GetWindowRect (hwnd, &rcmyw);

	/* Reset buttons and hide any grey ones */
	scrollRange = infoPtr->nWidth - (rcmyw.right - rcmyw.left);

	TRACE("nPos=%d, scrollrange=%d, nHeigth=%d, myw=(%d,%d)-(%d,%d), cursor=(%ld,%ld)\n",
	      infoPtr->nPos, scrollRange, infoPtr->nHeight,
	      rcmyw.left, rcmyw.top,
	      rcmyw.right, rcmyw.bottom,
	      cursor.x, cursor.y);
	PAGER_GrayAndRestoreBtns(infoPtr, scrollRange, &resizeClient, &repaintBtns);
	PAGER_HideGrayBtns(infoPtr, &resizeClient);

	if (PtInRect (&rcmyw, cursor)) {
	    GetWindowRect (hwnd, &wnrc);
	    ltrc = wnrc;
	    ltrc.right = ltrc.left + infoPtr->nButtonSize;
	    rbrc = wnrc;
	    rbrc.left = rbrc.right - infoPtr->nButtonSize;
	    TRACE("horz lt rect=(%d,%d)-(%d,%d), rb rect=(%d,%d)-(%d,%d)\n",
		  ltrc.left, ltrc.top, ltrc.right, ltrc.bottom,
		  rbrc.left, rbrc.top, rbrc.right, rbrc.bottom);
	    if (PtInRect (&ltrc, cursor) && infoPtr->TLbtnState)
		RedrawWindow (hwnd, 0, 0, RDW_INVALIDATE | RDW_ERASE);
	    if (PtInRect (&rbrc, cursor) && infoPtr->BRbtnState)
		RedrawWindow (hwnd, 0, 0, RDW_INVALIDATE | RDW_ERASE);
	}
	if (infoPtr->TLbtnState && (lpRect->left + infoPtr->nButtonSize < lpRect->right))
	    lpRect->left += infoPtr->nButtonSize;
	if (infoPtr->BRbtnState && (lpRect->right - infoPtr->nButtonSize > lpRect->left))
	    lpRect->right -= infoPtr->nButtonSize;
    }
    else
    {
	/* native does: (from trace of IE4 opening "Favorites" frame)
	 *        DefWindowProc
	 *        WM_NOITFY  PGN_CALCSIZE w/ dwFlag=2
	 *        GetWindowRect (child, &rc)
         *        MapWindowPoints (0, syspager, &rc, 2)
         *        GetCursorPos( &cur )
         *        GetWindowRect (syspager, &rc2)
         *        PtInRect (&rc2, cur.x, cur.y) rtns 0
         *        returns with rect empty
	 */
	infoPtr->nHeight = lpRect->bottom - lpRect->top;
	PAGER_CalcSize (hwnd, &infoPtr->nHeight, FALSE);
	GetWindowRect (infoPtr->hwndChild, &rcChildw);
	MapWindowPoints (0, hwnd, (LPPOINT)&rcChildw, 2);
	GetCursorPos (&cursor);
	GetWindowRect (hwnd, &rcmyw);

	/* Reset buttons and hide any grey ones */
	scrollRange = infoPtr->nHeight - (rcmyw.bottom - rcmyw.top);

	TRACE("nPos=%d, scrollrange=%d, nHeigth=%d, myw=(%d,%d)-(%d,%d), cursor=(%ld,%ld)\n",
	      infoPtr->nPos, scrollRange, infoPtr->nHeight,
	      rcmyw.left, rcmyw.top,
	      rcmyw.right, rcmyw.bottom,
	      cursor.x, cursor.y);
	PAGER_GrayAndRestoreBtns(infoPtr, scrollRange, &resizeClient, &repaintBtns);
	PAGER_HideGrayBtns(infoPtr, &resizeClient);

	if (PtInRect (&rcmyw, cursor)) {

	    /* native does:
	     *    GetWindowRect(pager, &rc)
	     *    PtInRect(btn-left????, cur.x, cur.y)
	     *    if true -> ???
	     *    PtInRect(btn-right????, cur.x, cur.y)
	     *    if true
	     *      RedrawWindow(pager, 0, 0, 5)
	     *      return TRUE
	     */

	    GetWindowRect (hwnd, &wnrc);
	    ltrc = wnrc;
	    ltrc.right = ltrc.left + infoPtr->nButtonSize;
	    rbrc = wnrc;
	    rbrc.left = rbrc.right - infoPtr->nButtonSize;
	    TRACE("vert lt rect=(%d,%d)-(%d,%d), rb rect=(%d,%d)-(%d,%d)\n",
		  ltrc.left, ltrc.top, ltrc.right, ltrc.bottom,
		  rbrc.left, rbrc.top, rbrc.right, rbrc.bottom);
	    if (PtInRect (&ltrc, cursor) && infoPtr->TLbtnState)
		RedrawWindow (hwnd, 0, 0, RDW_INVALIDATE | RDW_ERASE);
	    if (PtInRect (&rbrc, cursor) && infoPtr->BRbtnState)
		RedrawWindow (hwnd, 0, 0, RDW_INVALIDATE | RDW_ERASE);
	}
	if (infoPtr->TLbtnState && (lpRect->top + infoPtr->nButtonSize < lpRect->bottom))
	    lpRect->top += infoPtr->nButtonSize;
	if (infoPtr->BRbtnState && (lpRect->bottom - infoPtr->nButtonSize > lpRect->top))
	    lpRect->bottom -= infoPtr->nButtonSize;
	/* ???? */
	if ((lpRect->bottom < 0) || (lpRect->bottom > infoPtr->nHeight))
	    lpRect->bottom = infoPtr->nHeight;
    }

    TRACE("[%08x] client rect set to %dx%d at (%d,%d) BtnState[%d,%d]\n",
	  hwnd, lpRect->right-lpRect->left, lpRect->bottom-lpRect->top,
	  lpRect->left, lpRect->top,
	  infoPtr->TLbtnState, infoPtr->BRbtnState);

    return 0;
}

static LRESULT
PAGER_NCPaint (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO* infoPtr = PAGER_GetInfoPtr(hwnd);
    DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
    RECT rcWindow, rcBottomRight, rcTopLeft;
    HDC hdc;
    BOOL bHorizontal = PAGER_IsHorizontal(hwnd);

    if (dwStyle & WS_MINIMIZE)
        return 0;

    DefWindowProcA (hwnd, WM_NCPAINT, wParam, lParam);

    if (!(hdc = GetDCEx (hwnd, 0, DCX_USESTYLE | DCX_WINDOW)))
        return 0;

    GetWindowRect (hwnd, &rcWindow);
    OffsetRect (&rcWindow, -rcWindow.left, -rcWindow.top);

    rcTopLeft = rcBottomRight = rcWindow;
    if (bHorizontal)
    {
        rcTopLeft.right = rcTopLeft.left + infoPtr->nButtonSize;
        rcBottomRight.left = rcBottomRight.right - infoPtr->nButtonSize;
    }
    else
    {
        rcTopLeft.bottom = rcTopLeft.top + infoPtr->nButtonSize;
        rcBottomRight.top = rcBottomRight.bottom - infoPtr->nButtonSize;
    }

    PAGER_DrawButton(hdc, infoPtr->clrBk, rcTopLeft,
                     bHorizontal, TRUE, infoPtr->TLbtnState);
    PAGER_DrawButton(hdc, infoPtr->clrBk, rcBottomRight,
                     bHorizontal, FALSE, infoPtr->BRbtnState);

    ReleaseDC( hwnd, hdc );
    return 0;
}

static INT
PAGER_HitTest (HWND hwnd, LPPOINT pt)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    RECT clientRect;
    BOOL bHorizontal = PAGER_IsHorizontal(hwnd);

    GetClientRect (hwnd, &clientRect);

    if (PtInRect(&clientRect, *pt))
    {
        TRACE("HTCLIENT\n");
        return HTCLIENT;
    }

    if (infoPtr->TLbtnState && infoPtr->TLbtnState != PGF_GRAYED)
    {
        if (bHorizontal)
        {
            if (pt->x < clientRect.left)
            {
                TRACE("HTLEFT\n");
                return HTLEFT;
            }
        }
        else
        {
            if (pt->y < clientRect.top)
            {
                TRACE("HTTOP\n");
                return HTTOP;
            }
        }
    }

    if (infoPtr->BRbtnState && infoPtr->BRbtnState != PGF_GRAYED)
    {
        if (bHorizontal)
        {
            if (pt->x > clientRect.right)
            {
                TRACE("HTRIGHT\n");
                return HTRIGHT;
            }
        }
        else
        {
            if (pt->y > clientRect.bottom)
            {
                TRACE("HTBOTTOM\n");
                return HTBOTTOM;
            }
        }
    }

    TRACE("HTNOWHERE\n");
    return HTNOWHERE;
}

static LRESULT
PAGER_NCHitTest (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    POINT pt;

    pt.x = SLOWORD(lParam);
    pt.y = SHIWORD(lParam);

    ScreenToClient (hwnd, &pt);
    return PAGER_HitTest(hwnd, &pt);
}

static LRESULT
PAGER_SetCursor( HWND hwnd, WPARAM wParam, LPARAM lParam )
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    BOOL notCaptured = FALSE;

    switch(LOWORD(lParam))
    {
        case HTLEFT:
        case HTTOP:
            if ((notCaptured = infoPtr->TLbtnState != PGF_HOT))
                infoPtr->TLbtnState = PGF_HOT;
            break;
        case HTRIGHT:
        case HTBOTTOM:
            if ((notCaptured = infoPtr->BRbtnState != PGF_HOT))
               infoPtr->BRbtnState = PGF_HOT;
            break;
        default:
            return FALSE;
    }

    if (notCaptured)
    {
	PAGER_CaptureandTrack(infoPtr, hwnd);

        SendMessageA(hwnd, WM_NCPAINT, 0, 0);
    }

    return TRUE;
}

static LRESULT
PAGER_MouseLeave (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);

    KillTimer (hwnd, TIMERID1);
    KillTimer (hwnd, TIMERID2);

    TRACE("[%08x] ReleaseCapture\n", hwnd);
    ReleaseCapture();
    infoPtr->bCapture = FALSE;

    /* Notify parent of released mouse capture */
    {
        NMHDR nmhdr;
        ZeroMemory (&nmhdr, sizeof (NMHDR));
        nmhdr.hwndFrom = hwnd;
        nmhdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
        nmhdr.code = NM_RELEASEDCAPTURE;
        SendMessageA (GetParent(hwnd), WM_NOTIFY,
                        (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
    }

    /* make HOT btns NORMAL and hide gray btns */
    PAGER_UpdateBtns(hwnd, infoPtr, -1, TRUE);

    return TRUE;
}

static LRESULT
PAGER_MouseMove (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    POINT clpt, pt;
    RECT wnrect, TLbtnrect, BRbtnrect, *btnrect = NULL;
    DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
    BOOL topLeft = FALSE;
    INT btnstate = 0;
    INT hit;
    HDC hdc;

    pt.x = SLOWORD(lParam);
    pt.y = SHIWORD(lParam);

    TRACE("[%08x] to (%ld,%ld)\n", hwnd, pt.x, pt.y);
    ClientToScreen(hwnd, &pt);
    GetWindowRect(hwnd, &wnrect);
    if (PtInRect(&wnrect, pt)) {
	TLbtnrect = wnrect;
	BRbtnrect = wnrect;
	if (dwStyle & PGS_HORZ) {
	    TLbtnrect.right = TLbtnrect.left + infoPtr->nButtonSize;
	    BRbtnrect.left = BRbtnrect.right - infoPtr->nButtonSize;
	}
	else {
	    TLbtnrect.bottom = TLbtnrect.top + infoPtr->nButtonSize;
	    BRbtnrect.top = BRbtnrect.bottom - infoPtr->nButtonSize;
	}

	clpt = pt;
	MapWindowPoints(0, hwnd, &clpt, 1);
	hit = PAGER_HitTest(hwnd, &clpt);
	if (hit == HTLEFT || hit == HTTOP) {
	    topLeft = TRUE;
	    btnrect = &TLbtnrect;
	    infoPtr->TLbtnState = PGF_DEPRESSED;
	    btnstate = infoPtr->TLbtnState;
	}
	else if (hit == HTRIGHT || hit == HTBOTTOM) {
	    topLeft = FALSE;
	    btnrect = &BRbtnrect;
	    infoPtr->BRbtnState = PGF_DEPRESSED;
	    btnstate = infoPtr->BRbtnState;
	}

	/* If in one of the buttons the capture and draw buttons */
	if (btnrect) {
	    TRACE("[%08x] draw btn (%d,%d)-(%d,%d), Capture %s, style %08lx\n",
		  hwnd, btnrect->left, btnrect->top,
		  btnrect->right, btnrect->bottom,
		  (infoPtr->bCapture) ? "TRUE" : "FALSE",
		  dwStyle);
	    if (!infoPtr->bCapture)
		PAGER_CaptureandTrack(infoPtr, hwnd);
	    if (dwStyle & PGS_AUTOSCROLL)
		SetTimer(hwnd, TIMERID1, 0x3e, 0);
	    MapWindowPoints(0, hwnd, (LPPOINT)btnrect, 2);
	    hdc = GetWindowDC(hwnd);
	    /* OffsetRect(wnrect, 0 | 1, 0 | 1) */
	    PAGER_DrawButton(hdc, infoPtr->clrBk, *btnrect,
			     PAGER_IsHorizontal(hwnd), topLeft, btnstate);
	    ReleaseDC(hwnd, hdc);
	    return DefWindowProcA (hwnd, WM_MOUSEMOVE, wParam, lParam);
	}
    }

    /* If we think we are captured, then do release */
    if (infoPtr->bCapture) {
	infoPtr->bCapture = FALSE;

        if (GetCapture() == hwnd) {
	    ReleaseCapture();
	    /* Notify parent of released mouse capture */
	    {
		NMHDR nmhdr;
		ZeroMemory (&nmhdr, sizeof (NMHDR));
		nmhdr.hwndFrom = hwnd;
		nmhdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
		nmhdr.code = NM_RELEASEDCAPTURE;
		SendMessageA (GetParent(hwnd), WM_NOTIFY,
			      (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
	    }
	}
	if (IsWindow(hwnd))
	    KillTimer(hwnd, TIMERID1);
    }
    return DefWindowProcA (hwnd, WM_MOUSEMOVE, wParam, lParam);
}

static LRESULT
PAGER_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    BOOL repaintBtns = FALSE;
    POINT pt;
    INT hit;

    pt.x = SLOWORD(lParam);
    pt.y = SHIWORD(lParam);

    TRACE("[%08x] at (%d,%d)\n", hwnd, SLOWORD(lParam), SHIWORD(lParam));

    hit = PAGER_HitTest(hwnd, &pt);

    /* put btn in DEPRESSED state */
    if (hit == HTLEFT || hit == HTTOP)
    {
        repaintBtns = infoPtr->TLbtnState != PGF_DEPRESSED;
        infoPtr->TLbtnState = PGF_DEPRESSED;
        SetTimer(hwnd, TIMERID1, INITIAL_DELAY, 0);
    }
    else if (hit == HTRIGHT || hit == HTBOTTOM)
    {
        repaintBtns = infoPtr->BRbtnState != PGF_DEPRESSED;
        infoPtr->BRbtnState = PGF_DEPRESSED;
        SetTimer(hwnd, TIMERID1, INITIAL_DELAY, 0);
    }

    if (repaintBtns)
        SendMessageA(hwnd, WM_NCPAINT, 0, 0);

    switch(hit)
    {
    case HTLEFT:
        TRACE("[%08x] PGF_SCROLLLEFT\n", hwnd);
        PAGER_Scroll(hwnd, PGF_SCROLLLEFT);
        break;
    case HTTOP:
        TRACE("[%08x] PGF_SCROLLUP\n", hwnd);
        PAGER_Scroll(hwnd, PGF_SCROLLUP);
        break;
    case HTRIGHT:
        TRACE("[%08x] PGF_SCROLLRIGHT\n", hwnd);
        PAGER_Scroll(hwnd, PGF_SCROLLRIGHT);
        break;
    case HTBOTTOM:
        TRACE("[%08x] PGF_SCROLLDOWN\n", hwnd);
        PAGER_Scroll(hwnd, PGF_SCROLLDOWN);
        break;
    default:
        break;
    }

    return TRUE;
}

static LRESULT
PAGER_LButtonUp (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    TRACE("[%08x]\n", hwnd);

    KillTimer (hwnd, TIMERID1);
    KillTimer (hwnd, TIMERID2);

    /* make PRESSED btns NORMAL but don't hide gray btns */
    PAGER_UpdateBtns(hwnd, infoPtr, -1, FALSE);

    return 0;
}

static LRESULT
PAGER_NCLButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    POINT pt;

    pt.x = SLOWORD(lParam);
    pt.y = SHIWORD(lParam);

    TRACE("[%08x] at (%d,%d)\n", hwnd, SLOWORD(lParam), SHIWORD(lParam));
    MapWindowPoints(0, hwnd, &pt, 1);
    lParam = MAKELONG(pt.x, pt.y);
    return PAGER_LButtonDown (hwnd, wParam, lParam);
}

static LRESULT
PAGER_Timer (HWND hwnd, WPARAM wParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
    INT dir;

    /* if initial timer, kill it and start the repeat timer */
    if (wParam == TIMERID1) {
	if (PAGER_IsHorizontal(hwnd)) {
	    dir = (infoPtr->TLbtnState & PGF_DEPRESSED) ?
		PGF_SCROLLLEFT : PGF_SCROLLRIGHT;
	}
	else {
	    dir = (infoPtr->TLbtnState & PGF_DEPRESSED) ?
		PGF_SCROLLUP : PGF_SCROLLDOWN;
	}
	TRACE("[%08x] TIMERID1: style=%08lx, dir=%d\n", hwnd, dwStyle, dir);
	KillTimer(hwnd, TIMERID1);
	SetTimer(hwnd, TIMERID1, REPEAT_DELAY, 0);
	if (dwStyle & PGS_AUTOSCROLL) {
	    PAGER_Scroll(hwnd, dir);
	    SetWindowPos(hwnd, 0,0,0,0,0,
			 SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE |
			 SWP_NOZORDER | SWP_NOACTIVATE);
	}
	return 0;

    }

    TRACE("[%08x] TIMERID2: dir=%d\n", hwnd, infoPtr->direction);
    KillTimer(hwnd, TIMERID2);
    if (infoPtr->direction > 0) {
	PAGER_Scroll(hwnd, infoPtr->direction);
	SetTimer(hwnd, TIMERID2, REPEAT_DELAY, 0);
    }
    return 0;
}

static LRESULT
PAGER_EraseBackground (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    POINT pt, ptorig;
    HDC hdc = (HDC)wParam;
    HWND parent;

    /* native does:
     *   parent = GetParent(pager)
     *   pt.x=0; pt.y=0; ?????
     *   MapWindowPoints(pager, parent, &pt, 1)
     *   OffsetWindowOrgEx(hdc, pt.x, pt.y, &ptorg)
     *   SendMessageA(parent, WM_ERASEBKGND, hdc, 0)
     *   SetWindowOrgEx(hdc, 0, 0, 0)
     */

    pt.x = 0;
    pt.y = 0;
    parent = GetParent(hwnd);
    MapWindowPoints(hwnd, parent, &pt, 1);
    OffsetWindowOrgEx (hdc, pt.x, pt.y, &ptorig);
    SendMessageA (parent, WM_ERASEBKGND, wParam, lParam);
    SetWindowOrgEx (hdc, ptorig.x, ptorig.y, 0);


#if 0
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    HBRUSH hBrush = CreateSolidBrush(infoPtr->clrBk);
    RECT rect;

    GetClientRect (hwnd, &rect);
    FillRect ((HDC)wParam, &rect, hBrush);

    /* background color of the child should be the same as the pager */
    if (infoPtr->hwndChild)
    {
        GetClientRect (infoPtr->hwndChild, &rect);
        FillRect ((HDC)wParam, &rect, hBrush);
    }

    DeleteObject (hBrush);
#endif

    return TRUE;
}


static LRESULT
PAGER_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    /* note that WM_SIZE is sent whenever NCCalcSize resizes the client wnd */

    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);
    TRACE("[%08x] %dx%d\n", hwnd, SLOWORD(lParam), SHIWORD(lParam));

    if (PAGER_IsHorizontal(hwnd))
        infoPtr->nHeight = SHIWORD(lParam);
    else
        infoPtr->nWidth = SLOWORD(lParam);

    return PAGER_RecalcSize(hwnd);
}


static LRESULT WINAPI
PAGER_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    PAGER_INFO *infoPtr = PAGER_GetInfoPtr (hwnd);

    if (!infoPtr && (uMsg != WM_CREATE))
	return DefWindowProcA (hwnd, uMsg, wParam, lParam);

    switch (uMsg)
    {
        case EM_FMTLINES:
	    return PAGER_FmtLines(hwnd);

        case PGM_FORWARDMOUSE:
            return PAGER_ForwardMouse (hwnd, wParam);

        case PGM_GETBKCOLOR:
            return PAGER_GetBkColor(hwnd);

        case PGM_GETBORDER:
            return PAGER_GetBorder(hwnd);

        case PGM_GETBUTTONSIZE:
            return PAGER_GetButtonSize(hwnd);

        case PGM_GETPOS:
            return PAGER_GetPos(hwnd);

        case PGM_GETBUTTONSTATE:
            return PAGER_GetButtonState (hwnd, wParam, lParam);

/*      case PGM_GETDROPTARGET: */

        case PGM_RECALCSIZE:
            return PAGER_RecalcSize(hwnd);

        case PGM_SETBKCOLOR:
            return PAGER_SetBkColor (hwnd, wParam, lParam);

        case PGM_SETBORDER:
            return PAGER_SetBorder (hwnd, wParam, lParam);

        case PGM_SETBUTTONSIZE:
            return PAGER_SetButtonSize (hwnd, wParam, lParam);

        case PGM_SETCHILD:
            return PAGER_SetChild (hwnd, wParam, lParam);

        case PGM_SETPOS:
            return PAGER_SetPos(hwnd, (INT)lParam, FALSE);

        case WM_CREATE:
            return PAGER_Create (hwnd, wParam, lParam);

        case WM_DESTROY:
            return PAGER_Destroy (hwnd, wParam, lParam);

        case WM_SIZE:
            return PAGER_Size (hwnd, wParam, lParam);

        case WM_NCPAINT:
            return PAGER_NCPaint (hwnd, wParam, lParam);

        case WM_WINDOWPOSCHANGING:
            return PAGER_HandleWindowPosChanging (hwnd, wParam, (WINDOWPOS*)lParam);

        case WM_NCCALCSIZE:
            return PAGER_NCCalcSize (hwnd, wParam, lParam);

        case WM_NCHITTEST:
            return PAGER_NCHitTest (hwnd, wParam, lParam);

        case WM_SETCURSOR:
        {
            if (hwnd == (HWND)wParam)
                return PAGER_SetCursor(hwnd, wParam, lParam);
            else /* its for the child */
                return 0;
        }

        case WM_MOUSEMOVE:
            if (infoPtr->bForward && infoPtr->hwndChild)
                PostMessageA(infoPtr->hwndChild, WM_MOUSEMOVE, wParam, lParam);
            return PAGER_MouseMove (hwnd, wParam, lParam);

        case WM_MOUSELEAVE:
            return PAGER_MouseLeave (hwnd, wParam, lParam);

        case WM_NCLBUTTONDOWN:
            return PAGER_NCLButtonDown (hwnd, wParam, lParam);

        case WM_LBUTTONDOWN:
            return PAGER_LButtonDown (hwnd, wParam, lParam);

        case WM_NCLBUTTONUP:
        case WM_LBUTTONUP:
            return PAGER_LButtonUp (hwnd, wParam, lParam);

        case WM_ERASEBKGND:
            return PAGER_EraseBackground (hwnd, wParam, lParam);
/*
        case WM_PAINT:
            return PAGER_Paint (hwnd, wParam);
*/
        case WM_TIMER:
	    return PAGER_Timer (hwnd, wParam);

        case WM_NOTIFY:
        case WM_COMMAND:
            return SendMessageA (GetParent (hwnd), uMsg, wParam, lParam);

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

    return 0;
}


VOID
PAGER_Register (void)
{
    WNDCLASSA wndClass;

    ZeroMemory (&wndClass, sizeof(WNDCLASSA));
    wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_SAVEBITS;
    wndClass.lpfnWndProc   = (WNDPROC)PAGER_WindowProc;
    wndClass.cbClsExtra    = 0;
    wndClass.cbWndExtra    = sizeof(PAGER_INFO *);
    wndClass.hCursor       = LoadCursorA (0, IDC_ARROWA);
    wndClass.hbrBackground = 0;
    wndClass.lpszClassName = WC_PAGESCROLLERA;

    RegisterClassA (&wndClass);
}


VOID
PAGER_Unregister (void)
{
    UnregisterClassA (WC_PAGESCROLLERA, (HINSTANCE)NULL);
}
