xref: /reactos/win32ss/user/ntuser/scrollex.c (revision 682f85ad)
1 /*
2  *  COPYRIGHT:        See COPYING in the top level directory
3  *  PROJECT:          ReactOS Win32k subsystem
4  *  PURPOSE:          Window scrolling function
5  *  FILE:             win32ss/user/ntuser/scrollex.c
6  *  PROGRAMER:        Filip Navara (xnavara@volny.cz)
7  */
8 
9 #include <win32k.h>
10 
11 DBG_DEFAULT_CHANNEL(UserPainting);
12 
13 static
14 HWND FASTCALL
15 co_IntFixCaret(PWND Window, RECTL *lprc, UINT flags)
16 {
17    PTHRDCARETINFO CaretInfo;
18    PTHREADINFO pti;
19    PUSER_MESSAGE_QUEUE ThreadQueue;
20    HWND hWndCaret;
21    PWND WndCaret;
22 
23    ASSERT_REFS_CO(Window);
24 
25    pti = PsGetCurrentThreadWin32Thread();
26    ThreadQueue = pti->MessageQueue;
27    CaretInfo = &ThreadQueue->CaretInfo;
28    hWndCaret = CaretInfo->hWnd;
29 
30    WndCaret = ValidateHwndNoErr(hWndCaret);
31 
32    // FIXME: Check for WndCaret can be NULL
33    if (WndCaret == Window ||
34          ((flags & SW_SCROLLCHILDREN) && IntIsChildWindow(Window, WndCaret)))
35    {
36       POINT pt, FromOffset, ToOffset;
37       RECTL rcCaret;
38 
39       pt.x = CaretInfo->Pos.x;
40       pt.y = CaretInfo->Pos.y;
41       IntGetClientOrigin(WndCaret, &FromOffset);
42       IntGetClientOrigin(Window, &ToOffset);
43       rcCaret.left = pt.x;
44       rcCaret.top = pt.y;
45       rcCaret.right = pt.x + CaretInfo->Size.cx;
46       rcCaret.bottom = pt.y + CaretInfo->Size.cy;
47       if (RECTL_bIntersectRect(lprc, lprc, &rcCaret))
48       {
49          co_UserHideCaret(0);
50          lprc->left = pt.x;
51          lprc->top = pt.y;
52          return hWndCaret;
53       }
54    }
55 
56    return 0;
57 }
58 
59 /*
60     Old GetUpdateRgn, for scrolls, see above note.
61  */
62 INT FASTCALL
63 co_IntGetUpdateRgn(PWND Window, PREGION Rgn, BOOL bErase)
64 {
65     int RegionType;
66     RECTL Rect;
67     PREGION UpdateRgn;
68 
69     ASSERT_REFS_CO(Window);
70 
71     if (bErase)
72     {
73        co_IntPaintWindows(Window, RDW_NOCHILDREN, FALSE);
74     }
75 
76     Window->state &= ~WNDS_UPDATEDIRTY;
77 
78     if (Window->hrgnUpdate == NULL)
79     {
80         REGION_SetRectRgn(Rgn, 0, 0, 0, 0);
81         return NULLREGION;
82     }
83 
84     UpdateRgn = REGION_LockRgn(Window->hrgnUpdate);
85     if (!UpdateRgn)
86        return ERROR;
87 
88     Rect = Window->rcClient;
89     IntIntersectWithParents(Window, &Rect);
90     REGION_SetRectRgn(Rgn, Rect.left, Rect.top, Rect.right, Rect.bottom);
91     RegionType = IntGdiCombineRgn(Rgn, Rgn, UpdateRgn, RGN_AND);
92     REGION_bOffsetRgn(Rgn, -Window->rcClient.left, -Window->rcClient.top);
93     REGION_UnlockRgn(UpdateRgn);
94 
95     return RegionType;
96 }
97 
98 static
99 INT FASTCALL
100 UserScrollDC(
101    HDC hDC,
102    INT dx,
103    INT dy,
104    const RECTL *prcScroll,
105    const RECTL *prcClip,
106    HRGN hrgnUpdate,
107    PREGION RgnUpdate,
108    RECTL *prcUpdate)
109 {
110    PDC pDC;
111    RECTL rcScroll, rcClip, rcSrc, rcDst;
112    INT Result;
113 
114    if (GdiGetClipBox(hDC, &rcClip) == ERROR)
115    {
116        ERR("GdiGetClipBox failed for HDC %p\n", hDC);
117        return ERROR;
118    }
119 
120    rcScroll = rcClip;
121    if (prcClip)
122    {
123       RECTL_bIntersectRect(&rcClip, &rcClip, prcClip);
124    }
125 
126    if (prcScroll)
127    {
128       rcScroll = *prcScroll;
129       RECTL_bIntersectRect(&rcSrc, &rcClip, prcScroll);
130    }
131    else
132    {
133       rcSrc = rcClip;
134    }
135 
136    rcDst = rcSrc;
137    RECTL_vOffsetRect(&rcDst, dx, dy);
138    RECTL_bIntersectRect(&rcDst, &rcDst, &rcClip);
139 
140    if (!NtGdiBitBlt( hDC,
141                      rcDst.left,
142                      rcDst.top,
143                      rcDst.right - rcDst.left,
144                      rcDst.bottom - rcDst.top,
145                      hDC,
146                      rcDst.left - dx,
147                      rcDst.top - dy,
148                      SRCCOPY,
149                      0,
150                      0))
151    {
152       return ERROR;
153    }
154 
155    /* Calculate the region that was invalidated by moving or
156       could not be copied, because it was not visible */
157    if (RgnUpdate || hrgnUpdate || prcUpdate)
158    {
159       PREGION RgnOwn, RgnTmp;
160 
161       pDC = DC_LockDc(hDC);
162       if (!pDC)
163       {
164          return ERROR;
165       }
166 
167        if (hrgnUpdate)
168        {
169            NT_ASSERT(RgnUpdate == NULL);
170            RgnUpdate = REGION_LockRgn(hrgnUpdate);
171            if (!RgnUpdate)
172            {
173                DC_UnlockDc(pDC);
174                return ERROR;
175            }
176        }
177 
178       /* Begin with the shifted and then clipped scroll rect */
179       rcDst = rcScroll;
180       RECTL_vOffsetRect(&rcDst, dx, dy);
181       RECTL_bIntersectRect(&rcDst, &rcDst, &rcClip);
182       if (RgnUpdate)
183       {
184          RgnOwn = RgnUpdate;
185          REGION_SetRectRgn(RgnOwn, rcDst.left, rcDst.top, rcDst.right, rcDst.bottom);
186       }
187       else
188       {
189          RgnOwn = IntSysCreateRectpRgnIndirect(&rcDst);
190       }
191 
192       /* Add the source rect */
193       RgnTmp = IntSysCreateRectpRgnIndirect(&rcSrc);
194       IntGdiCombineRgn(RgnOwn, RgnOwn, RgnTmp, RGN_OR);
195 
196       /* Substract the part of the dest that was visible in source */
197       IntGdiCombineRgn(RgnTmp, RgnTmp, pDC->prgnVis, RGN_AND);
198       REGION_bOffsetRgn(RgnTmp, dx, dy);
199       Result = IntGdiCombineRgn(RgnOwn, RgnOwn, RgnTmp, RGN_DIFF);
200 
201       /* DO NOT Unlock DC while messing with prgnVis! */
202       DC_UnlockDc(pDC);
203 
204       REGION_Delete(RgnTmp);
205 
206       if (prcUpdate)
207       {
208          REGION_GetRgnBox(RgnOwn, prcUpdate);
209       }
210 
211       if (hrgnUpdate)
212       {
213          REGION_UnlockRgn(RgnUpdate);
214       }
215       else if (!RgnUpdate)
216       {
217          REGION_Delete(RgnOwn);
218       }
219    }
220    else
221       Result = NULLREGION;
222 
223    return Result;
224 }
225 
226 DWORD
227 FASTCALL
228 IntScrollWindowEx(
229    PWND Window,
230    INT dx,
231    INT dy,
232    const RECT *prcScroll,
233    const RECT *prcClip,
234    HRGN hrgnUpdate,
235    LPRECT prcUpdate,
236    UINT flags)
237 {
238    INT Result;
239    RECTL rcScroll, rcClip, rcCaret;
240    PWND CaretWnd;
241    HDC hDC;
242    PREGION RgnUpdate = NULL, RgnTemp, RgnWinupd = NULL;
243    HWND hwndCaret;
244    DWORD dcxflags = 0;
245    int rdw_flags;
246    USER_REFERENCE_ENTRY CaretRef;
247 
248    if (!Window || !IntIsWindowDrawable(Window))
249    {
250       return ERROR;
251    }
252 
253    IntGetClientRect(Window, &rcClip);
254 
255    if (prcScroll)
256       RECTL_bIntersectRect(&rcScroll, &rcClip, prcScroll);
257    else
258       rcScroll = rcClip;
259 
260    if (prcClip)
261       RECTL_bIntersectRect(&rcClip, &rcClip, prcClip);
262 
263    if (rcClip.right <= rcClip.left || rcClip.bottom <= rcClip.top ||
264          (dx == 0 && dy == 0))
265    {
266       return NULLREGION;
267    }
268 
269    /* We must use a copy of the region, as we can't hold an exclusive lock
270     * on it while doing callouts to user-mode */
271    RgnUpdate = IntSysCreateRectpRgn(0, 0, 0, 0);
272    if(!RgnUpdate)
273    {
274        EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
275        return ERROR;
276    }
277 
278    if (hrgnUpdate)
279    {
280        RgnTemp = REGION_LockRgn(hrgnUpdate);
281        if (!RgnTemp)
282        {
283            EngSetLastError(ERROR_INVALID_HANDLE);
284            Result = ERROR;
285            goto Cleanup;
286        }
287        IntGdiCombineRgn(RgnUpdate, RgnTemp, NULL, RGN_COPY);
288        REGION_UnlockRgn(RgnTemp);
289    }
290 
291    /* ScrollWindow uses the window DC, ScrollWindowEx doesn't */
292    if (flags & SW_SCROLLWNDDCE)
293    {
294       dcxflags = DCX_USESTYLE;
295 
296       if (!(Window->pcls->style & (CS_OWNDC|CS_CLASSDC)))
297          dcxflags |= DCX_CACHE; // AH??? wine~ If not Powned or with Class go Cheap!
298 
299       if (flags & SW_SCROLLCHILDREN && Window->style & WS_CLIPCHILDREN)
300          dcxflags |= DCX_CACHE|DCX_NOCLIPCHILDREN;
301    }
302    else
303    {
304        /* So in this case ScrollWindowEx uses Cache DC. */
305        dcxflags = DCX_CACHE|DCX_USESTYLE;
306        if (flags & SW_SCROLLCHILDREN) dcxflags |= DCX_NOCLIPCHILDREN;
307    }
308 
309    hDC = UserGetDCEx(Window, 0, dcxflags);
310    if (!hDC)
311    {
312       /* FIXME: SetLastError? */
313       Result = ERROR;
314       goto Cleanup;
315    }
316 
317    rdw_flags = (flags & SW_ERASE) && (flags & SW_INVALIDATE) ? RDW_INVALIDATE | RDW_ERASE : RDW_INVALIDATE ;
318 
319    rcCaret = rcScroll;
320    hwndCaret = co_IntFixCaret(Window, &rcCaret, flags);
321 
322    Result = UserScrollDC( hDC,
323                           dx,
324                           dy,
325                           &rcScroll,
326                           &rcClip,
327                           NULL,
328                           RgnUpdate,
329                           prcUpdate);
330 
331    UserReleaseDC(Window, hDC, FALSE);
332 
333    /*
334     * Take into account the fact that some damage may have occurred during
335     * the scroll. Keep a copy in hrgnWinupd to be added to hrngUpdate at the end.
336     */
337 
338    RgnTemp = IntSysCreateRectpRgn(0, 0, 0, 0);
339    if (!RgnTemp)
340    {
341        EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
342        Result = ERROR;
343        goto Cleanup;
344    }
345 
346    if (co_IntGetUpdateRgn(Window, RgnTemp, FALSE) != NULLREGION)
347    {
348       PREGION RgnClip = IntSysCreateRectpRgnIndirect(&rcClip);
349       if (RgnClip)
350       {
351           if (hrgnUpdate)
352           {
353              RgnWinupd = IntSysCreateRectpRgn(0, 0, 0, 0);
354              // FIXME: What to do if RgnWinupd == NULL??
355              IntGdiCombineRgn( RgnWinupd, RgnTemp, 0, RGN_COPY);
356           }
357 
358           REGION_bOffsetRgn(RgnTemp, dx, dy);
359 
360           IntGdiCombineRgn(RgnTemp, RgnTemp, RgnClip, RGN_AND);
361 
362           if (hrgnUpdate)
363               IntGdiCombineRgn( RgnWinupd, RgnWinupd, RgnTemp, RGN_OR );
364 
365           co_UserRedrawWindow(Window, NULL, RgnTemp, rdw_flags );
366 
367           REGION_Delete(RgnClip);
368       }
369    }
370    REGION_Delete(RgnTemp);
371 
372    if (flags & SW_SCROLLCHILDREN)
373    {
374       PWND Child;
375       RECTL rcChild;
376       POINT ClientOrigin;
377       USER_REFERENCE_ENTRY WndRef;
378       RECTL rcDummy;
379       LPARAM lParam;
380 
381       IntGetClientOrigin(Window, &ClientOrigin);
382 
383       for (Child = Window->spwndChild; Child; Child = Child->spwndNext)
384       {
385          rcChild = Child->rcWindow;
386          RECTL_vOffsetRect(&rcChild, -ClientOrigin.x, -ClientOrigin.y);
387 
388          /* Adjust window positions */
389          RECTL_vOffsetRect(&Child->rcWindow, dx, dy);
390          RECTL_vOffsetRect(&Child->rcClient, dx, dy);
391 
392          if (!prcScroll || RECTL_bIntersectRect(&rcDummy, &rcChild, &rcScroll))
393          {
394             UserRefObjectCo(Child, &WndRef);
395 
396             if (UserIsDesktopWindow(Window->spwndParent))
397                lParam = MAKELONG(Child->rcClient.left, Child->rcClient.top);
398             else
399                lParam = MAKELONG(rcChild.left + dx, rcChild.top + dy);
400 
401             /* wine sends WM_POSCHANGING, WM_POSCHANGED messages */
402             /* windows sometimes a WM_MOVE */
403             co_IntSendMessage(UserHMGetHandle(Child), WM_MOVE, 0, lParam);
404 
405             UserDerefObjectCo(Child);
406          }
407       }
408    }
409 
410    if (flags & (SW_INVALIDATE | SW_ERASE))
411    {
412       co_UserRedrawWindow( Window,
413                            NULL,
414                            RgnUpdate,
415                            rdw_flags |                                    /*    HACK    */
416                           ((flags & SW_SCROLLCHILDREN) ? RDW_ALLCHILDREN : RDW_NOCHILDREN) );
417    }
418 
419    if (hwndCaret && (CaretWnd = UserGetWindowObject(hwndCaret)))
420    {
421       UserRefObjectCo(CaretWnd, &CaretRef);
422 
423       co_IntSetCaretPos(rcCaret.left + dx, rcCaret.top + dy);
424       co_UserShowCaret(CaretWnd);
425 
426       UserDerefObjectCo(CaretWnd);
427    }
428 
429    if (hrgnUpdate && (Result != ERROR))
430    {
431        /* Give everything back to the caller */
432        RgnTemp = REGION_LockRgn(hrgnUpdate);
433        /* The handle should still be valid */
434        ASSERT(RgnTemp);
435        if (RgnWinupd)
436            IntGdiCombineRgn(RgnTemp, RgnUpdate, RgnWinupd, RGN_OR);
437        else
438            IntGdiCombineRgn(RgnTemp, RgnUpdate, NULL, RGN_COPY);
439        REGION_UnlockRgn(RgnTemp);
440    }
441 
442 Cleanup:
443    if (RgnWinupd)
444    {
445        REGION_Delete(RgnWinupd);
446    }
447 
448    if (RgnUpdate)
449    {
450       REGION_Delete(RgnUpdate);
451    }
452 
453    return Result;
454 }
455 
456 
457 BOOL FASTCALL
458 IntScrollWindow(PWND pWnd,
459                 int dx,
460                 int dy,
461                 CONST RECT *lpRect,
462                 CONST RECT *prcClip)
463 {
464    return IntScrollWindowEx( pWnd, dx, dy, lpRect, prcClip, 0, NULL,
465                             (lpRect ? 0 : SW_SCROLLCHILDREN) | (SW_ERASE|SW_INVALIDATE|SW_SCROLLWNDDCE)) != ERROR;
466 }
467 
468 /*
469  * NtUserScrollDC
470  *
471  * Status
472  *    @implemented
473  */
474 BOOL APIENTRY
475 NtUserScrollDC(
476    HDC hDC,
477    INT dx,
478    INT dy,
479    const RECT *prcUnsafeScroll,
480    const RECT *prcUnsafeClip,
481    HRGN hrgnUpdate,
482    LPRECT prcUnsafeUpdate)
483 {
484    DECLARE_RETURN(DWORD);
485    DWORD Result;
486    NTSTATUS Status = STATUS_SUCCESS;
487    RECTL rcScroll, rcClip, rcUpdate;
488 
489    TRACE("Enter NtUserScrollDC\n");
490    UserEnterExclusive();
491 
492    _SEH2_TRY
493    {
494       if (prcUnsafeScroll)
495       {
496          ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
497          rcScroll = *prcUnsafeScroll;
498       }
499       if (prcUnsafeClip)
500       {
501          ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
502          rcClip = *prcUnsafeClip;
503       }
504       if (prcUnsafeUpdate)
505       {
506          ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
507       }
508    }
509    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
510    {
511       Status = _SEH2_GetExceptionCode();
512    }
513    _SEH2_END;
514 
515    if (!NT_SUCCESS(Status))
516    {
517       SetLastNtError(Status);
518       RETURN(FALSE);
519    }
520 
521    Result = UserScrollDC( hDC,
522                           dx,
523                           dy,
524                           prcUnsafeScroll ? &rcScroll : NULL,
525                           prcUnsafeClip   ? &rcClip   : NULL,
526                           hrgnUpdate,
527                           NULL,
528                           prcUnsafeUpdate ? &rcUpdate : NULL);
529    if(Result == ERROR)
530    {
531       /* FIXME: Only if hRgnUpdate is invalid we should SetLastError(ERROR_INVALID_HANDLE) */
532       RETURN(FALSE);
533    }
534 
535    if (prcUnsafeUpdate)
536    {
537       _SEH2_TRY
538       {
539          *prcUnsafeUpdate = rcUpdate;
540       }
541       _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
542       {
543          Status = _SEH2_GetExceptionCode();
544       }
545       _SEH2_END;
546 
547       if (!NT_SUCCESS(Status))
548       {
549          /* FIXME: SetLastError? */
550          /* FIXME: correct? We have already scrolled! */
551          RETURN(FALSE);
552       }
553    }
554 
555    RETURN(TRUE);
556 
557 CLEANUP:
558    TRACE("Leave NtUserScrollDC, ret=%lu\n",_ret_);
559    UserLeave();
560    END_CLEANUP;
561 }
562 
563 /*
564  * NtUserScrollWindowEx
565  *
566  * Status
567  *    @implemented
568  */
569 
570 DWORD APIENTRY
571 NtUserScrollWindowEx(
572    HWND hWnd,
573    INT dx,
574    INT dy,
575    const RECT *prcUnsafeScroll,
576    const RECT *prcUnsafeClip,
577    HRGN hrgnUpdate,
578    LPRECT prcUnsafeUpdate,
579    UINT flags)
580 {
581    DECLARE_RETURN(DWORD);
582    INT Result;
583    NTSTATUS Status = STATUS_SUCCESS;
584    PWND Window = NULL;
585    RECTL rcScroll, rcClip, rcUpdate;
586    USER_REFERENCE_ENTRY Ref;
587 
588    TRACE("Enter NtUserScrollWindowEx\n");
589    UserEnterExclusive();
590 
591    Window = UserGetWindowObject(hWnd);
592    if (!Window || !IntIsWindowDrawable(Window))
593    {
594       Window = NULL; /* prevent deref at cleanup */
595       RETURN(ERROR);
596    }
597    UserRefObjectCo(Window, &Ref);
598 
599    _SEH2_TRY
600    {
601       if (prcUnsafeScroll)
602       {
603          ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
604          rcScroll = *prcUnsafeScroll;
605       }
606 
607       if (prcUnsafeClip)
608       {
609          ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
610          rcClip = *prcUnsafeClip;
611       }
612    }
613    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
614    {
615       Status = _SEH2_GetExceptionCode();
616    }
617    _SEH2_END;
618 
619    if (!NT_SUCCESS(Status))
620    {
621       SetLastNtError(Status);
622       RETURN(ERROR);
623    }
624 
625    Result = IntScrollWindowEx(Window,
626                               dx, dy,
627                               prcUnsafeScroll ? &rcScroll : NULL,
628                               prcUnsafeClip   ? &rcClip   : NULL,
629                               hrgnUpdate,
630                               prcUnsafeUpdate ? &rcUpdate : NULL,
631                               flags);
632 
633    if (prcUnsafeUpdate)
634    {
635       _SEH2_TRY
636       {
637          /* Probe here, to not fail on invalid pointer before scrolling */
638          ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
639          *prcUnsafeUpdate = rcUpdate;
640       }
641       _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
642       {
643          Status = _SEH2_GetExceptionCode();
644       }
645       _SEH2_END;
646 
647       if (!NT_SUCCESS(Status))
648       {
649          SetLastNtError(Status);
650          RETURN(ERROR);
651       }
652    }
653 
654    RETURN(Result);
655 
656 CLEANUP:
657    if (Window)
658       UserDerefObjectCo(Window);
659 
660    TRACE("Leave NtUserScrollWindowEx, ret=%lu\n",_ret_);
661    UserLeave();
662    END_CLEANUP;
663 }
664 
665 /* EOF */
666