[WIN32K:NTUSER]
[reactos.git] / reactos / win32ss / user / ntuser / scrollex.c
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 */