text: /*
   *  COPYRIGHT:        See COPYING in the top level directory
   *  PROJECT:          ReactOS Win32k subsystem
   *  PURPOSE:          Window scrolling function
   *  FILE:             win32ss/user/ntuser/scrollex.c
   *  PROGRAMER:        Filip Navara (xnavara@volny.cz)
   */
  
  #include <win32k.h>
  
  DBG_DEFAULT_CHANNEL(UserPainting);
  
  static
  HWND FASTCALL
  co_IntFixCaret(PWND Window, RECTL *lprc, UINT flags)
  {
     PTHRDCARETINFO CaretInfo;
     PTHREADINFO pti;
     PUSER_MESSAGE_QUEUE ThreadQueue;
     HWND hWndCaret;
     PWND WndCaret;
  
     ASSERT_REFS_CO(Window);
  
     pti = PsGetCurrentThreadWin32Thread();
     ThreadQueue = pti->MessageQueue;
     CaretInfo = &ThreadQueue->CaretInfo;
     hWndCaret = CaretInfo->hWnd;
  
     WndCaret = ValidateHwndNoErr(hWndCaret);
  
     // FIXME: Check for WndCaret can be NULL
     if (WndCaret == Window ||
           ((flags & SW_SCROLLCHILDREN) && IntIsChildWindow(Window, WndCaret)))
     {
        POINT pt, FromOffset, ToOffset;
        RECTL rcCaret;
  
        pt.x = CaretInfo->Pos.x;
        pt.y = CaretInfo->Pos.y;
        IntGetClientOrigin(WndCaret, &FromOffset);
        IntGetClientOrigin(Window, &ToOffset);
        rcCaret.left = pt.x;
        rcCaret.top = pt.y;
        rcCaret.right = pt.x + CaretInfo->Size.cx;
        rcCaret.bottom = pt.y + CaretInfo->Size.cy;
        if (RECTL_bIntersectRect(lprc, lprc, &rcCaret))
        {
           co_UserHideCaret(0);
           lprc->left = pt.x;
           lprc->top = pt.y;
           return hWndCaret;
        }
     }
  
     return 0;
  }
  
  /*
      Old GetUpdateRgn, for scrolls, see above note.
   */
  INT FASTCALL
  co_IntGetUpdateRgn(PWND Window, PREGION Rgn, BOOL bErase)
  {
      int RegionType;
      RECTL Rect;
      PREGION UpdateRgn;
  
      ASSERT_REFS_CO(Window);
  
      if (bErase)
      {
         co_IntPaintWindows(Window, RDW_NOCHILDREN, FALSE);
      }
  
      Window->state &= ~WNDS_UPDATEDIRTY;
  
      if (Window->hrgnUpdate == NULL)
      {
          REGION_SetRectRgn(Rgn, 0, 0, 0, 0);
          return NULLREGION;
      }
  
      UpdateRgn = REGION_LockRgn(Window->hrgnUpdate);
      if (!UpdateRgn)
         return ERROR;
  
      Rect = Window->rcClient;
      IntIntersectWithParents(Window, &Rect);
      REGION_SetRectRgn(Rgn, Rect.left, Rect.top, Rect.right, Rect.bottom);
      RegionType = IntGdiCombineRgn(Rgn, Rgn, UpdateRgn, RGN_AND);
      REGION_bOffsetRgn(Rgn, -Window->rcClient.left, -Window->rcClient.top);
      REGION_UnlockRgn(UpdateRgn);
  
      return RegionType;
  }
  
  static
  INT FASTCALL
  UserScrollDC(
     HDC hDC,
     INT dx,
     INT dy,
     const RECTL *prcScroll,
     const RECTL *prcClip,
     HRGN hrgnUpdate,
     PREGION RgnUpdate,
     RECTL *prcUpdate)
  {
     PDC pDC;
     RECTL rcScroll, rcClip, rcSrc, rcDst;
     INT Result;
  
     if (GdiGetClipBox(hDC, &rcClip) == ERROR)
     {
         ERR("GdiGetClipBox failed for HDC %p\n", hDC);
         return ERROR;
     }
  
     rcScroll = rcClip;
     if (prcClip)
     {
        RECTL_bIntersectRect(&rcClip, &rcClip, prcClip);
     }
  
     if (prcScroll)
     {
        rcScroll = *prcScroll;
        RECTL_bIntersectRect(&rcSrc, &rcClip, prcScroll);
     }
     else
     {
        rcSrc = rcClip;
     }
  
     rcDst = rcSrc;
     RECTL_vOffsetRect(&rcDst, dx, dy);
     RECTL_bIntersectRect(&rcDst, &rcDst, &rcClip);
  
     if (!NtGdiBitBlt( hDC,
                       rcDst.left,
                       rcDst.top,
                       rcDst.right - rcDst.left,
                       rcDst.bottom - rcDst.top,
                       hDC,
                       rcDst.left - dx,
                       rcDst.top - dy,
                       SRCCOPY,
                       0,
                       0))
     {
        return ERROR;
     }
  
     /* Calculate the region that was invalidated by moving or
        could not be copied, because it was not visible */
     if (RgnUpdate || hrgnUpdate || prcUpdate)
     {
        PREGION RgnOwn, RgnTmp;
  
        pDC = DC_LockDc(hDC);
        if (!pDC)
        {
           return ERROR;
        }
  
         if (hrgnUpdate)
         {
             NT_ASSERT(RgnUpdate == NULL);
             RgnUpdate = REGION_LockRgn(hrgnUpdate);
             if (!RgnUpdate)
             {
                 DC_UnlockDc(pDC);
                 return ERROR;
             }
         }
  
        /* Begin with the shifted and then clipped scroll rect */
        rcDst = rcScroll;
        RECTL_vOffsetRect(&rcDst, dx, dy);
        RECTL_bIntersectRect(&rcDst, &rcDst, &rcClip);
        if (RgnUpdate)
        {
           RgnOwn = RgnUpdate;
           REGION_SetRectRgn(RgnOwn, rcDst.left, rcDst.top, rcDst.right, rcDst.bottom);
        }
        else
        {
           RgnOwn = IntSysCreateRectpRgnIndirect(&rcDst);
        }
  
        /* Add the source rect */
        RgnTmp = IntSysCreateRectpRgnIndirect(&rcSrc);
        IntGdiCombineRgn(RgnOwn, RgnOwn, RgnTmp, RGN_OR);
  
        /* Substract the part of the dest that was visible in source */
        IntGdiCombineRgn(RgnTmp, RgnTmp, pDC->prgnVis, RGN_AND);
        REGION_bOffsetRgn(RgnTmp, dx, dy);
        Result = IntGdiCombineRgn(RgnOwn, RgnOwn, RgnTmp, RGN_DIFF);
  
        /* DO NOT Unlock DC while messing with prgnVis! */
        DC_UnlockDc(pDC);
  
        REGION_Delete(RgnTmp);
  
        if (prcUpdate)
        {
           REGION_GetRgnBox(RgnOwn, prcUpdate);
        }
  
        if (hrgnUpdate)
        {
           REGION_UnlockRgn(RgnUpdate);
        }
        else if (!RgnUpdate)
        {
           REGION_Delete(RgnOwn);
        }
     }
     else
        Result = NULLREGION;
  
     return Result;
  }
  
  DWORD
  FASTCALL
  IntScrollWindowEx(
     PWND Window,
     INT dx,
     INT dy,
     const RECT *prcScroll,
     const RECT *prcClip,
     HRGN hrgnUpdate,
     LPRECT prcUpdate,
     UINT flags)
  {
     INT Result;
     RECTL rcScroll, rcClip, rcCaret;
     PWND CaretWnd;
     HDC hDC;
     PREGION RgnUpdate = NULL, RgnTemp, RgnWinupd = NULL;
     HWND hwndCaret;
     DWORD dcxflags = 0;
     int rdw_flags;
     USER_REFERENCE_ENTRY CaretRef;
  
     if (!Window || !IntIsWindowDrawable(Window))
     {
        return ERROR;
     }
  
     IntGetClientRect(Window, &rcClip);
  
     if (prcScroll)
        RECTL_bIntersectRect(&rcScroll, &rcClip, prcScroll);
     else
        rcScroll = rcClip;
  
     if (prcClip)
        RECTL_bIntersectRect(&rcClip, &rcClip, prcClip);
  
     if (rcClip.right <= rcClip.left || rcClip.bottom <= rcClip.top ||
           (dx == 0 && dy == 0))
     {
        return NULLREGION;
     }
  
     /* We must use a copy of the region, as we can't hold an exclusive lock
      * on it while doing callouts to user-mode */
     RgnUpdate = IntSysCreateRectpRgn(0, 0, 0, 0);
     if(!RgnUpdate)
     {
         EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
         return ERROR;
     }
  
     if (hrgnUpdate)
     {
         RgnTemp = REGION_LockRgn(hrgnUpdate);
         if (!RgnTemp)
         {
             EngSetLastError(ERROR_INVALID_HANDLE);
             Result = ERROR;
             goto Cleanup;
         }
         IntGdiCombineRgn(RgnUpdate, RgnTemp, NULL, RGN_COPY);
         REGION_UnlockRgn(RgnTemp);
     }
  
     /* ScrollWindow uses the window DC, ScrollWindowEx doesn't */
     if (flags & SW_SCROLLWNDDCE)
     {
        dcxflags = DCX_USESTYLE;
  
        if (!(Window->pcls->style & (CS_OWNDC|CS_CLASSDC)))
           dcxflags |= DCX_CACHE; // AH??? wine~ If not Powned or with Class go Cheap!
  
        if (flags & SW_SCROLLCHILDREN && Window->style & WS_CLIPCHILDREN)
           dcxflags |= DCX_CACHE|DCX_NOCLIPCHILDREN;
     }
     else
     {
         /* So in this case ScrollWindowEx uses Cache DC. */
         dcxflags = DCX_CACHE|DCX_USESTYLE;
         if (flags & SW_SCROLLCHILDREN) dcxflags |= DCX_NOCLIPCHILDREN;
     }
  
     hDC = UserGetDCEx(Window, 0, dcxflags);
     if (!hDC)
     {
        /* FIXME: SetLastError? */
        Result = ERROR;
        goto Cleanup;
     }
  
     rdw_flags = (flags & SW_ERASE) && (flags & SW_INVALIDATE) ? RDW_INVALIDATE | RDW_ERASE : RDW_INVALIDATE ;
  
     rcCaret = rcScroll;
     hwndCaret = co_IntFixCaret(Window, &rcCaret, flags);
  
     Result = UserScrollDC( hDC,
                            dx,
                            dy,
                            &rcScroll,
                            &rcClip,
                            NULL,
                            RgnUpdate,
                            prcUpdate);
  
     UserReleaseDC(Window, hDC, FALSE);
  
     /*
      * Take into account the fact that some damage may have occurred during
      * the scroll. Keep a copy in hrgnWinupd to be added to hrngUpdate at the end.
      */
  
     RgnTemp = IntSysCreateRectpRgn(0, 0, 0, 0);
     if (!RgnTemp)
     {
         EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
         Result = ERROR;
         goto Cleanup;
     }
  
     if (co_IntGetUpdateRgn(Window, RgnTemp, FALSE) != NULLREGION)
     {
        PREGION RgnClip = IntSysCreateRectpRgnIndirect(&rcClip);
        if (RgnClip)
        {
            if (hrgnUpdate)
            {
               RgnWinupd = IntSysCreateRectpRgn(0, 0, 0, 0);
               // FIXME: What to do if RgnWinupd == NULL??
               IntGdiCombineRgn( RgnWinupd, RgnTemp, 0, RGN_COPY);
            }
  
            REGION_bOffsetRgn(RgnTemp, dx, dy);
  
            IntGdiCombineRgn(RgnTemp, RgnTemp, RgnClip, RGN_AND);
  
            if (hrgnUpdate)
                IntGdiCombineRgn( RgnWinupd, RgnWinupd, RgnTemp, RGN_OR );
  
            co_UserRedrawWindow(Window, NULL, RgnTemp, rdw_flags );
  
            REGION_Delete(RgnClip);
        }
     }
     REGION_Delete(RgnTemp);
  
     if (flags & SW_SCROLLCHILDREN)
     {
        PWND Child;
        RECTL rcChild;
        POINT ClientOrigin;
        USER_REFERENCE_ENTRY WndRef;
        RECTL rcDummy;
        LPARAM lParam;
  
        IntGetClientOrigin(Window, &ClientOrigin);
  
        for (Child = Window->spwndChild; Child; Child = Child->spwndNext)
        {
           rcChild = Child->rcWindow;
           RECTL_vOffsetRect(&rcChild, -ClientOrigin.x, -ClientOrigin.y);
  
           /* Adjust window positions */
           RECTL_vOffsetRect(&Child->rcWindow, dx, dy);
           RECTL_vOffsetRect(&Child->rcClient, dx, dy);
  
           if (!prcScroll || RECTL_bIntersectRect(&rcDummy, &rcChild, &rcScroll))
           {
              UserRefObjectCo(Child, &WndRef);
  
              if (UserIsDesktopWindow(Window->spwndParent))
                 lParam = MAKELONG(Child->rcClient.left, Child->rcClient.top);
              else
                 lParam = MAKELONG(rcChild.left + dx, rcChild.top + dy);
  
              /* wine sends WM_POSCHANGING, WM_POSCHANGED messages */
              /* windows sometimes a WM_MOVE */
              co_IntSendMessage(UserHMGetHandle(Child), WM_MOVE, 0, lParam);
  
              UserDerefObjectCo(Child);
           }
        }
     }
  
     if (flags & (SW_INVALIDATE | SW_ERASE))
     {
        co_UserRedrawWindow( Window,
                             NULL,
                             RgnUpdate,
                             rdw_flags |                                    /*    HACK    */
                            ((flags & SW_SCROLLCHILDREN) ? RDW_ALLCHILDREN : RDW_NOCHILDREN) );
     }
  
     if (hwndCaret && (CaretWnd = UserGetWindowObject(hwndCaret)))
     {
        UserRefObjectCo(CaretWnd, &CaretRef);
  
        co_IntSetCaretPos(rcCaret.left + dx, rcCaret.top + dy);
        co_UserShowCaret(CaretWnd);
  
        UserDerefObjectCo(CaretWnd);
     }
  
     if (hrgnUpdate && (Result != ERROR))
     {
         /* Give everything back to the caller */
         RgnTemp = REGION_LockRgn(hrgnUpdate);
         /* The handle should still be valid */
         ASSERT(RgnTemp);
         if (RgnWinupd)
             IntGdiCombineRgn(RgnTemp, RgnUpdate, RgnWinupd, RGN_OR);
         else
             IntGdiCombineRgn(RgnTemp, RgnUpdate, NULL, RGN_COPY);
         REGION_UnlockRgn(RgnTemp);
     }
  
  Cleanup:
     if (RgnWinupd)
     {
         REGION_Delete(RgnWinupd);
     }
  
     if (RgnUpdate)
     {
        REGION_Delete(RgnUpdate);
     }
  
     return Result;
  }
  
  
  BOOL FASTCALL
  IntScrollWindow(PWND pWnd,
                  int dx,
                  int dy,
                  CONST RECT *lpRect,
                  CONST RECT *prcClip)
  {
     return IntScrollWindowEx( pWnd, dx, dy, lpRect, prcClip, 0, NULL,
                              (lpRect ? 0 : SW_SCROLLCHILDREN) | (SW_ERASE|SW_INVALIDATE|SW_SCROLLWNDDCE)) != ERROR;
  }
  
  /*
   * NtUserScrollDC
   *
   * Status
   *    @implemented
   */
  BOOL APIENTRY
  NtUserScrollDC(
     HDC hDC,
     INT dx,
     INT dy,
     const RECT *prcUnsafeScroll,
     const RECT *prcUnsafeClip,
     HRGN hrgnUpdate,
     LPRECT prcUnsafeUpdate)
  {
     DECLARE_RETURN(DWORD);
     DWORD Result;
     NTSTATUS Status = STATUS_SUCCESS;
     RECTL rcScroll, rcClip, rcUpdate;
  
     TRACE("Enter NtUserScrollDC\n");
     UserEnterExclusive();
  
     _SEH2_TRY
     {
        if (prcUnsafeScroll)
        {
           ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
           rcScroll = *prcUnsafeScroll;
        }
        if (prcUnsafeClip)
        {
           ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
           rcClip = *prcUnsafeClip;
        }
        if (prcUnsafeUpdate)
        {
           ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
        }
     }
     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
     {
        Status = _SEH2_GetExceptionCode();
     }
     _SEH2_END;
  
     if (!NT_SUCCESS(Status))
     {
        SetLastNtError(Status);
        RETURN(FALSE);
     }
  
     Result = UserScrollDC( hDC,
                            dx,
                            dy,
                            prcUnsafeScroll ? &rcScroll : NULL,
                            prcUnsafeClip   ? &rcClip   : NULL,
                            hrgnUpdate,
                            NULL,
                            prcUnsafeUpdate ? &rcUpdate : NULL);
     if(Result == ERROR)
     {
        /* FIXME: Only if hRgnUpdate is invalid we should SetLastError(ERROR_INVALID_HANDLE) */
        RETURN(FALSE);
     }
  
     if (prcUnsafeUpdate)
     {
        _SEH2_TRY
        {
           *prcUnsafeUpdate = rcUpdate;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
           Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;
  
        if (!NT_SUCCESS(Status))
        {
           /* FIXME: SetLastError? */
           /* FIXME: correct? We have already scrolled! */
           RETURN(FALSE);
        }
     }
  
     RETURN(TRUE);
  
  CLEANUP:
     TRACE("Leave NtUserScrollDC, ret=%lu\n",_ret_);
     UserLeave();
     END_CLEANUP;
  }
  
  /*
   * NtUserScrollWindowEx
   *
   * Status
   *    @implemented
   */
  
  DWORD APIENTRY
  NtUserScrollWindowEx(
     HWND hWnd,
     INT dx,
     INT dy,
     const RECT *prcUnsafeScroll,
     const RECT *prcUnsafeClip,
     HRGN hrgnUpdate,
     LPRECT prcUnsafeUpdate,
     UINT flags)
  {
     DECLARE_RETURN(DWORD);
     INT Result;
     NTSTATUS Status = STATUS_SUCCESS;
     PWND Window = NULL;
     RECTL rcScroll, rcClip, rcUpdate;
     USER_REFERENCE_ENTRY Ref;
  
     TRACE("Enter NtUserScrollWindowEx\n");
     UserEnterExclusive();
  
     Window = UserGetWindowObject(hWnd);
     if (!Window || !IntIsWindowDrawable(Window))
     {
        Window = NULL; /* prevent deref at cleanup */
        RETURN(ERROR);
     }
     UserRefObjectCo(Window, &Ref);
  
     _SEH2_TRY
     {
        if (prcUnsafeScroll)
        {
           ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
           rcScroll = *prcUnsafeScroll;
        }
  
        if (prcUnsafeClip)
        {
           ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
           rcClip = *prcUnsafeClip;
        }
     }
     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
     {
        Status = _SEH2_GetExceptionCode();
     }
     _SEH2_END;
  
     if (!NT_SUCCESS(Status))
     {
        SetLastNtError(Status);
        RETURN(ERROR);
     }
  
     Result = IntScrollWindowEx(Window,
                                dx, dy,
                                prcUnsafeScroll ? &rcScroll : NULL,
                                prcUnsafeClip   ? &rcClip   : NULL,
                                hrgnUpdate,
                                prcUnsafeUpdate ? &rcUpdate : NULL,
                                flags);
  
     if (prcUnsafeUpdate)
     {
        _SEH2_TRY
        {
           /* Probe here, to not fail on invalid pointer before scrolling */
           ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
           *prcUnsafeUpdate = rcUpdate;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
           Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;
  
        if (!NT_SUCCESS(Status))
        {
           SetLastNtError(Status);
           RETURN(ERROR);
        }
     }
  
     RETURN(Result);
  
  CLEANUP:
     if (Window)
        UserDerefObjectCo(Window);
  
     TRACE("Leave NtUserScrollWindowEx, ret=%lu\n",_ret_);
     UserLeave();
     END_CLEANUP;
  }
  
  /* EOF */
  