Authors: Guy Albertelli <guy@codeweavers.com>, Mike McCormack <mike_mccormack@start...
[wine] / windows / dce.c
1 /*
2  * USER DCE functions
3  *
4  * Copyright 1993 Alexandre Julliard
5  *           1996,1997 Alex Korobka
6  *
7  *
8  * Note: Visible regions of CS_OWNDC/CS_CLASSDC window DCs 
9  * have to be updated dynamically. 
10  * 
11  * Internal DCX flags:
12  *
13  * DCX_DCEEMPTY    - dce is uninitialized
14  * DCX_DCEBUSY     - dce is in use
15  * DCX_DCEDIRTY    - ReleaseDC() should wipe instead of caching
16  * DCX_KEEPCLIPRGN - ReleaseDC() should not delete the clipping region
17  * DCX_WINDOWPAINT - BeginPaint() is in effect
18  */
19
20 #include <assert.h>
21 #include "dce.h"
22 #include "win.h"
23 #include "gdi.h"
24 #include "region.h"
25 #include "user.h"
26 #include "debugtools.h"
27 #include "windef.h"
28 #include "wingdi.h"
29 #include "wine/winbase16.h"
30 #include "wine/winuser16.h"
31
32 DEFAULT_DEBUG_CHANNEL(dc);
33
34 static DCE *firstDCE;
35 static HDC defaultDCstate;
36
37 static void DCE_DeleteClipRgn( DCE* );
38 static INT DCE_ReleaseDC( DCE* );
39
40
41 /***********************************************************************
42  *           DCE_DumpCache
43  */
44 static void DCE_DumpCache(void)
45 {
46     DCE *dce;
47     
48     USER_Lock();
49     dce = firstDCE;
50     
51     DPRINTF("DCE:\n");
52     while( dce )
53     {
54         DPRINTF("\t[0x%08x] hWnd 0x%04x, dcx %08x, %s %s\n",
55              (unsigned)dce, dce->hwndCurrent, (unsigned)dce->DCXflags, 
56              (dce->DCXflags & DCX_CACHE) ? "Cache" : "Owned", 
57              (dce->DCXflags & DCX_DCEBUSY) ? "InUse" : "" );
58         dce = dce->next;
59     }
60
61     USER_Unlock();
62 }
63
64 /***********************************************************************
65  *           DCE_AllocDCE
66  *
67  * Allocate a new DCE.
68  */
69 DCE *DCE_AllocDCE( HWND hWnd, DCE_TYPE type )
70 {
71     DCE * dce;
72     
73     if (!(dce = HeapAlloc( GetProcessHeap(), 0, sizeof(DCE) ))) return NULL;
74     if (!(dce->hDC = CreateDCA( "DISPLAY", NULL, NULL, NULL )))
75     {
76         HeapFree( GetProcessHeap(), 0, dce );
77         return 0;
78     }
79     if (!defaultDCstate) defaultDCstate = GetDCState16( dce->hDC );
80
81     /* store DCE handle in DC hook data field */
82
83     SetDCHook( dce->hDC, DCHook16, (DWORD)dce );
84
85     dce->hwndCurrent = WIN_GetFullHandle( hWnd );
86     dce->hClipRgn    = 0;
87
88     if( type != DCE_CACHE_DC ) /* owned or class DC */
89     {
90         dce->DCXflags = DCX_DCEBUSY;
91         if( hWnd )
92         {
93             LONG style = GetWindowLongW( hWnd, GWL_STYLE );
94             if (style & WS_CLIPCHILDREN) dce->DCXflags |= DCX_CLIPCHILDREN;
95             if (style & WS_CLIPSIBLINGS) dce->DCXflags |= DCX_CLIPSIBLINGS;
96         }
97         SetHookFlags16(dce->hDC,DCHF_INVALIDATEVISRGN);
98     }
99     else dce->DCXflags = DCX_CACHE | DCX_DCEEMPTY;
100     
101     USER_Lock();
102     dce->next = firstDCE;
103     firstDCE = dce;
104     USER_Unlock();
105     return dce;
106 }
107
108
109 /***********************************************************************
110  *           DCE_FreeDCE
111  */
112 DCE* DCE_FreeDCE( DCE *dce )
113 {
114     DCE **ppDCE, *ret;
115
116     if (!dce) return NULL;
117
118     USER_Lock();
119
120     ppDCE = &firstDCE;
121
122     while (*ppDCE && (*ppDCE != dce)) ppDCE = &(*ppDCE)->next;
123     if (*ppDCE == dce) *ppDCE = dce->next;
124     ret = *ppDCE;
125     USER_Unlock();
126
127     SetDCHook(dce->hDC, NULL, 0L);
128
129     DeleteDC( dce->hDC );
130     if( dce->hClipRgn && !(dce->DCXflags & DCX_KEEPCLIPRGN) )
131         DeleteObject(dce->hClipRgn);
132     HeapFree( GetProcessHeap(), 0, dce );
133
134     return ret;
135 }
136
137 /***********************************************************************
138  *           DCE_FreeWindowDCE
139  *
140  * Remove owned DCE and reset unreleased cache DCEs.
141  */
142 void DCE_FreeWindowDCE( HWND hwnd )
143 {
144     DCE *pDCE;
145     WND *pWnd = WIN_GetPtr( hwnd );
146
147     pDCE = firstDCE;
148     while( pDCE )
149     {
150         if( pDCE->hwndCurrent == hwnd )
151         {
152             if( pDCE == pWnd->dce ) /* owned or Class DCE*/
153             {
154                 if (pWnd->clsStyle & CS_OWNDC)  /* owned DCE*/
155                 {
156                     pDCE = DCE_FreeDCE( pDCE );
157                     pWnd->dce = NULL;
158                     continue;
159                 }
160                 else if( pDCE->DCXflags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN) ) /* Class DCE*/
161                 {
162                     DCE_DeleteClipRgn( pDCE );
163                     pDCE->hwndCurrent = 0;
164                 }
165             }
166             else
167             {
168                 if( pDCE->DCXflags & DCX_DCEBUSY ) /* shared cache DCE */
169                 {
170                     /* FIXME: AFAICS we are doing the right thing here so 
171                      * this should be a WARN. But this is best left as an ERR 
172                      * because the 'application error' is likely to come from 
173                      * another part of Wine (i.e. it's our fault after all). 
174                      * We should change this to WARN when Wine is more stable
175                      * (for 1.0?).
176                      */
177                     ERR("[%08x] GetDC() without ReleaseDC()!\n",hwnd);
178                     DCE_ReleaseDC( pDCE );
179                 }
180
181                 pDCE->DCXflags &= DCX_CACHE;
182                 pDCE->DCXflags |= DCX_DCEEMPTY;
183                 pDCE->hwndCurrent = 0;
184             }
185         }
186         pDCE = pDCE->next;
187     }
188     WIN_ReleasePtr( pWnd );
189 }
190
191
192 /***********************************************************************
193  *   DCE_DeleteClipRgn
194  */
195 static void DCE_DeleteClipRgn( DCE* dce )
196 {
197     dce->DCXflags &= ~(DCX_EXCLUDERGN | DCX_INTERSECTRGN | DCX_WINDOWPAINT);
198
199     if( dce->DCXflags & DCX_KEEPCLIPRGN )
200         dce->DCXflags &= ~DCX_KEEPCLIPRGN;
201     else
202         if( dce->hClipRgn > 1 )
203             DeleteObject( dce->hClipRgn );
204
205     dce->hClipRgn = 0;
206
207     /* make it dirty so that the vis rgn gets recomputed next time */
208     dce->DCXflags |= DCX_DCEDIRTY;
209     SetHookFlags16( dce->hDC, DCHF_INVALIDATEVISRGN );
210 }
211
212
213 /***********************************************************************
214  *   DCE_ReleaseDC
215  */
216 static INT DCE_ReleaseDC( DCE* dce )
217 {
218     if ((dce->DCXflags & (DCX_DCEEMPTY | DCX_DCEBUSY)) != DCX_DCEBUSY) return 0;
219
220     /* restore previous visible region */
221
222     if ((dce->DCXflags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN)) &&
223         (dce->DCXflags & (DCX_CACHE | DCX_WINDOWPAINT)) )
224         DCE_DeleteClipRgn( dce );
225
226     if (dce->DCXflags & DCX_CACHE)
227     {
228         SetDCState16( dce->hDC, defaultDCstate );
229         dce->DCXflags &= ~DCX_DCEBUSY;
230         if (dce->DCXflags & DCX_DCEDIRTY)
231         {
232             /* don't keep around invalidated entries 
233              * because SetDCState() disables hVisRgn updates
234              * by removing dirty bit. */
235
236             dce->hwndCurrent = 0;
237             dce->DCXflags &= DCX_CACHE;
238             dce->DCXflags |= DCX_DCEEMPTY;
239         }
240     }
241     return 1;
242 }
243
244
245 /***********************************************************************
246  *   DCE_InvalidateDCE
247  *
248  * It is called from SetWindowPos() and EVENT_MapNotify - we have to
249  * mark as dirty all busy DCEs for windows that have pWnd->parent as
250  * an ancestor and whose client rect intersects with specified update
251  * rectangle. In addition, pWnd->parent DCEs may need to be updated if
252  * DCX_CLIPCHILDREN flag is set.  */
253 BOOL DCE_InvalidateDCE(HWND hwnd, const RECT* pRectUpdate)
254 {
255     HWND hwndScope = GetAncestor( hwnd, GA_PARENT );
256     BOOL bRet = FALSE;
257
258     if( hwndScope )
259     {
260         DCE *dce;
261
262         TRACE("scope hwnd = %04x, (%i,%i - %i,%i)\n",
263                      hwndScope, pRectUpdate->left,pRectUpdate->top,
264                      pRectUpdate->right,pRectUpdate->bottom);
265         if(TRACE_ON(dc)) 
266           DCE_DumpCache();
267
268         /* walk all DCEs and fixup non-empty entries */
269
270         for (dce = firstDCE; (dce); dce = dce->next)
271         {
272             if (dce->DCXflags & DCX_DCEEMPTY) continue;
273             if ((dce->hwndCurrent == hwndScope) && !(dce->DCXflags & DCX_CLIPCHILDREN))
274                 continue;  /* child window positions don't bother us */
275
276             /* check if DCE window is within the z-order scope */
277
278             if (hwndScope == dce->hwndCurrent || IsChild( hwndScope, dce->hwndCurrent ))
279             {
280                 if (hwnd != dce->hwndCurrent)
281                 {
282                     /* check if the window rectangle intersects this DCE window */
283                     RECT rect;
284                     GetWindowRect( dce->hwndCurrent, &rect );
285                     MapWindowPoints( 0, hwndScope, (POINT *)&rect, 2 );
286                     if (!IntersectRect( &rect, &rect, pRectUpdate )) continue;
287
288                 }
289                 if( !(dce->DCXflags & DCX_DCEBUSY) )
290                 {
291                     /* Don't bother with visible regions of unused DCEs */
292
293                     TRACE("\tpurged %p dce [%04x]\n", dce, dce->hwndCurrent);
294                     dce->hwndCurrent = 0;
295                     dce->DCXflags &= DCX_CACHE;
296                     dce->DCXflags |= DCX_DCEEMPTY;
297                 }
298                 else
299                 {
300                     /* Set dirty bits in the hDC and DCE structs */
301
302                     TRACE("\tfixed up %p dce [%04x]\n", dce, dce->hwndCurrent);
303                     dce->DCXflags |= DCX_DCEDIRTY;
304                     SetHookFlags16(dce->hDC, DCHF_INVALIDATEVISRGN);
305                     bRet = TRUE;
306                 }
307             }
308         } /* dce list */
309     }
310     return bRet;
311 }
312
313
314 /***********************************************************************
315  *           DCE_ExcludeRgn
316  * 
317  *  Translate given region from the wnd client to the DC coordinates
318  *  and add it to the clipping region.
319  */
320 INT DCE_ExcludeRgn( HDC hDC, HWND hwnd, HRGN hRgn )
321 {
322   POINT  pt = {0, 0};
323   DCE     *dce = firstDCE;
324
325   while (dce && (dce->hDC != hDC)) dce = dce->next;
326   if (!dce) return ERROR;
327
328   MapWindowPoints( hwnd, dce->hwndCurrent, &pt, 1);
329   if( dce->DCXflags & DCX_WINDOW )
330   {
331       WND *wnd = WIN_FindWndPtr(dce->hwndCurrent);
332       pt.x += wnd->rectClient.left - wnd->rectWindow.left;
333       pt.y += wnd->rectClient.top - wnd->rectWindow.top;
334       WIN_ReleaseWndPtr(wnd);
335   }
336   OffsetRgn(hRgn, pt.x, pt.y);
337
338   return ExtSelectClipRgn( hDC, hRgn, RGN_DIFF );
339 }
340
341
342 /***********************************************************************
343  *              GetDCEx (USER32.@)
344  *
345  * Unimplemented flags: DCX_LOCKWINDOWUPDATE
346  *
347  * FIXME: Full support for hrgnClip == 1 (alias for entire window).
348  */
349 HDC WINAPI GetDCEx( HWND hwnd, HRGN hrgnClip, DWORD flags )
350 {
351     HDC         hdc = 0;
352     DCE *       dce;
353     WND *       wndPtr;
354     DWORD       dcxFlags = 0;
355     BOOL        bUpdateVisRgn = TRUE;
356     BOOL        bUpdateClipOrigin = FALSE;
357     HWND parent, full;
358
359     TRACE("hwnd %04x, hrgnClip %04x, flags %08x\n", 
360           hwnd, hrgnClip, (unsigned)flags);
361
362     if (!hwnd) hwnd = GetDesktopWindow();
363     if (!(full = WIN_IsCurrentProcess( hwnd )))
364     {
365         FIXME( "not supported yet on other process window %x\n", hwnd );
366         return 0;
367     }
368     hwnd = full;
369     if (!(wndPtr = WIN_GetPtr( hwnd ))) return 0;
370
371     /* fixup flags */
372
373     if (flags & (DCX_WINDOW | DCX_PARENTCLIP)) flags |= DCX_CACHE;
374
375     if (flags & DCX_USESTYLE)
376     {
377         flags &= ~( DCX_CLIPCHILDREN | DCX_CLIPSIBLINGS | DCX_PARENTCLIP);
378
379         if( wndPtr->dwStyle & WS_CLIPSIBLINGS )
380             flags |= DCX_CLIPSIBLINGS;
381
382         if ( !(flags & DCX_WINDOW) )
383         {
384             if (wndPtr->clsStyle & CS_PARENTDC) flags |= DCX_PARENTCLIP;
385
386             if (wndPtr->dwStyle & WS_CLIPCHILDREN &&
387                      !(wndPtr->dwStyle & WS_MINIMIZE) ) flags |= DCX_CLIPCHILDREN;
388             if (!wndPtr->dce) flags |= DCX_CACHE;
389         }
390     }
391
392     if (flags & DCX_WINDOW) flags &= ~DCX_CLIPCHILDREN;
393
394     parent = GetAncestor( hwnd, GA_PARENT );
395     if (!parent || (parent == GetDesktopWindow()))
396         flags = (flags & ~DCX_PARENTCLIP) | DCX_CLIPSIBLINGS;
397
398     /* it seems parent clip is ignored when clipping siblings or children */
399     if (flags & (DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN)) flags &= ~DCX_PARENTCLIP;
400
401     if( flags & DCX_PARENTCLIP )
402     {
403         LONG parent_style = GetWindowLongW( parent, GWL_STYLE );
404         if( (wndPtr->dwStyle & WS_VISIBLE) && (parent_style & WS_VISIBLE) )
405         {
406             flags &= ~DCX_CLIPCHILDREN;
407             if (parent_style & WS_CLIPSIBLINGS) flags |= DCX_CLIPSIBLINGS;
408         }
409     }
410
411     /* find a suitable DCE */
412
413     dcxFlags = flags & (DCX_PARENTCLIP | DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | 
414                         DCX_CACHE | DCX_WINDOW);
415
416     if (flags & DCX_CACHE)
417     {
418         DCE*    dceEmpty;
419         DCE*    dceUnused;
420
421         dceEmpty = dceUnused = NULL;
422
423         /* Strategy: First, we attempt to find a non-empty but unused DCE with
424          * compatible flags. Next, we look for an empty entry. If the cache is
425          * full we have to purge one of the unused entries.
426          */
427
428         for (dce = firstDCE; (dce); dce = dce->next)
429         {
430             if ((dce->DCXflags & (DCX_CACHE | DCX_DCEBUSY)) == DCX_CACHE )
431             {
432                 dceUnused = dce;
433
434                 if (dce->DCXflags & DCX_DCEEMPTY)
435                     dceEmpty = dce;
436                 else
437                 if ((dce->hwndCurrent == hwnd) &&
438                    ((dce->DCXflags & (DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN |
439                                       DCX_CACHE | DCX_WINDOW | DCX_PARENTCLIP)) == dcxFlags))
440                 {
441                     TRACE("\tfound valid %08x dce [%04x], flags %08x\n", 
442                                         (unsigned)dce, hwnd, (unsigned)dcxFlags );
443                     bUpdateVisRgn = FALSE; 
444                     bUpdateClipOrigin = TRUE;
445                     break;
446                 }
447             }
448         }
449
450         if (!dce) dce = (dceEmpty) ? dceEmpty : dceUnused;
451         
452         /* if there's no dce empty or unused, allocate a new one */
453         if (!dce)
454         {
455             dce = DCE_AllocDCE( 0, DCE_CACHE_DC );
456         }
457     }
458     else 
459     {
460         dce = wndPtr->dce;
461         if (dce && dce->hwndCurrent == hwnd)
462         {
463             TRACE("\tskipping hVisRgn update\n");
464             bUpdateVisRgn = FALSE; /* updated automatically, via DCHook() */
465         }
466     }
467     if (!dce)
468     {
469         hdc = 0;
470         goto END;
471     }
472
473     if (!(flags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN))) hrgnClip = 0;
474
475     if (((flags ^ dce->DCXflags) & (DCX_INTERSECTRGN | DCX_EXCLUDERGN)) &&
476         (dce->hClipRgn != hrgnClip))
477     {
478         /* if the extra clip region has changed, get rid of the old one */
479         DCE_DeleteClipRgn( dce );
480     }
481
482     dce->hwndCurrent = hwnd;
483     dce->hClipRgn = hrgnClip;
484     dce->DCXflags = flags & (DCX_PARENTCLIP | DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN |
485                              DCX_CACHE | DCX_WINDOW | DCX_WINDOWPAINT |
486                              DCX_KEEPCLIPRGN | DCX_INTERSECTRGN | DCX_EXCLUDERGN);
487     dce->DCXflags |= DCX_DCEBUSY;
488     dce->DCXflags &= ~DCX_DCEDIRTY;
489     hdc = dce->hDC;
490
491     if (bUpdateVisRgn) SetHookFlags16( hdc, DCHF_INVALIDATEVISRGN ); /* force update */
492
493     if (!USER_Driver.pGetDC( hwnd, hdc, hrgnClip, flags )) hdc = 0;
494
495     TRACE("(%04x,%04x,0x%lx): returning %04x\n", hwnd, hrgnClip, flags, hdc);
496 END:
497     WIN_ReleasePtr(wndPtr);
498     return hdc;
499 }
500
501
502 /***********************************************************************
503  *              GetDC (USER32.@)
504  * RETURNS
505  *      :Handle to DC
506  *      NULL: Failure
507  */
508 HDC WINAPI GetDC(
509              HWND hwnd /* [in] handle of window */
510 ) {
511     if (!hwnd)
512         return GetDCEx( 0, 0, DCX_CACHE | DCX_WINDOW );
513     return GetDCEx( hwnd, 0, DCX_USESTYLE );
514 }
515
516
517 /***********************************************************************
518  *              GetWindowDC (USER32.@)
519  */
520 HDC WINAPI GetWindowDC( HWND hwnd )
521 {
522     return GetDCEx( hwnd, 0, DCX_USESTYLE | DCX_WINDOW );
523 }
524
525
526 /***********************************************************************
527  *              ReleaseDC (USER32.@)
528  *
529  * RETURNS
530  *      1: Success
531  *      0: Failure
532  */
533 INT WINAPI ReleaseDC( 
534              HWND hwnd /* [in] Handle of window - ignored */, 
535              HDC hdc   /* [in] Handle of device context */
536 ) {
537     DCE * dce;
538     INT nRet = 0;
539
540     USER_Lock();
541     dce = firstDCE;
542     
543     TRACE("%04x %04x\n", hwnd, hdc );
544         
545     while (dce && (dce->hDC != hdc)) dce = dce->next;
546
547     if ( dce ) 
548         if ( dce->DCXflags & DCX_DCEBUSY )
549             nRet = DCE_ReleaseDC( dce );
550
551     USER_Unlock();
552
553     return nRet;
554 }
555
556 /***********************************************************************
557  *              DCHook (USER.362)
558  *
559  * See "Undoc. Windows" for hints (DC, SetDCHook, SetHookFlags)..  
560  */
561 BOOL16 WINAPI DCHook16( HDC16 hDC, WORD code, DWORD data, LPARAM lParam )
562 {
563     BOOL retv = TRUE;
564     DCE *dce = (DCE *)data;
565
566     TRACE("hDC = %04x, %i\n", hDC, code);
567
568     if (!dce) return 0;
569     assert(dce->hDC == hDC);
570
571     /* Grab the windows lock before doing anything else  */
572     USER_Lock();
573
574     switch( code )
575     {
576       case DCHC_INVALIDVISRGN:
577            /* GDI code calls this when it detects that the
578             * DC is dirty (usually after SetHookFlags()). This
579             * means that we have to recompute the visible region.
580             */
581            if( dce->DCXflags & DCX_DCEBUSY )
582            {
583                /* Dirty bit has been cleared by caller, set it again so that
584                 * pGetDC recomputes the visible region. */
585                SetHookFlags16( dce->hDC, DCHF_INVALIDATEVISRGN );
586                USER_Driver.pGetDC( dce->hwndCurrent, dce->hDC, dce->hClipRgn, dce->DCXflags );
587            }
588            else /* non-fatal but shouldn't happen */
589              WARN("DC is not in use!\n");
590            break;
591
592       case DCHC_DELETEDC:
593            /*
594             * Windows will not let you delete a DC that is busy
595             * (between GetDC and ReleaseDC)
596             */
597
598            if ( dce->DCXflags & DCX_DCEBUSY )
599            {
600                WARN("Application trying to delete a busy DC\n");
601                retv = FALSE;
602            }
603            else DCE_FreeDCE( dce );
604            break;
605
606       default:
607            FIXME("unknown code\n");
608     }
609
610   USER_Unlock();  /* Release the wnd lock */
611   return retv;
612 }
613
614
615 /**********************************************************************
616  *              WindowFromDC (USER32.@)
617  */
618 HWND WINAPI WindowFromDC( HDC hDC )
619 {
620     DCE *dce;
621     HWND hwnd;
622
623     USER_Lock();
624     dce = firstDCE;
625     
626     while (dce && (dce->hDC != hDC)) dce = dce->next;
627
628     hwnd = dce ? dce->hwndCurrent : 0;
629     USER_Unlock();
630     
631     return hwnd;
632 }
633
634
635 /***********************************************************************
636  *              LockWindowUpdate (USER32.@)
637  */
638 BOOL WINAPI LockWindowUpdate( HWND hwnd )
639 {
640     FIXME("(%x), stub!\n",hwnd);
641     return TRUE;
642 }