/* canvas.c: handles events on the canvas (drawing) window */ #include #include #include #include "canvas.h" #include "ui.h" #include "render.h" #include "camera.h" #include "error.h" /* Size of the canvas mode stack. Canvas mode: determines how the program will * react to mouse events and what shape of cursor is displayed in the canvas * window, eg spraycan when renderin ... When the mode changes, the old mode * is pushed on a stack and restored afterwards */ #define CANVASMODESTACKSIZE 5 static int cursordefined = 0, canvasmode = CANVASMODE_NORMAL; static int modestack[CANVASMODESTACKSIZE], modestackidx; static Cursor working_cursor, select_cursor, render_cursor; #define WORKING_CURSOR XC_watch #define SELECT_CURSOR XC_crosshair #define RENDER_CURSOR XC_spraycan /* initializes mouse event handling and cursor shape on the canvas widget */ void CanvasInit(Widget canvas) { canvasmode = CANVASMODE_NORMAL; modestackidx = 0; modestack[modestackidx] = canvasmode; cursordefined = FALSE; /* cursor to show that the program is busy computing */ working_cursor = XCreateFontCursor(XtDisplay(canvas), WORKING_CURSOR); /* cursor to show that the user is expected to select a patch */ select_cursor = XCreateFontCursor(XtDisplay(canvas), SELECT_CURSOR); /* cursor to show that the program is busy rendering */ render_cursor = XCreateFontCursor(XtDisplay(canvas), RENDER_CURSOR); } /* creates and initializes the canvas window. */ Widget CreateCanvasWindow(Widget parent) { Widget canvas = RenderCreateWindow(parent); Dimension hres, vres; /* get window size */ XtVaGetValues(canvas, XmNwidth, &hres, XmNheight, &vres, NULL); Camera.hres = hres; Camera.vres = vres; /* window contents damaged */ XtAddCallback(canvas, XmNexposeCallback, CanvasExposeCallback, (XtPointer)NULL); /* window resizes */ XtAddCallback(canvas, XmNresizeCallback, CanvasResizeCallback, (XtPointer)NULL); /* mouse events */ XtAddEventHandler(canvas, ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, False, /* no non-maskable event */ (XtEventHandler)CanvasMouseEvent, (XtPointer)NULL); /* client_data */ /* canvas key event handler */ XtAddEventHandler(canvas, /*KeyPressMask |*/ KeyReleaseMask, False, /* no non-maskable event */ (XtEventHandler)CanvasKeyEvent, (XtPointer)NULL); /* client_data */ return canvas; } /* set a new canvas mode */ static void CanvasSetMode(int mode) { switch (mode) { case CANVASMODE_NORMAL: if (cursordefined) { XUndefineCursor(XtDisplay(canvas), XtWindow(canvas)); cursordefined = FALSE; } canvasmode = CANVASMODE_NORMAL; break; case CANVASMODE_WORKING: /* clock cursor */ XDefineCursor(display, XtWindow(canvas), working_cursor); XSync(display, False); cursordefined = TRUE; canvasmode = CANVASMODE_WORKING; break; case CANVASMODE_SELECT_PATCH: /* crosshair cursor */ XDefineCursor(display, XtWindow(canvas), select_cursor); XSync(display, False); cursordefined = TRUE; canvasmode = CANVASMODE_SELECT_PATCH; break; case CANVASMODE_RENDER: /* spraycan cursor cursor */ XDefineCursor(display, XtWindow(canvas), render_cursor); XSync(display, False); cursordefined = TRUE; canvasmode = CANVASMODE_RENDER; break; default: Fatal(4, "CanvasSetMode", "Invalid mode %d - internal error.", mode); break; } modestack[modestackidx] = canvasmode; } /* returns the current canvas mode */ int CanvasGetMode(void) { return canvasmode; } /* pushes the current canvas mode on the canvas mode stack, so it can be * restored later */ void CanvasPushMode(int mode) { modestackidx++; if (modestackidx >= CANVASMODESTACKSIZE) Fatal(4, "CanvasPushMode", "Mode stack size (%d) exceeded.", CANVASMODESTACKSIZE); CanvasSetMode(mode); } /* restores the last saved canvas mode */ void CanvasPullMode(void) { modestackidx--; if (modestackidx < 0) Fatal(4, "CanvasPullMode", "Canvas mode stack underflow.\n"); CanvasSetMode(modestack[modestackidx]); } /* handles canvas window resize events */ void CanvasResizeCallback(Widget canvas, XtPointer client_data, XtPointer call_data) { Dimension newwidth, newheight; XtVaGetValues(canvas, XmNheight, &newheight, XmNwidth, &newwidth, NULL); CameraSet(&Camera, &Camera.eyep, &Camera.lookp, &Camera.updir, Camera.fov, newwidth, newheight, &Camera.background); RenderScene(); } /* Expose event handling: redraw the scene just once, nomatter how many expose * events are coming at the same time. */ static int RedrawWorkProcInstalled = FALSE; static XtWorkProcId RedrawWorkProcId; static Boolean RedrawWorkProc(XtPointer client_data) { RenderScene(); RedrawWorkProcInstalled = FALSE; return TRUE; } /* Takes care of possible expose events immediately: if the scene needs to be * redrawn, this routine removes the installed redraw work procedure and immediately * rerenders the scene. A redraw work procedure is used for expose event compression: * Normally, the scene is redrawn in the background, after all pending events have been * processed. */ void CanvasRedraw(void) { if (RedrawWorkProcInstalled) { XtRemoveWorkProc(RedrawWorkProcId); RedrawWorkProcInstalled = FALSE; } RenderScene(); } void CanvasExposeCallback(Widget canvas, XtPointer client_data, XtPointer call_data) { if (canvasmode == CANVASMODE_RENDER) return; /* ignore expose events while rendering */ /* Install a work procedure which will redraw the scene. Install it just once, * when the global variable RedrawWorkProcInstalled is FALSE, so the scene will * be redrawn just once, no matter how many expose events are coming at the same * time. This very nice, graphics library independent, trick was posted on the * Mesa mailing list (mesa@iqm.unicamp.br) by Jeroen van der Zijp * (jvz@cyberia.cfdrc.com). */ if (!RedrawWorkProcInstalled) { RedrawWorkProcId = XtAppAddWorkProc(app_context, RedrawWorkProc, (XtPointer)NULL); RedrawWorkProcInstalled = TRUE; } } static float move_factor = 1.0; static void CameraMoveFaster(void) { move_factor *= 1.41; } static void CameraMoveSlower(void) { move_factor /= 1.41; } /* this routine moves the camera corresponding to which mouse buttons were * pressed and the distance the mouse moved, and than rerenders the scene. * Conventions are the same as for a WALK viewer in CosmoWorlds. */ static void DoMotion(int x, int y, int lastx, int lasty, int buttonspressed) { Dimension maxx, maxy; float fov, aspect, a, w, view_dist; VECTOR d; XtVaGetValues(canvas, XmNheight, &maxy, XmNwidth, &maxx, NULL); fov = 2. * Camera.fov * M_PI / 180.; aspect = (float)maxx/(float)maxy; if (aspect > 1) fov *= aspect; VECTORSUBTRACT(Camera.lookp, Camera.eyep, d); view_dist = VECTORNORM(d); w = view_dist * fov; /* w = sin(angle between up direction and viewing direction). Used * to have slower rotations when viewing direction and updirection * almost coincide. It "feels" better this way. */ a = VECTORDOTPRODUCT(Camera.Z, Camera.updir) / VECTORNORM(Camera.updir); a = sin(acos(a < -1. ? -1. : (a > 1. ? 1. : a))); /* what motion to perform depends on which mouse buttons were pressed * while the pointer was moved */ switch (buttonspressed) { case 1: /* first mouse button pressed and moved */ if (x != lastx) CameraTurnRight(&Camera, (float)(x - lastx)/(float)maxx * fov * a); if (y != lasty) CameraTurnUp(&Camera, (float)(lasty - y)/(float)maxy * fov / aspect); break; case 2: /* second mouse button (middle button on a three button mouse) */ if (x != lastx) CameraMoveRight(&Camera, move_factor * (float)(x - lastx)/(float)maxx * w); if (y != lasty) CameraMoveUp(&Camera, move_factor * (float)(y - lasty)/(float)maxx * w / aspect); break; case 3: /* first and second button pressed simultaneously */ if (x != lastx) CameraMoveRight(&Camera, move_factor * (float)(x - lastx)/(float)maxx * w); if (y != lasty) CameraMoveForward(&Camera, move_factor * (float)(lasty - y)/(float)maxy * 2. * view_dist); break; default: /* do nothing */ return; } /* rerender the scene as seen from the new camera position etc... */ RenderScene(); } /* handles mouse events on the canvas window */ void CanvasMouseEvent(Widget canvas, XtPointer client_data, XEvent *event) { static int lastx, lasty, lastpressedx, lastpressedy; static int buttonspressed = 0; int x, y; if (canvasmode == CANVASMODE_RENDER) return; /* ignore mouse events */ switch (event->type) { case ButtonPress: x = lastx = event->xbutton.x; y = lasty = event->xbutton.y; switch (event->xbutton.button) { #ifdef TWOBUTTON_MOUSE case Button1: buttonspressed |= 1; break; /* left button */ case Button3: buttonspressed |= 2; break; /* right button */ case Button2: buttonspressed |= 3; break; /* when the two buttons are pressed simultaneously */ #else case Button1: buttonspressed = 1; break; case Button2: buttonspressed = 2; break; case Button3: buttonspressed = 3; break; #endif default: break; } lastpressedx = x; lastpressedy = y; break; case ButtonRelease: x = event->xbutton.x; y = event->xbutton.y; if (canvasmode == CANVASMODE_SELECT_PATCH) { switch (buttonspressed) { case 1: /* ... */ break; default: /* cancel the selection */ CanvasPullMode(); break; } } else { #ifdef SLOW_RENDERER /* do all motion at once when the user releases the mouse * button */ if (lastx != x || lasty != y) DoMotion(x, y, lastx, lasty, buttonspressed); lastx = x; lasty = y; #endif /*SLOW_RENDERER*/ } switch (event->xbutton.button) { #ifdef TWOBUTTON_MOUSE case Button1: buttonspressed &= ~1; break; case Button2: buttonspressed &= ~3; break; case Button3: buttonspressed &= ~2; break; #else case Button1: buttonspressed &= ~1; break; case Button2: buttonspressed &= ~2; break; case Button3: break; #endif default: break; } break; #ifndef SLOW_RENDERER case MotionNotify: /* do the motion immediately */ x = event->xmotion.x; y = event->xmotion.y; DoMotion(x, y, lastx, lasty, buttonspressed); lastx = x; lasty = y; break; #endif /*SLOW_RENDERER*/ default: /* ignore the event */ break; } } /* determines the key that was pressed/released from a XKeyEvent structure. */ static KeySym GetKey(XKeyEvent *event) { char buf[20]; int bufsize = 20; KeySym key; XComposeStatus compose; int charcount; charcount = XLookupString(event, buf, bufsize, &key, &compose); #ifdef DEBUG buf[charcount] = '\0'; switch (key) { case XK_Shift_L: fprintf(stderr, "Shift_L "); break; case XK_Shift_R: fprintf(stderr, "Shift_R "); break; case XK_Shift_Lock: fprintf(stderr, "Shift_Lock "); break; case XK_Caps_Lock: fprintf(stderr, "Caps_Lock "); break; default: fprintf(stderr, "Key %x '%s' ", (unsigned)key, buf); } #endif return key; } /* handles KeyPress and KeyRelease events on the canvas window */ void CanvasKeyEvent(Widget canvas, XtPointer client_data, XEvent *event) { switch (GetKey((XKeyEvent *)event)) { case XK_n: GoToNextView(); break; case XK_p: GoToPreviousView(); break; case XK_0: GoToViewNr(0); break; case XK_1: GoToViewNr(1); break; case XK_2: GoToViewNr(2); break; case XK_3: GoToViewNr(3); break; case XK_4: GoToViewNr(4); break; case XK_5: GoToViewNr(5); break; case XK_6: GoToViewNr(6); break; case XK_7: GoToViewNr(7); break; case XK_8: GoToViewNr(8); break; case XK_9: GoToViewNr(9); break; case XK_less: CameraMoveSlower(); break; case XK_greater: CameraMoveFaster(); break; default: /* ignore */ break; } }