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