summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/uev
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/uev
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/modules/uev')
-rw-r--r--tcllib/modules/uev/ChangeLog103
-rw-r--r--tcllib/modules/uev/pkgIndex.tcl3
-rw-r--r--tcllib/modules/uev/uevent.man196
-rw-r--r--tcllib/modules/uev/uevent.pcx58
-rw-r--r--tcllib/modules/uev/uevent.tcl470
-rw-r--r--tcllib/modules/uev/uevent.test478
-rw-r--r--tcllib/modules/uev/uevent_onidle.man64
-rw-r--r--tcllib/modules/uev/uevent_onidle.pcx27
-rw-r--r--tcllib/modules/uev/uevent_onidle.tcl51
9 files changed, 1450 insertions, 0 deletions
diff --git a/tcllib/modules/uev/ChangeLog b/tcllib/modules/uev/ChangeLog
new file mode 100644
index 0000000..7c2560c
--- /dev/null
+++ b/tcllib/modules/uev/ChangeLog
@@ -0,0 +1,103 @@
+2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.15 ========================
+ *
+
+2012-04-09 Andreas Kupries <andreask@activestate.com>
+
+ * uevent.man: Fixed typo in variable name (counter / tcounter).
+ * uevent.tcl: Bumped version to 0.3.1. Updated documentation.
+ * pkgIndex.tcl:
+
+2012-03-30 Andreas Kupries <andreask@activestate.com>
+
+ * uevent.man: New feature. Version bumped to 0.3.
+ * uevent.pcx: Get events when tag and/or tag/event combination
+ * uevent.tcl: begins to be observed, and when observation ends.
+ * uevent.test: This allows a caller of uevent::generate to
+ * pkgIndex.tcl: optimize its own behaviour, i.e. initialize
+ whatever resources are needed only when the tags/events it
+ generates are actually observed by somebody. Updated all of
+ documentation, testsuite, and checker definitions.
+
+ Refactored the internals a bit. When run under Tcl 8.5 or higher
+ the API ensemblifies itself.
+
+2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.14 ========================
+ *
+
+2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.13 ========================
+ *
+
+2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.12 ========================
+ *
+
+2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11.1 ========================
+ *
+
+2008-11-04 Andreas Kupries <andreask@activestate.com>
+
+ * uevent_onidle.pcx: Added new package 'uevent::onidle' for
+ * uevent_onidle.tcl: deferal of actions to idle time, merging
+ * uevent_onidle.man: multiple requests.
+ * pkgIndex.tcl:
+
+2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11 ========================
+ *
+
+2008-06-14 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * uevent.pcx: New file. Syntax definitions for the public commands
+ of the uevent package.
+
+2008-03-22 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * uevent.tcl: Added a new command 'uevent::list', in fulfilment of
+ * uevent.man: [Bug 1804388]. Extended the documentation. Bumped
+ * pkgIndex.tcl: the package to version 0.2.
+
+ * uevent.test: New, a testsuite for the package.
+
+2008-03-09 Michael Schlenker <mic42@users.sourceforge.net>
+
+ * uevent.tcl: Fixed wrong variable name tex in unbind.
+ * pkgIndex.tcl:
+ * uvent.man:
+
+2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.10 ========================
+ *
+
+2007-07-20 Andreas Kupries <andreask@activestate.com>
+
+ * uevent.tcl: Added missing loading of package logger. Bumped
+ * pkgIndex.tcl: version to 0.1.2.
+ * uevent.man:
+
+2007-07-18 Andreas Kupries <andreask@activestate.com>
+
+ * uevent.tcl (::uevent::generate): Fixed typo in variable name,
+ * uevent.man: used non-existing var. Bumped version to 0.1.1.
+ * pkgIndex.tcl:
+
+2007-07-17 Andreas Kupries <andreask@activestate.com>
+
+ * New package 'uevent', handling of user events.
diff --git a/tcllib/modules/uev/pkgIndex.tcl b/tcllib/modules/uev/pkgIndex.tcl
new file mode 100644
index 0000000..aa84550
--- /dev/null
+++ b/tcllib/modules/uev/pkgIndex.tcl
@@ -0,0 +1,3 @@
+if {![package vsatisfies [package provide Tcl] 8.4]} {return}
+package ifneeded uevent 0.3.1 [list source [file join $dir uevent.tcl]]
+package ifneeded uevent::onidle 0.1 [list source [file join $dir uevent_onidle.tcl]]
diff --git a/tcllib/modules/uev/uevent.man b/tcllib/modules/uev/uevent.man
new file mode 100644
index 0000000..a10dffc
--- /dev/null
+++ b/tcllib/modules/uev/uevent.man
@@ -0,0 +1,196 @@
+[manpage_begin uevent n 0.3.1]
+[see_also hook(n)]
+[keywords bind]
+[keywords event]
+[keywords {generate event}]
+[keywords hook]
+[keywords unbind]
+[copyright {2007-2012 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {User events}]
+[titledesc {User events}]
+[category {Programming tools}]
+[require Tcl 8.4]
+[require uevent [opt 0.3.1]]
+[require logger]
+[description]
+
+This package provides a general facility for the handling of user
+events. Allows the binding of arbitrary commands to arbitrary events
+on arbitrary tags, removal of bindings, and event generation.
+
+[para]
+
+The main difference to the event system built into the Tcl/Tk core is
+that the latter can generate only virtual events, and only for
+widgets. It is not possible to use the builtin facilities to bind to
+events on arbitrary (pseudo-)objects, nor is it able to generate
+events for such.
+
+[para]
+
+Here we can, by assuming that each object in question is represented
+by its own tag. Which is possible as we allow arbitrary tags.
+
+[para]
+
+More differences:
+
+[list_begin enumerated]
+[enum]
+
+The package uses only a two-level hierarchy, tags and events, to
+handle everything, whereas the Tcl/Tk system uses three levels, i.e.
+objects, tags, and events, with a n:m relationship between objects and
+tags.
+
+[enum]
+This package triggers all bound commands for a tag/event combination,
+and they are independent of each other. A bound command cannot force
+the event processing core to abort the processing of command coming
+after it.
+
+[list_end]
+
+[section API]
+
+The package exports eight commands, as specified below. Note that when
+the package is used from within Tcl 8.5+ all the higher commands are
+ensembles, i.e. the :: separators can be replaceed by spaces.
+
+[list_begin definitions]
+[comment ============================================================]
+[call [cmd ::uevent::bind] [arg tag] [arg event] [arg command]]
+
+Using this command registers the [arg command] prefix to be triggered
+when the [arg event] occurs for the [arg tag]. The result of the
+command is an opaque token representing the binding. Note that if the
+same combination of <[arg tag],[arg event],[arg command]> is used
+multiple times the same token is returned by every call.
+
+[para]
+
+The signature of the [arg command] prefix is
+
+[list_begin definitions]
+[call [cmd command] [arg tag] [arg event] [arg details]]
+[list_end]
+[para]
+
+where [arg details] contains the argument(s) of the event. Its
+contents are event specific and have to be agreed upon between actual
+event generator and consumer. This package simply transfers the
+information and does not perform any processing beyond that.
+
+[comment ============================================================]
+[call [cmd ::uevent::unbind] [arg token]]
+
+This command releases the event binding represented by the
+[arg token]. The token has to be the result of a call to
+[cmd ::uevent::bind]. The result of the command is the empty string.
+
+[comment ============================================================]
+[call [cmd ::uevent::generate] [arg tag] [arg event] [opt [arg details]]]
+
+This command generates an [arg event] for the [arg tag], triggering
+all commands bound to that combination. The [arg details] argument is
+simply passed unchanged to all event handlers. It is the
+responsibility of the code generating and consuming the event to have
+an agreement about the format and contents of the information carried
+therein. The result of the command is the empty string.
+
+[para]
+
+Note that all bound commands are triggered, independently of each
+other. The event handlers cannot assume a specific order. They are
+also [emph not] called synchronously with the invokation of this
+command, but simply put into the event queue for processing when the
+system returns to the event loop.
+
+[para]
+
+Generating an event for an unknown tag, or for a
+<[arg tag],[arg event]> combination which has no commands bound to it
+is allowed, such calls will be ignored.
+
+[comment ============================================================]
+[call [cmd ::uevent::list]]
+
+In this form the command returns a list containing the names of all
+tags which have events with commands bound to them.
+
+[comment ============================================================]
+[call [cmd ::uevent::list] [arg tag]]
+
+In this format the command returns a list containing the names of all
+events for the [arg tag] with commands bound to them. Specifying an
+unknown tag, i.e. a tag without event and commands, will cause the
+command to throw an error.
+
+[comment ============================================================]
+[call [cmd ::uevent::list] [arg tag] [arg event]]
+
+In this format the command returns a list containing all commands
+bound to the [arg event] for the [arg tag]. Specifying an unknown tag
+or unknown event, will cause the command to throw an error.
+
+[comment ============================================================]
+[call [cmd ::uevent::watch::tag::add] [arg pattern] [arg command]]
+
+This command sets up a sort of reverse events. Events generated,
+i.e. the [arg command] prefix invoked, when observers bind to and
+unbind from specific tags.
+
+[para] Note that the command prefix is only invoked twice per tag,
+first when the first command is bound to any event of the tag, and
+second when the last command bound to the tag is removed.
+
+[para] The signature of the [arg command] prefix is
+
+[list_begin definitions]
+[call [cmd "{*}command"] [const bound] [arg tag]]
+[call [cmd "{*}command"] [const unbound] [arg tag]]
+[list_end]
+
+[para] The result of the command is a token representing the watcher.
+
+[comment ============================================================]
+[call [cmd ::uevent::watch::tag::remove] [arg token]]
+
+This command removes a watcher for (un)bind events on tags.
+
+[para] The result of the command is the empty string.
+
+[comment ============================================================]
+[call [cmd ::uevent::watch::event::add] [arg tag_pattern] [arg event_pattern] [arg command]]
+
+This command sets up a sort of reverse events. Events generated,
+i.e. the [arg command] prefix invoked, when observers bind to and
+unbind from specific combinations of tags and events.
+
+[para] Note that the command prefix is only invoked twice per
+tag/event combination, first when the first command is bound to it,
+and second when the last command bound to the it is removed.
+
+[para] The signature of the [arg command] prefix is
+
+[list_begin definitions]
+[call [cmd "{*}command"] [const bound] [arg tag] [arg event]]
+[call [cmd "{*}command"] [const unbound] [arg tag] [arg event]]
+[list_end]
+
+[para] The result of the command is a token representing the watcher.
+
+[comment ============================================================]
+[call [cmd ::uevent::watch::event::remove] [arg token]]
+
+This command removes a watcher for (un)bind events on tag/event
+combinations.
+
+[para] The result of the command is the empty string.
+
+[comment ============================================================]
+[list_end]
+
+[vset CATEGORY uevent]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/uev/uevent.pcx b/tcllib/modules/uev/uevent.pcx
new file mode 100644
index 0000000..10f3f8d
--- /dev/null
+++ b/tcllib/modules/uev/uevent.pcx
@@ -0,0 +1,58 @@
+# -*- tcl -*- uevent.pcx
+# Syntax of the commands provided by package uevent.
+
+# For use by TclDevKit's static syntax checker.
+# See http://www.activestate.com/solutions/tcl/
+# See http://aspn.activestate.com/ASPN/docs/Tcl_Dev_Kit/4.0/Checker.html#pcx_api
+# for the documentation describing the format of the code contained in this file
+#
+
+package require pcx
+pcx::register uevent
+pcx::tcldep 0.1.3 needs tcl 8.4
+pcx::tcldep 0.3 needs tcl 8.4
+
+namespace eval ::uevent {}
+
+#pcx::message FOO {... text ...} type
+#pcx::scan <VERSION> <NAME> <RULE>
+
+pcx::check 0.1.3 std ::uevent::bind \
+ {checkSimpleArgs 3 3 {
+ checkWord
+ checkWord
+ checkWord
+ }}
+pcx::check 0.1.3 std ::uevent::generate \
+ {checkSimpleArgs 2 3 {
+ checkWord
+ checkWord
+ checkWord
+ }}
+pcx::check 0.1.3 std ::uevent::unbind \
+ {checkSimpleArgs 1 1 {
+ checkWord
+ }}
+pcx::check 0.3 std ::uevent::watch::tag::add \
+ {checkSimpleArgs 2 2 {
+ checkWord
+ checkWord
+ }}
+pcx::check 0.3 std ::uevent::watch::tag::remove \
+ {checkSimpleArgs 1 1 {
+ checkWord
+ }}
+pcx::check 0.3 std ::uevent::watch::event::add \
+ {checkSimpleArgs 3 3 {
+ checkWord
+ checkWord
+ checkWord
+ }}
+pcx::check 0.3 std ::uevent::watch::event::remove \
+ {checkSimpleArgs 1 1 {
+ checkWord
+ }}
+
+# Initialization via pcx::init.
+# Use a ::uevent::init procedure for non-standard initialization.
+pcx::complete
diff --git a/tcllib/modules/uev/uevent.tcl b/tcllib/modules/uev/uevent.tcl
new file mode 100644
index 0000000..41717c4
--- /dev/null
+++ b/tcllib/modules/uev/uevent.tcl
@@ -0,0 +1,470 @@
+# -*- tcl -*-
+# ### ### ### ######### ######### #########
+## UEvent - User Event Service - Tcl-level general Event Handling
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+package require Tcl 8.4
+package require logger
+
+namespace eval ::uevent {}
+namespace eval ::uevent::token {}
+namespace eval ::uevent::watch::tag {}
+namespace eval ::uevent::watch::event {}
+
+# ### ### ### ######### ######### #########
+## API: bind, unbind, generate
+
+proc ::uevent::bind {tag event command} {
+ # Register command (prefix!) as observer for events on the tag.
+ # Command will take 3 arguments: tag, event, and dictionary of
+ # detail information. Result is token by which the observer can
+ # be removed.
+
+ variable db
+ variable dt
+ variable tk
+ variable ex
+
+ log::debug [::list bind: $tag $event -> $command]
+
+ set tec [::list $tag $event $command]
+
+ # Same combination as before, same token
+ if {[info exists ex($tec)]} {
+ log::debug [::list known! $ex($tec)]
+ return $ex($tec)
+ }
+
+ # New token, and enter everything ...
+
+ set te [::list $tag $event]
+ set t [NewToken]
+
+ set tk($t) $tec
+ set ex($tec) $t
+ lappend db($te) $t
+ lappend dt($tag) $t
+
+ if {[llength $dt($tag)] == 1} {
+ # Notify any watchers that at least one observers is now bound
+ # to the tag
+ watch::tag::Invoke bound $tag
+ }
+ if {[llength $db($te)] == 1} {
+ # Notify any watchers that at least one observers is now bound
+ # to the tag/event combination.
+ watch::event::Invoke bound $tag $event
+ }
+
+ log::debug [::list new! $t]
+ return $t
+}
+
+proc ::uevent::unbind {token} {
+ # Removes the event binding represented by the token.
+
+ variable db
+ variable dt
+ variable tk
+ variable ex
+
+ log::debug [::list unbind: $token]
+
+ if {![info exists tk($token)]} return
+
+ set tec $tk($token)
+ set te [lrange $tec 0 1]
+
+ log::debug [linsert [linsert $tec 0 =] end-1 ->]
+
+ unset ex($tec)
+ unset tk($token)
+
+ set pos [lsearch -exact $db($te) $token]
+ if {$pos < 0} return
+
+ foreach {tag event} $te break
+
+ if {[llength $db($te)] == 1} {
+ # Last observer for this tag,event combination is gone.
+ log::debug [linsert $te 0 last!]
+ unset db($te)
+
+ # Notify any watchers that no observers are bound to the
+ # tag/event combination anymore.
+ watch::event::Invoke unbound $tag $event
+ } else {
+ # Shrink list of observers
+ log::debug [linsert [linsert $te 0 shrink!] end @ $pos]
+ set db($te) [lreplace $db($te) $pos $pos]
+ }
+
+ if {[llength $dt($tag)] == 1} {
+ # Last observer for this tag in itself
+ log::debug [linsert $tag 0 last!]
+ unset dt($tag)
+
+ # Notify any watchers that no observers are bound to the tag
+ # anymore.
+ watch::tag::Invoke unbound $tag
+ } else {
+ # Shrink list of observers
+ log::debug [linsert [linsert $tag 0 shrink!] end @ $pos]
+ set dt($tag) [lreplace $dt($tag) $pos $pos]
+ }
+
+ return
+}
+
+proc ::uevent::generate {tag event {details {}}} {
+ # Generates the event on the tag, with detail information (a
+ # dictionary). This notifies all registered observers. The
+ # notifications are put into the Tcl event queue via 'after 0'
+ # events, decoupling them in time from the issueing code.
+
+ variable db
+ variable tk
+
+ log::debug [::list generate: $tag $event $details]
+
+ set key [::list $tag $event]
+ if {![info exists db($key)]} return
+
+ foreach t $db($key) {
+ set cmd [lindex $tk($t) 2]
+ log::debug [::list trigger! $t = $cmd]
+ after 0 [linsert $cmd end $tag $event $details]
+ }
+
+ return
+}
+
+proc ::uevent::list {args} {
+ # list - Return all known tags
+ # list tag - Return all events bound to the tag
+ # list tag event - Return commands bound to event in tag
+
+ switch -- [llength $args] {
+ 0 {
+ variable db
+ # Return all known tags.
+ set res {}
+ foreach te [array names db] {
+ lappend res [lindex $te 0]
+ }
+ return [lsort -uniq $res]
+ }
+ 1 {
+ variable db
+ # Return all known events for a specific tag
+ set res {}
+ set tag [lindex $args 0]
+ foreach te [array names db [::list $tag *]] {
+ lappend res [lindex $te 1]
+ }
+ if {![llength $res]} {
+ return -code error "Tag \"$tag\" is not known"
+ }
+ return $res
+ }
+ 2 {
+ variable db
+ variable tk
+ # Return all commands bound to a tag/event combination
+ if {![info exists db($args)]} {
+ foreach {tag event} $args break
+ return -code error "Tag/Event \"$tag\"/\"$event\" is not known"
+ }
+ set res {}
+ foreach t $db($args) {
+ lappend res [lindex $tk($t) 2]
+ }
+ return $res
+ }
+ default {
+ return -code error "wrong#args: expected ?tag? ?event?"
+ }
+ }
+}
+
+# ### ### ### ######### ######### #########
+
+proc ::uevent::watch::tag::add {pattern cmdprefix} {
+ variable db
+ variable tk
+ variable ex
+
+ set token [Place uevmt $pattern $cmdprefix new]
+ if {!$new} { return $token }
+
+ # Check if there are already bindings on tags matching the
+ # specified pattern. If yes, we have to invoke the command for
+ # them all.
+
+ # Situation: Part of the application binds to events on the tag
+ # before the system genrating these events on the tag is
+ # present. Thus watching is adding at a time when bindings already
+ # exist.
+
+ upvar \#0 ::uevent::dt map
+
+ foreach tag [array names map] {
+ if {![string match $pattern $tag]} continue
+ uplevel \#0 [linsert $cmdprefix end bound $tag]
+ }
+
+ return $token
+}
+
+proc ::uevent::watch::tag::remove {token} {
+ variable db
+ variable tk
+ variable ex
+
+ Remove $token
+ return
+}
+
+proc ::uevent::watch::tag::Invoke {action tag} {
+ variable db
+ variable tk
+
+ foreach pattern [array names db] {
+ if {![string match $pattern $tag]} continue
+
+ foreach token $db($pattern) {
+ set cmd [lindex $tk($token) end]
+ uplevel \#0 [linsert $cmd end $action $tag]
+ }
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+
+proc ::uevent::watch::event::add {tpattern epattern cmdprefix} {
+ set key [list $tpattern $epattern]
+
+ variable db
+ variable tk
+ variable ex
+
+ set token [Place uevme $key $cmdprefix new]
+ if {!$new} { return $token }
+
+ # Check if there are already bindings on tag/event combinations
+ # matching the specified pattern. If yes, we have to invoke the
+ # command for them all.
+
+ # Situation: Part of the application binds to events on the tag
+ # before the system genrating these events on the tag is
+ # present. Thus watching is adding at a time when bindings already
+ # exist.
+
+ upvar \#0 ::uevent::db map
+
+ foreach key [array names map] {
+ foreach {tag event} $key break
+ if {![string match $tpattern $tag]} continue
+ if {![string match $epattern $event]} continue
+ uplevel \#0 [linsert $cmdprefix end bound $tag $event]
+ }
+
+ return $token
+}
+
+proc ::uevent::watch::event::remove {token} {
+ variable db
+ variable tk
+ variable ex
+
+ Remove $token
+ return
+}
+
+proc ::uevent::watch::event::Invoke {action tag event} {
+ variable db
+ variable tk
+
+ foreach key [array names db] {
+ foreach {tpattern epattern} $key break
+ if {![string match $tpattern $tag]} continue
+ if {![string match $epattern $event]} continue
+
+ foreach token $db($key) {
+ set cmd [lindex $tk($token) end]
+ uplevel \#0 [linsert $cmd end $action $tag $event]
+ }
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Initialization - Tracing, System state
+
+logger::initNamespace ::uevent
+namespace eval ::uevent {
+ # ### ### ### ######### ######### #########
+ # Information needed:
+ # (1) Per <tag,event> the commands bound to it.
+ # (1a) Per <tag> the commands bound to it.
+ # (2) Per <tag,event,command> a token representing it.
+ # (3) For all <tag,event,command> a quick way to check their existence
+
+ # (Ad 1) db : array (list (tag, event) -> list (token))
+ # (Ad 1a) dt : array (tag -> list (token))
+ # (Ad 2) tk : array (token -> list (tag, event, command))
+ # (Ad 3) ex : array (list (tag, event, command) -> token)
+
+ variable db ; array set db {}
+ variable dt ; array set dt {}
+ variable tk ; array set tk {}
+ variable ex ; array set ex {}
+
+ # (1a) is for bind watching.
+
+ # ### ### ### ######### ######### #########
+
+ namespace export bind unbind generate list
+}
+
+# ### ### ### ######### ######### #########
+namespace eval ::uevent::watch::tag {
+ # ### ### ### ######### ######### #########
+ # Information needed for (un)bind monitoring (tags).
+
+ # (1) Per <tag> (patterns) the commands bound to it.
+ # (2) Per <tag,command> a token representing it.
+ # (3) For all <tag,command> a quick way to check their existence
+
+ # (Ad 1) db : array (tagp -> list (token))
+ # (Ad 2) tk : array (token -> list (tagp, command))
+ # (Ad 3) ex : array (list (tagp, command) -> token)
+
+ variable db ; array set db {}
+ variable tk ; array set tk {}
+ variable ex ; array set ex {}
+
+ namespace export add remove
+ # ### ### ### ######### ######### #########
+}
+
+# ### ### ### ######### ######### #########
+namespace eval ::uevent::watch::event {
+ # ### ### ### ######### ######### #########
+ # Information needed for (un)bind monitoring (tag/events).
+
+ # (1) Per <tag,event> (patterns) the commands bound to it.
+ # (2) Per <<tag,event>,command> a token representing it.
+ # (3) For all <<tag,event>,command> a quick way to check their existence
+
+ # (Ad 1) db : array (list (tagp, eventp) -> list (token))
+ # (Ad 2) tk : array (token -> list ((atgp, eventp), command))
+ # (Ad 3) ex : array (list ((tagp, eventp), command) -> token)
+
+ namespace export add remove
+ # ### ### ### ######### ######### #########
+}
+
+# ### ### ### ######### ######### #########
+## Internals: Token Generator, and general DB management
+## (same structure)
+
+proc ::uevent::token::NewToken {{type uev}} {
+ variable tcounter
+ return ${type}[incr tcounter]
+}
+
+proc ::uevent::token::Place {type key command nv} {
+ upvar 1 db db tk tk ex ex $nv new
+
+ set kc [::list $key $command]
+
+ # Same key/command combination as before => same token
+ if {[info exists ex($kc)]} {
+ set new 0
+ return $ex($kc)
+ }
+
+ # New token, and enter everything ...
+ set token [NewToken $type]
+
+ set tk($token) $kc
+ set ex($kc) $token
+ lappend db($key) $token
+
+ set new 1
+ return $token
+}
+
+proc ::uevent::token::Remove {token} {
+ upvar 1 db db tk tk ex ex
+
+ if {![info exists tk($token)]} return
+
+ set kc $tk($token)
+ set key [lindex $kc 0]
+
+ unset ex($kc)
+ unset tk($token)
+
+ set pos [lsearch -exact $db($key) $token]
+ if {$pos < 0} return
+
+ if {[llength $db($key)] == 1} {
+ unset db($key)
+ } else {
+ set db($key) [lreplace $db($key) $pos $pos]
+ }
+ return
+}
+
+namespace eval ::uevent::token {
+ variable tcounter 0
+ namespace export NewToken Place Remove
+}
+
+# ### ### ### ######### ######### #########
+## Link general internal parts to their users.
+
+namespace eval ::uevent {
+ namespace import ::uevent::token::*
+}
+
+namespace eval ::uevent::watch::tag {
+ namespace import ::uevent::token::*
+}
+
+namespace eval ::uevent::watch::event {
+ namespace import ::uevent::token::*
+}
+
+# ### ### ### ######### ######### #########
+## Ensemblify the system when running under Tcl 8.5 or higher.
+
+if {[package vsatisfies [package present Tcl] 8.5]} {
+ namespace eval ::uevent {
+ namespace eval watch {
+ namespace eval tag {
+ namespace ensemble create
+ }
+ namespace eval event {
+ namespace ensemble create
+ }
+ namespace export tag event
+ namespace ensemble create
+ }
+ namespace export watch
+ namespace ensemble create
+ }
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide uevent 0.3.1
+
+##
+# ### ### ### ######### ######### #########
diff --git a/tcllib/modules/uev/uevent.test b/tcllib/modules/uev/uevent.test
new file mode 100644
index 0000000..42e6413
--- /dev/null
+++ b/tcllib/modules/uev/uevent.test
@@ -0,0 +1,478 @@
+# -*- tcl -*-
+# uevent.test: Tests for the UEVENT utilities.
+#
+# Copyright (c) 2008 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
+# All rights reserved.
+#
+# UEVENT: @(#) $Id: uevent.test,v 1.2 2012/03/30 22:47:15 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.4
+testsNeedTcltest 1.0
+
+support {
+ use log/logger.tcl logger
+}
+testing {
+ useLocal uevent.tcl uevent
+}
+
+# -------------------------------------------------------------------------
+## Serialize the tag/event/command database.
+
+proc uestate {} {
+ set res {}
+ foreach tag [lsort -dict [uevent::list]] {
+ set buf {}
+ foreach event [lsort -dict [uevent::list $tag]] {
+ lappend buf $event [uevent::list $tag $event]
+ }
+ lappend res $tag $buf
+ }
+ return $res
+}
+
+# -------------------------------------------------------------------------
+
+test uevent-1.0 {bind error, wrong#args, not enough} {
+ catch {::uevent::bind} msg
+ set msg
+} [tcltest::wrongNumArgs {::uevent::bind} {tag event command} 0]
+
+test uevent-1.1 {bind error, wrong#args, not enough} {
+ catch {::uevent::bind foo} msg
+ set msg
+} [tcltest::wrongNumArgs {::uevent::bind} {tag event command} 1]
+
+test uevent-1.2 {bind error, wrong#args, not enough} {
+ catch {::uevent::bind foo bar} msg
+ set msg
+} [tcltest::wrongNumArgs {::uevent::bind} {tag event command} 2]
+
+test uevent-1.3 {bind error, wrong#args, too many} {
+ catch {::uevent::bind foo bar barf more} msg
+ set msg
+} [tcltest::tooManyArgs {::uevent::bind} {tag event command}]
+
+# -------------------------------------------------------------------------
+
+test uevent-2.0 {bind} {
+ set res {}
+ lappend res [uestate]
+ set t [::uevent::bind tag event command]
+ lappend res [uestate]
+ uevent::unbind $t
+ set res
+} {{} {tag {event command}}}
+
+test uevent-2.1 {bind, multiple times of the same combination} {
+ set res {}
+ lappend res [uestate]
+ set ta [::uevent::bind tag event command]
+ lappend res [uestate]
+ set tb [::uevent::bind tag event command]
+ lappend res [uestate]
+ uevent::unbind $ta
+ lappend res [uestate]
+ lappend res [expr {$ta eq $tb}]
+ set res
+} {{} {tag {event command}} {tag {event command}} {} 1}
+
+test uevent-2.2 {bind, same tag/event, different commands} {
+ set res {}
+ lappend res [uestate]
+ set ta [::uevent::bind tag event command1]
+ lappend res [uestate]
+ set tb [::uevent::bind tag event command2]
+ lappend res [uestate]
+ uevent::unbind $ta
+ uevent::unbind $tb
+ lappend res [uestate]
+ lappend res [expr {$ta eq $tb}]
+ set res
+} {{} {tag {event command1}} {tag {event {command1 command2}}} {} 0}
+
+test uevent-2.3 {bind, same tag/command, different events} {
+ set res {}
+ lappend res [uestate]
+ set ta [::uevent::bind tag event1 command]
+ lappend res [uestate]
+ set tb [::uevent::bind tag event2 command]
+ lappend res [uestate]
+ uevent::unbind $ta
+ uevent::unbind $tb
+ lappend res [uestate]
+ lappend res [expr {$ta eq $tb}]
+ set res
+} {{} {tag {event1 command}} {tag {event1 command event2 command}} {} 0}
+
+test uevent-2.4 {bind, same event/command, different tags} {
+ set res {}
+ lappend res [uestate]
+ set ta [::uevent::bind tag1 event command]
+ lappend res [uestate]
+ set tb [::uevent::bind tag2 event command]
+ lappend res [uestate]
+ uevent::unbind $ta
+ uevent::unbind $tb
+ lappend res [uestate]
+ lappend res [expr {$ta eq $tb}]
+ set res
+} {{} {tag1 {event command}} {tag1 {event command} tag2 {event command}} {} 0}
+
+# -------------------------------------------------------------------------
+
+test uevent-3.0 {unbind error, wrong#args, not enough} {
+ catch {::uevent::unbind} msg
+ set msg
+} [tcltest::wrongNumArgs {::uevent::unbind} {token} 0]
+
+test uevent-3.1 {unbind error, wrong#args, too many} {
+ catch {::uevent::unbind foo bar} msg
+ set msg
+} [tcltest::tooManyArgs {::uevent::unbind} {token}]
+
+# -------------------------------------------------------------------------
+
+test uevent-4.0 {unbind} {
+ set ta [::uevent::bind tag1 event1 command1]
+ set tb [::uevent::bind tag1 event1 command2]
+ set tc [::uevent::bind tag1 event2 command1]
+ set td [::uevent::bind tag2 event1 command1]
+ ::uevent::unbind $ta
+ set res [uestate]
+ ::uevent::unbind $tb
+ ::uevent::unbind $tc
+ ::uevent::unbind $td
+ set res
+} {tag1 {event1 command2 event2 command1} tag2 {event1 command1}}
+
+test uevent-4.1 {unbind} {
+ set ta [::uevent::bind tag1 event1 command1]
+ set tb [::uevent::bind tag1 event1 command2]
+ set tc [::uevent::bind tag1 event2 command1]
+ set td [::uevent::bind tag2 event1 command1]
+ ::uevent::unbind $tb
+ set res [uestate]
+ ::uevent::unbind $ta
+ ::uevent::unbind $tc
+ ::uevent::unbind $td
+ set res
+} {tag1 {event1 command1 event2 command1} tag2 {event1 command1}}
+
+test uevent-4.2 {unbind} {
+ set ta [::uevent::bind tag1 event1 command1]
+ set tb [::uevent::bind tag1 event1 command2]
+ set tc [::uevent::bind tag1 event2 command1]
+ set td [::uevent::bind tag2 event1 command1]
+ ::uevent::unbind $tc
+ set res [uestate]
+ ::uevent::unbind $tb
+ ::uevent::unbind $ta
+ ::uevent::unbind $td
+ set res
+} {tag1 {event1 {command1 command2}} tag2 {event1 command1}}
+
+test uevent-4.3 {unbind} {
+ set ta [::uevent::bind tag1 event1 command1]
+ set tb [::uevent::bind tag1 event1 command2]
+ set tc [::uevent::bind tag1 event2 command1]
+ set td [::uevent::bind tag2 event1 command1]
+ ::uevent::unbind $td
+ set res [uestate]
+ ::uevent::unbind $tb
+ ::uevent::unbind $tc
+ ::uevent::unbind $ta
+ set res
+} {tag1 {event1 {command1 command2} event2 command1}}
+
+# -------------------------------------------------------------------------
+
+test uevent-5.0 {generate error, wrong#args, not enough} {
+ catch {::uevent::generate} msg
+ set msg
+} [tcltest::wrongNumArgs {::uevent::generate} {tag event ?details?} 0]
+
+test uevent-5.1 {generate error, wrong#args, not enough} {
+ catch {::uevent::generate foo} msg
+ set msg
+} [tcltest::wrongNumArgs {::uevent::generate} {tag event ?details?} 1]
+
+test uevent-5.2 {generate error, wrong#args, too many} {
+ catch {::uevent::generate foo bar barf more} msg
+ set msg
+} [tcltest::tooManyArgs {::uevent::generate} {tag event ?details?}]
+
+# -------------------------------------------------------------------------
+
+proc EVENT {t e d} {
+ global res
+ lappend res $t $e $d
+ return
+}
+
+proc EVENT2 {t e d} {
+ global res
+ lappend res 2/$t $e $d
+ return
+}
+
+test uevent-6.0 {generate, single command} {
+ set t [::uevent::bind tag event EVENT]
+ set res {}
+ uevent::generate tag event ClientData
+ vwait ::res ; # Allow event to fire.
+ uevent::unbind $t
+ set res
+} {tag event ClientData}
+
+test uevent-6.1 {generate, single command, multiple issues} {
+ set t [::uevent::bind tag event EVENT]
+ set res {}
+ uevent::generate tag event ClientData1
+ uevent::generate tag event ClientData2
+ uevent::generate tag event ClientData3
+ vwait ::res ; # Allow events to fire.
+ uevent::unbind $t
+ set res
+} {tag event ClientData1 tag event ClientData2 tag event ClientData3}
+
+test uevent-6.2 {generate, multiple commands} {
+ # Note: While we do not document the order of firing multiple
+ # commands on one tag/event this test does capture the current
+ # order and will trigger should we change it in the future, making
+ # us aware of the incompatibility.
+ set ta [::uevent::bind tag event EVENT]
+ set tb [::uevent::bind tag event EVENT2]
+ set res {}
+ uevent::generate tag event ClientData
+ vwait ::res ; # Allow events to fire.
+ uevent::unbind $ta
+ uevent::unbind $tb
+ set res
+} {tag event ClientData 2/tag event ClientData}
+
+
+# -------------------------------------------------------------------------
+
+proc WATCHT {a t} {
+ global res
+ lappend res $a $t
+ return
+}
+
+test uevent-7.0 {watch tag add, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::tag::add
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::tag::add pattern cmdprefix"}
+
+test uevent-7.1 {watch tag add, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::tag::add TAG
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::tag::add pattern cmdprefix"}
+
+test uevent-7.2 {watch tag add, wrong#args, too many} {
+ catch {
+ ::uevent::watch::tag::add TAG CMD foo
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::tag::add pattern cmdprefix"}
+
+test uevent-7.3 {watch tag remove, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::tag::remove
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::tag::remove token"}
+
+test uevent-7.4 {watch tag remove, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::tag::remove TOKEN foo
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::tag::remove token"}
+
+test uevent-8.0 {watch tags, bind after watch, exact} {
+ set res {}
+ set tw [::uevent::watch::tag::add TAG WATCHT]
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::watch::tag::remove $tw
+ set res
+} {bound TAG unbound TAG}
+
+test uevent-8.1 {watch tags, watch after bind, exact} {
+ set res {}
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ set tw [::uevent::watch::tag::add TAG WATCHT]
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::watch::tag::remove $tw
+ set res
+} {bound TAG unbound TAG}
+
+test uevent-8.2 {watch tags, bind after watch, glob} {
+ set res {}
+ set tw [::uevent::watch::tag::add TAG* WATCHT]
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::watch::tag::remove $tw
+ set res
+} {bound TAGX bound TAG unbound TAGX unbound TAG}
+
+test uevent-8.3 {watch tags, watch after bind, glob} {
+ set res {}
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ set tw [::uevent::watch::tag::add TAG* WATCHT]
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::watch::tag::remove $tw
+ set res
+} {bound TAG bound TAGX unbound TAGX unbound TAG}
+
+# -------------------------------------------------------------------------
+
+proc WATCHE {a t e} {
+ global res
+ lappend res $a $t $e
+ return
+}
+proc SORTE {} {
+ global res
+ set tmp {}
+ foreach {a t e} $res {
+ lappend tmp [list $a $t $e]
+ }
+ set res {}
+ foreach item [lsort -dict $tmp] {
+ foreach {a t e} $item break
+ lappend res $a $t $e
+ }
+ return $res
+}
+
+test uevent-9.0 {watch event add, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::event::add
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::event::add tpattern epattern cmdprefix"}
+
+test uevent-9.1 {watch event add, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::event::add TAG
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::event::add tpattern epattern cmdprefix"}
+
+test uevent-9.2 {watch event add, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::event::add TAG EVENT
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::event::add tpattern epattern cmdprefix"}
+
+test uevent-9.3 {watch event add, wrong#args, too many} {
+ catch {
+ ::uevent::watch::event::add TAG EVENT CMD foo
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::event::add tpattern epattern cmdprefix"}
+
+test uevent-9.4 {watch event remove, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::event::remove
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::event::remove token"}
+
+test uevent-9.5 {watch event remove, wrong#args, not enough} {
+ catch {
+ ::uevent::watch::event::remove TOKEN foo
+ } msg
+ set msg
+} {wrong # args: should be "::uevent::watch::event::remove token"}
+
+test uevent-10.0 {watch events, bind after watch, exact} {
+ set res {}
+ set tw [::uevent::watch::event::add TAG E WATCHE]
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ set t3 [::uevent::bind TAGX EX FOO]
+ set t4 [::uevent::bind TAG EX FOO]
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::unbind $t3
+ ::uevent::unbind $t4
+ ::uevent::watch::event::remove $tw
+ set res
+} {bound TAG E unbound TAG E}
+
+test uevent-10.1 {watch events, watch after bind, exact} {
+ set res {}
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ set t3 [::uevent::bind TAGX EX FOO]
+ set t4 [::uevent::bind TAG EX FOO]
+ set tw [::uevent::watch::event::add TAG E WATCHE]
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::unbind $t3
+ ::uevent::unbind $t4
+ ::uevent::watch::event::remove $tw
+ set res
+} {bound TAG E unbound TAG E}
+
+test uevent-10.2 {watch events, bind after watch, glob} {
+ set res {}
+ set tw [::uevent::watch::event::add TAG* E* WATCHE]
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ set t3 [::uevent::bind TAGX EX FOO]
+ set t4 [::uevent::bind TAG EX FOO]
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::unbind $t3
+ ::uevent::unbind $t4
+ ::uevent::watch::event::remove $tw
+ set res
+} {bound TAGX E bound TAG E bound TAGX EX bound TAG EX unbound TAGX E unbound TAG E unbound TAGX EX unbound TAG EX}
+
+test uevent-10.3 {watch events, watch after bind, glob} {
+ set res {}
+ set t1 [::uevent::bind TAGX E FOO]
+ set t2 [::uevent::bind TAG E FOO]
+ set t3 [::uevent::bind TAGX EX FOO]
+ set t4 [::uevent::bind TAG EX FOO]
+ set tw [::uevent::watch::event::add TAG* E* WATCHE]
+ SORTE ;# ensure a canonical order
+ ::uevent::unbind $t1
+ ::uevent::unbind $t2
+ ::uevent::unbind $t3
+ ::uevent::unbind $t4
+ ::uevent::watch::event::remove $tw
+ set res
+} {bound TAG E bound TAG EX bound TAGX E bound TAGX EX unbound TAGX E unbound TAG E unbound TAGX EX unbound TAG EX}
+
+# -------------------------------------------------------------------------
+rename EVENT {}
+rename EVENT2 {}
+rename WATCHT {}
+rename WATCHE {}
+catch {unset res}
+testsuiteCleanup
diff --git a/tcllib/modules/uev/uevent_onidle.man b/tcllib/modules/uev/uevent_onidle.man
new file mode 100644
index 0000000..358292b
--- /dev/null
+++ b/tcllib/modules/uev/uevent_onidle.man
@@ -0,0 +1,64 @@
+[manpage_begin uevent::onidle n 0.1]
+[keywords callback]
+[keywords deferal]
+[keywords event]
+[keywords idle]
+[keywords merge]
+[keywords on-idle]
+[copyright {2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {User events}]
+[titledesc {Request merging and deferal to idle time}]
+[require Tcl 8.4]
+[require uevent::onidle [opt 0.1]]
+[require logger]
+[description]
+
+This package provides objects which can merge multiple requestes for
+an action and execute the action the moment the system (event loop)
+becomes idle. The action to be run is configured during object
+construction.
+
+[section API]
+
+The package exports a class, [class uevent::onidle], as specified
+below.
+
+[list_begin definitions]
+
+[call [cmd ::uevent::onidle] [arg objectName] [arg commandprefix]]
+
+The command creates a new [term onidle] object with an associated
+global Tcl command whose name is [arg objectName]. This command may
+be used to invoke various operations on the object.
+
+[para]
+
+The [arg commandprefix] is the action to perform when the event loop
+is idle and the user asked for it using the method [method request]
+(See below).
+
+[list_end]
+
+The object commands created by the class commands above have
+the form:
+
+[list_begin definitions]
+
+[call [arg objectName] [method request]]
+
+This method requests the execution of the command prefix specified
+during the construction of [arg objectName] the next time the event
+loop is idle. Multiple requests are merged and cause only one
+execution of the command prefix.
+
+[list_end]
+
+[section Examples]
+
+Examples of this type of deferal are buried in the (C-level)
+implementations all the Tk widgets, defering geometry calculations and
+window redraw activity in this manner.
+
+[vset CATEGORY uevent]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/uev/uevent_onidle.pcx b/tcllib/modules/uev/uevent_onidle.pcx
new file mode 100644
index 0000000..35c1ef8
--- /dev/null
+++ b/tcllib/modules/uev/uevent_onidle.pcx
@@ -0,0 +1,27 @@
+# -*- tcl -*- uevent::onidle.pcx
+# Syntax of the commands provided by package uevent.
+
+# For use by TclDevKit's static syntax checker.
+# See http://www.activestate.com/solutions/tcl/
+# See http://aspn.activestate.com/ASPN/docs/Tcl_Dev_Kit/4.0/Checker.html#pcx_api
+# for the documentation describing the format of the code contained in this file
+#
+
+package require pcx
+pcx::register uevent::onidle
+pcx::tcldep 0.1 needs tcl 8.4
+
+namespace eval ::uevent::onidle {}
+
+#pcx::message FOO {... text ...} type
+#pcx::scan <VERSION> <NAME> <RULE>
+
+pcx::check 0.1 std ::uevent::onidle \
+ {checkSimpleArgs 2 2 {
+ checkWord
+ checkWord
+ }}
+
+# Initialization via pcx::init.
+# Use a ::uevent::init procedure for non-standard initialization.
+pcx::complete
diff --git a/tcllib/modules/uev/uevent_onidle.tcl b/tcllib/modules/uev/uevent_onidle.tcl
new file mode 100644
index 0000000..b43ab31
--- /dev/null
+++ b/tcllib/modules/uev/uevent_onidle.tcl
@@ -0,0 +1,51 @@
+## -*- tcl -*-
+# ### ### ### ######### ######### #########
+
+# ### ### ### ######### ######### #########
+## Requisites
+
+package require Tcl 8.4 ; #
+package require snit ; #
+
+# ### ### ### ######### ######### #########
+##
+
+snit::type uevent::onidle {
+ # ### ### ### ######### ######### #########
+ ## API
+
+ constructor {cmd} {
+ set mycmd $cmd
+ return
+ }
+
+ method request {} {
+ if {$myhasrequest} return
+ after idle [mymethod RunAction]
+ set myhasrequest 1
+ return
+ }
+
+ # ### ### ### ######### ######### #########
+ ## Internal commands
+
+ method RunAction {} {
+ set myhasrequest 0
+ uplevel \#0 $mycmd
+ return
+ }
+
+ # ### ### ### ######### ######### #########
+ ## State
+
+ variable mycmd {} ; # Command prefix of the action to perform
+ variable myhasrequest 0 ; # Boolean flag, set when the action has
+ # ; # been requested
+
+ # ### ### ### ######### ######### #########
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide uevent::onidle 0.1