/*********************************************************** * Copyright (C) 1997, Be Inc. Copyright (C) 1999, Jake Hamby. * * This program is freely distributable without licensing fees * and is provided without guarantee or warrantee expressed or * implied. This program is -not- in the public domain. * * * FILE: glutEvent.cpp * * DESCRIPTION: here it is, the BeOS GLUT event loop ***********************************************************/ /*********************************************************** * Headers ***********************************************************/ #include #include "glutint.h" #include "glutState.h" #include "glutBlocker.h" /*********************************************************** * CLASS: GLUTtimer * * DESCRIPTION: list of timer callbacks ***********************************************************/ struct GLUTtimer { GLUTtimer *next; // list of timers bigtime_t timeout; // time to be called GLUTtimerCB func; // function to call int value; // value }; /*********************************************************** * Private variables ***********************************************************/ static GLUTtimer *__glutTimerList = 0; // list of timer callbacks static GLUTtimer *freeTimerList = 0; /*********************************************************** * FUNCTION: glutTimerFunc (7.19) * * DESCRIPTION: register a new timer callback ***********************************************************/ void APIENTRY glutTimerFunc(unsigned int interval, GLUTtimerCB timerFunc, int value) { GLUTtimer *timer, *other; GLUTtimer **prevptr; if (!timerFunc) return; if (freeTimerList) { timer = freeTimerList; freeTimerList = timer->next; } else { timer = new GLUTtimer(); if (!timer) __glutFatalError("out of memory."); } timer->func = timerFunc; timer->value = value; timer->next = NULL; timer->timeout = system_time() + (interval*1000); // 1000 ticks in a millisecond prevptr = &__glutTimerList; other = *prevptr; while (other && (other->timeout < timer->timeout)) { prevptr = &other->next; other = *prevptr; } timer->next = other; *prevptr = timer; } /*********************************************************** * FUNCTION: handleTimeouts * * DESCRIPTION: private function to handle outstanding timeouts ***********************************************************/ static void handleTimeouts(void) { bigtime_t now; GLUTtimer *timer; /* Assumption is that __glutTimerList is already determined to be non-NULL. */ now = system_time(); while (__glutTimerList->timeout <= now) { timer = __glutTimerList; if(gState.currentWindow) gState.currentWindow->LockGL(); timer->func(timer->value); if(gState.currentWindow) gState.currentWindow->UnlockGL(); __glutTimerList = timer->next; timer->next = freeTimerList; freeTimerList = timer; if (!__glutTimerList) break; } } /*********************************************************** * FUNCTION: processEventsAndTimeouts * * DESCRIPTION: clear gBlock, then check all windows for events ***********************************************************/ static void processEventsAndTimeouts(void) { gBlock.WaitEvent(); // if there is already an event, returns // immediately, otherwise wait forever gBlock.ClearEvents(); if(gState.quitAll) exit(0); // exit handler cleans up windows and quits nicely if (gState.currentWindow) gState.currentWindow->LockGL(); for(int i=0; ianyevents) { win->anyevents = false; if (win->reshapeEvent) { win->reshapeEvent = false; __glutSetWindow(win); win->reshape(win->m_width, win->m_height); } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->displayEvent) { win->displayEvent = false; __glutSetWindow(win); win->display(); } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->mouseEvent) { win->mouseEvent = false; __glutSetWindow(win); if (win->mouse) { gState.modifierKeys = win->modifierKeys; win->mouse(win->button, win->mouseState, win->mouseX, win->mouseY); gState.modifierKeys = ~0; } } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->menuEvent) { win->menuEvent = false; __glutSetWindow(win); GlutMenu *menu = __glutGetMenuByNum(win->menuNumber); if (menu) { gState.currentMenu = menu; menu->select(win->menuValue); } } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->statusEvent) { win->statusEvent = false; __glutSetWindow(win); if (gState.menuStatus) { gState.currentMenu = __glutGetMenuByNum(win->menuNumber); gState.menuStatus(win->menuStatus, win->statusX, win->statusY); } } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->motionEvent) { win->motionEvent = false; __glutSetWindow(win); if (win->motion) win->motion(win->motionX, win->motionY); } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->passiveEvent) { win->passiveEvent = false; __glutSetWindow(win); if (win->passive) win->passive(win->passiveX, win->passiveY); } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->keybEvent) { win->keybEvent = false; __glutSetWindow(win); if (win->keyboard) { gState.modifierKeys = win->modifierKeys; win->keyboard(win->key, win->keyX, win->keyY); gState.modifierKeys = ~0; } } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->specialEvent) { win->specialEvent = false; __glutSetWindow(win); if (win->special) { gState.modifierKeys = win->modifierKeys; win->special(win->specialKey, win->specialX, win->specialY); gState.modifierKeys = ~0; } } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->entryEvent) { win->entryEvent = false; __glutSetWindow(win); if (win->entry) win->entry(win->entryState); } if (!gState.windowList[i]) continue; // window was destroyed by callback! if (win->windowStatusEvent) { win->windowStatusEvent = false; __glutSetWindow(win); if (win->windowStatus) win->windowStatus(win->visState); } if (!gState.windowList[i]) continue; // window was destroyed by callback! } } } if (gState.currentWindow) gState.currentWindow->UnlockGL(); // This code isn't necessary since BGLView automatically traps errors #if 0 if(gState.debug) { for(int i=0; iLockGL(); glutReportErrors(); gState.windowList[i]->UnlockGL(); } } } #endif if (__glutTimerList) { handleTimeouts(); } } /*********************************************************** * FUNCTION: waitForSomething * * DESCRIPTION: use gBlock to wait for a new event or timeout ***********************************************************/ static void waitForSomething(void) { bigtime_t timeout = __glutTimerList->timeout; bigtime_t now = system_time(); if (gBlock.PendingEvent()) goto immediatelyHandleEvent; if(timeout>now) gBlock.WaitEvent(timeout-now); if (gBlock.PendingEvent()) { immediatelyHandleEvent: processEventsAndTimeouts(); } else { if (__glutTimerList) handleTimeouts(); } } /*********************************************************** * FUNCTION: idleWait * * DESCRIPTION: check for events, then call idle function ***********************************************************/ static void idleWait(void) { if (gBlock.PendingEvent()) { processEventsAndTimeouts(); } else { if (__glutTimerList) handleTimeouts(); } /* Make sure idle func still exists! */ if(gState.currentWindow) gState.currentWindow->LockGL(); if (gState.idle) { gState.idle(); } if(gState.currentWindow) gState.currentWindow->UnlockGL(); } /*********************************************************** * FUNCTION: glutMainLoop (3.1) * * DESCRIPTION: enter the event processing loop ***********************************************************/ void glutMainLoop() { if (!gState.windowListSize) __glutFatalUsage("main loop entered with no windows created."); if(gState.currentWindow) gState.currentWindow->UnlockGL(); for (;;) { if (gState.idle) { idleWait(); } else { if (__glutTimerList) { waitForSomething(); } else { processEventsAndTimeouts(); } } } } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: KeyDown * * DESCRIPTION: handles keyboard and special events ***********************************************************/ void GlutWindow::KeyDown(const char *s, int32 slen) { ulong aChar = s[0]; BGLView::KeyDown(s,slen); BPoint p; switch (aChar) { case B_FUNCTION_KEY: switch(Window()->CurrentMessage()->FindInt32("key")) { case B_F1_KEY: aChar = GLUT_KEY_F1; goto specialLabel; case B_F2_KEY: aChar = GLUT_KEY_F2; goto specialLabel; case B_F3_KEY: aChar = GLUT_KEY_F3; goto specialLabel; case B_F4_KEY: aChar = GLUT_KEY_F4; goto specialLabel; case B_F5_KEY: aChar = GLUT_KEY_F5; goto specialLabel; case B_F6_KEY: aChar = GLUT_KEY_F6; goto specialLabel; case B_F7_KEY: aChar = GLUT_KEY_F7; goto specialLabel; case B_F8_KEY: aChar = GLUT_KEY_F8; goto specialLabel; case B_F9_KEY: aChar = GLUT_KEY_F9; goto specialLabel; case B_F10_KEY: aChar = GLUT_KEY_F10; goto specialLabel; case B_F11_KEY: aChar = GLUT_KEY_F11; goto specialLabel; case B_F12_KEY: aChar = GLUT_KEY_F12; goto specialLabel; default: return; } case B_LEFT_ARROW: aChar = GLUT_KEY_LEFT; goto specialLabel; case B_UP_ARROW: aChar = GLUT_KEY_UP; goto specialLabel; case B_RIGHT_ARROW: aChar = GLUT_KEY_RIGHT; goto specialLabel; case B_DOWN_ARROW: aChar = GLUT_KEY_DOWN; goto specialLabel; case B_PAGE_UP: aChar = GLUT_KEY_PAGE_UP; goto specialLabel; case B_PAGE_DOWN: aChar = GLUT_KEY_PAGE_DOWN; goto specialLabel; case B_HOME: aChar = GLUT_KEY_HOME; goto specialLabel; case B_END: aChar = GLUT_KEY_END; goto specialLabel; case B_INSERT: aChar = GLUT_KEY_INSERT; specialLabel: if (special) { anyevents = specialEvent = true; GetMouse(&p,&m_buttons); specialKey = aChar; specialX = (int)p.x; specialY = (int)p.y; goto setModifiers; // set the modifier variable } return; default: break; } if (keyboard) { anyevents = keybEvent = true; GetMouse(&p,&m_buttons); key = aChar; keyX = (int)p.x; keyY = (int)p.y; setModifiers: modifierKeys = 0; uint32 beMod = Window()->CurrentMessage()->FindInt32("modifiers"); if(beMod & B_SHIFT_KEY) modifierKeys |= GLUT_ACTIVE_SHIFT; if(beMod & B_CONTROL_KEY) modifierKeys |= GLUT_ACTIVE_CTRL; if(beMod & B_OPTION_KEY) { // since the window traps B_COMMAND_KEY, we'll have to settle // for the option key.. but we need to get the raw character, // not the Unicode-enhanced version key = Window()->CurrentMessage()->FindInt32("raw_char"); modifierKeys |= GLUT_ACTIVE_ALT; } gBlock.NewEvent(); } } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: MouseDown * * DESCRIPTION: handles mouse and menustatus events ***********************************************************/ void GlutWindow::MouseDown(BPoint point) { BGLView::MouseDown(point); MouseCheck(); } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: MouseCheck * * DESCRIPTION: checks for button state changes ***********************************************************/ void GlutWindow::MouseCheck() { if (mouseEvent) return; // we already have an outstanding mouse event BPoint point; uint32 newButtons; GetMouse(&point, &newButtons); if (m_buttons != newButtons) { if (newButtons&B_PRIMARY_MOUSE_BUTTON && !(m_buttons&B_PRIMARY_MOUSE_BUTTON)) { button = GLUT_LEFT_BUTTON; mouseState = GLUT_DOWN; } else if (m_buttons&B_PRIMARY_MOUSE_BUTTON && !(newButtons&B_PRIMARY_MOUSE_BUTTON)) { button = GLUT_LEFT_BUTTON; mouseState = GLUT_UP; } else if (newButtons&B_SECONDARY_MOUSE_BUTTON && !(m_buttons&B_SECONDARY_MOUSE_BUTTON)) { button = GLUT_RIGHT_BUTTON; mouseState = GLUT_DOWN; } else if (m_buttons&B_SECONDARY_MOUSE_BUTTON && !(newButtons&B_SECONDARY_MOUSE_BUTTON)) { button = GLUT_RIGHT_BUTTON; mouseState = GLUT_UP; } else if (newButtons&B_TERTIARY_MOUSE_BUTTON && !(m_buttons&B_TERTIARY_MOUSE_BUTTON)) { button = GLUT_MIDDLE_BUTTON; mouseState = GLUT_DOWN; } else if (m_buttons&B_TERTIARY_MOUSE_BUTTON && !(newButtons&B_TERTIARY_MOUSE_BUTTON)) { button = GLUT_MIDDLE_BUTTON; mouseState = GLUT_UP; } } else { return; // no change, return } m_buttons = newButtons; if (mouseState == GLUT_DOWN) { BWindow *w = Window(); GlutMenu *m = __glutGetMenuByNum(menu[button]); if (m) { if (gState.menuStatus) { anyevents = statusEvent = true; menuNumber = menu[button]; menuStatus = GLUT_MENU_IN_USE; statusX = (int)point.x; statusY = (int)point.y; gBlock.NewEvent(); } BRect bounds = w->Frame(); point.x += bounds.left; point.y += bounds.top; GlutPopUp *bmenu = static_cast(m->CreateBMenu()); // start menu bmenu->point = point; bmenu->win = this; thread_id menu_thread = spawn_thread(MenuThread, "menu thread", B_NORMAL_PRIORITY, bmenu); resume_thread(menu_thread); return; } } if (mouse) { anyevents = mouseEvent = true; mouseX = (int)point.x; mouseY = (int)point.y; modifierKeys = 0; uint32 beMod = modifiers(); if(beMod & B_SHIFT_KEY) modifierKeys |= GLUT_ACTIVE_SHIFT; if(beMod & B_CONTROL_KEY) modifierKeys |= GLUT_ACTIVE_CTRL; if(beMod & B_OPTION_KEY) { modifierKeys |= GLUT_ACTIVE_ALT; } gBlock.NewEvent(); } } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: MouseMoved * * DESCRIPTION: handles entry, motion, and passive events ***********************************************************/ void GlutWindow::MouseMoved(BPoint point, ulong transit, const BMessage *msg) { BGLView::MouseMoved(point,transit,msg); if(transit != B_INSIDE_VIEW) { if (entry) { anyevents = entryEvent = true; gBlock.NewEvent(); } if (transit == B_ENTERED_VIEW) { entryState = GLUT_ENTERED; MakeFocus(); // make me the current focus __glutSetCursor(cursor); } else entryState = GLUT_LEFT; } MouseCheck(); if(m_buttons) { if(motion) { anyevents = motionEvent = true; motionX = (int)point.x; motionY = (int)point.y; gBlock.NewEvent(); } } else { if(passive) { anyevents = passiveEvent = true; passiveX = (int)point.x; passiveY = (int)point.y; gBlock.NewEvent(); } } } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: FrameResized * * DESCRIPTION: handles reshape event ***********************************************************/ void GlutWindow::FrameResized(float width, float height) { BGLView::FrameResized(width, height); if (visible) { anyevents = reshapeEvent = true; m_width = (int)(width)+1; m_height = (int)(height)+1; gBlock.NewEvent(); } } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: Draw * * DESCRIPTION: handles reshape and display events ***********************************************************/ void GlutWindow::Draw(BRect updateRect) { BGLView::Draw(updateRect); BRect frame = Frame(); if (m_width != (frame.Width()+1) || m_height != (frame.Height()+1)) { FrameResized(frame.Width(), frame.Height()); } Window()->Lock(); if (visible) { anyevents = displayEvent = true; gBlock.NewEvent(); } Window()->Unlock(); } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: Pulse * * DESCRIPTION: handles mouse up event (MouseUp is broken) ***********************************************************/ void GlutWindow::Pulse() { BGLView::Pulse(); if (m_buttons) { // if there are buttons pressed MouseCheck(); } } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: ErrorCallback * * DESCRIPTION: handles GL error messages ***********************************************************/ void GlutWindow::ErrorCallback(GLenum errorCode) { __glutWarning("GL error: %s", gluErrorString(errorCode)); } /*********************************************************** * CLASS: GlutWindow * * FUNCTION: MenuThread * * DESCRIPTION: a new thread to launch popup menu, wait * wait for response, then clean up afterwards and * send appropriate messages ***********************************************************/ long GlutWindow::MenuThread(void *m) { GlutPopUp *bmenu = static_cast(m); GlutWindow *win = bmenu->win; // my window GlutBMenuItem *result = (GlutBMenuItem*)bmenu->Go(bmenu->point); win->Window()->Lock(); win->anyevents = win->statusEvent = true; win->menuStatus = GLUT_MENU_NOT_IN_USE; win->menuNumber = bmenu->menu; BPoint cursor; uint32 buttons; win->GetMouse(&cursor, &buttons); win->statusX = (int)cursor.x; win->statusY = (int)cursor.y; if(result && result->menu) { win->menuEvent = true; win->menuNumber = result->menu; // in case it was a submenu win->menuValue = result->value; } win->Window()->Unlock(); gBlock.NewEvent(); delete bmenu; return 0; }