More explicit argument list for INVOKE_WCB callbacks
[freeglut] / src / fg_main.c
1 /*
2  * fg_main.c
3  *
4  * The windows message processing methods.
5  *
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>
8  * Creation date: Fri Dec 3 1999
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26  */
27
28 #include <GL/freeglut.h>
29 #include "fg_internal.h"
30 #include <errno.h>
31 #include <stdarg.h>
32
33 /*
34  * Try to get the maximum value allowed for ints, falling back to the minimum
35  * guaranteed by ISO C99 if there is no suitable header.
36  */
37 #ifdef HAVE_LIMITS_H
38 #    include <limits.h>
39 #endif
40 #ifndef INT_MAX
41 #    define INT_MAX 32767
42 #endif
43
44 #ifndef MIN
45 #    define MIN(a,b) (((a)<(b)) ? (a) : (b))
46 #endif
47
48 extern void fgProcessWork   ( SFG_Window *window );
49 extern fg_time_t fgPlatformSystemTime ( void );
50 extern void fgPlatformSleepForEvents( fg_time_t msec );
51 extern void fgPlatformProcessSingleEvent ( void );
52 extern void fgPlatformMainLoopPreliminaryWork ( void );
53
54 extern void fgPlatformInitWork(SFG_Window* window);
55 extern void fgPlatformPosResZordWork(SFG_Window* window, unsigned int workMask);
56 extern void fgPlatformVisibilityWork(SFG_Window* window);
57
58
59 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
60
61 void fghOnReshapeNotify(SFG_Window *window, int width, int height, GLboolean forceNotify)
62 {
63     GLboolean notify = GL_FALSE;
64
65     if( width  != window->State.Width ||
66         height != window->State.Height )
67     {
68         window->State.Width = width;
69         window->State.Height = height;
70
71         notify = GL_TRUE;
72     }
73
74     if (notify || forceNotify)
75     {
76         SFG_Window *saved_window = fgStructure.CurrentWindow;
77
78         INVOKE_WCB( *window, Reshape, ( width, height ) );
79
80         /*
81          * Force a window redraw.  In Windows at least this is only a partial
82          * solution:  if the window is increasing in size in either dimension,
83          * the already-drawn part does not get drawn again and things look funny.
84          * But without this we get this bad behaviour whenever we resize the
85          * window.
86          * DN: Hmm.. the above sounds like a concern only in single buffered mode...
87          */
88         window->State.WorkMask |= GLUT_DISPLAY_WORK;
89         if( window->IsMenu )
90             fgSetWindow( saved_window );
91     }
92 }
93
94 void fghOnPositionNotify(SFG_Window *window, int x, int y, GLboolean forceNotify)
95 {
96     GLboolean notify = GL_FALSE;
97
98     if( x  != window->State.Xpos ||
99         y != window->State.Ypos )
100     {
101         window->State.Xpos = x;
102         window->State.Ypos = y;
103
104         notify = GL_TRUE;
105     }
106
107     if (notify || forceNotify)
108     {
109         SFG_Window *saved_window = fgStructure.CurrentWindow;
110         INVOKE_WCB( *window, Position, ( x, y ) );
111         fgSetWindow( saved_window );
112     }
113 }
114
115 /*
116  * Calls a window's redraw method. This is used when
117  * a redraw is forced by the incoming window messages,
118  * or if a redisplay is otherwise pending.
119  * this is lean and mean without checks as it is
120  * currently only called from fghcbDisplayWindow which
121  * only calls this if the window is visible and needs
122  * a redisplay.
123  * Note that the fgSetWindow call on Windows makes the
124  * right device context current on windows, allowing
125  * direct drawing without BeginPaint/EndPaint in the
126  * WM_PAINT handler.
127  */
128 void fghRedrawWindow ( SFG_Window *window )
129 {
130     SFG_Window *current_window = fgStructure.CurrentWindow;
131
132     fgSetWindow( window );
133     INVOKE_WCB( *window, Display, ( ) );
134
135     fgSetWindow( current_window );
136 }
137
138 void fghRedrawWindowAndChildren ( SFG_Window *window )
139 {
140     SFG_Window* child;
141
142     fghRedrawWindow(window);
143
144     for( child = ( SFG_Window * )window->Children.First;
145          child;
146          child = ( SFG_Window * )child->Node.Next )
147     {
148         fghRedrawWindowAndChildren(child);
149     }
150 }
151
152
153 static void fghcbProcessWork( SFG_Window *window,
154                               SFG_Enumerator *enumerator )
155 {
156     if( window->State.WorkMask )
157         fgProcessWork ( window );
158
159     fgEnumSubWindows( window, fghcbProcessWork, enumerator );
160 }
161
162 /*
163  * Make all windows process their work list
164  */
165 static void fghProcessWork( void )
166 {
167     SFG_Enumerator enumerator;
168
169     enumerator.found = GL_FALSE;
170     enumerator.data  =  NULL;
171
172     fgEnumWindows( fghcbProcessWork, &enumerator );
173 }
174
175 /*
176  * Window enumerator callback to check for the joystick polling code
177  */
178 static void fghcbCheckJoystickPolls( SFG_Window *window,
179                                      SFG_Enumerator *enumerator )
180 {
181     fg_time_t checkTime;
182     
183     if (window->State.JoystickPollRate > 0 && FETCH_WCB( *window, Joystick ))
184     {
185         /* This window has a joystick to be polled (if pollrate <= 0, user needs to poll manually with glutForceJoystickFunc */
186         checkTime= fgElapsedTime( );
187
188         if( window->State.JoystickLastPoll + window->State.JoystickPollRate <=
189             checkTime )
190         {
191 #if !defined(_WIN32_WCE)
192             fgJoystickPollWindow( window );
193 #endif /* !defined(_WIN32_WCE) */
194             window->State.JoystickLastPoll = checkTime;
195         }
196     }
197
198     fgEnumSubWindows( window, fghcbCheckJoystickPolls, enumerator );
199 }
200
201 /*
202  * Check all windows for joystick polling
203  * 
204  * The real way to do this is to make use of the glutTimer() API
205  * to more cleanly re-implement the joystick API.  Then, this code
206  * and all other "joystick timer" code can be yanked.
207  */
208 static void fghCheckJoystickPolls( void )
209 {
210     SFG_Enumerator enumerator;
211
212     enumerator.found = GL_FALSE;
213     enumerator.data  =  NULL;
214
215     fgEnumWindows( fghcbCheckJoystickPolls, &enumerator );
216 }
217
218 /*
219  * Check the global timers
220  */
221 static void fghCheckTimers( void )
222 {
223     fg_time_t checkTime = fgElapsedTime( );
224
225     while( fgState.Timers.First )
226     {
227         SFG_Timer *timer = fgState.Timers.First;
228
229         if( timer->TriggerTime > checkTime )
230             /* Timers are sorted by triggerTime */
231             break;
232
233         fgListRemove( &fgState.Timers, &timer->Node );
234         fgListAppend( &fgState.FreeTimers, &timer->Node );
235
236         timer->Callback( timer->ID, timer->CallbackData );
237     }
238 }
239
240  
241 /* Platform-dependent time in milliseconds, as an unsigned 64-bit integer.
242  * This doesn't overflow in any reasonable time, so no need to worry about
243  * that. The GLUT API return value will however overflow after 49.7 days,
244  * which means you will still get in trouble when running the
245  * application for more than 49.7 days.
246  */  
247 fg_time_t fgSystemTime(void)
248 {
249         return fgPlatformSystemTime();
250 }
251   
252 /*
253  * Elapsed Time
254  */
255 fg_time_t fgElapsedTime( void )
256 {
257     return fgSystemTime() - fgState.Time;
258 }
259
260 /*
261  * Error Messages.
262  */
263 void fgError( const char *fmt, ... )
264 {
265     va_list ap;
266
267     if (fgState.ErrorFunc) {
268
269         va_start( ap, fmt );
270
271         /* call user set error handler here */
272         fgState.ErrorFunc(fmt, ap, fgState.ErrorFuncData);
273
274         va_end( ap );
275
276     } else {
277 #ifdef FREEGLUT_PRINT_ERRORS
278         va_start( ap, fmt );
279
280         fprintf( stderr, "freeglut ");
281         if( fgState.ProgramName )
282             fprintf( stderr, "(%s): ", fgState.ProgramName );
283         vfprintf( stderr, fmt, ap );
284         fprintf( stderr, "\n" );
285
286         va_end( ap );
287 #endif
288
289         if ( fgState.Initialised )
290             fgDeinitialize ();
291
292         exit( 1 );
293     }
294 }
295
296 void fgWarning( const char *fmt, ... )
297 {
298     va_list ap;
299
300     if (fgState.WarningFunc) {
301
302         va_start( ap, fmt );
303
304         /* call user set warning handler here */
305         fgState.WarningFunc(fmt, ap, fgState.WarningFuncData);
306
307         va_end( ap );
308
309     } else {
310 #ifdef FREEGLUT_PRINT_WARNINGS
311         va_start( ap, fmt );
312
313         fprintf( stderr, "freeglut ");
314         if( fgState.ProgramName )
315             fprintf( stderr, "(%s): ", fgState.ProgramName );
316         vfprintf( stderr, fmt, ap );
317         fprintf( stderr, "\n" );
318
319         va_end( ap );
320 #endif
321     }
322 }
323
324
325 /*
326  * Indicates whether work is pending for ANY window.
327  *
328  * The current mechanism is to walk all of the windows and ask if
329  * work is pending. We have a short-circuit early return if we find any.
330  */
331 static void fghHavePendingWorkCallback( SFG_Window* w, SFG_Enumerator* e)
332 {
333     if( w->State.WorkMask )
334     {
335         e->found = GL_TRUE;
336         e->data = w;
337         return;
338     }
339     fgEnumSubWindows( w, fghHavePendingWorkCallback, e );
340 }
341 static int fghHavePendingWork (void)
342 {
343     SFG_Enumerator enumerator;
344
345     enumerator.found = GL_FALSE;
346     enumerator.data = NULL;
347     fgEnumWindows( fghHavePendingWorkCallback, &enumerator );
348     return !!enumerator.data;
349 }
350
351 /*
352  * Returns the number of GLUT ticks (milliseconds) till the next timer event.
353  */
354 static fg_time_t fghNextTimer( void )
355 {
356     fg_time_t currentTime;
357     SFG_Timer *timer = fgState.Timers.First;    /* timers are sorted by trigger time, so only have to check the first */
358
359     if( !timer )
360         return INT_MAX;
361
362     currentTime = fgElapsedTime();
363     if( timer->TriggerTime < currentTime )
364         return 0;
365     else
366         return timer->TriggerTime - currentTime;
367 }
368
369 static void fghSleepForEvents( void )
370 {
371     fg_time_t msec;
372
373     if( fghHavePendingWork( ) )
374         return;
375
376     msec = fghNextTimer( );
377     /* XXX Should use GLUT timers for joysticks... */
378     /* XXX Dumb; forces granularity to .01sec */
379     if( fgState.NumActiveJoysticks>0 && ( msec > 10 ) )
380         msec = 10;
381
382     fgPlatformSleepForEvents ( msec );
383 }
384
385
386 /* Step through the work list */
387 void fgProcessWork(SFG_Window *window)
388 {
389     unsigned int workMask = window->State.WorkMask;
390     /* Now clear it so that any callback generated by the actions below can set work again */
391     window->State.WorkMask = 0;
392
393     if (workMask&~GLUT_DISPLAY_WORK)    /* Display work is the common case, skip all the below at once */
394     {
395         if (workMask & GLUT_INIT_WORK)
396         {
397             /* This is before the first display callback: if needed for the platform,
398              * call a few callbacks to inform user of window size, position, etc
399              */
400             fgPlatformInitWork(window);
401
402             /* Call init context callback */
403             INVOKE_WCB( *window, InitContext, ( ) );
404
405             /* Lastly, check if we have a display callback, error out if not
406              * This is the right place to do it, as the redisplay will be
407              * next right after we exit this function, so there is no more
408              * opportunity for the user to register a callback for this window.
409              */
410             if (!FETCH_WCB(*window, Display))
411                 fgError ( "ERROR:  No display callback registered for window %d\n", window->ID );
412         }
413
414         /* On windows we can position, resize and change z order at the same time */
415         if (workMask & (GLUT_POSITION_WORK|GLUT_SIZE_WORK|GLUT_ZORDER_WORK|GLUT_FULL_SCREEN_WORK))
416         {
417             fgPlatformPosResZordWork(window,workMask);
418         }
419
420         if (workMask & GLUT_VISIBILITY_WORK)
421         {
422             fgPlatformVisibilityWork(window);
423         }
424     }
425
426     /* check window state's workmask as well as some of the above callbacks might have generated redisplay requests. We can deal with those right now instead of wait for the next mainloop iteration. */
427     if (workMask & GLUT_DISPLAY_WORK || window->State.WorkMask & GLUT_DISPLAY_WORK)
428     {
429         if( window->State.Visible )
430         {
431             /* Strip out display work from the work list */
432             /* NB: do this before the display callback is called as user might call postredisplay in his display callback */
433             window->State.WorkMask &= ~GLUT_DISPLAY_WORK;
434
435             fghRedrawWindow ( window );
436         }
437     }
438 }
439
440
441 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
442
443 /*
444  * Executes a single iteration in the freeglut processing loop.
445  */
446 void FGAPIENTRY glutMainLoopEvent( void )
447 {
448     /* Process input */
449         fgPlatformProcessSingleEvent ();
450
451     if( fgState.Timers.First )
452         fghCheckTimers( );
453     if (fgState.NumActiveJoysticks>0)   /* If zero, don't poll joysticks */
454         fghCheckJoystickPolls( );
455
456     /* Perform work on the window (position, reshape, display, etc) */
457     fghProcessWork( );
458
459     /* Check OpenGL error state if requested.
460      * Don't call if no more open windows (can happen if user closes window from
461      * title bar), would lead to infinite error loop in glutReportErrors
462      */
463     if (fgState.GLDebugSwitch && fgStructure.CurrentWindow)
464         glutReportErrors( );
465
466     fgCloseWindows( );
467 }
468
469 /*
470  * Enters the freeglut processing loop.
471  * Stays until the "ExecState" changes to "GLUT_EXEC_STATE_STOP".
472  */
473 void FGAPIENTRY glutMainLoop( void )
474 {
475     int action;
476
477     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutMainLoop" );
478
479     if (!fgStructure.Windows.First)
480         fgError(" ERROR:  glutMainLoop called with no windows created.");
481
482     fgPlatformMainLoopPreliminaryWork ();
483
484     fgState.ExecState = GLUT_EXEC_STATE_RUNNING ;
485     for(;;)
486     {
487         SFG_Window *window;
488
489         glutMainLoopEvent( );
490         if( fgState.ExecState != GLUT_EXEC_STATE_RUNNING )
491             break;
492         /*
493          * Step through the list of windows, seeing if there are any
494          * that are not menus
495          */
496         for( window = ( SFG_Window * )fgStructure.Windows.First;
497              window;
498              window = ( SFG_Window * )window->Node.Next )
499             if ( ! ( window->IsMenu ) )
500                 break;
501
502         if( ! window )
503             fgState.ExecState = GLUT_EXEC_STATE_STOP;
504         else
505         {
506             if( fgState.IdleCallback )
507             {
508                 if( fgStructure.CurrentWindow &&
509                     fgStructure.CurrentWindow->IsMenu )
510                     /* fail safe */
511                     fgSetWindow( window );
512                 fgState.IdleCallback( fgState.IdleCallbackData );
513             }
514             else
515                 fghSleepForEvents( );
516         }
517     }
518
519     /*
520      * When this loop terminates, destroy the display, state and structure
521      * of a freeglut session, so that another glutInit() call can happen
522      *
523      * Save the "ActionOnWindowClose" because "fgDeinitialize" resets it.
524      */
525     action = fgState.ActionOnWindowClose;
526     fgDeinitialize( );
527     if( action == GLUT_ACTION_EXIT )
528         exit( 0 );
529 }
530
531 /*
532  * Leaves the freeglut processing loop.
533  */
534 void FGAPIENTRY glutLeaveMainLoop( void )
535 {
536     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutLeaveMainLoop" );
537     fgState.ExecState = GLUT_EXEC_STATE_STOP ;
538 }
539
540
541
542 /*** END OF FILE ***/