4dee9b9948c71ae726e2f0cdcc6082472db05c31
[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         window->State.WorkMask |= GLUT_DISPLAY_WORK;
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  * Window enumerator callback to check for the joystick polling code
184  */
185 static void fghcbCheckJoystickPolls( SFG_Window *window,
186                                      SFG_Enumerator *enumerator )
187 {
188     fg_time_t checkTime;
189     
190     if (window->State.JoystickPollRate > 0 && FETCH_WCB( *window, Joystick ))
191     {
192         /* This window has a joystick to be polled (if pollrate <= 0, user needs to poll manually with glutForceJoystickFunc */
193         checkTime= fgElapsedTime( );
194
195         if( window->State.JoystickLastPoll + window->State.JoystickPollRate <=
196             checkTime )
197         {
198 #if !defined(_WIN32_WCE)
199             fgJoystickPollWindow( window );
200 #endif /* !defined(_WIN32_WCE) */
201             window->State.JoystickLastPoll = checkTime;
202         }
203     }
204
205     fgEnumSubWindows( window, fghcbCheckJoystickPolls, enumerator );
206 }
207
208 /*
209  * Check all windows for joystick polling
210  * 
211  * The real way to do this is to make use of the glutTimer() API
212  * to more cleanly re-implement the joystick API.  Then, this code
213  * and all other "joystick timer" code can be yanked.
214  */
215 static void fghCheckJoystickPolls( void )
216 {
217     SFG_Enumerator enumerator;
218
219     enumerator.found = GL_FALSE;
220     enumerator.data  =  NULL;
221
222     fgEnumWindows( fghcbCheckJoystickPolls, &enumerator );
223 }
224
225 /*
226  * Check the global timers
227  */
228 static void fghCheckTimers( void )
229 {
230     fg_time_t checkTime = fgElapsedTime( );
231
232     while( fgState.Timers.First )
233     {
234         SFG_Timer *timer = fgState.Timers.First;
235
236         if( timer->TriggerTime > checkTime )
237             /* Timers are sorted by triggerTime */
238             break;
239
240         fgListRemove( &fgState.Timers, &timer->Node );
241         fgListAppend( &fgState.FreeTimers, &timer->Node );
242
243         timer->Callback( timer->ID );
244     }
245 }
246
247  
248 /* Platform-dependent time in milliseconds, as an unsigned 64-bit integer.
249  * This doesn't overflow in any reasonable time, so no need to worry about
250  * that. The GLUT API return value will however overflow after 49.7 days,
251  * which means you will still get in trouble when running the
252  * application for more than 49.7 days.
253  */  
254 fg_time_t fgSystemTime(void)
255 {
256         return fgPlatformSystemTime();
257 }
258   
259 /*
260  * Elapsed Time
261  */
262 fg_time_t fgElapsedTime( void )
263 {
264     return fgSystemTime() - fgState.Time;
265 }
266
267 /*
268  * Error Messages.
269  */
270 void fgError( const char *fmt, ... )
271 {
272     va_list ap;
273
274     if (fgState.ErrorFunc) {
275
276         va_start( ap, fmt );
277
278         /* call user set error handler here */
279         fgState.ErrorFunc(fmt, ap);
280
281         va_end( ap );
282
283     } else {
284 #ifdef FREEGLUT_PRINT_ERRORS
285         va_start( ap, fmt );
286
287         fprintf( stderr, "freeglut ");
288         if( fgState.ProgramName )
289             fprintf( stderr, "(%s): ", fgState.ProgramName );
290         VFPRINTF( stderr, fmt, ap );
291         fprintf( stderr, "\n" );
292
293         va_end( ap );
294 #endif
295
296         if ( fgState.Initialised )
297             fgDeinitialize ();
298
299         exit( 1 );
300     }
301 }
302
303 void fgWarning( const char *fmt, ... )
304 {
305     va_list ap;
306
307     if (fgState.WarningFunc) {
308
309         va_start( ap, fmt );
310
311         /* call user set warning handler here */
312         fgState.WarningFunc(fmt, ap);
313
314         va_end( ap );
315
316     } else {
317 #ifdef FREEGLUT_PRINT_WARNINGS
318         va_start( ap, fmt );
319
320         fprintf( stderr, "freeglut ");
321         if( fgState.ProgramName )
322             fprintf( stderr, "(%s): ", fgState.ProgramName );
323         VFPRINTF( stderr, fmt, ap );
324         fprintf( stderr, "\n" );
325
326         va_end( ap );
327 #endif
328     }
329 }
330
331
332 /*
333  * Indicates whether work is pending for ANY window.
334  *
335  * The current mechanism is to walk all of the windows and ask if
336  * work is pending. We have a short-circuit early return if we find any.
337  */
338 static void fghHavePendingWorkCallback( SFG_Window* w, SFG_Enumerator* e)
339 {
340     if( w->State.WorkMask )
341     {
342         e->found = GL_TRUE;
343         e->data = w;
344         return;
345     }
346     fgEnumSubWindows( w, fghHavePendingWorkCallback, e );
347 }
348 static int fghHavePendingWork (void)
349 {
350     SFG_Enumerator enumerator;
351
352     enumerator.found = GL_FALSE;
353     enumerator.data = NULL;
354     fgEnumWindows( fghHavePendingWorkCallback, &enumerator );
355     return !!enumerator.data;
356 }
357
358 /*
359  * Returns the number of GLUT ticks (milliseconds) till the next timer event.
360  */
361 static fg_time_t fghNextTimer( void )
362 {
363     fg_time_t currentTime;
364     SFG_Timer *timer = fgState.Timers.First;    /* timers are sorted by trigger time, so only have to check the first */
365
366     if( !timer )
367         return INT_MAX;
368
369     currentTime = fgElapsedTime();
370     if( timer->TriggerTime < currentTime )
371         return 0;
372     else
373         return timer->TriggerTime - currentTime;
374 }
375
376 static void fghSleepForEvents( void )
377 {
378     fg_time_t msec;
379
380     if( fghHavePendingWork( ) )
381         return;
382
383     msec = fghNextTimer( );
384     /* XXX Should use GLUT timers for joysticks... */
385     /* XXX Dumb; forces granularity to .01sec */
386     if( fgState.NumActiveJoysticks>0 && ( msec > 10 ) )     
387         msec = 10;
388
389         fgPlatformSleepForEvents ( msec );
390 }
391
392
393 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
394
395 /*
396  * Executes a single iteration in the freeglut processing loop.
397  */
398 void FGAPIENTRY glutMainLoopEvent( void )
399 {
400     /* Process input */
401         fgPlatformProcessSingleEvent ();
402
403     if( fgState.Timers.First )
404         fghCheckTimers( );
405     if (fgState.NumActiveJoysticks>0)   /* If zero, don't poll joysticks */
406         fghCheckJoystickPolls( );
407
408     /* Perform work on the window (position, reshape, display, etc) */
409     fghProcessWork( );
410
411     fgCloseWindows( );
412 }
413
414 /*
415  * Enters the freeglut processing loop.
416  * Stays until the "ExecState" changes to "GLUT_EXEC_STATE_STOP".
417  */
418 void FGAPIENTRY glutMainLoop( void )
419 {
420     int action;
421
422     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutMainLoop" );
423
424     if (!fgStructure.Windows.First)
425         fgError(" ERROR:  glutMainLoop called with no windows created.");
426
427         fgPlatformMainLoopPreliminaryWork ();
428
429     fgState.ExecState = GLUT_EXEC_STATE_RUNNING ;
430     while( fgState.ExecState == GLUT_EXEC_STATE_RUNNING )
431     {
432         SFG_Window *window;
433
434         glutMainLoopEvent( );
435         /*
436          * Step through the list of windows, seeing if there are any
437          * that are not menus
438          */
439         for( window = ( SFG_Window * )fgStructure.Windows.First;
440              window;
441              window = ( SFG_Window * )window->Node.Next )
442             if ( ! ( window->IsMenu ) )
443                 break;
444
445         if( ! window )
446             fgState.ExecState = GLUT_EXEC_STATE_STOP;
447         else
448         {
449             if( fgState.IdleCallback )
450             {
451                 if( fgStructure.CurrentWindow &&
452                     fgStructure.CurrentWindow->IsMenu )
453                     /* fail safe */
454                     fgSetWindow( window );
455                 fgState.IdleCallback( );
456             }
457             else
458                 fghSleepForEvents( );
459         }
460     }
461
462     /*
463      * When this loop terminates, destroy the display, state and structure
464      * of a freeglut session, so that another glutInit() call can happen
465      *
466      * Save the "ActionOnWindowClose" because "fgDeinitialize" resets it.
467      */
468     action = fgState.ActionOnWindowClose;
469     fgDeinitialize( );
470     if( action == GLUT_ACTION_EXIT )
471         exit( 0 );
472 }
473
474 /*
475  * Leaves the freeglut processing loop.
476  */
477 void FGAPIENTRY glutLeaveMainLoop( void )
478 {
479     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutLeaveMainLoop" );
480     fgState.ExecState = GLUT_EXEC_STATE_STOP ;
481 }
482
483
484
485 /*** END OF FILE ***/