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