############################################################################
#
#	File:     evmux.icn
#
#	Subject:  Procedures for window event multiplexor
#
#	Author:   Gregg M. Townsend
#
#	Date:     August 14, 1996
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	These procedures implement a simple event handling package.  This
#	package has more recently been superseded by the vidget library.
#
#	The event multiplexor is configured by registering *sensors*, which
#	respond to events that occur when the mouse cursor is within a
#	particular region.  When a sensor fires, it calls a user procedure
#	that was registered when the sensor was created.
#
#	These routines interpret window events and invoke callbacks:
#
#		sensor()	registers the events of interest.
#
#		evhandle()	reads and responds to the next event.
#
#		evmux()		loops forever, handling events.
#
#	Two other small procedures help build event-driven programs:
#
#		quitsensor()	registers a standardized response to Q or q.
#
#		argless()	is a "glue" procedure usable as a callback.
#
############################################################################
#
#   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* and returns
#	a handle.  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.
#
#	delsensor(win, x) deletes sensor x from the specified window.
#	If x is null, all sensors are deleted.
#
#
#   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 either
#	"q" or "Q" is typed in the window.
#
#	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.
#
############################################################################
#
#  Requires:  Version 9 graphics
#
############################################################################
#
#  See also:  button.icn, slider.icn
#
############################################################################

record EvMux_Rec(ev, proc, arg, x, y, w, h)
global EvMux_Windows


## 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

   /EvMux_Windows := table()
   /EvMux_Windows[win] := list()
   evlist := EvMux_Windows[win]

   /x := -WAttrib(win, "dx")
   /y := -WAttrib(win, "dy")
   /w := WAttrib(win, "width") - (x + WAttrib(win, "dx"))
   /h := WAttrib(win, "height") - (y + WAttrib(win, "dy"))

   if w < 0 then
      x -:= (w := -w)
   if h < 0 then
      y -:= (h := -h)

   if type(ev) == ("cset" | "string") then
      ev := cset(ev)
   else
      ev := cset(evchar(ev)) | stop("invalid event specification: ", image(ev))
   push(evlist, r := EvMux_Rec(ev, proc, arg, x, y, w, h))
   return r
end


## delsensor(win, x) -- delete sensor x, or all sensors, from window.

procedure delsensor(win, x)
   local t

   t := \EvMux_Windows[win] | fail

   if /x then {
      delete(EvMux_Windows, win)	# delete whole set of sensors
      return
      }

   if not (x === !t) then
      fail				# not registered in this window

   # Sensor is registered for this window.  Disable it.
   x.ev := ''

   # Remove disabled sensors from list, if possible.
   while *t[1].ev = 0 do
      pop(t)
   while *t[-1].ev = 0 do
      pull(t)

   # If nothing is left on list, delete from table.
   if *t = 0 then
      delete(EvMux_Windows, win)
   return
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 := (\EvMux_Windows)[win] | stop("no events registered for window")
   ev := Event(win)
   x := &x
   y := &y

   # 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, 'qQ', argless, exit)
   if \wait then evmux(win)
   return
end


## argless(win, proc) -- call proc with no arguments.

procedure argless(win, proc)
   return proc()
end
