/* 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 "select.h" #include "error.h" static CANVAS_TYPE canvastype; static Window canvas_window; static Display *canvas_display; static int canvas_inited = FALSE; static void CanvasResizeCallback(Widget canvas, XtPointer client_data, XtPointer call_data); static void CanvasExposeCallback(Widget canvas, XtPointer client_data, XtPointer call_data); static void CanvasMouseEvent(Widget canvas, XtPointer client_data, XEvent *event); static void CanvasKeyEvent(Widget canvas, XtPointer client_data, XEvent *event); int twobutton_motion; /* creates an integrated canvas window. */ Widget CreateIntegratedCanvasWindow(Widget parent, Dimension hres, Dimension vres) { canvastype = INTEGRATED_CANVAS; canvas = RenderCreateWindow(parent); if (hres > 0) /* horizontal resolution specified as an option */ XtVaSetValues(canvas, XmNwidth, hres, NULL); else XtVaGetValues(canvas, XmNwidth, &hres, NULL); if (vres > 0) /* vertical resolution specified as an option */ XtVaSetValues(canvas, XmNheight, vres, NULL); else XtVaGetValues(canvas, XmNheight, &vres, NULL); /* make sure the current camera has the correct horizontal and vertical * resolution */ CameraSet(&Camera, &Camera.eyep, &Camera.lookp, &Camera.updir, Camera.fov, hres, vres, &Camera.background); /* 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; } /* connects to an external canvas window with given ID on the given display. * The display needs not be the same display as the one containing the rest of * the user interface. */ void ConnectExternalCanvasWindow(Display *display, Window window) { XWindowAttributes wattr; XVisualInfo *vinfo, vinfo_template; long vinfo_mask; int nreturn; canvas_window = window; canvas_display = display; canvastype = EXTERNAL_CANVAS; /* get window attributes */ if (!XGetWindowAttributes(display, window, &wattr)) Fatal(1, NULL, "Bad external canvas window"); /* look up the XVisualInfo for the window */ vinfo_template.visualid = XVisualIDFromVisual(wattr.visual); vinfo_mask = VisualIDMask; vinfo = XGetVisualInfo(display, vinfo_mask, &vinfo_template, &nreturn); if (nreturn < 1) Fatal(1, NULL, "Couldn't find X visual info for external canvas window"); /* see XSelectInput manpage. */ if (wattr.all_event_masks & ButtonPressMask) Warning(NULL, "ButtonPress events are already being handled on the canvas window.\nNavigation via the mouse will not be possible"); /* tell what events we want to receive for the window */ XSelectInput(canvas_display, canvas_window, ExposureMask | StructureNotifyMask | ((wattr.all_event_masks & ButtonPressMask) ? 0 : ButtonPressMask) | ButtonReleaseMask | ButtonMotionMask | /* KeyPressMask | */ KeyReleaseMask); /* initialize rendering into the window */ RenderInitWindow(display, window, vinfo); /* set correct width and height for the camera */ CameraSet(&Camera, &Camera.eyep, &Camera.lookp, &Camera.updir, Camera.fov, wattr.width, wattr.height, &Camera.background); /* render the scene (no expose events on the external canvas window!) */ RenderScene(); } /* creates an offscreen canvas "window" with given horizontal and vertical * resolution. */ void CreateOffscreenCanvasWindow(int hres, int vres) { canvastype = OFFSCREEN_CANVAS; RenderCreateOffscreenWindow(hres, vres); /* set correct width and height for the camera */ CameraSet(&Camera, &Camera.eyep, &Camera.lookp, &Camera.updir, Camera.fov, hres, vres, &Camera.background); /* render the scene (no expose events on the external canvas window!) */ RenderScene(); } /* 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, modestackidx; static CANVAS_MODE canvasmode = CANVASMODE_NORMAL; static CANVAS_MODE modestack[CANVASMODESTACKSIZE]; static Cursor working_cursor, select_cursor, render_cursor; #define WORKING_CURSOR XC_watch #define SELECT_CURSOR XC_crosshair #define RENDER_CURSOR XC_spraycan /* initializes event handling cursor shape on the canvas widget */ void CanvasInit(void) { switch (canvastype) { case INTEGRATED_CANVAS: canvas_window = XtWindow(canvas); canvas_display = XtDisplay(canvas); break; case EXTERNAL_CANVAS: break; case OFFSCREEN_CANVAS: return; default: Fatal(-1, "CanvasInit", "Invalid canvas type 5d", canvastype); } canvasmode = CANVASMODE_NORMAL; modestackidx = 0; modestack[modestackidx] = canvasmode; cursordefined = FALSE; /* cursor to show that the program is busy computing */ working_cursor = XCreateFontCursor(canvas_display, WORKING_CURSOR); /* cursor to show that the user is expected to select a patch */ select_cursor = XCreateFontCursor(canvas_display, SELECT_CURSOR); /* cursor to show that the program is busy rendering */ render_cursor = XCreateFontCursor(canvas_display, RENDER_CURSOR); canvas_inited = TRUE; } /* set a new canvas mode */ static void CanvasSetMode(CANVAS_MODE mode) { if (!canvas_inited || canvastype == OFFSCREEN_CANVAS) return; switch (mode) { case CANVASMODE_NORMAL: if (cursordefined) { XUndefineCursor(canvas_display, canvas_window); cursordefined = FALSE; } canvasmode = CANVASMODE_NORMAL; break; case CANVASMODE_WORKING: /* clock cursor */ XDefineCursor(canvas_display, canvas_window, working_cursor); XSync(canvas_display, False); cursordefined = TRUE; canvasmode = CANVASMODE_WORKING; break; case CANVASMODE_SELECT_PATCH: /* crosshair cursor */ XDefineCursor(canvas_display, canvas_window, select_cursor); XSync(canvas_display, False); cursordefined = TRUE; canvasmode = CANVASMODE_SELECT_PATCH; break; case CANVASMODE_SELECT_PIXEL: /* crosshair cursor */ XDefineCursor(canvas_display, canvas_window, select_cursor); XSync(canvas_display, False); cursordefined = TRUE; canvasmode = CANVASMODE_SELECT_PIXEL; break; case CANVASMODE_RENDER: /* spraycan cursor cursor */ XDefineCursor(canvas_display, canvas_window, render_cursor); XSync(canvas_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 */ CANVAS_MODE CanvasGetMode(void) { return canvasmode; } /* pushes the current canvas mode on the canvas mode stack, so it can be * restored later */ void CanvasPushMode(CANVAS_MODE 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 */ static void CanvasResize(int width, int height) { CameraSet(&Camera, &Camera.eyep, &Camera.lookp, &Camera.updir, Camera.fov, width, height, &Camera.background); RenderScene(); } static void CanvasResizeCallback(Widget canvas, XtPointer client_data, XtPointer call_data) { Dimension width, height; XtVaGetValues(canvas, XmNwidth, &width, XmNheight, &height, NULL); CanvasResize(width, height); } /* 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; } /* 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. */ void CanvasPostRedraw(void) { if (canvastype == INTEGRATED_CANVAS && !RedrawWorkProcInstalled) { RedrawWorkProcId = XtAppAddWorkProc(app_context, RedrawWorkProc, (XtPointer)NULL); RedrawWorkProcInstalled = TRUE; } } /* cancels previous redrawing requests due to e.g. expose events on the * canvas window. */ void CanvasCancelRedraw(void) { if (canvastype == INTEGRATED_CANVAS && RedrawWorkProcInstalled) { XtRemoveWorkProc(RedrawWorkProcId); RedrawWorkProcInstalled = FALSE; } } /* 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) { RenderScene(); CanvasCancelRedraw(); } static void ExternalCanvasExpose(void) { if (canvasmode == CANVASMODE_RENDER) return; /* ignore expose events while rendering */ RenderScene(); } /* handles canvas expose events on the integrated canvas widget */ static void CanvasExposeCallback(Widget canvas, XtPointer client_data, XtPointer call_data) { if (canvasmode == CANVASMODE_RENDER) return; /* ignore expose events while rendering */ CanvasPostRedraw(); } /* 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; maxx = Camera.hres; maxy = Camera.vres; 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 */ if (twobutton_motion) { 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) CameraMoveHorizontal(&Camera, (float)(lasty - y)/(float)maxy * 2. * view_dist); break; case 2: /* second mouse button (middle button on a three button mouse) */ if (x != lastx) CameraMoveRight(&Camera, (float)(x - lastx)/(float)maxx * w); if (y != lasty) CameraMoveUp(&Camera, (float)(y - lasty)/(float)maxx * w / aspect); break; case 3: /* first and second button pressed simultaneously */ 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; default: /* do nothing */ return; } } else { /*three button motion */ 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, (float)(x - lastx)/(float)maxx * w); if (y != lasty) CameraMoveUp(&Camera, (float)(y - lasty)/(float)maxx * w / aspect); break; case 3: /* first and second button pressed simultaneously */ case 4: /* right button on three-button mouse */ if (x != lastx) CameraMoveRight(&Camera, (float)(x - lastx)/(float)maxx * w); if (y != lasty) CameraMoveForward(&Camera, (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(); } static PATCH *the_patch; static POINT the_point; static void GetThePatch(PATCH *patch, POINT *hitp) { the_patch = patch; the_point = *hitp; } static void CanvasClick(XEvent *event) { the_patch = (PATCH *)NULL; SelectPatchSetCallback(GetThePatch); CanvasPushMode(CANVASMODE_SELECT_PATCH); SelectPatch(event->xbutton.x, event->xbutton.y); if (the_patch) PopUpCanvasMenu(event, the_patch, &the_point); } /* handles mouse events on the canvas window */ static int buttonspressed = 0; /* global because we want to be able to simulate * buttonpresses/releases with the keyboard */ static void CanvasMouse(XEvent *event) { static int lastx, lasty; 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) { case Button1: buttonspressed |= 1; break; case Button2: buttonspressed |= 2; break; case Button3: if (twobutton_motion) CanvasClick(event); else buttonspressed |= 4; break; default: break; } break; case ButtonRelease: x = event->xbutton.x; y = event->xbutton.y; if (canvasmode == CANVASMODE_SELECT_PATCH || canvasmode == CANVASMODE_SELECT_PIXEL) { switch (buttonspressed) { case 1: /* select callbacks know what to do with the selected patch ... */ if(canvasmode == CANVASMODE_SELECT_PATCH) SelectPatch(event->xbutton.x, event->xbutton.y); else SelectPixel(event->xbutton.x, event->xbutton.y); 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) { case Button1: buttonspressed &= ~1; break; case Button2: buttonspressed &= ~2; break; case Button3: if (!twobutton_motion) buttonspressed &= ~4; break; default: break; } break; #ifndef SLOW_RENDERER /* do the motion immediately */ case MotionNotify: 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; } } static void CanvasMouseEvent(Widget canvas, XtPointer client_data, XEvent *event) { CanvasMouse(event); } /* 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); buf[charcount] = '\0'; #ifdef DEBUG 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 KeyRelease events on the canvas window */ static void CanvasKey(XKeyEvent *event) { /* extern void TestGlobalLine(void); */ switch (GetKey(event)) { case XK_Q: exit(0); break; /* case XK_t: TestGlobalLine(); break; */ default: /* ignore */ break; } } static void CanvasKeyEvent(Widget canvas, XtPointer client_data, XEvent *event) { CanvasKey((XKeyEvent *)event); } /* handles XEvents on external canvas window (which may even be on another * display than the rest of the interface ...*/ void ExternalCanvasEvent(XEvent *event) { switch (event->type) { case Expose: /* compress expose events: only handle the last one in the event queue. We * don't bother compute=ing the union of the exposed window regions as * these are not used. */ while (XCheckWindowEvent(canvas_display, canvas_window, ExposureMask, event)) {} ExternalCanvasExpose(); break; case ConfigureNotify: CanvasResize(event->xconfigure.width, event->xconfigure.height); break; case KeyPress: case KeyRelease: CanvasKey((XKeyEvent *)event); break; case ButtonPress: case ButtonRelease: CanvasMouse(event); break; case MotionNotify: /* compress motion events: only handle the last one in the event queue */ while (XCheckWindowEvent(canvas_display, canvas_window, ButtonMotionMask, event)) {} CanvasMouse(event); break; case DestroyNotify: fprintf(stderr, "External canvas window destroyed!\n"); exit(0); break; default: /* fprintf(stderr, "I don't handle it.\n"); */ break; } }