Moved scrollbar tracking code to scroll.c.
[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     FARPROC16 hookProc;
72     DCE * dce;
73     
74     if (!(dce = HeapAlloc( GetProcessHeap(), 0, sizeof(DCE) ))) return NULL;
75     if (!(dce->hDC = CreateDCA( "DISPLAY", NULL, NULL, NULL )))
76     {
77         HeapFree( GetProcessHeap(), 0, dce );
78         return 0;
79     }
80     if (!defaultDCstate) defaultDCstate = GetDCState16( dce->hDC );
81
82     /* store DCE handle in DC hook data field */
83
84     hookProc = GetProcAddress16( GetModuleHandle16("USER"), (LPCSTR)362 );
85     SetDCHook( dce->hDC, hookProc, (DWORD)dce );
86
87     dce->hwndCurrent = WIN_GetFullHandle( hWnd );
88     dce->hClipRgn    = 0;
89
90     if( type != DCE_CACHE_DC ) /* owned or class DC */
91     {
92         dce->DCXflags = DCX_DCEBUSY;
93         if( hWnd )
94         {
95             LONG style = GetWindowLongW( hWnd, GWL_STYLE );
96             if (style & WS_CLIPCHILDREN) dce->DCXflags |= DCX_CLIPCHILDREN;
97             if (style & WS_CLIPSIBLINGS) dce->DCXflags |= DCX_CLIPSIBLINGS;
98         }
99         SetHookFlags16(dce->hDC,DCHF_INVALIDATEVISRGN);
100     }
101     else dce->DCXflags = DCX_CACHE | DCX_DCEEMPTY;
102     
103     USER_Lock();
104     dce->next = firstDCE;
105     firstDCE = dce;
106     USER_Unlock();
107     return dce;
108 }
109
110
111 /***********************************************************************
112  *           DCE_FreeDCE
113  */
114 DCE* DCE_FreeDCE( DCE *dce )
115 {
116     DCE **ppDCE, *ret;
117
118     if (!dce) return NULL;
119
120     USER_Lock();
121
122     ppDCE = &firstDCE;
123
124     while (*ppDCE && (*ppDCE != dce)) ppDCE = &(*ppDCE)->next;
125     if (*ppDCE == dce) *ppDCE = dce->next;
126     ret = *ppDCE;
127     USER_Unlock();
128
129     SetDCHook(dce->hDC, NULL, 0L);
130
131     DeleteDC( dce->hDC );
132     if( dce->hClipRgn && !(dce->DCXflags & DCX_KEEPCLIPRGN) )
133         DeleteObject(dce->hClipRgn);
134     HeapFree( GetProcessHeap(), 0, dce );
135
136     return ret;
137 }
138
139 /***********************************************************************
140  *           DCE_FreeWindowDCE
141  *
142  * Remove owned DCE and reset unreleased cache DCEs.
143  */
144 void DCE_FreeWindowDCE( HWND hwnd )
145 {
146     DCE *pDCE;
147     WND *pWnd = WIN_FindWndPtr( hwnd );
148
149     pDCE = firstDCE;
150     hwnd = pWnd->hwndSelf;  /* make it a full handle */
151
152     while( pDCE )
153     {
154         if( pDCE->hwndCurrent == hwnd )
155         {
156             if( pDCE == pWnd->dce ) /* owned or Class DCE*/
157             {
158                 if (pWnd->clsStyle & CS_OWNDC)  /* owned DCE*/
159                 {
160                     pDCE = DCE_FreeDCE( pDCE );
161                     pWnd->dce = NULL;
162                     continue;
163                 }
164                 else if( pDCE->DCXflags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN) ) /* Class DCE*/
165                 {
166                     DCE_DeleteClipRgn( pDCE );
167                     pDCE->hwndCurrent = 0;
168                 }
169             }
170             else
171             {
172                 if( pDCE->DCXflags & DCX_DCEBUSY ) /* shared cache DCE */
173                 {
174                     /* FIXME: AFAICS we are doing the right thing here so 
175                      * this should be a WARN. But this is best left as an ERR 
176                      * because the 'application error' is likely to come from 
177                      * another part of Wine (i.e. it's our fault after all). 
178                      * We should change this to WARN when Wine is more stable
179                      * (for 1.0?).
180                      */
181                     ERR("[%08x] GetDC() without ReleaseDC()!\n",hwnd);
182                     DCE_ReleaseDC( pDCE );
183                 }
184
185                 pDCE->DCXflags &= DCX_CACHE;
186                 pDCE->DCXflags |= DCX_DCEEMPTY;
187                 pDCE->hwndCurrent = 0;
188             }
189         }
190         pDCE = pDCE->next;
191     }
192     
193     WIN_ReleaseWndPtr( pWnd );
194 }
195
196
197 /***********************************************************************
198  *   DCE_DeleteClipRgn
199  */
200 static void DCE_DeleteClipRgn( DCE* dce )
201 {
202     dce->DCXflags &= ~(DCX_EXCLUDERGN | DCX_INTERSECTRGN | DCX_WINDOWPAINT);
203
204     if( dce->DCXflags & DCX_KEEPCLIPRGN )
205         dce->DCXflags &= ~DCX_KEEPCLIPRGN;
206     else
207         if( dce->hClipRgn > 1 )
208             DeleteObject( dce->hClipRgn );
209
210     dce->hClipRgn = 0;
211
212     /* make it dirty so that the vis rgn gets recomputed next time */
213     dce->DCXflags |= DCX_DCEDIRTY;
214     SetHookFlags16( dce->hDC, DCHF_INVALIDATEVISRGN );
215 }
216
217
218 /***********************************************************************
219  *   DCE_ReleaseDC
220  */
221 static INT DCE_ReleaseDC( DCE* dce )
222 {
223     if ((dce->DCXflags & (DCX_DCEEMPTY | DCX_DCEBUSY)) != DCX_DCEBUSY) return 0;
224
225     /* restore previous visible region */
226
227     if ((dce->DCXflags & (DCX_INTERSECTRGN | DCX_EXCLUDERGN)) &&
228         (dce->DCXflags & (DCX_CACHE | DCX_WINDOWPAINT)) )
229         DCE_DeleteClipRgn( dce );
230
231     if (dce->DCXflags & DCX_CACHE)
232     {
233         SetDCState16( dce->hDC, defaultDCstate );
234         dce->DCXflags &= ~DCX_DCEBUSY;
235         if (dce->DCXflags & DCX_DCEDIRTY)
236         {
237             /* don't keep around invalidated entries 
238              * because SetDCState() disables hVisRgn updates
239              * by removing dirty bit. */
240
241             dce->hwndCurrent = 0;
242             dce->DCXflags &= DCX_CACHE;
243             dce->DCXflags |= DCX_DCEEMPTY;
244         }
245     }
246     return 1;
247 }
248
249
250 /***********************************************************************
251  *   DCE_InvalidateDCE
252  *
253  * It is called from SetWindowPos() and EVENT_MapNotify - we have to
254  * mark as dirty all busy DCEs for windows that have pWnd->parent as
255  * an ancestor and whose client rect intersects with specified update
256  * rectangle. In addition, pWnd->parent DCEs may need to be updated if
257  * DCX_CLIPCHILDREN flag is set.  */
258 BOOL DCE_InvalidateDCE(HWND hwnd, const RECT* pRectUpdate)
259 {
260     HWND hwndScope = GetAncestor( hwnd, GA_PARENT );
261     BOOL bRet = FALSE;
262
263     if( hwndScope )
264     {
265         DCE *dce;
266
267         TRACE("scope hwnd = %04x, (%i,%i - %i,%i)\n",
268                      hwndScope, pRectUpdate->left,pRectUpdate->top,
269                      pRectUpdate->right,pRectUpdate->bottom);
270         if(TRACE_ON(dc)) 
271           DCE_DumpCache();
272
273         /* walk all DCEs and fixup non-empty entries */
274
275         for (dce = firstDCE; (dce); dce = dce->next)
276         {
277             if (dce->DCXflags & DCX_DCEEMPTY) continue;
278             if ((dce->hwndCurrent == hwndScope) && !(dce->DCXflags & DCX_CLIPCHILDREN))
279                 continue;  /* child window positions don't bother us */
280
281             /* check if DCE window is within the z-order scope */
282
283             if (hwndScope == dce->hwndCurrent || IsChild( hwndScope, dce->hwndCurrent ))
284             {
285                 if (hwnd != dce->hwndCurrent)
286                 {
287                     /* check if the window rectangle intersects this DCE window */
288                     RECT rect;
289                     GetWindowRect( dce->hwndCurrent, &rect );
290                     MapWindowPoints( 0, hwndScope, (POINT *)&rect, 2 );
291                     if (!IntersectRect( &rect, &rect, pRectUpdate )) continue;
292
293                 }
294                 if( !(dce->DCXflags & DCX_DCEBUSY) )
295                 {
296                     /* Don't bother with visible regions of unused DCEs */
297
298                     TRACE("\tpurged %p dce [%04x]\n", dce, dce->hwndCurrent);
299                     dce->hwndCurrent = 0;
300                     dce->DCXflags &= DCX_CACHE;
301                     dce->DCXflags |= DCX_DCEEMPTY;
302                 }
303                 else
304                 {
305                     /* Set dirty bits in the hDC and DCE structs */
306
307                     TRACE("\tfixed up %p dce [%04x]\n", dce, dce->hwndCurrent);
308                     dce->DCXflags |= DCX_DCEDIRTY;
309                     SetHookFlags16(dce->hDC, DCHF_INVALIDATEVISRGN);
310                     bRet = TRUE;
311                 }
312             }
313         } /* dce list */
314     }
315     return bRet;
316 }
317
318
319 /***********************************************************************
320  *           DCE_ExcludeRgn
321  * 
322  *  Translate given region from the wnd client to the DC coordinates
323  *  and add it to the clipping region.
324  */
325 INT DCE_ExcludeRgn( HDC hDC, HWND hwnd, HRGN hRgn )
326 {
327   POINT  pt = {0, 0};
328   DCE     *dce = firstDCE;
329
330   while (dce && (dce->hDC != hDC)) dce = dce->next;
331   if (!dce) return ERROR;
332
333   MapWindowPoints( hwnd, dce->hwndCurrent, &pt, 1);
334   if( dce->DCXflags & DCX_WINDOW )
335   {
336       WND *wnd = WIN_FindWndPtr(dce->hwndCurrent);
337       pt.x += wnd->rectClient.left - wnd->rectWindow.left;
338       pt.y += wnd->rectClient.top - wnd->rectWindow.top;
339       WIN_ReleaseWndPtr(wnd);
340   }
341   OffsetRgn(hRgn, pt.x, pt.y);
342
343   return ExtSelectClipRgn( hDC, hRgn, RGN_DIFF );
344 }
345
346
347 /***********************************************************************
348  *              GetDCEx (USER32.@)
349  *
350  * Unimplemented flags: DCX_LOCKWINDOWUPDATE
351  *
352  * FIXME: Full support for hrgnClip == 1 (alias for entire window).
353  */
354 HDC WINAPI GetDCEx( HWND hwnd, HRGN hrgnClip, DWORD flags )
355 {
356     HDC         hdc = 0;
357     DCE *       dce;
358     WND *       wndPtr;
359     DWORD       dcxFlags = 0;
360     BOOL        bUpdateVisRgn = TRUE;
361     BOOL        bUpdateClipOrigin = FALSE;
362     HWND parent;
363
364     TRACE("hwnd %04x, hrgnClip %04x, flags %08x\n", 
365           hwnd, hrgnClip, (unsigned)flags);
366
367     if (!hwnd) hwnd = GetDesktopWindow();
368     if (!(wndPtr = WIN_FindWndPtr( hwnd ))) return 0;
369     hwnd = wndPtr->hwndSelf;  /* make it a full handle */
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_ReleaseWndPtr(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 }