b3870681b7ecf44f0f92acd5d38d4601f7a15130
[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 #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 fgProcessWork   ( 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                 fgProcessWork ( 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 fgProcessWork(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     /* 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. */
436     if (workMask & GLUT_DISPLAY_WORK || window->State.WorkMask & GLUT_DISPLAY_WORK)
437     {
438         if( window->State.Visible )
439         {
440             /* Strip out display work from the work list */
441             /* NB: do this before the display callback is called as user might call postredisplay in his display callback */
442             window->State.WorkMask &= ~GLUT_DISPLAY_WORK;
443
444             fghRedrawWindow ( window );
445         }
446     }
447 }
448
449
450 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
451
452 /*
453  * Executes a single iteration in the freeglut processing loop.
454  */
455 void FGAPIENTRY glutMainLoopEvent( void )
456 {
457     /* Process input */
458         fgPlatformProcessSingleEvent ();
459
460     if( fgState.Timers.First )
461         fghCheckTimers( );
462     if (fgState.NumActiveJoysticks>0)   /* If zero, don't poll joysticks */
463         fghCheckJoystickPolls( );
464
465     /* Perform work on the window (position, reshape, display, etc) */
466     fghProcessWork( );
467
468     /* Check OpenGL error state if requested.
469      * Don't call if no more open windows (can happen if user closes window from
470      * title bar), would lead to infinite error loop in glutReportErrors
471      */
472     if (fgState.GLDebugSwitch && fgStructure.CurrentWindow)
473         glutReportErrors( );
474
475     fgCloseWindows( );
476 }
477
478 /*
479  * Enters the freeglut processing loop.
480  * Stays until the "ExecState" changes to "GLUT_EXEC_STATE_STOP".
481  */
482 void FGAPIENTRY glutMainLoop( void )
483 {
484     int action;
485
486     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutMainLoop" );
487
488     if (!fgStructure.Windows.First)
489         fgError(" ERROR:  glutMainLoop called with no windows created.");
490
491         fgPlatformMainLoopPreliminaryWork ();
492
493     fgState.ExecState = GLUT_EXEC_STATE_RUNNING ;
494     while( fgState.ExecState == GLUT_EXEC_STATE_RUNNING )
495     {
496         SFG_Window *window;
497
498         glutMainLoopEvent( );
499         /*
500          * Step through the list of windows, seeing if there are any
501          * that are not menus
502          */
503         for( window = ( SFG_Window * )fgStructure.Windows.First;
504              window;
505              window = ( SFG_Window * )window->Node.Next )
506             if ( ! ( window->IsMenu ) )
507                 break;
508
509         if( ! window )
510             fgState.ExecState = GLUT_EXEC_STATE_STOP;
511         else
512         {
513             if( fgState.IdleCallback )
514             {
515                 if( fgStructure.CurrentWindow &&
516                     fgStructure.CurrentWindow->IsMenu )
517                     /* fail safe */
518                     fgSetWindow( window );
519                 fgState.IdleCallback( );
520             }
521             else
522                 fghSleepForEvents( );
523         }
524     }
525
526     /*
527      * When this loop terminates, destroy the display, state and structure
528      * of a freeglut session, so that another glutInit() call can happen
529      *
530      * Save the "ActionOnWindowClose" because "fgDeinitialize" resets it.
531      */
532     action = fgState.ActionOnWindowClose;
533     fgDeinitialize( );
534     if( action == GLUT_ACTION_EXIT )
535         exit( 0 );
536 }
537
538 /*
539  * Leaves the freeglut processing loop.
540  */
541 void FGAPIENTRY glutLeaveMainLoop( void )
542 {
543     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutLeaveMainLoop" );
544     fgState.ExecState = GLUT_EXEC_STATE_STOP ;
545 }
546
547
548
549 /*** END OF FILE ***/