glutEvent.cpp revision c041511d
1/***********************************************************
2 *      Copyright (C) 1997, Be Inc.  Copyright (C) 1999, Jake Hamby.
3 *
4 * This program is freely distributable without licensing fees
5 * and is provided without guarantee or warrantee expressed or
6 * implied. This program is -not- in the public domain.
7 *
8 *
9 *  FILE:	glutEvent.cpp
10 *
11 *	DESCRIPTION:	here it is, the BeOS GLUT event loop
12 ***********************************************************/
13
14/***********************************************************
15 *	Headers
16 ***********************************************************/
17#include <GL/glut.h>
18#include "glutint.h"
19#include "glutState.h"
20#include "glutBlocker.h"
21
22/***********************************************************
23 *	CLASS:	GLUTtimer
24 *
25 *	DESCRIPTION:	list of timer callbacks
26 ***********************************************************/
27struct GLUTtimer {
28	GLUTtimer *next;	// list of timers
29	bigtime_t timeout;	// time to be called
30	GLUTtimerCB func;	// function to call
31	int value;			// value
32};
33
34/***********************************************************
35 *	Private variables
36 ***********************************************************/
37static GLUTtimer *__glutTimerList = 0;			// list of timer callbacks
38static GLUTtimer *freeTimerList = 0;
39
40/***********************************************************
41 *	FUNCTION:	glutTimerFunc (7.19)
42 *
43 *	DESCRIPTION:  register a new timer callback
44 ***********************************************************/
45void APIENTRY
46glutTimerFunc(unsigned int interval, GLUTtimerCB timerFunc, int value)
47{
48  GLUTtimer *timer, *other;
49  GLUTtimer **prevptr;
50
51  if (!timerFunc)
52    return;
53
54  if (freeTimerList) {
55    timer = freeTimerList;
56    freeTimerList = timer->next;
57  } else {
58    timer = new GLUTtimer();
59    if (!timer)
60      __glutFatalError("out of memory.");
61  }
62
63  timer->func = timerFunc;
64  timer->value = value;
65  timer->next = NULL;
66  timer->timeout = system_time() + (interval*1000);	// 1000 ticks in a millisecond
67  prevptr = &__glutTimerList;
68  other = *prevptr;
69  while (other && (other->timeout < timer->timeout)) {
70    prevptr = &other->next;
71    other = *prevptr;
72  }
73  timer->next = other;
74  *prevptr = timer;
75}
76
77/***********************************************************
78 *	FUNCTION:	handleTimeouts
79 *
80 *	DESCRIPTION:  private function to handle outstanding timeouts
81 ***********************************************************/
82static void
83handleTimeouts(void)
84{
85  bigtime_t now;
86  GLUTtimer *timer;
87
88  /* Assumption is that __glutTimerList is already determined
89     to be non-NULL. */
90  now = system_time();
91  while (__glutTimerList->timeout <= now) {
92    timer = __glutTimerList;
93    if(gState.currentWindow)
94	    gState.currentWindow->LockGL();
95    timer->func(timer->value);
96    if(gState.currentWindow)
97	    gState.currentWindow->UnlockGL();
98    __glutTimerList = timer->next;
99    timer->next = freeTimerList;
100    freeTimerList = timer;
101    if (!__glutTimerList)
102      break;
103  }
104}
105
106
107/***********************************************************
108 *	FUNCTION:	processEventsAndTimeouts
109 *
110 *	DESCRIPTION:  clear gBlock, then check all windows for events
111 ***********************************************************/
112static void
113processEventsAndTimeouts(void)
114{
115	gBlock.WaitEvent();		// if there is already an event, returns
116							// immediately, otherwise wait forever
117	gBlock.ClearEvents();
118
119	if(gState.quitAll)
120		exit(0);		// exit handler cleans up windows and quits nicely
121
122	if (gState.currentWindow)
123		gState.currentWindow->LockGL();
124	for(int i=0; i<gState.windowListSize; i++) {
125		if (gState.windowList[i]) {
126			GlutWindow *win = gState.windowList[i];
127			// NOTE: we can use win as a shortcut for gState.windowList[i]
128			// in callbacks, EXCEPT we need to check the original variable
129			// after each callback to make sure the window hasn't been destroyed
130			if (win->anyevents) {
131				win->anyevents = false;
132				if (win->reshapeEvent) {
133					win->reshapeEvent = false;
134					__glutSetWindow(win);
135					win->reshape(win->m_width, win->m_height);
136				}
137				if (!gState.windowList[i])
138					continue;	// window was destroyed by callback!
139
140				if (win->displayEvent) {
141					win->displayEvent = false;
142					__glutSetWindow(win);
143					win->display();
144				}
145				if (!gState.windowList[i])
146					continue;	// window was destroyed by callback!
147
148				if (win->mouseEvent) {
149					win->mouseEvent = false;
150					__glutSetWindow(win);
151					if (win->mouse) {
152						gState.modifierKeys = win->modifierKeys;
153						win->mouse(win->button, win->mouseState, win->mouseX, win->mouseY);
154						gState.modifierKeys = ~0;
155					}
156				}
157				if (!gState.windowList[i])
158					continue;	// window was destroyed by callback!
159
160				if (win->menuEvent) {
161					win->menuEvent = false;
162					__glutSetWindow(win);
163					GlutMenu *menu = __glutGetMenuByNum(win->menuNumber);
164					if (menu) {
165						gState.currentMenu = menu;
166						menu->select(win->menuValue);
167					}
168				}
169				if (!gState.windowList[i])
170					continue;	// window was destroyed by callback!
171
172				if (win->statusEvent) {
173					win->statusEvent = false;
174					__glutSetWindow(win);
175					if (gState.menuStatus) {
176						gState.currentMenu = __glutGetMenuByNum(win->menuNumber);
177						gState.menuStatus(win->menuStatus, win->statusX, win->statusY);
178					}
179				}
180				if (!gState.windowList[i])
181					continue;	// window was destroyed by callback!
182
183				if (win->motionEvent) {
184					win->motionEvent = false;
185					__glutSetWindow(win);
186					if (win->motion)
187						win->motion(win->motionX, win->motionY);
188				}
189				if (!gState.windowList[i])
190					continue;	// window was destroyed by callback!
191
192				if (win->passiveEvent) {
193					win->passiveEvent = false;
194					__glutSetWindow(win);
195					if (win->passive)
196						win->passive(win->passiveX, win->passiveY);
197				}
198				if (!gState.windowList[i])
199					continue;	// window was destroyed by callback!
200
201				if (win->keybEvent) {
202					win->keybEvent = false;
203					__glutSetWindow(win);
204					if (win->keyboard) {
205						gState.modifierKeys = win->modifierKeys;
206						win->keyboard(win->key, win->keyX, win->keyY);
207						gState.modifierKeys = ~0;
208					}
209				}
210				if (!gState.windowList[i])
211					continue;	// window was destroyed by callback!
212
213				if (win->specialEvent) {
214					win->specialEvent = false;
215					__glutSetWindow(win);
216					if (win->special) {
217						gState.modifierKeys = win->modifierKeys;
218						win->special(win->specialKey, win->specialX, win->specialY);
219						gState.modifierKeys = ~0;
220					}
221				}
222				if (!gState.windowList[i])
223					continue;	// window was destroyed by callback!
224
225				if (win->entryEvent) {
226					win->entryEvent = false;
227					__glutSetWindow(win);
228					if (win->entry)
229						win->entry(win->entryState);
230				}
231				if (!gState.windowList[i])
232					continue;	// window was destroyed by callback!
233
234				if (win->windowStatusEvent) {
235					win->windowStatusEvent = false;
236					__glutSetWindow(win);
237					if (win->windowStatus)
238						win->windowStatus(win->visState);
239				}
240				if (!gState.windowList[i])
241					continue;	// window was destroyed by callback!
242			}
243		}
244	}
245	if (gState.currentWindow)
246		gState.currentWindow->UnlockGL();
247
248	// This code isn't necessary since BGLView automatically traps errors
249#if 0
250	if(gState.debug) {
251		for(int i=0; i<gState.windowListSize; i++) {
252			if (gState.windowList[i]) {
253				gState.windowList[i]->LockGL();
254				glutReportErrors();
255				gState.windowList[i]->UnlockGL();
256			}
257		}
258	}
259#endif
260	if (__glutTimerList) {
261      handleTimeouts();
262    }
263}
264
265/***********************************************************
266 *	FUNCTION:	waitForSomething
267 *
268 *	DESCRIPTION:  use gBlock to wait for a new event or timeout
269 ***********************************************************/
270static void
271waitForSomething(void)
272{
273	bigtime_t timeout = __glutTimerList->timeout;
274	bigtime_t now = system_time();
275
276	if (gBlock.PendingEvent())
277		goto immediatelyHandleEvent;
278
279	if(timeout>now)
280		gBlock.WaitEvent(timeout-now);
281	if (gBlock.PendingEvent()) {
282	immediatelyHandleEvent:
283		processEventsAndTimeouts();
284	} else {
285		if (__glutTimerList)
286			handleTimeouts();
287	}
288}
289
290/***********************************************************
291 *	FUNCTION:	idleWait
292 *
293 *	DESCRIPTION:  check for events, then call idle function
294 ***********************************************************/
295static void
296idleWait(void)
297{
298  if (gBlock.PendingEvent()) {
299    processEventsAndTimeouts();
300  } else {
301    if (__glutTimerList)
302      handleTimeouts();
303  }
304  /* Make sure idle func still exists! */
305  if(gState.currentWindow)
306	  gState.currentWindow->LockGL();
307  if (gState.idle) {
308    gState.idle();
309  }
310  if(gState.currentWindow)
311	  gState.currentWindow->UnlockGL();
312}
313
314/***********************************************************
315 *	FUNCTION:	glutMainLoop (3.1)
316 *
317 *	DESCRIPTION:  enter the event processing loop
318 ***********************************************************/
319void glutMainLoop()
320{
321  if (!gState.windowListSize)
322    __glutFatalUsage("main loop entered with no windows created.");
323
324  if(gState.currentWindow)
325	  gState.currentWindow->UnlockGL();
326
327  for (;;) {
328    if (gState.idle) {
329      idleWait();
330    } else {
331      if (__glutTimerList) {
332        waitForSomething();
333      } else {
334        processEventsAndTimeouts();
335      }
336    }
337  }
338}
339
340/***********************************************************
341 *	CLASS:		GlutWindow
342 *
343 *	FUNCTION:	KeyDown
344 *
345 *	DESCRIPTION:  handles keyboard and special events
346 ***********************************************************/
347void GlutWindow::KeyDown(const char *s, int32 slen)
348{
349  ulong aChar = s[0];
350  BGLView::KeyDown(s,slen);
351
352  BPoint p;
353
354	switch (aChar) {
355		case B_FUNCTION_KEY:
356		switch(Window()->CurrentMessage()->FindInt32("key")) {
357			case B_F1_KEY:
358				aChar = GLUT_KEY_F1;
359				goto specialLabel;
360			case B_F2_KEY:
361				aChar = GLUT_KEY_F2;
362				goto specialLabel;
363			case B_F3_KEY:
364				aChar = GLUT_KEY_F3;
365				goto specialLabel;
366			case B_F4_KEY:
367				aChar = GLUT_KEY_F4;
368				goto specialLabel;
369			case B_F5_KEY:
370				aChar = GLUT_KEY_F5;
371				goto specialLabel;
372			case B_F6_KEY:
373				aChar = GLUT_KEY_F6;
374				goto specialLabel;
375			case B_F7_KEY:
376				aChar = GLUT_KEY_F7;
377				goto specialLabel;
378			case B_F8_KEY:
379				aChar = GLUT_KEY_F8;
380				goto specialLabel;
381			case B_F9_KEY:
382				aChar = GLUT_KEY_F9;
383				goto specialLabel;
384			case B_F10_KEY:
385				aChar = GLUT_KEY_F10;
386				goto specialLabel;
387			case B_F11_KEY:
388				aChar = GLUT_KEY_F11;
389				goto specialLabel;
390			case B_F12_KEY:
391				aChar = GLUT_KEY_F12;
392				goto specialLabel;
393			default:
394				return;
395		}
396		case B_LEFT_ARROW:
397			aChar = GLUT_KEY_LEFT;
398			goto specialLabel;
399		case B_UP_ARROW:
400			aChar = GLUT_KEY_UP;
401			goto specialLabel;
402		case B_RIGHT_ARROW:
403			aChar = GLUT_KEY_RIGHT;
404			goto specialLabel;
405		case B_DOWN_ARROW:
406			aChar = GLUT_KEY_DOWN;
407			goto specialLabel;
408		case B_PAGE_UP:
409			aChar = GLUT_KEY_PAGE_UP;
410			goto specialLabel;
411		case B_PAGE_DOWN:
412			aChar = GLUT_KEY_PAGE_DOWN;
413			goto specialLabel;
414		case B_HOME:
415			aChar = GLUT_KEY_HOME;
416			goto specialLabel;
417		case B_END:
418			aChar = GLUT_KEY_END;
419			goto specialLabel;
420		case B_INSERT:
421            aChar = GLUT_KEY_INSERT;
422specialLabel:
423			if (special) {
424				anyevents = specialEvent = true;
425				GetMouse(&p,&m_buttons);
426				specialKey = aChar;
427				specialX = (int)p.x;
428				specialY = (int)p.y;
429				goto setModifiers;	// set the modifier variable
430			}
431			return;
432
433		default:
434			break;
435	}
436
437	if (keyboard) {
438		anyevents = keybEvent = true;
439		GetMouse(&p,&m_buttons);
440		key = aChar;
441		keyX = (int)p.x;
442		keyY = (int)p.y;
443setModifiers:
444		modifierKeys = 0;
445		uint32 beMod = Window()->CurrentMessage()->FindInt32("modifiers");
446		if(beMod & B_SHIFT_KEY)
447			modifierKeys |= GLUT_ACTIVE_SHIFT;
448		if(beMod & B_CONTROL_KEY)
449			modifierKeys |= GLUT_ACTIVE_CTRL;
450		if(beMod & B_OPTION_KEY) {
451			// since the window traps B_COMMAND_KEY, we'll have to settle
452			// for the option key.. but we need to get the raw character,
453			// not the Unicode-enhanced version
454			key = Window()->CurrentMessage()->FindInt32("raw_char");
455			modifierKeys |= GLUT_ACTIVE_ALT;
456		}
457		gBlock.NewEvent();
458	}
459}
460
461/***********************************************************
462 *	CLASS:		GlutWindow
463 *
464 *	FUNCTION:	MouseDown
465 *
466 *	DESCRIPTION:  handles mouse and menustatus events
467 ***********************************************************/
468void GlutWindow::MouseDown(BPoint point)
469{
470	BGLView::MouseDown(point);
471	MouseCheck();
472}
473
474/***********************************************************
475 *	CLASS:		GlutWindow
476 *
477 *	FUNCTION:	MouseCheck
478 *
479 *	DESCRIPTION:  checks for button state changes
480 ***********************************************************/
481void GlutWindow::MouseCheck()
482{
483	if (mouseEvent)
484		return;		// we already have an outstanding mouse event
485
486	BPoint point;
487	uint32 newButtons;
488	GetMouse(&point, &newButtons);
489	if (m_buttons != newButtons) {
490		if (newButtons&B_PRIMARY_MOUSE_BUTTON && !(m_buttons&B_PRIMARY_MOUSE_BUTTON)) {
491			button = GLUT_LEFT_BUTTON;
492			mouseState = GLUT_DOWN;
493		} else if (m_buttons&B_PRIMARY_MOUSE_BUTTON && !(newButtons&B_PRIMARY_MOUSE_BUTTON)) {
494			button = GLUT_LEFT_BUTTON;
495			mouseState = GLUT_UP;
496		} else if (newButtons&B_SECONDARY_MOUSE_BUTTON && !(m_buttons&B_SECONDARY_MOUSE_BUTTON)) {
497			button = GLUT_RIGHT_BUTTON;
498			mouseState = GLUT_DOWN;
499		} else if (m_buttons&B_SECONDARY_MOUSE_BUTTON && !(newButtons&B_SECONDARY_MOUSE_BUTTON)) {
500			button = GLUT_RIGHT_BUTTON;
501			mouseState = GLUT_UP;
502		} else if (newButtons&B_TERTIARY_MOUSE_BUTTON && !(m_buttons&B_TERTIARY_MOUSE_BUTTON)) {
503			button = GLUT_MIDDLE_BUTTON;
504			mouseState = GLUT_DOWN;
505		} else if (m_buttons&B_TERTIARY_MOUSE_BUTTON && !(newButtons&B_TERTIARY_MOUSE_BUTTON)) {
506			button = GLUT_MIDDLE_BUTTON;
507			mouseState = GLUT_UP;
508		}
509	} else {
510		return;		// no change, return
511	}
512	m_buttons = newButtons;
513
514	if (mouseState == GLUT_DOWN) {
515		BWindow *w = Window();
516		GlutMenu *m = __glutGetMenuByNum(menu[button]);
517		if (m) {
518			if (gState.menuStatus) {
519				anyevents = statusEvent = true;
520				menuNumber = menu[button];
521				menuStatus = GLUT_MENU_IN_USE;
522				statusX = (int)point.x;
523				statusY = (int)point.y;
524				gBlock.NewEvent();
525			}
526			BRect bounds = w->Frame();
527			point.x += bounds.left;
528			point.y += bounds.top;
529			GlutPopUp *bmenu = static_cast<GlutPopUp*>(m->CreateBMenu());	// start menu
530			bmenu->point = point;
531			bmenu->win = this;
532			thread_id menu_thread = spawn_thread(MenuThread, "menu thread", B_NORMAL_PRIORITY, bmenu);
533			resume_thread(menu_thread);
534			return;
535		}
536	}
537
538	if (mouse) {
539		anyevents = mouseEvent = true;
540		mouseX = (int)point.x;
541		mouseY = (int)point.y;
542		modifierKeys = 0;
543		uint32 beMod = modifiers();
544		if(beMod & B_SHIFT_KEY)
545			modifierKeys |= GLUT_ACTIVE_SHIFT;
546		if(beMod & B_CONTROL_KEY)
547			modifierKeys |= GLUT_ACTIVE_CTRL;
548		if(beMod & B_OPTION_KEY) {
549			modifierKeys |= GLUT_ACTIVE_ALT;
550		}
551		gBlock.NewEvent();
552	}
553}
554
555/***********************************************************
556 *	CLASS:		GlutWindow
557 *
558 *	FUNCTION:	MouseMoved
559 *
560 *	DESCRIPTION:  handles entry, motion, and passive events
561 ***********************************************************/
562void GlutWindow::MouseMoved(BPoint point,
563						ulong transit, const BMessage *msg)
564{
565	BGLView::MouseMoved(point,transit,msg);
566
567	if(transit != B_INSIDE_VIEW) {
568		if (entry) {
569			anyevents = entryEvent = true;
570			gBlock.NewEvent();
571		}
572		if (transit == B_ENTERED_VIEW) {
573			entryState = GLUT_ENTERED;
574			MakeFocus();	// make me the current focus
575			__glutSetCursor(cursor);
576		} else
577			entryState = GLUT_LEFT;
578	}
579
580	MouseCheck();
581	if(m_buttons) {
582		if(motion) {
583			anyevents = motionEvent = true;
584			motionX = (int)point.x;
585			motionY = (int)point.y;
586			gBlock.NewEvent();
587		}
588	} else {
589		if(passive) {
590			anyevents = passiveEvent = true;
591			passiveX = (int)point.x;
592			passiveY = (int)point.y;
593			gBlock.NewEvent();
594		}
595	}
596}
597
598/***********************************************************
599 *	CLASS:		GlutWindow
600 *
601 *	FUNCTION:	FrameResized
602 *
603 *	DESCRIPTION:  handles reshape event
604 ***********************************************************/
605void GlutWindow::FrameResized(float width, float height)
606{
607	BGLView::FrameResized(width, height);
608	if (visible) {
609		anyevents = reshapeEvent = true;
610		m_width = (int)(width)+1;
611		m_height = (int)(height)+1;
612		gBlock.NewEvent();
613	}
614}
615
616/***********************************************************
617 *	CLASS:		GlutWindow
618 *
619 *	FUNCTION:	Draw
620 *
621 *	DESCRIPTION:  handles reshape and display events
622 ***********************************************************/
623void GlutWindow::Draw(BRect updateRect)
624{
625	BGLView::Draw(updateRect);
626	BRect frame = Frame();
627	if (m_width != (frame.Width()+1) || m_height != (frame.Height()+1)) {
628		FrameResized(frame.Width(), frame.Height());
629	}
630	Window()->Lock();
631	if (visible) {
632		anyevents = displayEvent = true;
633		gBlock.NewEvent();
634	}
635	Window()->Unlock();
636}
637
638/***********************************************************
639 *	CLASS:		GlutWindow
640 *
641 *	FUNCTION:	Pulse
642 *
643 *	DESCRIPTION:  handles mouse up event (MouseUp is broken)
644 ***********************************************************/
645void GlutWindow::Pulse()
646{
647	BGLView::Pulse();
648	if (m_buttons) {	// if there are buttons pressed
649		MouseCheck();
650	}
651}
652
653/***********************************************************
654 *	CLASS:		GlutWindow
655 *
656 *	FUNCTION:	ErrorCallback
657 *
658 *	DESCRIPTION:  handles GL error messages
659 ***********************************************************/
660void GlutWindow::ErrorCallback(GLenum errorCode) {
661	__glutWarning("GL error: %s", gluErrorString(errorCode));
662}
663
664/***********************************************************
665 *	CLASS:		GlutWindow
666 *
667 *	FUNCTION:	MenuThread
668 *
669 *	DESCRIPTION:  a new thread to launch popup menu, wait
670 *			wait for response, then clean up afterwards and
671 *			send appropriate messages
672 ***********************************************************/
673long GlutWindow::MenuThread(void *m) {
674	GlutPopUp *bmenu = static_cast<GlutPopUp*>(m);
675	GlutWindow *win = bmenu->win;	// my window
676	GlutBMenuItem *result = (GlutBMenuItem*)bmenu->Go(bmenu->point);
677	win->Window()->Lock();
678	win->anyevents = win->statusEvent = true;
679	win->menuStatus = GLUT_MENU_NOT_IN_USE;
680	win->menuNumber = bmenu->menu;
681	BPoint cursor;
682	uint32 buttons;
683	win->GetMouse(&cursor, &buttons);
684	win->statusX = (int)cursor.x;
685	win->statusY = (int)cursor.y;
686	if(result && result->menu) {
687		win->menuEvent = true;
688		win->menuNumber = result->menu;  // in case it was a submenu
689		win->menuValue = result->value;
690	}
691	win->Window()->Unlock();
692	gBlock.NewEvent();
693	delete bmenu;
694	return 0;
695}
696