############################################################################ # # Name: evmux.icn # # Title: Callback-based event multiplexor for X-windows # # Author: Gregg Townsend # # Date: December, 1990; October, 1991 # ############################################################################ # # These procedures help organize event-driven X-windows programs. # They are configured by registering *sensors*, which respond to # X events that occur when the mouse cursor is within a particular # region. When a sensor fires, it calls a user procedure that was # registerd when the sensor was created. # # These routines interpret window events and respond by calling user code: # sensor() registers the events of interest # evhandle() reads and responds to the next event # evmux() loops forever, handling events # # Two other little routines help build event-driven programs: # quitsensor() registers a standardized response to ^C, DEL, etc. # argless() responds by calling any proc with no arguments, e.g. exit(). # # # sensor(win,ev,proc,arg,x,y,w,h) -- register an event responder. # # registers *proc* as the procedure to be called when the event[s] # *ev* occur within the given bounds inside window *win*. The default # bounds encompass the entire window. # # The event set *ev* can be either: # -- a cset or string specifying particular keypresses of interest # -- one of the event keywords (&lpress, &rdrag, &resize, etc.) # # When a matching event occurs, proc(win,arg,x,y,e) is called. proc, # win, and arg are as recorded from the sensor call. x and y give the # current mouse position and e the event; for a keypress, this is the # character. # # No event generates more than one procedure call. # In the case of conflicting entries, the later registrant wins. # # # evmux(win) -- loop forever, calling event handlers as appropriate. # evhandle(win) -- wait for the next event, and handle it. # # evmux(win) is an infinite loop that calls user routines in response # to window events. It is for programs that don't need to do other # work while waiting for window input. # # evhandle(win) processes one event and then returns to its caller, # allowing external loop control. evhandle returns the outcome of # the handler proc, or fails if there is no handler for the event. # # quitsensor(win,wait) -- standardized "quit" sensor # # quitsensor() registers a sensor that calls exit() when any of these # characters are typed in the window: Q, q, ^C, ^D, ^? (DEL). # # If wait is non-null, quitsensor does not return but just waits for # the signal (useful in non-interactive display programs). # # # argless(win,proc) -- call proc with no arguments. # # Useful for registering argless procedures as in quitsensor() above. # ############################################################################ record evrec (ev, proc, arg, x, y, w, h) global ewtab ## sensor(win,ev,proc,arg,x,y,w,h) -- register an event responder. procedure sensor (win, ev, proc, arg, x, y, w, h) local evlist, r, e /ewtab := table() /ewtab[win] := list() evlist := ewtab[win] /x := 0 /y := 0 /w := XAttrib (win, "width") - x /h := XAttrib (win, "height") - y if type(ev) == ("cset" | "string") then ev := cset(ev) else ev := cset(evchar(ev)) | stop ("invalid event specification: ", image(ev)) push (evlist, r := evrec (ev, proc, arg, x, y, w, h)) return r end ## evchar (e) -- map mouse event to character code. # # Internally, *all* events are single-character strings, and mouse & resizing # events are mapped into characters that are never returned as keypress events. procedure evchar (s) return case s of { &lpress: "\237" # mouse button 1 down &mpress: "\236" # mouse button 2 down &rpress: "\235" # mouse button 3 down &lrelease: "\234" # mouse button 1 up &mrelease: "\233" # mouse button 2 up &rrelease: "\232" # mouse button 3 up &ldrag: "\231" # mouse button 1 is dragging &mdrag: "\230" # mouse button 2 is dragging &rdrag: "\227" # mouse button 3 is dragging &resize: "\226" # window has resized } fail end ## evmux(win) -- loop forever, calling event handlers as appropriate. ## evhandle(win) -- wait for the next event, and handle it. # produce result of the handler proc; fail if nobody handles. procedure evmux (win) repeat evhandle (win) end procedure evhandle (win) local x, y, ev, e, r, t t := (\ewtab)[win] | stop ("no events registered for window") ev := XEvent (win) # convert event code to single character if type(ev) == "integer" then e := evchar(ev) | "" else e := ev # find and call the first (most recent) matching handler # (just a simple serial search) every r := !t do if any (r.ev, e) & ontarget (r, &x, &y) then return r.proc (win, r.arg, &x, &y, ev) fail end ## ontarget (r, x, y) -- check if an event is within bounds # # checks that (x, y) are within the bounds of (r.x, r.y, r.w, r.h). procedure ontarget (r, x, y) return (x -:= r.x) >= 0 & x < r.w & (y -:= r.y) >= 0 & y < r.h end ## quitsensor(win,wait) -- standardized "quit" sensor procedure quitsensor (win, wait) sensor (win, '\^c\^d\dqQ', argless, exit) if \wait then evmux (win) return end ## argless(win,proc) -- call proc with no arguments. procedure argless (win, proc) return proc () end