e0cd6359c692969b8c54a5c402c95bd7b333323f
[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 /*
230 * NtUserScrollDC
231 *
232 * Status
233 * @implemented
234 */
235 BOOL APIENTRY
236 NtUserScrollDC(
237 HDC hDC,
238 INT dx,
239 INT dy,
240 const RECT *prcUnsafeScroll,
241 const RECT *prcUnsafeClip,
242 HRGN hrgnUpdate,
243 LPRECT prcUnsafeUpdate)
244 {
245 DECLARE_RETURN(DWORD);
246 RECTL rcScroll, rcClip, rcUpdate;
247 NTSTATUS Status = STATUS_SUCCESS;
248 DWORD Result;
249
250 TRACE("Enter NtUserScrollDC\n");
251 UserEnterExclusive();
252
253 _SEH2_TRY
254 {
255 if (prcUnsafeScroll)
256 {
257 ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
258 rcScroll = *prcUnsafeScroll;
259 }
260 if (prcUnsafeClip)
261 {
262 ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
263 rcClip = *prcUnsafeClip;
264 }
265 if (prcUnsafeUpdate)
266 {
267 ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
268 }
269 }
270 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
271 {
272 Status = _SEH2_GetExceptionCode();
273 }
274 _SEH2_END
275 if (!NT_SUCCESS(Status))
276 {
277 SetLastNtError(Status);
278 RETURN(FALSE);
279 }
280
281 Result = UserScrollDC( hDC,
282 dx,
283 dy,
284 prcUnsafeScroll? &rcScroll : 0,
285 prcUnsafeClip? &rcClip : 0,
286 hrgnUpdate,
287 NULL,
288 prcUnsafeUpdate? &rcUpdate : NULL);
289 if(Result == ERROR)
290 {
291 /* FIXME: Only if hRgnUpdate is invalid we should SetLastError(ERROR_INVALID_HANDLE) */
292 RETURN(FALSE);
293 }
294
295 if (prcUnsafeUpdate)
296 {
297 _SEH2_TRY
298 {
299 *prcUnsafeUpdate = rcUpdate;
300 }
301 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
302 {
303 Status = _SEH2_GetExceptionCode();
304 }
305 _SEH2_END
306 if (!NT_SUCCESS(Status))
307 {
308 /* FIXME: SetLastError? */
309 /* FIXME: correct? We have already scrolled! */
310 RETURN(FALSE);
311 }
312 }
313
314 RETURN(TRUE);
315
316 CLEANUP:
317 TRACE("Leave NtUserScrollDC, ret=%lu\n",_ret_);
318 UserLeave();
319 END_CLEANUP;
320 }
321
322 /*
323 * NtUserScrollWindowEx
324 *
325 * Status
326 * @implemented
327 */
328
329 DWORD APIENTRY
330 NtUserScrollWindowEx(
331 HWND hWnd,
332 INT dx,
333 INT dy,
334 const RECT *prcUnsafeScroll,
335 const RECT *prcUnsafeClip,
336 HRGN hrgnUpdate,
337 LPRECT prcUnsafeUpdate,
338 UINT flags)
339 {
340 RECTL rcScroll, rcClip, rcCaret, rcUpdate;
341 INT Result;
342 PWND Window = NULL, CaretWnd;
343 HDC hDC;
344 PREGION RgnUpdate = NULL, RgnTemp, RgnWinupd = NULL;
345 HWND hwndCaret;
346 DWORD dcxflags = 0;
347 int rdw_flags;
348 NTSTATUS Status = STATUS_SUCCESS;
349 DECLARE_RETURN(DWORD);
350 USER_REFERENCE_ENTRY Ref, CaretRef;
351
352 TRACE("Enter NtUserScrollWindowEx\n");
353 UserEnterExclusive();
354
355 Window = UserGetWindowObject(hWnd);
356 if (!Window || !IntIsWindowDrawable(Window))
357 {
358 Window = NULL; /* prevent deref at cleanup */
359 RETURN( ERROR);
360 }
361 UserRefObjectCo(Window, &Ref);
362
363 IntGetClientRect(Window, &rcClip);
364
365 _SEH2_TRY
366 {
367 if (prcUnsafeScroll)
368 {
369 ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1);
370 RECTL_bIntersectRect(&rcScroll, &rcClip, prcUnsafeScroll);
371 }
372 else
373 rcScroll = rcClip;
374
375 if (prcUnsafeClip)
376 {
377 ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1);
378 RECTL_bIntersectRect(&rcClip, &rcClip, prcUnsafeClip);
379 }
380 }
381 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
382 {
383 Status = _SEH2_GetExceptionCode();
384 }
385 _SEH2_END
386
387 if (!NT_SUCCESS(Status))
388 {
389 SetLastNtError(Status);
390 RETURN(ERROR);
391 }
392
393 if (rcClip.right <= rcClip.left || rcClip.bottom <= rcClip.top ||
394 (dx == 0 && dy == 0))
395 {
396 RETURN(NULLREGION);
397 }
398
399 /* We must use a copy of the region, as we can't hold an exclusive lock
400 * on it while doing callouts to user-mode */
401 RgnUpdate = IntSysCreateRectpRgn(0, 0, 0, 0);
402 if(!RgnUpdate)
403 {
404 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
405 RETURN(ERROR);
406 }
407
408 if (hrgnUpdate)
409 {
410 RgnTemp = REGION_LockRgn(hrgnUpdate);
411 if (!RgnTemp)
412 {
413 EngSetLastError(ERROR_INVALID_HANDLE);
414 RETURN(ERROR);
415 }
416 IntGdiCombineRgn(RgnUpdate, RgnTemp, NULL, RGN_COPY);
417 REGION_UnlockRgn(RgnTemp);
418 }
419
420 /* ScrollWindow uses the window DC, ScrollWindowEx doesn't */
421 if (flags & SW_SCROLLWNDDCE)
422 {
423 dcxflags = DCX_USESTYLE;
424
425 if (!(Window->pcls->style & (CS_OWNDC|CS_CLASSDC)))
426 dcxflags |= DCX_CACHE; // AH??? wine~ If not Powned or with Class go Cheap!
427
428 if (flags & SW_SCROLLCHILDREN && Window->style & WS_CLIPCHILDREN)
429 dcxflags |= DCX_CACHE|DCX_NOCLIPCHILDREN;
430 }
431 else
432 {
433 /* So in this case ScrollWindowEx uses Cache DC. */
434 dcxflags = DCX_CACHE|DCX_USESTYLE;
435 if (flags & SW_SCROLLCHILDREN) dcxflags |= DCX_NOCLIPCHILDREN;
436 }
437
438 hDC = UserGetDCEx(Window, 0, dcxflags);
439 if (!hDC)
440 {
441 /* FIXME: SetLastError? */
442 RETURN(ERROR);
443 }
444
445 rdw_flags = (flags & SW_ERASE) && (flags & SW_INVALIDATE) ? RDW_INVALIDATE | RDW_ERASE : RDW_INVALIDATE ;
446
447 rcCaret = rcScroll;
448 hwndCaret = co_IntFixCaret(Window, &rcCaret, flags);
449
450 Result = UserScrollDC( hDC,
451 dx,
452 dy,
453 &rcScroll,
454 &rcClip,
455 NULL,
456 RgnUpdate,
457 prcUnsafeUpdate? &rcUpdate : NULL);
458
459 UserReleaseDC(Window, hDC, FALSE);
460
461 /*
462 * Take into account the fact that some damage may have occurred during
463 * the scroll. Keep a copy in hrgnWinupd to be added to hrngUpdate at the end.
464 */
465
466 RgnTemp = IntSysCreateRectpRgn(0, 0, 0, 0);
467 if (!RgnTemp)
468 {
469 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
470 RETURN(ERROR);
471 }
472
473 if (co_IntGetUpdateRgn(Window, RgnTemp, FALSE) != NULLREGION)
474 {
475 PREGION RgnClip = IntSysCreateRectpRgnIndirect(&rcClip);
476 if (RgnClip)
477 {
478 if (hrgnUpdate)
479 {
480 RgnWinupd = IntSysCreateRectpRgn( 0, 0, 0, 0);
481 IntGdiCombineRgn( RgnWinupd, RgnTemp, 0, RGN_COPY);
482 }
483
484 REGION_bOffsetRgn(RgnTemp, dx, dy);
485
486 IntGdiCombineRgn(RgnTemp, RgnTemp, RgnClip, RGN_AND);
487
488 if (hrgnUpdate)
489 IntGdiCombineRgn( RgnWinupd, RgnWinupd, RgnTemp, RGN_OR );
490
491 co_UserRedrawWindow(Window, NULL, RgnTemp, rdw_flags );
492
493 REGION_Delete(RgnClip);
494 }
495 }
496 REGION_Delete(RgnTemp);
497
498 if (flags & SW_SCROLLCHILDREN)
499 {
500 PWND Child;
501 RECTL rcChild;
502 POINT ClientOrigin;
503 USER_REFERENCE_ENTRY WndRef;
504 RECTL rcDummy;
505 LPARAM lParam;
506
507 IntGetClientOrigin(Window, &ClientOrigin);
508
509 for (Child = Window->spwndChild; Child; Child = Child->spwndNext)
510 {
511 rcChild = Child->rcWindow;
512 rcChild.left -= ClientOrigin.x;
513 rcChild.top -= ClientOrigin.y;
514 rcChild.right -= ClientOrigin.x;
515 rcChild.bottom -= ClientOrigin.y;
516
517 if (!prcUnsafeScroll || RECTL_bIntersectRect(&rcDummy, &rcChild, &rcScroll))
518 {
519 UserRefObjectCo(Child, &WndRef);
520
521 if (Window->spwndParent == UserGetDesktopWindow()) // Window->spwndParent->fnid == FNID_DESKTOP )
522 lParam = MAKELONG(Child->rcClient.left, Child->rcClient.top);
523 else
524 lParam = MAKELONG(rcChild.left + dx, rcChild.top + dy);
525
526 /* wine sends WM_POSCHANGING, WM_POSCHANGED messages */
527 /* windows sometimes a WM_MOVE */
528 co_IntSendMessage(UserHMGetHandle(Child), WM_MOVE, 0, lParam);
529
530 UserDerefObjectCo(Child);
531 }
532 }
533 }
534
535 if (flags & (SW_INVALIDATE | SW_ERASE))
536 {
537 co_UserRedrawWindow( Window,
538 NULL,
539 RgnUpdate,
540 rdw_flags | /* HACK */
541 ((flags & SW_SCROLLCHILDREN) ? RDW_ALLCHILDREN : RDW_NOCHILDREN) );
542 }
543
544 if (hwndCaret && (CaretWnd = UserGetWindowObject(hwndCaret)))
545 {
546 UserRefObjectCo(CaretWnd, &CaretRef);
547
548 co_IntSetCaretPos(rcCaret.left + dx, rcCaret.top + dy);
549 co_UserShowCaret(CaretWnd);
550
551 UserDerefObjectCo(CaretWnd);
552 }
553
554 if (prcUnsafeUpdate)
555 {
556 _SEH2_TRY
557 {
558 /* Probe here, to not fail on invalid pointer before scrolling */
559 ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1);
560 *prcUnsafeUpdate = rcUpdate;
561 }
562 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
563 {
564 Status = _SEH2_GetExceptionCode();
565 }
566 _SEH2_END
567
568 if (!NT_SUCCESS(Status))
569 {
570 SetLastNtError(Status);
571 RETURN(ERROR);
572 }
573 }
574
575 RETURN(Result);
576
577 CLEANUP:
578 if (hrgnUpdate && (_ret_ != ERROR))
579 {
580 /* Give everything back to the caller */
581 RgnTemp = REGION_LockRgn(hrgnUpdate);
582 /* The handle should still be valid */
583 ASSERT(RgnTemp);
584 if (RgnWinupd)
585 IntGdiCombineRgn(RgnTemp, RgnUpdate, RgnWinupd, RGN_OR);
586 else
587 IntGdiCombineRgn(RgnTemp, RgnUpdate, NULL, RGN_COPY);
588 REGION_UnlockRgn(RgnTemp);
589 }
590
591 if (RgnWinupd)
592 {
593 REGION_Delete(RgnWinupd);
594 }
595
596 if (RgnUpdate)
597 {
598 REGION_Delete(RgnUpdate);
599 }
600
601 if (Window)
602 UserDerefObjectCo(Window);
603
604 TRACE("Leave NtUserScrollWindowEx, ret=%lu\n",_ret_);
605 UserLeave();
606 END_CLEANUP;
607 }
608
609
610 /* EOF */