diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
commit | ea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch) | |
tree | 6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/uev | |
parent | b5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff) | |
parent | 7c621da28f07e449ad90c387344f07a453927569 (diff) | |
download | blt-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/ChangeLog | 103 | ||||
-rw-r--r-- | tcllib/modules/uev/pkgIndex.tcl | 3 | ||||
-rw-r--r-- | tcllib/modules/uev/uevent.man | 196 | ||||
-rw-r--r-- | tcllib/modules/uev/uevent.pcx | 58 | ||||
-rw-r--r-- | tcllib/modules/uev/uevent.tcl | 470 | ||||
-rw-r--r-- | tcllib/modules/uev/uevent.test | 478 | ||||
-rw-r--r-- | tcllib/modules/uev/uevent_onidle.man | 64 | ||||
-rw-r--r-- | tcllib/modules/uev/uevent_onidle.pcx | 27 | ||||
-rw-r--r-- | tcllib/modules/uev/uevent_onidle.tcl | 51 |
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 |