xref: /reactos/win32ss/user/ntuser/scrollex.c (revision 7eead935)
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          if (!prcScroll || RECTL_bIntersectRect(&rcDummy, &rcChild, &rcScroll))
389          {
390             UserRefObjectCo(Child, &WndRef);
391 
392             if (UserIsDesktopWindow(Window->spwndParent))
393                lParam = MAKELONG(Child->rcClient.left, Child->rcClient.top);
394             else
395                lParam = MAKELONG(rcChild.left + dx, rcChild.top + dy);
396 
397             /* wine sends WM_POSCHANGING, WM_POSCHANGED messages */
398             /* windows sometimes a WM_MOVE */
399             co_IntSendMessage(UserHMGetHandle(Child), WM_MOVE, 0, lParam);
400 
401             UserDerefObjectCo(Child);
402          }
403       }
404    }
405 
406    if (flags & (SW_INVALIDATE | SW_ERASE))
407    {
408       co_UserRedrawWindow( Window,
409                            NULL,
410                            RgnUpdate,
411                            rdw_flags |                                    /*    HACK    */
412                           ((flags & SW_SCROLLCHILDREN) ? RDW_ALLCHILDREN : RDW_NOCHILDREN) );
413    }
414 
415    if (hwndCaret && (CaretWnd = UserGetWindowObject(hwndCaret)))
416    {
417       UserRefObjectCo(CaretWnd, &CaretRef);
418 
419       co_IntSetCaretPos(rcCaret.left + dx, rcCaret.top + dy);
420       co_UserShowCaret(CaretWnd);
421 
422       UserDerefObjectCo(CaretWnd);
423    }
424 
425    if (hrgnUpdate && (Result != ERROR))
426    {
427        /* Give everything back to the caller */
428        RgnTemp = REGION_LockRgn(hrgnUpdate);
429        /* The handle should still be valid */
430        ASSERT(RgnTemp);
431        if (RgnWinupd)
432            IntGdiCombineRgn(RgnTemp, RgnUpdate, RgnWinupd, RGN_OR);
433        else
434            IntGdiCombineRgn(RgnTemp, RgnUpdate, NULL, RGN_COPY);
435        REGION_UnlockRgn(RgnTemp);
436    }
437 
438 Cleanup:
439    if (RgnWinupd)
440    {
441        REGION_Delete(RgnWinupd);
442    }
443 
444    if (RgnUpdate)
445    {
446       REGION_Delete(RgnUpdate);
447    }
448 
449    return Result;
450 }
451 
452 
453 BOOL FASTCALL
454 IntScrollWindow(PWND pWnd,
455                 int dx,
456                 int dy,
457                 CONST RECT *lpRect,
458                 CONST RECT *prcClip)
459 {
460    return IntScrollWindowEx( pWnd, dx, dy, lpRect, prcClip, 0, NULL,
461                             (lpRect ? 0 : SW_SCROLLCHILDREN) | (SW_ERASE|SW_INVALIDATE|SW_SCROLLWNDDCE)) != ERROR;
462 }
463 
464 /*
465  * NtUserScrollDC
466  *
467  * Status
468  *    @implemented
469  */
470 BOOL APIENTRY
471 NtUserScrollDC(
472    HDC hDC,
473    INT dx,
474    INT dy,
475    const RECT *prcUnsafeScroll,
476    const RECT *prcUnsafeClip,
477    HRGN hrgnUpdate,
478    LPRECT prcUnsafeUpdate)
479 {
480    DECLARE_RETURN(DWORD);
481    DWORD Result;
482    NTSTATUS Status = STATUS_SUCCESS;
483    RECTL rcScroll, rcClip, rcUpdate;
484 
485    TRACE("Enter NtUserScrollDC\n");
486    UserEnterExclusive();
487 
488    _SEH2_TRY
489    {
490       if (prcUnsafeScroll)
491       {
492          ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
493          rcScroll = *prcUnsafeScroll;
494       }
495       if (prcUnsafeClip)
496       {
497          ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
498          rcClip = *prcUnsafeClip;
499       }
500       if (prcUnsafeUpdate)
501       {
502          ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
503       }
504    }
505    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
506    {
507       Status = _SEH2_GetExceptionCode();
508    }
509    _SEH2_END;
510 
511    if (!NT_SUCCESS(Status))
512    {
513       SetLastNtError(Status);
514       RETURN(FALSE);
515    }
516 
517    Result = UserScrollDC( hDC,
518                           dx,
519                           dy,
520                           prcUnsafeScroll ? &rcScroll : NULL,
521                           prcUnsafeClip   ? &rcClip   : NULL,
522                           hrgnUpdate,
523                           NULL,
524                           prcUnsafeUpdate ? &rcUpdate : NULL);
525    if(Result == ERROR)
526    {
527       /* FIXME: Only if hRgnUpdate is invalid we should SetLastError(ERROR_INVALID_HANDLE) */
528       RETURN(FALSE);
529    }
530 
531    if (prcUnsafeUpdate)
532    {
533       _SEH2_TRY
534       {
535          *prcUnsafeUpdate = rcUpdate;
536       }
537       _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
538       {
539          Status = _SEH2_GetExceptionCode();
540       }
541       _SEH2_END;
542 
543       if (!NT_SUCCESS(Status))
544       {
545          /* FIXME: SetLastError? */
546          /* FIXME: correct? We have already scrolled! */
547          RETURN(FALSE);
548       }
549    }
550 
551    RETURN(TRUE);
552 
553 CLEANUP:
554    TRACE("Leave NtUserScrollDC, ret=%lu\n",_ret_);
555    UserLeave();
556    END_CLEANUP;
557 }
558 
559 /*
560  * NtUserScrollWindowEx
561  *
562  * Status
563  *    @implemented
564  */
565 
566 DWORD APIENTRY
567 NtUserScrollWindowEx(
568    HWND hWnd,
569    INT dx,
570    INT dy,
571    const RECT *prcUnsafeScroll,
572    const RECT *prcUnsafeClip,
573    HRGN hrgnUpdate,
574    LPRECT prcUnsafeUpdate,
575    UINT flags)
576 {
577    DECLARE_RETURN(DWORD);
578    INT Result;
579    NTSTATUS Status = STATUS_SUCCESS;
580    PWND Window = NULL;
581    RECTL rcScroll, rcClip, rcUpdate;
582    USER_REFERENCE_ENTRY Ref;
583 
584    TRACE("Enter NtUserScrollWindowEx\n");
585    UserEnterExclusive();
586 
587    Window = UserGetWindowObject(hWnd);
588    if (!Window || !IntIsWindowDrawable(Window))
589    {
590       Window = NULL; /* prevent deref at cleanup */
591       RETURN(ERROR);
592    }
593    UserRefObjectCo(Window, &Ref);
594 
595    _SEH2_TRY
596    {
597       if (prcUnsafeScroll)
598       {
599          ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
600          rcScroll = *prcUnsafeScroll;
601       }
602 
603       if (prcUnsafeClip)
604       {
605          ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
606          rcClip = *prcUnsafeClip;
607       }
608    }
609    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
610    {
611       Status = _SEH2_GetExceptionCode();
612    }
613    _SEH2_END;
614 
615    if (!NT_SUCCESS(Status))
616    {
617       SetLastNtError(Status);
618       RETURN(ERROR);
619    }
620 
621    Result = IntScrollWindowEx(Window,
622                               dx, dy,
623                               prcUnsafeScroll ? &rcScroll : NULL,
624                               prcUnsafeClip   ? &rcClip   : NULL,
625                               hrgnUpdate,
626                               prcUnsafeUpdate ? &rcUpdate : NULL,
627                               flags);
628 
629    if (prcUnsafeUpdate)
630    {
631       _SEH2_TRY
632       {
633          /* Probe here, to not fail on invalid pointer before scrolling */
634          ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
635          *prcUnsafeUpdate = rcUpdate;
636       }
637       _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
638       {
639          Status = _SEH2_GetExceptionCode();
640       }
641       _SEH2_END;
642 
643       if (!NT_SUCCESS(Status))
644       {
645          SetLastNtError(Status);
646          RETURN(ERROR);
647       }
648    }
649 
650    RETURN(Result);
651 
652 CLEANUP:
653    if (Window)
654       UserDerefObjectCo(Window);
655 
656    TRACE("Leave NtUserScrollWindowEx, ret=%lu\n",_ret_);
657    UserLeave();
658    END_CLEANUP;
659 }
660 
661 /* EOF */
662