/****************************** Module Header ******************************\
* Module Name: scrollw.c
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* Window and DC scrolling routines.
*
* History:
* 18-Jul-1991 DarrinM   Recreated from Win 3.1 source.
\***************************************************************************/

#include "precomp.h"
#pragma hdrstop

/*
 * Problems so far:
 * DCs not at origin (0, 0)
 * funny coordinate systems
 */

/***************************************************************************\
* GetTrueClipRgn
*
* Get copy of true clip region and its bounds.
*
* History:
* 18-Jul-1991 DarrinM   Ported from Win 3.1 sources.
\***************************************************************************/

int GetTrueClipRgn(
    HDC  hdc,
    HRGN hrgnClip)
{
    POINT pt;
    int   code;

    code = GreCopyVisRgn(hdc, hrgnClip);

    /*
     * NOTE!!! The global ghrgnScrl2 is used in this routine!
     */
    GreGetDCOrg(hdc, &pt);

    if (GreGetRandomRgn(hdc, ghrgnScrl2, 1)) {
        GreOffsetRgn(ghrgnScrl2, pt.x, pt.y);
        code = IntersectRgn(hrgnClip, hrgnClip, ghrgnScrl2);
    }

    /*
     * Finally convert the result to DC coordinates
     */
    GreOffsetRgn(hrgnClip, -pt.x, -pt.y);

    return code;
}

/***************************************************************************\
* InternalScrollDC
*
* This function requires all input parameters in device coordinates
* (NOT screen!)
*
* History:
* 18-Jul-1991 DarrinM   Ported from Win 3.1 sources.
\***************************************************************************/

int InternalScrollDC(
    HDC    hdc,
    int    dx,
    int    dy,
    RECT   *prcSrc,
    RECT   *prcClip,
    HRGN   hrgnInvalid,
    HRGN   hrgnUpdate,
    LPRECT prcUpdate,
    BOOL   fLogUnits)
{
    RECT  rcVis;
    RECT  rcSrc;
    RECT  rcClip;
    RECT  rcUnclippedSrc;
    RECT  rcDst;
    RECT  rcUpdate;
    RECT  rcValid;
    BOOL  fSrcNotEmpty;
    BOOL  fHaveVisRgn;
    POINT rgpt[2];
    int   dxLog;
    int   dyLog;
    int   wClip;
    int   wClipValid;
    BOOL  bMirroredDC=FALSE;

    fHaveVisRgn = FALSE;

    /*
     * Enter a critical region to ensure that no one changes visrgns
     * or update regions while we scroll bits around.
     */
    GreLockDisplay(gpDispInfo->hDev);

    if ((wClip = GreGetClipBox(hdc, &rcVis, TRUE)) == ERROR) {

ErrorExit:

        GreUnlockDisplay(gpDispInfo->hDev);
        return ERROR;
    }

    CopyRect(&rcSrc, (prcSrc) ? prcSrc : &rcVis);
    if (prcClip) {
        CopyRect(&rcClip, prcClip);
    }

    dxLog = dx;
    dyLog = dy;

    if (fLogUnits) {

        /*
         * Convert input parameters to device coordinates
         */
        GreLPtoDP(hdc, (LPPOINT)&rcVis, 2);
        GreLPtoDP(hdc, (LPPOINT)&rcSrc, 2);

        //
        // Since this is a mirrored DC, then the resulting
        // device coord will be flowing from right to left
        // (i.e. rc.right < rc.left) so they should be flipped.
        // [samera]
        //
        if (GreGetLayout(hdc) & LAYOUT_RTL) {
            int iTemp   = rcVis.left;
            rcVis.left  = rcVis.right;
            rcVis.right = iTemp;

            iTemp       = rcSrc.left;
            rcSrc.left  = rcSrc.right;
            rcSrc.right = iTemp;

            bMirroredDC = TRUE;
        }

        if (prcClip) {
            GreLPtoDP(hdc, (LPPOINT)&rcClip, 2);

            //
            // Since this is a mirrored DC, then the resulting
            // device coord will be flowing from right to left
            // (i.e. rc.right < rc.left) so they should be flipped.
            // [samera]
            //
            if (bMirroredDC) {
                int iTemp    = rcClip.left;
                rcClip.left  = rcClip.right;
                rcClip.right = iTemp;
            }
        }

        /*
         * The delta values must be treated as a vector from
         * the point (0, 0) to (dx, dy).  Scale it as such, then
         * compute the difference.  This handles flipped coordinate systems.
         */
        rgpt[0].x = rgpt[0].y = 0;
        rgpt[1].x = dx;
        rgpt[1].y = dy;

        GreLPtoDP(hdc, rgpt, 2);

        dx = rgpt[1].x - rgpt[0].x;
        dy = rgpt[1].y - rgpt[0].y;
    }

    switch (wClip) {
    case NULLREGION:

NullExit:

        if (hrgnUpdate && !SetEmptyRgn(hrgnUpdate))
            goto ErrorExit;

        if (prcUpdate) {
            SetRectEmpty(prcUpdate);
        }

        GreUnlockDisplay(gpDispInfo->hDev);
        return NULLREGION;

    case COMPLEXREGION:
        GetTrueClipRgn(hdc, ghrgnScrlVis);
        fHaveVisRgn = TRUE;
        break;
    }

    /*
     * First compute the source and destination rectangles.
     *
     * rcDst = Offset(rcSrc, dx, dy)
     */
    rcDst.left   = rcSrc.left   + dx;
    rcDst.right  = rcSrc.right  + dx;
    rcDst.top    = rcSrc.top    + dy;
    rcDst.bottom = rcSrc.bottom + dy;

    /*
     * If necessary, intersect with caller-supplied clip rect.
     */
    if (prcClip) {

        if ((wClip == SIMPLEREGION) &&
            ((hrgnInvalid == NULL) || (hrgnInvalid == HRGN_FULL))) {

            /*
             * Simple clip region: just a rect intersection
             */
            if (!IntersectRect(&rcVis, &rcVis, &rcClip))
                goto NullExit;

        } else {

            if (!fHaveVisRgn) {

                if (GetTrueClipRgn(hdc, ghrgnScrlVis) == ERROR)
                    goto ErrorExit;

                fHaveVisRgn = TRUE;
            }

            SetRectRgnIndirect(ghrgnScrl1, &rcClip);
            wClip = IntersectRgn(ghrgnScrlVis, ghrgnScrl1, ghrgnScrlVis);
            switch (wClip) {
            case ERROR:
                goto ErrorExit;

            case NULLREGION:
                goto NullExit;

            case SIMPLEREGION:

                /*
                 * If the clipped region is simple, we're back in fat
                 * rect city.
                 */
                GreGetRgnBox(ghrgnScrlVis, &rcVis);
                break;

            case COMPLEXREGION:
                break;
            }
        }
    }

    /*
     * Time for basic scrolling area calculations:
     *
     * Dst    = Offset(Src, dx, dy) & Vis
     * Src    = Src & Vis
     * Valid  = Offset(Src, dx, dy) & Dst
     * Valid  = Valid & Invalid & Offset(Invalid, dx, dy)
     * Update = (Src | Dst) - Valid
     *
     * If the vis region is simple, then we know that the valid region
     * will be rectangular.
     *
     * The rectangular calculation case can only deal with
     * ghrgnInvalid == NULL or (HRGN)1: the region case is handled the hard way.
     */
    if ((wClip == SIMPLEREGION) &&
            ((hrgnInvalid == NULL) || (hrgnInvalid == HRGN_FULL))) {

        /*
         * Save a copy of this for update rect calc optimization.
         */
        CopyRect(&rcUnclippedSrc, &rcSrc);

        /*
         * Dst = Offset(Src, dx, dy) & Vis.
         */
        IntersectRect(&rcDst, &rcDst, &rcVis);

        /*
         * Src = Src & Vis.
         */
        fSrcNotEmpty = IntersectRect(&rcSrc, &rcSrc, &rcVis);

        /*
         * Valid = Offset(Src, dx, dy) & Dst.
         */
        if (hrgnInvalid == HRGN_FULL) {
            SetRectEmpty(&rcValid);
        } else {

            rcValid.left   = rcSrc.left   + dx;
            rcValid.right  = rcSrc.right  + dx;
            rcValid.top    = rcSrc.top    + dy;
            rcValid.bottom = rcSrc.bottom + dy;

            IntersectRect(&rcValid, &rcValid, &rcDst);
        }

        /*
         * Now calculate the update area.
         *
         * There are two cases where the result will be a rectangle:
         *
         * 1) The source rectangle lies completely within the visrgn,
         *    and the source and destination don't overlap.  In this
         *    case the update region is equal to the source rect.
         *
         * 2) The clipped source rectangle is empty, in which case
         *    the update region is equal to the clipped dest rect.
         *
         * 3) We're scrolling in one dimension only, and the source
         *    and destination DO overlap.  In this case we can use
         *    UnionRect() and SubtractRect() to do the area arithmetic.
         */
        if (!fSrcNotEmpty) {

            /*
             * Clipped source is empty.  Update area is the clipped dest.
             */
            CopyRect(&rcUpdate, &rcDst);
            goto RectUpdate;

        } else if (IntersectRect(&rcUpdate, &rcSrc, &rcDst)) {

            /*
             * They overlap.  If we're scrolling in one dimension only
             * then we can use rect arithmetic...
             */
            if (dx == 0 || dy == 0) {

                UnionRect(&rcUpdate, &rcSrc, &rcDst);
                SubtractRect(&rcUpdate, &rcUpdate, &rcValid);
                goto RectUpdate;
            }

        } else if (EqualRect(&rcSrc, &rcUnclippedSrc)) {

            /*
             * They don't overlap, and the source lies completely
             * within the visible region.  Update region is the source.
             */
            CopyRect(&rcUpdate, &rcSrc);
RectUpdate:
            if (prcUpdate) {
                CopyRect(prcUpdate, &rcUpdate);
            }

            if (hrgnUpdate && !SetRectRgnIndirect(hrgnUpdate, &rcUpdate)) {
                goto ErrorExit;
            }

            wClip = SIMPLEREGION;
            if (rcUpdate.left >= rcUpdate.right ||
                rcUpdate.top >= rcUpdate.bottom)

                wClip = NULLREGION;

            goto DoRectBlt;
        }

        /*
         * The update region isn't rectangular.  Need to do our
         * area calculations with region calls.  Skip all this
         * if the caller doesn't care about the update region.
         *
         * If he wants a rectangle but no region, use ghrgnScrl2 as a temp.
         */
        if (hrgnUpdate == NULL && prcUpdate) {
            hrgnUpdate = ghrgnScrl2;
        }

        if (hrgnUpdate != NULL) {

            /*
             * hrgnUpdateCalc = (rcSrc | rcDst) - rcBltDst
             */
            SetRectRgnIndirect(ghrgnScrl1, &rcSrc);
            SetRectRgnIndirect(hrgnUpdate, &rcDst);
            if (UnionRgn(hrgnUpdate, hrgnUpdate, ghrgnScrl1) == ERROR)
                goto ErrorExit;

            SetRectRgnIndirect(ghrgnScrl1, &rcValid);
            wClip = SubtractRgn(hrgnUpdate, hrgnUpdate, ghrgnScrl1);
            if (wClip == ERROR)
                goto ErrorExit;

            if (prcUpdate) {
                GreGetRgnBox(hrgnUpdate, prcUpdate);
            }
        }

DoRectBlt:

        /*
         * If the valid rectangle's not empty, then copy those bits...
         */
        if (rcValid.left < rcValid.right && rcValid.top < rcValid.bottom) {

            /*
             * If the DC is in a funny map mode, then be sure to map from
             * device to logical coordinates for BLT call...
             */
            if (fLogUnits)
                GreDPtoLP(hdc, (LPPOINT)&rcValid, 2);

            GreBitBlt(hdc,
                      rcValid.left,
                      rcValid.top,
                      rcValid.right - rcValid.left,
                      rcValid.bottom - rcValid.top,
                      hdc,
                      rcValid.left - dxLog,
                      rcValid.top - dyLog,
                      SRCCOPY,
                      0);
        }

    } else {

        /*
         * Get the true visrgn if we haven't already.
         */
        if (!fHaveVisRgn) {

            if (GetTrueClipRgn(hdc, ghrgnScrlVis) == ERROR)
                goto ErrorExit;

            fHaveVisRgn = TRUE;
        }

        /*
         * The visrgn is not empty.  Need to do all our calculations
         * with regions.
         *
         * hrgnSrc = hrgnSrc & ghrgnScrlVis
         */
        SetRectRgnIndirect(ghrgnScrlSrc, &rcSrc);
        if (IntersectRgn(ghrgnScrlSrc, ghrgnScrlSrc, ghrgnScrlVis) == ERROR)
            goto ErrorExit;

        /*
         * hrgnDst = hrgnDst & ghrgnScrlVis
         */
        SetRectRgnIndirect(ghrgnScrlDst, &rcDst);
        if (IntersectRgn(ghrgnScrlDst, ghrgnScrlDst, ghrgnScrlVis) == ERROR)
            goto ErrorExit;

        /*
         * Now compute the valid region:
         *
         * Valid = Offset(Src, dx, dy) & Dst.
         * Valid = Valid & Invalid & Offset(Invalid, dx, dy)
         *
         * If hrgnInvalid is (HRGN)1, then the valid area is empty.
         */
        wClipValid = NULLREGION;
        if (hrgnInvalid != HRGN_FULL) {

            /*
             * Valid = Offset(Src, dx, dy) & Dst
             */
            if (CopyRgn(ghrgnScrlValid, ghrgnScrlSrc) == ERROR)
                goto ErrorExit;

            GreOffsetRgn(ghrgnScrlValid, dx, dy);
            wClipValid = IntersectRgn(ghrgnScrlValid,
                                      ghrgnScrlValid,
                                      ghrgnScrlDst);

            /*
             * Valid = Valid - Invalid - Offset(Invalid, dx, dy)
             * We need bother only if hrgnInvalid is a real region.
             */
            if (hrgnInvalid > HRGN_FULL) {

                if (wClipValid != ERROR && wClipValid != NULLREGION) {
                    POINT pt;

                    GetDCOrgOnScreen(hdc, &pt);

                    /*
                     * hrgnInvalid is in screen coordinates: map to dc coords
                     */
                    CopyRgn(ghrgnScrl2, hrgnInvalid);
                    GreOffsetRgn(ghrgnScrl2, -pt.x, -pt.y);

                    wClipValid = SubtractRgn(ghrgnScrlValid,
                                             ghrgnScrlValid,
                                             ghrgnScrl2);
                }

                if (wClipValid != ERROR && wClipValid != NULLREGION) {
                    GreOffsetRgn(ghrgnScrl2, dx, dy);

                    wClipValid = SubtractRgn(ghrgnScrlValid,
                                             ghrgnScrlValid,
                                             ghrgnScrl2);
                }
            }

            if (wClipValid == ERROR)
                goto ErrorExit;
        }

        /*
         * If he wants a rectangle but no region, use ghrgnScrl2 as a temp.
         */
        if (hrgnUpdate == NULL && prcUpdate) {
            hrgnUpdate = ghrgnScrl2;
        }

        if (hrgnUpdate != NULL) {

            /*
             * Update = (Src | Dst) - Valid.
             */
            wClip = UnionRgn(hrgnUpdate, ghrgnScrlDst, ghrgnScrlSrc);
            if (wClip == ERROR)
                goto ErrorExit;

            if (wClipValid != NULLREGION) {
                wClip = SubtractRgn(hrgnUpdate, hrgnUpdate, ghrgnScrlValid);
            }

            if (prcUpdate) {
                GreGetRgnBox(hrgnUpdate, prcUpdate);
            }
        }

        if (wClipValid != NULLREGION) {

            #ifdef LATER

                /*
                 * don't use the visrgn here
                 */
                HRGN hrgnSaveVis = CreateEmptyRgn();
                if (hrgnSaveVis != NULL) {

                    BOOL fClipped;

                    fClipped = (GreGetRandomRgn(hdc, hrgnSaveVis, 1) == 1);
                    GreExtSelectClipRgn(hdc, ghrgnScrlValid, RGN_COPY);

                    /*
                     * If the DC is in a funny map mode, then be sure to
                     * map from device to logical coordinates for BLT call...
                     */
                    if (fLogUnits)
                        GreDPtoLP(hdc, (LPPOINT)&rcDst, 2);

                    /*
                     * Gdi can take along time to process this call if
                     * it's a printer DC
                     */
                    GreBitBlt(hdc,
                              rcDst.left,
                              rcDst.top,
                              rcDst.right - rcDst.left,
                              rcDst.bottom - rcDst.top,
                              hdc,
                              rcDst.left - dxLog,
                              rcDst.top - dyLog,
                              SRCCOPY,
                              0);

                    GreExtSelectClipRgn(hdc,
                                        (fClipped ? hrgnSaveVis : NULL),
                                        RGN_COPY);

                    GreDeleteObject(hrgnSaveVis);
                }

            #else

                /*
                 * Visrgn is expected in DC surface coordinates: offset
                 * as appropriate.
                 */
                POINT pt;
                GreGetDCOrg(hdc, &pt);

                GreOffsetRgn(ghrgnScrlValid, pt.x, pt.y);

                /*
                 * Select in the temporary vis rgn, saving the old
                 */

                GreSelectVisRgn(hdc, ghrgnScrlValid, SVR_SWAP);

                /*
                 * If the DC is in a funny map mode, then be sure to map from
                 * device to logical coordinates for BLT call...
                 */
                if (fLogUnits)
                    GreDPtoLP(hdc, (LPPOINT)&rcDst, 2);

                /*
                 * Gdi can take along time to process this call if it's
                 * a printer DC.
                 */
                GreBitBlt(hdc,
                          rcDst.left,
                          rcDst.top,
                          rcDst.right - rcDst.left,
                          rcDst.bottom - rcDst.top,
                          hdc,
                          rcDst.left - dxLog,
                          rcDst.top - dyLog,
                          SRCCOPY,
                          0);

                /*
                 * Restore the old vis rgn, leaving ghrgnScrlValid with
                 * a valid rgn
                 */
                GreSelectVisRgn(hdc, ghrgnScrlValid, SVR_SWAP);

            #endif
        }
    }

    /*
     * If necessary, convert the resultant update rect back
     * to logical coordinates.
     */
    if (fLogUnits && prcUpdate) {
        GreDPtoLP(hdc, (LPPOINT)prcUpdate, 2);
    }

    GreUnlockDisplay(gpDispInfo->hDev);

    return wClip;
}

/***************************************************************************\
* _ScrollDC (API)
*
*
* History:
* 18-Jul-1991 DarrinM   Ported from Win 3.1 sources.
\***************************************************************************/

BOOL _ScrollDC(
    HDC    hdc,
    int    dx,
    int    dy,
    LPRECT prcSrc,
    LPRECT prcClip,
    HRGN   hrgnUpdate,
    LPRECT prcUpdate)
{
    RECT rcSrc;
    RECT rcSpb;
    PWND pwnd;
    HRGN hrgnInvalid;
    BOOL fRet;

    /*
     * ScrollDC does not scroll update region. Under WinNT, an app calling
     * GetUpdateRgn() then ScrollDC() then InvalidateRgn() will not get
     * any new update region that happened between the Get and Scroll. Under
     * Win3.1, that was not a problem because no other app ran during this
     * time. So pass hrgnInvalid - this will affect the hrgnUpdate and
     * prcUpdate values being returned from ScrollDC with the update region.
     */
    hrgnInvalid = NULL;
    if ((pwnd = FastWindowFromDC(hdc)) != NULL) {

        hrgnInvalid = pwnd->hrgnUpdate;

        if (hrgnInvalid == HRGN_FULL) {

            /*
             * This is a fix for winhell, a performance testing app
             * written by some guy working for a windows magazine.
             * this app scrolls it's window while it is completely
             * invalid.  We normaly won't scroll invalid bits but
             * but we make the exception here
             */
            hrgnInvalid = NULL;
        }
    }

    fRet = InternalScrollDC(hdc,
                            dx,
                            dy,
                            prcSrc,
                            prcClip,
                            hrgnInvalid,
                            hrgnUpdate,
                            prcUpdate,
                            TRUE) != ERROR;

    /*
     * InternalScrollDC() only scrolls those areas inside the visible region.
     * This means it does no operations on parts of the window if the window
     * isn't visible. This means SPBs don't get properly invalidated. This
     * could be seen by starting a dir, then moving another window with the
     * mouse (and keeping the mouse down until the dir finished). The
     * screen is remembered with an SPB, and the dir window doesn't get
     * properly invalidated because of this.
     */
    if (pwnd != NULL && AnySpbs()) {

        if (prcSrc) {

            rcSrc = *prcSrc;
            OffsetRect(&rcSrc, pwnd->rcClient.left, pwnd->rcClient.top);

            rcSpb = rcSrc;
            OffsetRect(&rcSpb, dx, dy);
            UnionRect(&rcSpb, &rcSpb, &rcSrc);

        } else {
            rcSpb = pwnd->rcClient;
        }

        SpbCheckRect(pwnd, &rcSpb, 0);
    }

    return fRet;
}

/***************************************************************************\
* ScrollWindowEx (API)
*
*
* History:
* 18-Jul-1991 DarrinM   Ported from Win 3.1 sources.
\***************************************************************************/
int xxxScrollWindowEx(
    PWND    pwnd,
    int    dx,
    int    dy,
    RECT   *prcScroll,
    RECT   *prcClip,
    HRGN   hrgnUpdate,
    LPRECT prcUpdate,
    DWORD  flags)
{
    INT    code;
    HDC    hdc;
    int    dxDev;
    int    dyDev;
    RECT   rcSrcDev;
    RECT   rcSpb, rcSrc;
    DWORD  flagsDCX;
    BOOL   fHideCaret;
    BOOL   fRcScroll = (prcScroll != NULL);
    BOOL   fInvisible = FALSE;
    PCARET pcaret;
    POINT  pt;
    TL     tlpwndChild;
    HRGN   hrgnInvalid;
    PTHREADINFO ptiCurrent = PtiCurrent();

    CheckLock(pwnd);
    UserAssert(IsWinEventNotifyDeferredOK());


    if (pwnd == NULL)
        pwnd = ptiCurrent->rpdesk->pDeskInfo->spwnd;       // pwndDesktop

    if (TestWF(pwnd, WEFLAYOUTRTL)) {
        dx = -dx;

        MirrorRegion(pwnd, hrgnUpdate, TRUE);

        if(prcScroll) {
            MirrorClientRect(pwnd, prcScroll);
        }

        if (prcClip) {
            MirrorClientRect(pwnd, prcClip);
        }
    }

    /*
     * If nothing's moving, nothing to do.
     */
    if ((dx | dy) == 0 ) {

        goto DoNothing;

    } else if (!IsVisible(pwnd)) {

        /* We want to offset our children if we're not minimized.  IsVisible()
         * will return FALSE if we're minimized, invisible, or the child of
         * a minimized/invisible ancestore.
         */
        if (!TestWF(pwnd, WFMINIMIZED) &&
          (flags & SW_SCROLLCHILDREN) &&
          !fRcScroll) {

            fInvisible = TRUE;
            flags &= ~SW_INVALIDATE;
        }

DoNothing:

        if (hrgnUpdate) {
            SetEmptyRgn(hrgnUpdate);
        }

        if (prcUpdate) {
            SetRectEmpty(prcUpdate);
        }

        if (!fInvisible)
            return NULLREGION;
    }

    /*
     * Hide the caret.
     */
    fHideCaret = FALSE;

    if (!fInvisible) {
        pcaret = &ptiCurrent->pq->caret;
        if (pcaret->spwnd != NULL && _IsDescendant(pcaret->spwnd, pwnd)) {
            fHideCaret = TRUE;
            zzzInternalHideCaret();
        }
    }

    /*
     * If scrollwindow, and window is clipchildren, use a cache entry.
     * Otherwise, always use a
     *
     * Determine what kind of DC we'll be needing.  If the DCX_CACHE bit
     * isn't set, it means that we'll be operating in logical coordinates.
     */
    if (flags & SW_SCROLLWINDOW) {

        /*
         *  ScrollWindow() call: use the cache if not OWNDC or CLASSDC.
         */
        flagsDCX = DCX_USESTYLE;
        if (!TestCF(pwnd, CFOWNDC) && !TestCF(pwnd, CFCLASSDC))
            flagsDCX |= DCX_CACHE;

        /*
         * If SW_SCROLLCHILDREN (i.e., lprcScroll == NULL) and CLIPCHILDREN,
         * then use the cache and don't clip children.
         * This is screwy, but 3.0 backward compatible.
         */
        if ((flags & SW_SCROLLCHILDREN) && TestWF(pwnd, WFCLIPCHILDREN))
            flagsDCX |= DCX_NOCLIPCHILDREN | DCX_CACHE;

    } else {

        /*
         * ScrollWindowEx() call: always use the cache
         */
        flagsDCX = DCX_USESTYLE | DCX_CACHE;

        /*
         * if SW_SCROLLCHILDREN, always use noclipchildren.
         */
        if (flags & SW_SCROLLCHILDREN)
            flagsDCX |= DCX_NOCLIPCHILDREN;
    }

    flagsDCX |= DCX_NOMIRROR;
    hdc = _GetDCEx(pwnd, NULL, flagsDCX);

    if (flags & SW_INVALIDATE) {

        /*
         * Get device origin while DC is valid, for later offsetting
         */
        GetDCOrgOnScreen(hdc, &pt);

        /*
         * If the user didn't give us a region to use, use ghrgnSW.
         */
        if (hrgnUpdate == NULL)
            hrgnUpdate = ghrgnSW;
    }

    /*
     * The DC will be in some logical coordinate system if OWNDC or CLASSDC.
     */
    if (!fRcScroll) {
        prcScroll = &rcSrc;

        /*
         * IMPORTANT:
         * We have to use CopyOffsetRect() here because GetClientRect() gives
         * unreliable results for minimized windows.  3.1 dudes get told that
         * their client is non-empty, for compatibility reasons.
         */
        GetRect(pwnd, &rcSrc, GRECT_CLIENT | GRECT_CLIENTCOORDS);

        /*
         * If the DC might be a screwy one, then map the
         * rect to logical units.
         */
        if (!(flagsDCX & DCX_CACHE))
            GreDPtoLP(hdc, (LPPOINT)&rcSrc, 2);
    }

    /*
     * If the DC is in logical coordinates, map *prcScroll and dx, dy
     * to device units for use later.
     */
    dxDev = dx;
    dyDev = dy;
    rcSrcDev = *prcScroll;

    if (!(flagsDCX & DCX_CACHE)) {

        POINT rgpt[2];

        GreLPtoDP(hdc, (POINT FAR*)&rcSrcDev, 2);

        /*
         * The delta values must be treated as a vector from
         * the point (0, 0) to (dx, dy).  Scale it as such, then
         * compute the difference.  This handles flipped coordinate systems.
         */
        rgpt[0].x = rgpt[0].y = 0;
        rgpt[1].x = dx;
        rgpt[1].y = dy;

        GreLPtoDP(hdc, rgpt, 2);

        dxDev = rgpt[1].x - rgpt[0].x;
        dyDev = rgpt[1].y - rgpt[0].y;
    }

    if (fInvisible)
        code = NULLREGION;
    else {
        hrgnInvalid = pwnd->hrgnUpdate;
        if ((flags & SW_SCROLLWINDOW) && !TestWF(pwnd, WFWIN31COMPAT)) {
            /*
             * 3.0 Backward compatibility hack:
             * The following incorrect code is what 3.0 used to do, and
             * there are apps such as Finale and Scrapbook+ that have worked
             * around this bug in ways that don't work with the "correct" code.
             */
            if (pwnd->hrgnUpdate > HRGN_FULL) {
                RECT rc;

                GreGetRgnBox(pwnd->hrgnUpdate, &rc);
                OffsetRect(&rc,
                           dxDev - pwnd->rcClient.left,
                           dyDev - pwnd->rcClient.top);

                xxxRedrawWindow(pwnd,
                                &rc, NULL,
                                RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
            }
            hrgnInvalid = NULL;
        }

        code = InternalScrollDC(hdc,
                                dx,
                                dy,
                                prcScroll,
                                prcClip,
                                hrgnInvalid,
                                hrgnUpdate,
                                prcUpdate,
                                !(flagsDCX & DCX_CACHE));
        if (prcUpdate && TestWF(pwnd, WEFLAYOUTRTL)) {
            MirrorClientRect(pwnd, prcUpdate);
        }
    }

    /*
     * Release the hdc we used.
     */
    _ReleaseDC(hdc);

    /*
     * Check the union of the src and dst rectangle against any SPBs.
     * We do this because the window
     * might be completely obscured by some window with an SPB, but
     * since we're completely covered no BitBlt call will be made
     * to accumulate bounds in that area.
     */
    if (!fInvisible && AnySpbs()) {

        if (fRcScroll) {
            if (pwnd == PWNDDESKTOP(pwnd)) {
                rcSrc = rcSrcDev;
            } else {
                CopyOffsetRect(
                        &rcSrc,
                        &rcSrcDev,
                        pwnd->rcClient.left,
                        pwnd->rcClient.top);
            }

            rcSpb = rcSrc;
            OffsetRect(&rcSpb, dxDev, dyDev);
            UnionRect(&rcSpb, &rcSpb, &rcSrc);

        } else {

            /*
             * Use the entire client area.
             */
            rcSpb = pwnd->rcClient;
        }

        SpbCheckRect(pwnd, &rcSpb, 0);
    }

    /*
     * If this guy wants to scroll his children, go at it.  Only scroll those
     * children intersecting prcScroll.  Then invalidate any vis rgns
     * calculated for these child windows.
     */
    if (flags & SW_SCROLLCHILDREN) {

        RECT rc;

        /*
         * If this window has the caret then offset it if:
         * a) The whole window is scrolling
         * b) The rectangle scrolled contains the caret rectangle
         */
        if (!fInvisible && (pwnd == pcaret->spwnd)) {

            if (fRcScroll)
                SetRect(&rc,
                        pcaret->x,
                        pcaret->y,
                        pcaret->x + pcaret->cx,
                        pcaret->y + pcaret->cy);

            if (!fRcScroll || IntersectRect(&rc, &rc, &rcSrcDev)) {
                pcaret->x += dxDev;
                pcaret->y += dyDev;
            }
        }

        if (fRcScroll) {

            /*
             * Create a copy of prcScroll and map to absolute coordinates...
             */
            if (pwnd == PWNDDESKTOP(pwnd)) {
                CopyRect(&rc, &rcSrcDev);
            } else {
                CopyOffsetRect(
                        &rc,
                        &rcSrcDev,
                        pwnd->rcClient.left,
                        pwnd->rcClient.top);
            }
        }

        if (pwnd->spwndChild) {

            OffsetChildren(pwnd,
                           dxDev,
                           dyDev,
                           (fRcScroll ? (LPRECT)&rc : NULL));

            /*
             * If we're clipchildren, then shuffling our children
             * will affect our client visrgn (but not our window visrgn).
             * Otherwise, only our children's
             * visrgns were affected by the scroll.
             * No need to DeferWinEventNotify() judging by xxxInternalInvalidate() below
             */
            zzzInvalidateDCCache(pwnd,
                              TestWF(pwnd, WFCLIPCHILDREN) ?
                                  IDC_CLIENTONLY : IDC_CHILDRENONLY);

        }
    }

    if (flags & SW_INVALIDATE) {

        /*
         * If the caller supplied a region, invalidate using a copy,
         * because InternalInvalidate may trash the passed-in region.
         */
        if (hrgnUpdate != ghrgnSW)
            CopyRgn(ghrgnSW, hrgnUpdate);

        /*
         * Make ghrgnSW screen-relative before invalidation...
         */
        GreOffsetRgn(ghrgnSW, pt.x, pt.y);

        xxxInternalInvalidate(
                pwnd,
                ghrgnSW,
                (flags & SW_ERASE) ?
                    (RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_ERASE) :
                    (RDW_INVALIDATE | RDW_ALLCHILDREN));
    }

    /*
     * Send child move messages if needed.
     */
    if (flags & SW_SCROLLCHILDREN) {

        PWND pwndChild;
        RECT rc;
        RECT rcScrolledChildren;

        /*
         * NOTE: the following code will send MOVE messages
         * to windows that didn't move but were in the source rectangle.
         * This is not a big deal, and definitely not worth fixing.
         */
        if (fRcScroll) {
            if (pwnd->spwndParent == PWNDDESKTOP(pwnd)) {
                CopyOffsetRect(&rcScrolledChildren, &rcSrcDev, dxDev, dyDev);
            } else {
                CopyOffsetRect(
                        &rcScrolledChildren,
                        &rcSrcDev,
                        dxDev + pwnd->spwndParent->rcClient.left,
                        dyDev + pwnd->spwndParent->rcClient.top);
            }
        }

        ThreadLockNever(&tlpwndChild);
        pwndChild = pwnd->spwndChild;
        while (pwndChild != NULL) {

            if (    !fRcScroll ||
                    IntersectRect(&rc, &rcScrolledChildren, &pwndChild->rcWindow)) {

                /*
                 * NOTE: Win 3.0 and below passed wParam == TRUE here.
                 * This was not documented or used, so it was changed
                 * to be consistent with the documentation.
                 */
                ThreadLockExchangeAlways(pwndChild, &tlpwndChild);
                xxxSendMessage(
                        pwndChild,
                        WM_MOVE,
                        0,
                        (pwnd == PWNDDESKTOP(pwnd)) ?
                            MAKELONG(pwndChild->rcClient.left, pwndChild->rcClient.top) :
                            MAKELONG(pwndChild->rcClient.left - pwnd->rcClient.left,
                                     pwndChild->rcClient.top - pwnd->rcClient.top));
            }

            pwndChild = pwndChild->spwndNext;
        }

        ThreadUnlock(&tlpwndChild);
    }

    if (fHideCaret) {

        /*
         * Show the caret again.
         */
        zzzInternalShowCaret();
    }

    /*
     * Return the region code.
     */
    return code;
}