#include "ctlspriv.h"
#pragma hdrstop
#include "usrctl32.h"

//---------------------------------------------------------------------------//
#define TIMERID         1

#define RDRMODE_VERT    0x00000001
#define RDRMODE_HORZ    0x00000002
#define RDRMODE_DIAG    0x00000004

#define RDRCODE_START   1
#define RDRCODE_SCROLL  2
#define RDRCODE_END     3

//
// Instance data pointer access functions
//
#define ReaderMode_GetPtr(hwnd)    \
            (PREADERINFO)GetWindowPtr(hwnd, 0)

#define ReaderMode_SetPtr(hwnd, p) \
            (PREADERINFO)SetWindowPtr(hwnd, 0, p)


//---------------------------------------------------------------------------//
typedef LONG (CALLBACK* READERMODEPROC)(LPARAM lParam, int nCode, int dx, int dy);


//---------------------------------------------------------------------------//
typedef struct tagREADERMODE 
{
    UINT    cbSize;
    DWORD   dwFlags;
    READERMODEPROC pfnReaderModeProc;
    LPARAM  lParam;
} READERMODE, *PREADERMODE;


//---------------------------------------------------------------------------//
typedef struct tagREADERINFO 
{
    READERMODE;
    READERMODE  rm;
    int         dx;
    int         dy;
    UINT        uCursor;
    HBITMAP     hbm;
    UINT        dxBmp;
    UINT        dyBmp;
} READERINFO, *PREADERINFO;


//---------------------------------------------------------------------------//
typedef struct tagREADERWND 
{
    HWND        hwnd;
    PREADERINFO prdr;
} READERWND, *PREADERWND;


//---------------------------------------------------------------------------//
__inline FReader2Dim(PREADERINFO prdr)
{
    return ((prdr->dwFlags & (RDRMODE_HORZ | RDRMODE_VERT)) ==
            (RDRMODE_HORZ | RDRMODE_VERT));
}
__inline FReaderVert(PREADERINFO prdr)
{
    return (prdr->dwFlags & RDRMODE_VERT);
}
__inline FReaderHorz(PREADERINFO prdr)
{
    return (prdr->dwFlags & RDRMODE_HORZ);
}
__inline FReaderDiag(PREADERINFO prdr)
{
    return (prdr->dwFlags & RDRMODE_DIAG);
}


//---------------------------------------------------------------------------//
void ReaderMode_SetCursor(PREADERINFO prdr, UINT uCursor)
{
    if (prdr->uCursor != uCursor) 
    {
        SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(uCursor)));
        prdr->uCursor = uCursor;
    }
}


//---------------------------------------------------------------------------//
//
// ReaderMode_MouseMove
//
// Calculate dx and dy based on the flags passed in.  Provide visual
// feedback for the reader mode by setting the correct cursor.
//
void ReaderMode_MouseMove(HWND hwnd, PREADERINFO prdr, LPARAM lParam)
{
    int dx = 0, dy = 0;
    RECT rc;
    UINT uCursor;
    POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};

    GetWindowRect(hwnd, &rc);

    ClientToScreen(hwnd, &pt);

    if (FReaderVert(prdr)) 
    {
        if (pt.y < rc.top) 
        {
            dy = pt.y - rc.top;
        } 
        else if (pt.y > rc.bottom) 
        {
            dy = pt.y - rc.bottom;
        }
    }

    if (FReaderHorz(prdr)) 
    {
        if (pt.x < rc.left) 
        {
            dx = pt.x - rc.left;
        } 
        else if (pt.x > rc.right) 
        {
            dx = pt.x - rc.right;
        }
    }

    if (FReader2Dim(prdr)) 
    {
        if (dx == 0 && dy == 0) 
        {
            ReaderMode_SetCursor(prdr, OCR_RDR2DIM);
            goto Exit;
        }

        if (!FReaderDiag(prdr)) 
        {
            if (prdr->dy != 0) 
            {
                if (abs(dx) > abs(prdr->dy)) 
                {
                    dy = 0;
                } 
                else 
                {
                    dx = 0;
                }
            } 
            else if (prdr->dx != 0) 
            {
                if (abs(dy) > abs(prdr->dx)) 
                {
                    dx = 0;
                } 
                else 
                {
                    dy = 0;
                }
            } 
            else if (dy != 0) 
            {
                dx = 0;
            }
        }
    } 
    else if (FReaderVert(prdr) && dy == 0) 
    {
        ReaderMode_SetCursor(prdr, OCR_RDRVERT);
        goto Exit;
    } 
    else if (FReaderHorz(prdr) && dx == 0) 
    {
        ReaderMode_SetCursor(prdr, OCR_RDRHORZ);
        goto Exit;
    }

    if (dx == 0) 
    {
        uCursor = (dy > 0) ? OCR_RDRSOUTH : OCR_RDRNORTH;
    } 
    else if (dx > 0) 
    {
        if (dy == 0) 
        {
            uCursor = OCR_RDREAST;
        } 
        else 
        {
            uCursor = (dy > 0) ? OCR_RDRSOUTHEAST : OCR_RDRNORTHEAST;
        }
    } 
    else if (dx < 0) 
    {
        if (dy == 0) 
        {
            uCursor = OCR_RDRWEST;
        } 
        else 
        {
            uCursor = (dy > 0) ? OCR_RDRSOUTHWEST : OCR_RDRNORTHWEST;
        }
    }

    ReaderMode_SetCursor(prdr, uCursor);

Exit:
    prdr->dx = dx;
    prdr->dy = dy;
}


//---------------------------------------------------------------------------//
void ReaderMode_Feedback(HWND hwnd, PREADERINFO prdr)
{
    if (prdr->dx || prdr->dy)
    {
        if (prdr->pfnReaderModeProc(prdr->lParam, RDRCODE_SCROLL, prdr->dx, prdr->dy) == 0) 
        {
            DestroyWindow(hwnd);
        }
    }
}


//---------------------------------------------------------------------------//
LRESULT CALLBACK ReaderMode_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC    hdc, hdcMem;
    HPEN   hpen, hpenOld;
    HBRUSH hbrOld;
    HRGN   hrgn;
    RECT   rc;
    POINT  pt;
    int    nBitmap, cx, cy;
    PREADERINFO    prdr;
    LPCREATESTRUCT pcs;
    PREADERMODE    prdrm;
    BITMAP bmp;

    prdr = ReaderMode_GetPtr(hwnd);

    if (prdr || msg == WM_CREATE)
    {
        switch (msg) 
        {
        case WM_TIMER:
            ReaderMode_Feedback(hwnd, prdr);
            return 0;

        case WM_MOUSEWHEEL:
        case WM_LBUTTONUP:
        case WM_RBUTTONUP:
        case WM_XBUTTONUP:
        case WM_LBUTTONDOWN:
        case WM_RBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_XBUTTONDOWN:
        case WM_KEYDOWN:
            ReleaseCapture();
            return 0;

        case WM_MOUSEMOVE:
            ReaderMode_MouseMove(hwnd, prdr, lParam);
            return 0;

        case WM_MBUTTONUP:
            pt.x = GET_X_LPARAM(lParam);
            pt.y = GET_Y_LPARAM(lParam);
            GetClientRect(hwnd, &rc);
            if (!PtInRect(&rc, pt)) 
            {
                ReleaseCapture();
            }
            return 0;

        case WM_CAPTURECHANGED:
            DestroyWindow(hwnd);
            return 0;

        case WM_NCDESTROY:
            KillTimer(hwnd, TIMERID);

            prdr->pfnReaderModeProc(prdr->lParam, RDRCODE_END, 0, 0);

            if (prdr->hbm != NULL) 
            {
                DeleteObject(prdr->hbm);
            }
            UserLocalFree(prdr);
            return 0;

        case WM_CREATE:
            prdr = (PREADERINFO)UserLocalAlloc(HEAP_ZERO_MEMORY, sizeof(READERINFO));
            if (prdr == NULL)
            {
                return -1;
            }

            pcs = (LPCREATESTRUCT)lParam;
            prdrm = (PREADERMODE)pcs->lpCreateParams;
            CopyMemory(prdr, prdrm, sizeof(READERMODE));
            ReaderMode_SetPtr(hwnd, prdr);

            if (prdr->pfnReaderModeProc == NULL) 
            {
                return -1;
            }

            if (FReader2Dim(prdr)) 
            {
                nBitmap = OBM_RDR2DIM;
            } 
            else if (FReaderVert(prdr)) 
            {
                nBitmap = OBM_RDRVERT;
            } 
            else if (FReaderHorz(prdr)) 
            {
                nBitmap = OBM_RDRHORZ;
            } 
            else 
            {
                return -1;
            }

            SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
            SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_CLIPSIBLINGS);

            prdr->hbm = LoadBitmap(NULL, MAKEINTRESOURCE(nBitmap));
            if (prdr->hbm == NULL ||
                GetObject(prdr->hbm, sizeof(BITMAP), &bmp) == 0) 
            {
                return -1;
            }

            if (prdr->pfnReaderModeProc(prdr->lParam, RDRCODE_START, 0, 0) == 0) 
            {
                return -1;
            }

            prdr->dxBmp = bmp.bmWidth;
            prdr->dyBmp = bmp.bmHeight;

            cx = bmp.bmWidth + 1;
            cy = bmp.bmHeight + 1;

            GetCursorPos(&pt);
            pt.x -= cx/2;
            pt.y -= cy/2;

            if ((hrgn = CreateEllipticRgn(0, 0, cx, cy)) != NULL) 
            {
                SetWindowRgn(hwnd, hrgn, FALSE);
            }

            SetWindowPos(hwnd, HWND_TOPMOST, pt.x, pt.y, cx, cy,
                    SWP_SHOWWINDOW | SWP_NOACTIVATE);

            SetCapture(hwnd);
            SetFocus(hwnd);
            SetTimer(hwnd, TIMERID, 10, NULL);
            return 0;

        case WM_ERASEBKGND:
            hdc = (HDC)wParam;

            if ((hdcMem = CreateCompatibleDC(hdc)) == NULL)
            {
                return FALSE;
            }

            SelectObject(hdcMem, prdr->hbm);
            hpen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
            if (hpen)
            {
                hpenOld = (HPEN)SelectObject(hdc, hpen);
                hbrOld = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH));

                BitBlt(hdc, 0, 0, prdr->dxBmp, prdr->dyBmp, hdcMem, 0, 0, SRCCOPY);
                Ellipse(hdc, 0, 0, prdr->dxBmp, prdr->dyBmp);

                SelectObject(hdc, hpenOld);
                SelectObject(hdc, hbrOld);

                DeleteObject(hpen);
            }
            DeleteObject(hdcMem);
            return TRUE;
        }
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}


//---------------------------------------------------------------------------//
LONG ReaderMode_InternalProc(LPARAM lParam, int nCode, int dx, int dy)
{
    DWORD dwDelay;
    UINT uMsg, uCode;
    int n, nAbs;

    if (nCode != RDRCODE_SCROLL)
        return TRUE;

    if (dy != 0) 
    {
        uCode = SB_LINEUP;
        uMsg = WM_VSCROLL;
        n = dy;
    } 
    else 
    {
        uCode = SB_LINELEFT;
        uMsg = WM_HSCROLL;
        n = dx;
    }

    nAbs = abs(n);
    if (nAbs >= 120) 
    {
        uCode += 2;
        dwDelay = 0;
    } 
    else 
    {
        dwDelay = 1000 - (nAbs / 2) * 15;
    }

    if (n > 0) 
    {
        uCode += 1;
    }

    SendMessage((HWND)lParam, uMsg, MAKELONG(uCode, dwDelay), 0);
    UpdateWindow((HWND)lParam);
    return TRUE;
}


//---------------------------------------------------------------------------//
BOOL InitReaderModeClass(HINSTANCE hinst)
{
    WNDCLASS wc;

    wc.lpfnWndProc   = ReaderMode_WndProc;
    wc.lpszClassName = WC_READERMODE;
    wc.style         = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = sizeof(READERINFO);
    wc.hInstance     = hinst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = NULL;

    if (!RegisterClass(&wc) && !GetClassInfo(hinst, WC_BUTTON, &wc))
        return FALSE;


    return TRUE;
}


//---------------------------------------------------------------------------//
BOOL ReaderMode_ScrollEnabled(HWND hwnd, BOOL fVert)
{
    SCROLLBARINFO sbi = {0};
    BOOL fResult = FALSE;

    sbi.cbSize = sizeof(sbi);
    if ( GetScrollBarInfo(hwnd, fVert ? OBJID_VSCROLL : OBJID_HSCROLL, &sbi) )
    {
        fResult = (sbi.rgstate[0] & (STATE_SYSTEM_UNAVAILABLE|STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) ? FALSE : TRUE;
    }

    return fResult;
}

//---------------------------------------------------------------------------//
//
// EnterReaderMode - entry point to the ReaderMode control displayed when
//                   the user presses the scroll wheel. Renders an eliptical
//                   window that traps mouse movements in order to autoscroll
//                   the given hwnd.
//
BOOL EnterReaderMode(HWND hwnd)
{
    BOOL fResult = FALSE;

    if (GetCapture() == NULL)
    {
        READERMODE rdrm;

        rdrm.cbSize = sizeof(READERMODE);
        rdrm.pfnReaderModeProc = ReaderMode_InternalProc;
        rdrm.lParam = (LPARAM)hwnd;
        rdrm.dwFlags = 0;

        if (ReaderMode_ScrollEnabled(hwnd, TRUE)) 
        {
            rdrm.dwFlags |= RDRMODE_VERT;
        }

        if (ReaderMode_ScrollEnabled(hwnd, FALSE)) 
        {
            rdrm.dwFlags |= RDRMODE_HORZ;
        }

        if (rdrm.dwFlags)
        {
            fResult = (CreateWindowEx(0, 
                            WC_READERMODE, 
                            NULL, 
                            0, 0, 0, 0, 0, 
                            NULL, 
                            NULL, 
                            NULL, 
                            (LPVOID)&rdrm) != NULL);
        }
    }

    return fResult;
}