| /*********************************************************** |
| * 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 <GL/glut.h> |
| #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; i<gState.windowListSize; i++) { |
| if (gState.windowList[i]) { |
| GlutWindow *win = gState.windowList[i]; |
| // NOTE: we can use win as a shortcut for gState.windowList[i] |
| // in callbacks, EXCEPT we need to check the original variable |
| // after each callback to make sure the window hasn't been destroyed |
| if (win->anyevents) { |
| 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; i<gState.windowListSize; i++) { |
| if (gState.windowList[i]) { |
| gState.windowList[i]->LockGL(); |
| 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<GlutPopUp*>(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<GlutPopUp*>(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; |
| } |