Indentation fix.
[reactos.git] / reactos / subsys / win32k / ntuser / painting.c
1 /*
2 * ReactOS W32 Subsystem
3 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 *
19 * $Id$
20 *
21 * COPYRIGHT: See COPYING in the top level directory
22 * PROJECT: ReactOS kernel
23 * PURPOSE: Window painting function
24 * FILE: subsys/win32k/ntuser/painting.c
25 * PROGRAMER: Filip Navara (xnavara@volny.cz)
26 * REVISION HISTORY:
27 * 06/06/2001 Created (?)
28 * 18/11/2003 Complete rewrite
29 */
30
31 /* INCLUDES ******************************************************************/
32
33 #include <w32k.h>
34
35 #define NDEBUG
36 #include <debug.h>
37
38 /* PRIVATE FUNCTIONS **********************************************************/
39
40 /**
41 * @name IntIntersectWithParents
42 *
43 * Intersect window rectangle with all parent client rectangles.
44 *
45 * @param Child
46 * Pointer to child window to start intersecting from.
47 * @param WindowRect
48 * Pointer to rectangle that we want to intersect in screen
49 * coordinates on input and intersected rectangle on output (if TRUE
50 * is returned).
51 *
52 * @return
53 * If any parent is minimized or invisible or the resulting rectangle
54 * is empty then FALSE is returned. Otherwise TRUE is returned.
55 */
56
57 BOOL FASTCALL
58 IntIntersectWithParents(PWINDOW_OBJECT Child, PRECT WindowRect)
59 {
60 PWINDOW_OBJECT ParentWindow;
61
62 ParentWindow = Child->Parent;
63 while (ParentWindow != NULL)
64 {
65 if (!(ParentWindow->Style & WS_VISIBLE) ||
66 (ParentWindow->Style & WS_MINIMIZE))
67 {
68 return FALSE;
69 }
70
71 if (!IntGdiIntersectRect(WindowRect, WindowRect, &ParentWindow->ClientRect))
72 {
73 return FALSE;
74 }
75
76 /* FIXME: Layered windows. */
77
78 ParentWindow = ParentWindow->Parent;
79 }
80
81 return TRUE;
82 }
83
84 VOID FASTCALL
85 IntValidateParent(PWINDOW_OBJECT Child, HRGN ValidRegion)
86 {
87 PWINDOW_OBJECT ParentWindow = Child->Parent;
88
89 while (ParentWindow)
90 {
91 if (ParentWindow->Style & WS_CLIPCHILDREN)
92 break;
93
94 if (ParentWindow->UpdateRegion != 0)
95 {
96 NtGdiCombineRgn(ParentWindow->UpdateRegion, ParentWindow->UpdateRegion,
97 ValidRegion, RGN_DIFF);
98 /* FIXME: If the resulting region is empty, remove fake posted paint message */
99 }
100
101 ParentWindow = ParentWindow->Parent;
102 }
103 }
104
105 /**
106 * @name IntCalcWindowRgn
107 *
108 * Get a window or client region.
109 */
110
111 HRGN FASTCALL
112 IntCalcWindowRgn(PWINDOW_OBJECT Window, BOOL Client)
113 {
114 HRGN hRgnWindow;
115 UINT RgnType;
116
117 if (Client)
118 hRgnWindow = UnsafeIntCreateRectRgnIndirect(&Window->ClientRect);
119 else
120 hRgnWindow = UnsafeIntCreateRectRgnIndirect(&Window->WindowRect);
121
122 if (Window->WindowRegion != NULL && !(Window->Style & WS_MINIMIZE))
123 {
124 NtGdiOffsetRgn(hRgnWindow,
125 -Window->WindowRect.left,
126 -Window->WindowRect.top);
127 RgnType = NtGdiCombineRgn(hRgnWindow, hRgnWindow, Window->WindowRegion, RGN_AND);
128 NtGdiOffsetRgn(hRgnWindow,
129 Window->WindowRect.left,
130 Window->WindowRect.top);
131 }
132
133 return hRgnWindow;
134 }
135
136 /**
137 * @name IntGetNCUpdateRgn
138 *
139 * Get non-client update region of a window and optionally validate it.
140 *
141 * @param Window
142 * Pointer to window to get the NC update region from.
143 * @param Validate
144 * Set to TRUE to force validating the NC update region.
145 *
146 * @return
147 * Handle to NC update region. The caller is responsible for deleting
148 * it.
149 */
150
151 HRGN FASTCALL
152 IntGetNCUpdateRgn(PWINDOW_OBJECT Window, BOOL Validate)
153 {
154 HRGN hRgnNonClient;
155 HRGN hRgnWindow;
156 UINT RgnType;
157
158 if (Window->UpdateRegion != NULL &&
159 Window->UpdateRegion != (HRGN)1)
160 {
161 hRgnNonClient = NtGdiCreateRectRgn(0, 0, 0, 0);
162 hRgnWindow = IntCalcWindowRgn(Window, TRUE);
163
164 /*
165 * If region creation fails it's safe to fallback to whole
166 * window region.
167 */
168
169 if (hRgnNonClient == NULL)
170 {
171 return (HRGN)1;
172 }
173
174 RgnType = NtGdiCombineRgn(hRgnNonClient, Window->UpdateRegion,
175 hRgnWindow, RGN_DIFF);
176 if (RgnType == ERROR)
177 {
178 NtGdiDeleteObject(hRgnNonClient);
179 return (HRGN)1;
180 }
181 else if (RgnType == NULLREGION)
182 {
183 NtGdiDeleteObject(hRgnNonClient);
184 return NULL;
185 }
186
187 /*
188 * Remove the nonclient region from the standard update region if
189 * we were asked for it.
190 */
191
192 if (Validate)
193 {
194 if (NtGdiCombineRgn(Window->UpdateRegion, Window->UpdateRegion,
195 hRgnWindow, RGN_AND) == NULLREGION)
196 {
197 GDIOBJ_SetOwnership(Window->UpdateRegion, PsGetCurrentProcess());
198 NtGdiDeleteObject(Window->UpdateRegion);
199 Window->UpdateRegion = NULL;
200 if (!(Window->Flags & WINDOWOBJECT_NEED_INTERNALPAINT))
201 MsqDecPaintCountQueue(Window->MessageQueue);
202 }
203 }
204
205 NtGdiDeleteObject(hRgnWindow);
206
207 return hRgnNonClient;
208 }
209 else
210 {
211 return Window->UpdateRegion;
212 }
213 }
214
215 /*
216 * IntPaintWindows
217 *
218 * Internal function used by IntRedrawWindow.
219 */
220
221 STATIC VOID FASTCALL
222 co_IntPaintWindows(PWINDOW_OBJECT Window, ULONG Flags)
223 {
224 HDC hDC;
225 HWND hWnd = Window->hSelf;
226 HRGN TempRegion;
227
228 if (Flags & (RDW_ERASENOW | RDW_UPDATENOW))
229 {
230 if (Window->UpdateRegion)
231 {
232 IntValidateParent(Window, Window->UpdateRegion);
233 }
234
235 if (Flags & RDW_UPDATENOW)
236 {
237 if (Window->UpdateRegion != NULL ||
238 Window->Flags & WINDOWOBJECT_NEED_INTERNALPAINT)
239 {
240 co_IntSendMessage(hWnd, WM_PAINT, 0, 0);
241 }
242 }
243 else
244 {
245 if (Window->Flags & WINDOWOBJECT_NEED_NCPAINT)
246 {
247 TempRegion = IntGetNCUpdateRgn(Window, TRUE);
248 Window->Flags &= ~WINDOWOBJECT_NEED_NCPAINT;
249 MsqDecPaintCountQueue(Window->MessageQueue);
250 co_IntSendMessage(hWnd, WM_NCPAINT, (WPARAM)TempRegion, 0);
251 if ((HANDLE) 1 != TempRegion && NULL != TempRegion)
252 {
253 /* NOTE: The region can already be deleted! */
254 GDIOBJ_FreeObj(TempRegion, GDI_OBJECT_TYPE_REGION | GDI_OBJECT_TYPE_SILENT);
255 }
256 }
257
258 if (Window->Flags & WINDOWOBJECT_NEED_ERASEBKGND)
259 {
260 if (Window->UpdateRegion)
261 {
262 hDC = UserGetDCEx(Window, Window->UpdateRegion,
263 DCX_CACHE | DCX_USESTYLE |
264 DCX_INTERSECTRGN | DCX_KEEPCLIPRGN);
265 if (co_IntSendMessage(hWnd, WM_ERASEBKGND, (WPARAM)hDC, 0))
266 {
267 Window->Flags &= ~WINDOWOBJECT_NEED_ERASEBKGND;
268 }
269 UserReleaseDC(Window, hDC, FALSE);
270 }
271 }
272 }
273 }
274
275 /*
276 * Check that the window is still valid at this point
277 */
278 if (!IntIsWindow(hWnd))
279 {
280 return;
281 }
282
283 /*
284 * Paint child windows.
285 */
286 if (!(Flags & RDW_NOCHILDREN) && !(Window->Style & WS_MINIMIZE) &&
287 ((Flags & RDW_ALLCHILDREN) || !(Window->Style & WS_CLIPCHILDREN)))
288 {
289 HWND *List, *phWnd;
290
291 if ((List = IntWinListChildren(Window)))
292 {
293 for (phWnd = List; *phWnd; ++phWnd)
294 {
295 Window = IntGetWindowObject(*phWnd);
296 if (Window && (Window->Style & WS_VISIBLE))
297 {
298 co_IntPaintWindows(Window, Flags);
299 ObmDereferenceObject(Window);
300 }
301 }
302 ExFreePool(List);
303 }
304 }
305 }
306
307 /*
308 * IntInvalidateWindows
309 *
310 * Internal function used by IntRedrawWindow.
311 */
312
313 VOID FASTCALL
314 IntInvalidateWindows(PWINDOW_OBJECT Window, HRGN hRgn, ULONG Flags)
315 {
316 INT RgnType;
317 BOOL HadPaintMessage, HadNCPaintMessage;
318 BOOL HasPaintMessage, HasNCPaintMessage;
319
320 /*
321 * Clip the given region with window rectangle (or region)
322 */
323
324 if (!Window->WindowRegion || (Window->Style & WS_MINIMIZE))
325 {
326 HRGN hRgnWindow;
327
328 hRgnWindow = UnsafeIntCreateRectRgnIndirect(&Window->WindowRect);
329 RgnType = NtGdiCombineRgn(hRgn, hRgn, hRgnWindow, RGN_AND);
330 NtGdiDeleteObject(hRgnWindow);
331 }
332 else
333 {
334 NtGdiOffsetRgn(hRgn,
335 -Window->WindowRect.left,
336 -Window->WindowRect.top);
337 RgnType = NtGdiCombineRgn(hRgn, hRgn, Window->WindowRegion, RGN_AND);
338 NtGdiOffsetRgn(hRgn,
339 Window->WindowRect.left,
340 Window->WindowRect.top);
341 }
342
343 /*
344 * Save current state of pending updates
345 */
346
347 HadPaintMessage = Window->UpdateRegion != NULL ||
348 Window->Flags & WINDOWOBJECT_NEED_INTERNALPAINT;
349 HadNCPaintMessage = Window->Flags & WINDOWOBJECT_NEED_NCPAINT;
350
351 /*
352 * Update the region and flags
353 */
354
355 if (Flags & RDW_INVALIDATE && RgnType != NULLREGION)
356 {
357 if (Window->UpdateRegion == NULL)
358 {
359 Window->UpdateRegion = NtGdiCreateRectRgn(0, 0, 0, 0);
360 GDIOBJ_SetOwnership(Window->UpdateRegion, NULL);
361 }
362
363 if (NtGdiCombineRgn(Window->UpdateRegion, Window->UpdateRegion,
364 hRgn, RGN_OR) == NULLREGION)
365 {
366 GDIOBJ_SetOwnership(Window->UpdateRegion, PsGetCurrentProcess());
367 NtGdiDeleteObject(Window->UpdateRegion);
368 Window->UpdateRegion = NULL;
369 }
370
371 if (Flags & RDW_FRAME)
372 Window->Flags |= WINDOWOBJECT_NEED_NCPAINT;
373 if (Flags & RDW_ERASE)
374 Window->Flags |= WINDOWOBJECT_NEED_ERASEBKGND;
375
376 Flags |= RDW_FRAME;
377 }
378
379 if (Flags & RDW_VALIDATE && RgnType != NULLREGION)
380 {
381 if (Window->UpdateRegion != NULL)
382 {
383 if (NtGdiCombineRgn(Window->UpdateRegion, Window->UpdateRegion,
384 hRgn, RGN_DIFF) == NULLREGION)
385 {
386 GDIOBJ_SetOwnership(Window->UpdateRegion, PsGetCurrentProcess());
387 NtGdiDeleteObject(Window->UpdateRegion);
388 Window->UpdateRegion = NULL;
389 }
390 }
391
392 if (Window->UpdateRegion == NULL)
393 Window->Flags &= ~WINDOWOBJECT_NEED_ERASEBKGND;
394 if (Flags & RDW_NOFRAME)
395 Window->Flags &= ~WINDOWOBJECT_NEED_NCPAINT;
396 if (Flags & RDW_NOERASE)
397 Window->Flags &= ~WINDOWOBJECT_NEED_ERASEBKGND;
398 }
399
400 if (Flags & RDW_INTERNALPAINT)
401 {
402 Window->Flags |= WINDOWOBJECT_NEED_INTERNALPAINT;
403 }
404
405 if (Flags & RDW_NOINTERNALPAINT)
406 {
407 Window->Flags &= ~WINDOWOBJECT_NEED_INTERNALPAINT;
408 }
409
410 /*
411 * Process children if needed
412 */
413
414 if (!(Flags & RDW_NOCHILDREN) && !(Window->Style & WS_MINIMIZE) &&
415 ((Flags & RDW_ALLCHILDREN) || !(Window->Style & WS_CLIPCHILDREN)))
416 {
417 HWND *List, *phWnd;
418 PWINDOW_OBJECT Child;
419
420 if ((List = IntWinListChildren(Window)))
421 {
422 for (phWnd = List; *phWnd; ++phWnd)
423 {
424 if(!(Child = UserGetWindowObject(*phWnd)))
425 {
426 continue;
427 }
428
429 if (Child->Style & WS_VISIBLE)
430 {
431 /*
432 * Recursive call to update children UpdateRegion
433 */
434 HRGN hRgnTemp = NtGdiCreateRectRgn(0, 0, 0, 0);
435 NtGdiCombineRgn(hRgnTemp, hRgn, 0, RGN_COPY);
436 IntInvalidateWindows(Child, hRgnTemp, Flags);
437 NtGdiDeleteObject(hRgnTemp);
438 }
439
440 }
441 ExFreePool(List);
442 }
443 }
444
445 /*
446 * Fake post paint messages to window message queue if needed
447 */
448
449 HasPaintMessage = Window->UpdateRegion != NULL ||
450 Window->Flags & WINDOWOBJECT_NEED_INTERNALPAINT;
451 HasNCPaintMessage = Window->Flags & WINDOWOBJECT_NEED_NCPAINT;
452
453 if (HasPaintMessage != HadPaintMessage)
454 {
455 if (HadPaintMessage)
456 MsqDecPaintCountQueue(Window->MessageQueue);
457 else
458 MsqIncPaintCountQueue(Window->MessageQueue);
459 }
460
461 if (HasNCPaintMessage != HadNCPaintMessage)
462 {
463 if (HadNCPaintMessage)
464 MsqDecPaintCountQueue(Window->MessageQueue);
465 else
466 MsqIncPaintCountQueue(Window->MessageQueue);
467 }
468
469 }
470
471 /*
472 * IntIsWindowDrawable
473 *
474 * Remarks
475 * Window is drawable when it is visible and all parents are not
476 * minimized.
477 */
478
479 BOOL FASTCALL
480 IntIsWindowDrawable(PWINDOW_OBJECT Window)
481 {
482 PWINDOW_OBJECT Wnd;
483
484 for (Wnd = Window; Wnd != NULL; Wnd = Wnd->Parent)
485 {
486 if (!(Wnd->Style & WS_VISIBLE) ||
487 ((Wnd->Style & WS_MINIMIZE) && (Wnd != Window)))
488 {
489 return FALSE;
490 }
491 }
492
493 return TRUE;
494 }
495
496 /*
497 * IntRedrawWindow
498 *
499 * Internal version of NtUserRedrawWindow that takes WINDOW_OBJECT as
500 * first parameter.
501 */
502
503 BOOL FASTCALL
504 co_UserRedrawWindow(PWINDOW_OBJECT Window, const RECT* UpdateRect, HRGN UpdateRgn,
505 ULONG Flags)
506 {
507 HRGN hRgn = NULL;
508
509 /*
510 * Step 1.
511 * Validation of passed parameters.
512 */
513
514 if (!IntIsWindowDrawable(Window) ||
515 (Flags & (RDW_VALIDATE | RDW_INVALIDATE)) ==
516 (RDW_VALIDATE | RDW_INVALIDATE))
517 {
518 return FALSE;
519 }
520
521 /*
522 * Step 2.
523 * Transform the parameters UpdateRgn and UpdateRect into
524 * a region hRgn specified in screen coordinates.
525 */
526
527 if (Flags & (RDW_INVALIDATE | RDW_VALIDATE))
528 {
529 if (UpdateRgn != NULL)
530 {
531 hRgn = NtGdiCreateRectRgn(0, 0, 0, 0);
532 if (NtGdiCombineRgn(hRgn, UpdateRgn, NULL, RGN_COPY) == NULLREGION)
533 NtGdiDeleteObject(hRgn);
534 else
535 NtGdiOffsetRgn(hRgn, Window->ClientRect.left, Window->ClientRect.top);
536 }
537 else if (UpdateRect != NULL)
538 {
539 if (!IntGdiIsEmptyRect(UpdateRect))
540 {
541 hRgn = UnsafeIntCreateRectRgnIndirect((RECT *)UpdateRect);
542 NtGdiOffsetRgn(hRgn, Window->ClientRect.left, Window->ClientRect.top);
543 }
544 }
545 else if ((Flags & (RDW_INVALIDATE | RDW_FRAME)) == (RDW_INVALIDATE | RDW_FRAME) ||
546 (Flags & (RDW_VALIDATE | RDW_NOFRAME)) == (RDW_VALIDATE | RDW_NOFRAME))
547 {
548 if (!IntGdiIsEmptyRect(&Window->WindowRect))
549 hRgn = UnsafeIntCreateRectRgnIndirect(&Window->WindowRect);
550 }
551 else
552 {
553 if (!IntGdiIsEmptyRect(&Window->ClientRect))
554 hRgn = UnsafeIntCreateRectRgnIndirect(&Window->ClientRect);
555 }
556 }
557
558 /*
559 * Step 3.
560 * Adjust the window update region depending on hRgn and flags.
561 */
562
563 if (Flags & (RDW_INVALIDATE | RDW_VALIDATE | RDW_INTERNALPAINT | RDW_NOINTERNALPAINT) &&
564 hRgn != NULL)
565 {
566 IntInvalidateWindows(Window, hRgn, Flags);
567 }
568
569 /*
570 * Step 4.
571 * Repaint and erase windows if needed.
572 */
573
574 if (Flags & (RDW_ERASENOW | RDW_UPDATENOW))
575 {
576 co_IntPaintWindows(Window, Flags);
577 }
578
579 /*
580 * Step 5.
581 * Cleanup ;-)
582 */
583
584 if (hRgn != NULL)
585 {
586 NtGdiDeleteObject(hRgn);
587 }
588
589 return TRUE;
590 }
591
592 BOOL FASTCALL
593 IntIsWindowDirty(PWINDOW_OBJECT Window)
594 {
595 return (Window->Style & WS_VISIBLE) &&
596 ((Window->UpdateRegion != NULL) ||
597 (Window->Flags & WINDOWOBJECT_NEED_INTERNALPAINT) ||
598 (Window->Flags & WINDOWOBJECT_NEED_NCPAINT));
599 }
600
601 HWND FASTCALL
602 IntFindWindowToRepaint(PWINDOW_OBJECT Window, PW32THREAD Thread)
603 {
604 HWND hChild;
605 PWINDOW_OBJECT TempWindow;
606
607 for (; Window != NULL; Window = Window->NextSibling)
608 {
609 if (IntWndBelongsToThread(Window, Thread) &&
610 IntIsWindowDirty(Window))
611 {
612 /* Make sure all non-transparent siblings are already drawn. */
613 if (Window->ExStyle & WS_EX_TRANSPARENT)
614 {
615 for (TempWindow = Window->NextSibling; TempWindow != NULL;
616 TempWindow = TempWindow->NextSibling)
617 {
618 if (!(TempWindow->ExStyle & WS_EX_TRANSPARENT) &&
619 IntWndBelongsToThread(TempWindow, Thread) &&
620 IntIsWindowDirty(TempWindow))
621 {
622 return TempWindow->hSelf;
623 }
624 }
625 }
626
627 return Window->hSelf;
628 }
629
630 if (Window->FirstChild)
631 {
632 hChild = IntFindWindowToRepaint(Window->FirstChild, Thread);
633 if (hChild != NULL)
634 return hChild;
635 }
636 }
637
638 return NULL;
639 }
640
641 BOOL FASTCALL
642 IntGetPaintMessage(HWND hWnd, UINT MsgFilterMin, UINT MsgFilterMax,
643 PW32THREAD Thread, MSG *Message, BOOL Remove)
644 {
645 PUSER_MESSAGE_QUEUE MessageQueue = (PUSER_MESSAGE_QUEUE)Thread->MessageQueue;
646
647 if (!MessageQueue->PaintCount)
648 return FALSE;
649
650 if ((MsgFilterMin != 0 || MsgFilterMax != 0) &&
651 (MsgFilterMin > WM_PAINT || MsgFilterMax < WM_PAINT))
652 return FALSE;
653
654 Message->hwnd = IntFindWindowToRepaint(UserGetDesktopWindow(), PsGetWin32Thread());
655
656 if (Message->hwnd == NULL)
657 {
658 DPRINT1("PAINTING BUG: Thread marked as containing dirty windows, but no dirty windows found!\n");
659 return FALSE;
660 }
661
662 if (hWnd != NULL && Message->hwnd != hWnd)
663 return FALSE;
664
665 Message->message = WM_PAINT;
666 Message->wParam = Message->lParam = 0;
667
668 return TRUE;
669 }
670
671 static
672 HWND FASTCALL
673 co_IntFixCaret(PWINDOW_OBJECT Window, LPRECT lprc, UINT flags)
674 {
675 PDESKTOP_OBJECT Desktop;
676 PTHRDCARETINFO CaretInfo;
677 HWND hWndCaret;
678 PWINDOW_OBJECT WndCaret;
679
680 ASSERT_REFS_CO(Window);
681
682 Desktop = PsGetCurrentThread()->Tcb.Win32Thread->Desktop;
683 CaretInfo = ((PUSER_MESSAGE_QUEUE)Desktop->ActiveMessageQueue)->CaretInfo;
684 hWndCaret = CaretInfo->hWnd;
685
686 WndCaret = UserGetWindowObject(hWndCaret);
687
688 //fix: check for WndCaret can be null
689 if (WndCaret == Window ||
690 ((flags & SW_SCROLLCHILDREN) && IntIsChildWindow(Window, WndCaret)))
691 {
692 POINT pt, FromOffset, ToOffset, Offset;
693 RECT rcCaret;
694
695 pt.x = CaretInfo->Pos.x;
696 pt.y = CaretInfo->Pos.y;
697 IntGetClientOrigin(WndCaret, &FromOffset);
698 IntGetClientOrigin(Window, &ToOffset);
699 Offset.x = FromOffset.x - ToOffset.x;
700 Offset.y = FromOffset.y - ToOffset.y;
701 rcCaret.left = pt.x;
702 rcCaret.top = pt.y;
703 rcCaret.right = pt.x + CaretInfo->Size.cx;
704 rcCaret.bottom = pt.y + CaretInfo->Size.cy;
705 if (IntGdiIntersectRect(lprc, lprc, &rcCaret))
706 {
707 co_UserHideCaret(0);
708 lprc->left = pt.x;
709 lprc->top = pt.y;
710 return hWndCaret;
711 }
712 }
713
714 return 0;
715 }
716
717 /* PUBLIC FUNCTIONS ***********************************************************/
718
719 /*
720 * NtUserBeginPaint
721 *
722 * Status
723 * @implemented
724 */
725
726 HDC STDCALL
727 NtUserBeginPaint(HWND hWnd, PAINTSTRUCT* UnsafePs)
728 {
729 PWINDOW_OBJECT Window = NULL;
730 PAINTSTRUCT Ps;
731 PROSRGNDATA Rgn;
732 NTSTATUS Status;
733 DECLARE_RETURN(HDC);
734
735 DPRINT("Enter NtUserBeginPaint\n");
736 UserEnterExclusive();
737
738 if (!(Window = UserGetWindowObject(hWnd)))
739 {
740 RETURN( NULL);
741 }
742
743 UserRefObjectCo(Window);
744
745 co_UserHideCaret(Window);
746
747 if (Window->Flags & WINDOWOBJECT_NEED_NCPAINT)
748 {
749 HRGN hRgn;
750
751 hRgn = IntGetNCUpdateRgn(Window, FALSE);
752 Window->Flags &= ~WINDOWOBJECT_NEED_NCPAINT;
753 MsqDecPaintCountQueue(Window->MessageQueue);
754 co_IntSendMessage(hWnd, WM_NCPAINT, (WPARAM)hRgn, 0);
755 if (hRgn != (HANDLE)1 && hRgn != NULL)
756 {
757 /* NOTE: The region can already by deleted! */
758 GDIOBJ_FreeObj(hRgn, GDI_OBJECT_TYPE_REGION | GDI_OBJECT_TYPE_SILENT);
759 }
760 }
761
762 RtlZeroMemory(&Ps, sizeof(PAINTSTRUCT));
763
764 Ps.hdc = UserGetDCEx(Window, Window->UpdateRegion, DCX_INTERSECTRGN | DCX_USESTYLE);
765 if (!Ps.hdc)
766 {
767 RETURN( NULL);
768 }
769
770 if (Window->UpdateRegion != NULL)
771 {
772 MsqDecPaintCountQueue(Window->MessageQueue);
773 Rgn = RGNDATA_LockRgn(Window->UpdateRegion);
774 if (NULL != Rgn)
775 {
776 UnsafeIntGetRgnBox(Rgn, &Ps.rcPaint);
777 RGNDATA_UnlockRgn(Rgn);
778 IntGdiOffsetRect(&Ps.rcPaint,
779 -Window->ClientRect.left,
780 -Window->ClientRect.top);
781 }
782 else
783 {
784 IntGetClientRect(Window, &Ps.rcPaint);
785 }
786 GDIOBJ_SetOwnership(Window->UpdateRegion, PsGetCurrentProcess());
787 Window->UpdateRegion = NULL;
788 }
789 else
790 {
791 if (Window->Flags & WINDOWOBJECT_NEED_INTERNALPAINT)
792 MsqDecPaintCountQueue(Window->MessageQueue);
793
794 IntGetClientRect(Window, &Ps.rcPaint);
795 }
796
797 Window->Flags &= ~WINDOWOBJECT_NEED_INTERNALPAINT;
798
799 if (Window->Flags & WINDOWOBJECT_NEED_ERASEBKGND)
800 {
801 Window->Flags &= ~WINDOWOBJECT_NEED_ERASEBKGND;
802 Ps.fErase = !co_IntSendMessage(hWnd, WM_ERASEBKGND, (WPARAM)Ps.hdc, 0);
803 }
804 else
805 {
806 Ps.fErase = FALSE;
807 }
808
809 Status = MmCopyToCaller(UnsafePs, &Ps, sizeof(PAINTSTRUCT));
810 if (! NT_SUCCESS(Status))
811 {
812 SetLastNtError(Status);
813 RETURN( NULL);
814 }
815
816 RETURN( Ps.hdc);
817
818 CLEANUP:
819 if (Window) UserDerefObjectCo(Window);
820
821 DPRINT("Leave NtUserBeginPaint, ret=%i\n",_ret_);
822 UserLeave();
823 END_CLEANUP;
824
825 }
826
827 /*
828 * NtUserEndPaint
829 *
830 * Status
831 * @implemented
832 */
833
834 BOOL STDCALL
835 NtUserEndPaint(HWND hWnd, CONST PAINTSTRUCT* lPs)
836 {
837 PWINDOW_OBJECT Window;
838 DECLARE_RETURN(BOOL);
839
840 DPRINT("Enter NtUserEndPaint\n");
841 UserEnterExclusive();
842
843 if (!(Window = UserGetWindowObject(hWnd)))
844 {
845 RETURN(FALSE);
846 }
847
848 UserReleaseDC(Window, lPs->hdc, TRUE);
849
850 UserRefObjectCo(Window);
851 co_UserShowCaret(Window);
852 UserDerefObjectCo(Window);
853
854 RETURN(TRUE);
855
856 CLEANUP:
857 DPRINT("Leave NtUserEndPaint, ret=%i\n",_ret_);
858 UserLeave();
859 END_CLEANUP;
860 }
861
862 /*
863 * NtUserInvalidateRect
864 *
865 * Status
866 * @implemented
867 */
868
869 DWORD STDCALL
870 NtUserInvalidateRect(HWND hWnd, CONST RECT *Rect, BOOL Erase)
871 {
872 return NtUserRedrawWindow(hWnd, Rect, 0, RDW_INVALIDATE | (Erase ? RDW_ERASE : 0));
873 }
874
875 /*
876 * NtUserInvalidateRgn
877 *
878 * Status
879 * @implemented
880 */
881
882 DWORD STDCALL
883 NtUserInvalidateRgn(HWND hWnd, HRGN Rgn, BOOL Erase)
884 {
885 return NtUserRedrawWindow(hWnd, NULL, Rgn, RDW_INVALIDATE | (Erase ? RDW_ERASE : 0));
886 }
887
888
889
890 BOOL FASTCALL
891 co_UserValidateRgn(PWINDOW_OBJECT Window, HRGN hRgn)
892 {
893 return co_UserRedrawWindow(Window, NULL, hRgn, RDW_VALIDATE | RDW_NOCHILDREN);
894 }
895
896 /*
897 * NtUserValidateRgn
898 *
899 * Status
900 * @implemented
901 */
902
903 BOOL STDCALL
904 NtUserValidateRgn(HWND hWnd, HRGN hRgn)
905 {
906 return NtUserRedrawWindow(hWnd, NULL, hRgn, RDW_VALIDATE | RDW_NOCHILDREN);
907 }
908
909 /*
910 * NtUserUpdateWindow
911 *
912 * Status
913 * @implemented
914 */
915
916 BOOL STDCALL
917 NtUserUpdateWindow(HWND hWnd)
918 {
919 return NtUserRedrawWindow(hWnd, NULL, 0, RDW_UPDATENOW | RDW_ALLCHILDREN);
920 }
921
922
923
924
925
926 INT FASTCALL
927 co_UserGetUpdateRgn(PWINDOW_OBJECT Window, HRGN hRgn, BOOL bErase)
928 {
929 int RegionType;
930
931 ASSERT_REFS_CO(Window);
932
933 if (Window->UpdateRegion == NULL)
934 {
935 RegionType = (NtGdiSetRectRgn(hRgn, 0, 0, 0, 0) ? NULLREGION : ERROR);
936 }
937 else
938 {
939 RegionType = NtGdiCombineRgn(hRgn, Window->UpdateRegion, hRgn, RGN_COPY);
940 NtGdiOffsetRgn(hRgn, -Window->ClientRect.left, -Window->ClientRect.top);
941 }
942
943 if (bErase && RegionType != NULLREGION && RegionType != ERROR)
944 {
945 co_UserRedrawWindow(Window, NULL, NULL, RDW_ERASENOW | RDW_NOCHILDREN);
946 }
947
948 return RegionType;
949 }
950 /*
951 * NtUserGetUpdateRgn
952 *
953 * Status
954 * @implemented
955 */
956
957 INT STDCALL
958 NtUserGetUpdateRgn(HWND hWnd, HRGN hRgn, BOOL bErase)
959 {
960 DECLARE_RETURN(INT);
961 PWINDOW_OBJECT Window;
962 INT ret;
963
964 DPRINT("Enter NtUserGetUpdateRgn\n");
965 UserEnterExclusive();
966
967 if (!(Window = UserGetWindowObject(hWnd)))
968 {
969 RETURN(ERROR);
970 }
971
972 UserRefObjectCo(Window);
973 ret = co_UserGetUpdateRgn(Window, hRgn, bErase);
974 UserDerefObjectCo(Window);
975
976 RETURN(ret);
977
978 CLEANUP:
979 DPRINT("Leave NtUserGetUpdateRgn, ret=%i\n",_ret_);
980 UserLeave();
981 END_CLEANUP;
982 }
983
984 /*
985 * NtUserGetUpdateRect
986 *
987 * Status
988 * @implemented
989 */
990
991 BOOL STDCALL
992 NtUserGetUpdateRect(HWND hWnd, LPRECT UnsafeRect, BOOL bErase)
993 {
994 PWINDOW_OBJECT Window;
995 RECT Rect;
996 INT RegionType;
997 PROSRGNDATA RgnData;
998 BOOL AlwaysPaint;
999 NTSTATUS Status;
1000 DECLARE_RETURN(BOOL);
1001
1002 DPRINT("Enter NtUserGetUpdateRect\n");
1003 UserEnterExclusive();
1004
1005 if (!(Window = UserGetWindowObject(hWnd)))
1006 {
1007 RETURN( ERROR);
1008 }
1009
1010 if (Window->UpdateRegion == NULL)
1011 {
1012 Rect.left = Rect.top = Rect.right = Rect.bottom = 0;
1013 }
1014 else
1015 {
1016 RgnData = RGNDATA_LockRgn(Window->UpdateRegion);
1017 ASSERT(RgnData != NULL);
1018 RegionType = UnsafeIntGetRgnBox(RgnData, &Rect);
1019 ASSERT(RegionType != ERROR);
1020 RGNDATA_UnlockRgn(RgnData);
1021 }
1022 AlwaysPaint = (Window->Flags & WINDOWOBJECT_NEED_NCPAINT) ||
1023 (Window->Flags & WINDOWOBJECT_NEED_INTERNALPAINT);
1024
1025 if (bErase && Rect.left < Rect.right && Rect.top < Rect.bottom)
1026 {
1027 UserRefObjectCo(Window);
1028 co_UserRedrawWindow(Window, NULL, NULL, RDW_ERASENOW | RDW_NOCHILDREN);
1029 UserDerefObjectCo(Window);
1030 }
1031
1032 if (UnsafeRect != NULL)
1033 {
1034 Status = MmCopyToCaller(UnsafeRect, &Rect, sizeof(RECT));
1035 if (!NT_SUCCESS(Status))
1036 {
1037 SetLastWin32Error(ERROR_INVALID_PARAMETER);
1038 RETURN( FALSE);
1039 }
1040 }
1041
1042 RETURN( (Rect.left < Rect.right && Rect.top < Rect.bottom) || AlwaysPaint);
1043
1044 CLEANUP:
1045 DPRINT("Leave NtUserGetUpdateRect, ret=%i\n",_ret_);
1046 UserLeave();
1047 END_CLEANUP;
1048 }
1049
1050 /*
1051 * NtUserRedrawWindow
1052 *
1053 * Status
1054 * @implemented
1055 */
1056
1057 BOOL STDCALL
1058 NtUserRedrawWindow(HWND hWnd, CONST RECT *lprcUpdate, HRGN hrgnUpdate,
1059 UINT flags)
1060 {
1061 RECT SafeUpdateRect;
1062 NTSTATUS Status;
1063 PWINDOW_OBJECT Wnd;
1064 DECLARE_RETURN(BOOL);
1065
1066 DPRINT("Enter NtUserRedrawWindow\n");
1067 UserEnterExclusive();
1068
1069 if (!(Wnd = UserGetWindowObject(hWnd ? hWnd : IntGetDesktopWindow())))
1070 {
1071 RETURN( FALSE);
1072 }
1073
1074 if (lprcUpdate != NULL)
1075 {
1076 Status = MmCopyFromCaller(&SafeUpdateRect, (PRECT)lprcUpdate,
1077 sizeof(RECT));
1078
1079 if (!NT_SUCCESS(Status))
1080 {
1081 SetLastWin32Error(ERROR_INVALID_PARAMETER);
1082 RETURN( FALSE);
1083 }
1084 }
1085
1086 UserRefObjectCo(Wnd);
1087
1088 Status = co_UserRedrawWindow(Wnd, NULL == lprcUpdate ? NULL : &SafeUpdateRect,
1089 hrgnUpdate, flags);
1090
1091 UserDerefObjectCo(Wnd);
1092
1093 if (!NT_SUCCESS(Status))
1094 {
1095 /* IntRedrawWindow fails only in case that flags are invalid */
1096 SetLastWin32Error(ERROR_INVALID_PARAMETER);
1097 RETURN( FALSE);
1098 }
1099
1100 RETURN( TRUE);
1101
1102 CLEANUP:
1103 DPRINT("Leave NtUserRedrawWindow, ret=%i\n",_ret_);
1104 UserLeave();
1105 END_CLEANUP;
1106 }
1107
1108
1109
1110 static
1111 DWORD FASTCALL
1112 UserScrollDC(HDC hDC, INT dx, INT dy, const RECT *lprcScroll,
1113 const RECT *lprcClip, HRGN hrgnUpdate, LPRECT lprcUpdate)
1114 {
1115 RECT rSrc, rClipped_src, rClip, rDst, offset;
1116 PDC DC;
1117
1118 /*
1119 * Compute device clipping region (in device coordinates).
1120 */
1121
1122 DC = DC_LockDc(hDC);
1123 if (NULL == DC)
1124 {
1125 return FALSE;
1126 }
1127 if (lprcScroll)
1128 rSrc = *lprcScroll;
1129 else
1130 IntGdiGetClipBox(hDC, &rSrc);
1131 IntLPtoDP(DC, (LPPOINT)&rSrc, 2);
1132
1133 if (lprcClip)
1134 rClip = *lprcClip;
1135 else
1136 IntGdiGetClipBox(hDC, &rClip);
1137 IntLPtoDP(DC, (LPPOINT)&rClip, 2);
1138
1139 IntGdiIntersectRect(&rClipped_src, &rSrc, &rClip);
1140
1141 rDst = rClipped_src;
1142 IntGdiSetRect(&offset, 0, 0, dx, dy);
1143 IntLPtoDP(DC, (LPPOINT)&offset, 2);
1144 IntGdiOffsetRect(&rDst, offset.right - offset.left, offset.bottom - offset.top);
1145 IntGdiIntersectRect(&rDst, &rDst, &rClip);
1146
1147 /*
1148 * Copy bits, if possible.
1149 */
1150
1151 if (rDst.bottom > rDst.top && rDst.right > rDst.left)
1152 {
1153 RECT rDst_lp = rDst, rSrc_lp = rDst;
1154
1155 IntGdiOffsetRect(&rSrc_lp, offset.left - offset.right, offset.top - offset.bottom);
1156 IntDPtoLP(DC, (LPPOINT)&rDst_lp, 2);
1157 IntDPtoLP(DC, (LPPOINT)&rSrc_lp, 2);
1158 DC_UnlockDc(DC);
1159
1160 if (!NtGdiBitBlt(hDC, rDst_lp.left, rDst_lp.top, rDst_lp.right - rDst_lp.left,
1161 rDst_lp.bottom - rDst_lp.top, hDC, rSrc_lp.left, rSrc_lp.top,
1162 SRCCOPY))
1163 return FALSE;
1164 }
1165 else
1166 {
1167 DC_UnlockDc(DC);
1168 }
1169
1170 /*
1171 * Compute update areas. This is the clipped source or'ed with the
1172 * unclipped source translated minus the clipped src translated (rDst)
1173 * all clipped to rClip.
1174 */
1175
1176 if (hrgnUpdate || lprcUpdate)
1177 {
1178 HRGN hRgn = hrgnUpdate, hRgn2;
1179
1180 if (hRgn)
1181 NtGdiSetRectRgn(hRgn, rClipped_src.left, rClipped_src.top, rClipped_src.right, rClipped_src.bottom);
1182 else
1183 hRgn = NtGdiCreateRectRgn(rClipped_src.left, rClipped_src.top, rClipped_src.right, rClipped_src.bottom);
1184
1185 hRgn2 = UnsafeIntCreateRectRgnIndirect(&rSrc);
1186 NtGdiOffsetRgn(hRgn2, offset.right - offset.left, offset.bottom - offset.top);
1187 NtGdiCombineRgn(hRgn, hRgn, hRgn2, RGN_OR);
1188
1189 NtGdiSetRectRgn(hRgn2, rDst.left, rDst.top, rDst.right, rDst.bottom);
1190 NtGdiCombineRgn(hRgn, hRgn, hRgn2, RGN_DIFF);
1191
1192 NtGdiSetRectRgn(hRgn2, rClip.left, rClip.top, rClip.right, rClip.bottom);
1193 NtGdiCombineRgn(hRgn, hRgn, hRgn2, RGN_AND);
1194
1195 if (lprcUpdate)
1196 {
1197 NtGdiGetRgnBox(hRgn, lprcUpdate);
1198
1199 /* Put the lprcUpdate in logical coordinate */
1200 NtGdiDPtoLP(hDC, (LPPOINT)lprcUpdate, 2);
1201 }
1202 if (!hrgnUpdate)
1203 NtGdiDeleteObject(hRgn);
1204 NtGdiDeleteObject(hRgn2);
1205 }
1206 return TRUE;
1207 }
1208
1209
1210
1211
1212 /*
1213 * NtUserScrollDC
1214 *
1215 * Status
1216 * @implemented
1217 */
1218
1219 DWORD STDCALL
1220 NtUserScrollDC(HDC hDC, INT dx, INT dy, const RECT *lprcScroll,
1221 const RECT *lprcClip, HRGN hrgnUpdate, LPRECT lprcUpdate)
1222 {
1223 DECLARE_RETURN(DWORD);
1224
1225 DPRINT("Enter NtUserScrollDC\n");
1226 UserEnterExclusive();
1227
1228 RETURN( UserScrollDC(hDC, dx, dy, lprcScroll, lprcClip, hrgnUpdate, lprcUpdate));
1229
1230 CLEANUP:
1231 DPRINT("Leave NtUserScrollDC, ret=%i\n",_ret_);
1232 UserLeave();
1233 END_CLEANUP;
1234
1235 }
1236
1237 /*
1238 * NtUserScrollWindowEx
1239 *
1240 * Status
1241 * @implemented
1242 */
1243
1244 DWORD STDCALL
1245 NtUserScrollWindowEx(HWND hWnd, INT dx, INT dy, const RECT *UnsafeRect,
1246 const RECT *UnsafeClipRect, HRGN hrgnUpdate, LPRECT rcUpdate, UINT flags)
1247 {
1248 RECT rc, cliprc, caretrc, rect, clipRect;
1249 INT Result;
1250 PWINDOW_OBJECT Window = NULL, CaretWnd;
1251 HDC hDC;
1252 HRGN hrgnTemp;
1253 HWND hwndCaret;
1254 BOOL bUpdate = (rcUpdate || hrgnUpdate || flags & (SW_INVALIDATE | SW_ERASE));
1255 BOOL bOwnRgn = TRUE;
1256 NTSTATUS Status;
1257 DECLARE_RETURN(DWORD);
1258
1259 DPRINT("Enter NtUserScrollWindowEx\n");
1260 UserEnterExclusive();
1261
1262 Window = UserGetWindowObject(hWnd);
1263 if (!Window || !IntIsWindowDrawable(Window))
1264 {
1265 Window = NULL; /* prevent deref at cleanup */
1266 RETURN( ERROR);
1267 }
1268 UserRefObjectCo(Window);
1269
1270 IntGetClientRect(Window, &rc);
1271
1272 if (NULL != UnsafeRect)
1273 {
1274 Status = MmCopyFromCaller(&rect, UnsafeRect, sizeof(RECT));
1275 if (! NT_SUCCESS(Status))
1276 {
1277 SetLastNtError(Status);
1278 RETURN( ERROR);
1279 }
1280 IntGdiIntersectRect(&rc, &rc, &rect);
1281 }
1282
1283 if (NULL != UnsafeClipRect)
1284 {
1285 Status = MmCopyFromCaller(&clipRect, UnsafeClipRect, sizeof(RECT));
1286 if (! NT_SUCCESS(Status))
1287 {
1288 SetLastNtError(Status);
1289 RETURN( ERROR);
1290 }
1291 IntGdiIntersectRect(&cliprc, &rc, &clipRect);
1292 }
1293 else
1294 cliprc = rc;
1295
1296 if (cliprc.right <= cliprc.left || cliprc.bottom <= cliprc.top ||
1297 (dx == 0 && dy == 0))
1298 {
1299 RETURN( NULLREGION);
1300 }
1301
1302 caretrc = rc;
1303 hwndCaret = co_IntFixCaret(Window, &caretrc, flags);
1304
1305 if (hrgnUpdate)
1306 bOwnRgn = FALSE;
1307 else if (bUpdate)
1308 hrgnUpdate = NtGdiCreateRectRgn(0, 0, 0, 0);
1309
1310 hDC = UserGetDCEx(Window, 0, DCX_CACHE | DCX_USESTYLE);
1311 if (hDC)
1312 {
1313 UserScrollDC(hDC, dx, dy, &rc, &cliprc, hrgnUpdate, rcUpdate);
1314 UserReleaseDC(Window, hDC, FALSE);
1315 }
1316
1317 /*
1318 * Take into account the fact that some damage may have occurred during
1319 * the scroll.
1320 */
1321
1322 hrgnTemp = NtGdiCreateRectRgn(0, 0, 0, 0);
1323 Result = co_UserGetUpdateRgn(Window, hrgnTemp, FALSE);
1324 if (Result != NULLREGION)
1325 {
1326 HRGN hrgnClip = UnsafeIntCreateRectRgnIndirect(&cliprc);
1327 NtGdiOffsetRgn(hrgnTemp, dx, dy);
1328 NtGdiCombineRgn(hrgnTemp, hrgnTemp, hrgnClip, RGN_AND);
1329 co_UserRedrawWindow(Window, NULL, hrgnTemp, RDW_INVALIDATE | RDW_ERASE);
1330 NtGdiDeleteObject(hrgnClip);
1331 }
1332
1333 NtGdiDeleteObject(hrgnTemp);
1334
1335 if (flags & SW_SCROLLCHILDREN)
1336 {
1337 HWND *List = IntWinListChildren(Window);
1338 if (List)
1339 {
1340 int i;
1341 RECT r, dummy;
1342 POINT ClientOrigin;
1343 PWINDOW_OBJECT Wnd;
1344
1345 IntGetClientOrigin(Window, &ClientOrigin);
1346 for (i = 0; List[i]; i++)
1347 {
1348 if (!(Wnd = UserGetWindowObject(List[i])))
1349 continue;
1350
1351 r = Wnd->WindowRect;
1352 r.left -= ClientOrigin.x;
1353 r.top -= ClientOrigin.y;
1354 r.right -= ClientOrigin.x;
1355 r.bottom -= ClientOrigin.y;
1356
1357 if (! UnsafeRect || IntGdiIntersectRect(&dummy, &r, &rc))
1358 {
1359 UserRefObjectCo(Wnd);
1360 co_WinPosSetWindowPos(Wnd, 0, r.left + dx, r.top + dy, 0, 0,
1361 SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE |
1362 SWP_NOREDRAW);
1363 UserDerefObjectCo(Wnd);
1364 }
1365
1366 }
1367 ExFreePool(List);
1368 }
1369 }
1370
1371 if (flags & (SW_INVALIDATE | SW_ERASE))
1372 co_UserRedrawWindow(Window, NULL, hrgnUpdate, RDW_INVALIDATE | RDW_ERASE |
1373 ((flags & SW_ERASE) ? RDW_ERASENOW : 0) |
1374 ((flags & SW_SCROLLCHILDREN) ? RDW_ALLCHILDREN : 0));
1375
1376 if (bOwnRgn && hrgnUpdate)
1377 NtGdiDeleteObject(hrgnUpdate);
1378
1379 if ((CaretWnd = UserGetWindowObject(hwndCaret)))
1380 {
1381 UserRefObjectCo(CaretWnd);
1382
1383 co_IntSetCaretPos(caretrc.left + dx, caretrc.top + dy);
1384 co_UserShowCaret(CaretWnd);
1385
1386 UserDerefObjectCo(CaretWnd);
1387 }
1388
1389 RETURN( Result);
1390
1391 CLEANUP:
1392 if (Window)
1393 UserDerefObjectCo(Window);
1394
1395 DPRINT("Leave NtUserScrollWindowEx, ret=%i\n",_ret_);
1396 UserLeave();
1397 END_CLEANUP;
1398 }
1399
1400 /* EOF */