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