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