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