Source file connectable.icn
package util

#
# A Connectable is an object that is capable of sending events to objects
# that want to be informed when something of interest happens. Generally it
# is an object to which other objects may connect for info. In the GUI classes
# for example, every component is a Connectable and those of them that
# actually receive input are probably connected to by some object.  The
# Connectable informs its subscribers of an event via its fire() method,
# which generally calls one of their methods as specified when they connected.
#
# In many Connectable architectures there is only one listener so a list of
# listeners is overkill. On the other hand, there are other connectable
# architectures where a central Connectable is managing large numbers of
# relationships. So Connectable is all about scalability. The code promotes
# on demand down the following list of representations (it does not demote).
#
# 0  listeners: listeners is &null to begin with. Many connectables will
#    have no listener.
# 1  listener: listeners is the Subscription object. This is common in GUIs.
# 2+ listeners, all the same type: listeners is a list
# 2+ listeners, different types: listeners is a table of lists, keyed by type
#
class Connectable(listeners)
   method genlisteners(typ)
      case ::type(listeners) of {
	 "table": suspend !\(listeners[typ])
	 "list": every x := !listeners do
		    if x.type === typ then suspend x
	 default: if (\listeners).type === typ then return listeners
	 }
   end

   #
   # Helper method to create and fire an Event object from the given parameters.
   #
   method fire(typ, param)
      local e, l

      e := Notification(self, typ, param)
      every l := genlisteners(\typ|&null) do {
	 case type(l.obj) of {
	    "procedure" | "string" | "integer": {
	       suspend l.obj(self, typ, param)
	       }
	    # list invocation here uses substitution rules per future UniLib
	    # integration.  This probably won't work well until that happens.
	    "list": {
	       a := ::copy(l.obj)
	       fcn := ::pop(a)
	       args := [self, typ, param]
	       every i := 1 to *a do if a[i] === Arg then a[i] := ::pop(args)
	       suspend fcn ! a
	       }
	    "co-expression": {
	       args := [self, typ, param]
	       while suspend args @ l.obj
	       }
	    default: {
	       l.meth(l.obj, e)
	       }
	    }
	 }
      return e
   end

   #
   # Create and connect a Subscription to this object.
   # @return the Subscription created and added.
   #
   method connect(obj, meth, typ)
      local l, p, sum

      p := lang::find_method(obj, meth) | ::stop("No such method ", meth)

      # omit duplicate requests
      every l := genlisteners(typ) do
	 if (l.obj === obj) & (l.meth === p) then
	    fail

      l := Subscription(obj, p, typ)

      if /listeners then return listeners := l

      else if ::type(listeners) ~== "list" & ::type(listeners) ~== "table" then
	 listeners := [listeners] # promote to list and continue

      if ::type(listeners) == "list" then {
	 if (!listeners).type ~=== typ then {
	    T := ::table()
	    every x := !listeners do {
	       /T[x.type] := []
	       ::put(T[x.type], x)
	       }
	    listeners := T # promote to table and continue
	    }
	 else { ::put(listeners, l); return l }
	 }

      /listeners[typ] := []
      ::put(listeners[typ], l)

      return l
   end

   method disconnect_fromlist(L, obj)
      local elem, newL := []
      every elem := !L do if elem.obj ~=== obj then ::put(newL, elem)
      return newL
   end

   #
   # Remove all {Subscription}s with the given object field.
   #
   method disconnect_all(obj)
      local k, t, l

      if ::type(listeners) == "table" then {
	 every k := ::key(listeners) do
	    listeners[k] := disconnect_fromlist(listeners[k], obj)
	 }
      else if ::type(listeners) == "list" then
	 listeners := disconnect_fromlist(listeners, obj)

      else if ::type(listeners) == "Subscription" then
	 if listeners.obj === obj then listeners := &null

   end

   #
   # Remove a Subscription previously returned by {connect}
   #
   method disconnect(l)
      local k := l.type, t

      if l === listeners then listeners := &null

      t := []

      if ::type(listeners) == "list" then {
	 every ::put(t, l ~=== !listeners)
	 listeners := t
	 }
      else if ::type(listeners) == "table" then {
	 every ::put(t, l ~=== !listeners[k])
	 listeners[k] := t
	 }
   end

initially
end

This page produced by UniDoc on 2021/04/15 @ 23:59:44.