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