source: branches/blt4/gui/scripts/dispatcher.tcl @ 2287

Last change on this file since 2287 was 1923, checked in by gah, 14 years ago
File size: 12.0 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: dispatcher - central notification mechanism
3#
4#  The DispatchObj is used within other objects to manage events that
5#  the object sends to itself or other clients.  Each event type
6#  must first be registered via the "register" method.  After that,
7#  an event can be dispatched by calling the "event" method.  Clients
8#  can bind to the various event types by registering a callback via
9#  the "dispatch" method.
10#
11#  The Dispatcher base class provides a foundation for objects that
12#  use the DispatchObj internally.
13# ======================================================================
14#  AUTHOR:  Michael McLennan, Purdue University
15#  Copyright (c) 2004-2005  Purdue Research Foundation
16#
17#  See the file "license.terms" for information on usage and
18#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
19# ======================================================================
20package require Itcl
21
22namespace eval Rappture { # forward declaration }
23
24proc Rappture::dispatcher {varName} {
25    upvar $varName obj
26    set obj [Rappture::DispatchObj ::#auto]
27    trace variable obj u "[list rename $obj {}]; list"
28}
29
30itcl::class Rappture::DispatchObj {
31    constructor {} { # defined below }
32    destructor { # defined below }
33
34    public method register {args}
35    public method dispatch {option args}
36    public method event {name args}
37    public method cancel {args}
38    public method ispending {name}
39
40    protected method _send {event caller {arglist ""}}
41
42    private variable _event2clients  ;# maps !event => clients
43    private variable _event2datacb   ;# maps !event => data callback
44    private variable _dispatch       ;# maps !event/client => callback
45    private variable _extraargs      ;# extra args for dispatch calls
46}
47                                                                               
48# ----------------------------------------------------------------------
49# CONSTRUCTOR
50# ----------------------------------------------------------------------
51itcl::body Rappture::DispatchObj::constructor {} {
52    # nothing to do
53}
54                                                                               
55# ----------------------------------------------------------------------
56# DESTRUCTOR
57# ----------------------------------------------------------------------
58itcl::body Rappture::DispatchObj::destructor {} {
59    eval cancel [array names _event2clients]
60}
61
62# ----------------------------------------------------------------------
63# USAGE: register !event ?dataCallback?
64#
65# Clients use this to register new event types with this dispatcher.
66# Once registered, each !event name can be used in the "dispatch"
67# method to bind to the event, and in the "event" method to raise
68# the event.  If the optional dataCallback is supplied, then it is
69# invoked whenever the event is triggered to generate event data
70# that gets passed along to the various dispatch callbacks.
71# ----------------------------------------------------------------------
72itcl::body Rappture::DispatchObj::register {name {datacb ""}} {
73    if {![regexp {^![-_a-zA-Z0-9]} $name]} {
74        error "bad name \"$name\": should be !name"
75    }
76    if {[info exists _event2clients($name)]} {
77        error "event \"$name\" already exists"
78    }
79    set _event2clients($name) ""
80    set _event2datacb($name) $datacb
81}
82
83# ----------------------------------------------------------------------
84# USAGE: dispatch caller ?-now? ?!event ...? ?callback?
85#
86# Clients use this to query binding information, or to bind a callback
87# to one or more of the !events recognized by this object.
88#
89# With no args, this returns a list of all events that are currently
90# bound to the caller.
91#
92# With one argument, this returns the callback associated with the
93# specified !event for the given caller.
94#
95# Otherwise, it sets the callback for one or more events.  If the
96# -now flag is specified, then the callback is invoked immediately
97# to send data back to this caller.  This comes in handy when the
98# caller is set up to react to changes.  It provides a way of
99# initializing the client, as if an event had just occurred.
100# ----------------------------------------------------------------------
101itcl::body Rappture::DispatchObj::dispatch {caller args} {
102    if {[llength $args] == 0} {
103        # no args? then return a list of events for this caller
104        foreach key [array names _dispatch $caller-*] {
105            set name [lindex [split $key -] 1]
106            set events($name) 1
107        }
108        return [array names events]
109    } elseif {[llength $args] == 1} {
110        set name [lindex $args 0]
111        if {[info exists _dispatch($caller-$name)]} {
112            return $_dispatch($caller-$name)
113        }
114        return ""
115    }
116
117    # set a callback for one or more events
118    set now 0
119    set events ""
120    set callback [lindex $args end]
121    foreach str [lrange $args 0 end-1] {
122        if {$str == "-now"} {
123            set now 1
124        } elseif {[info exists _event2clients($str)]} {
125            lappend events $str
126        } else {
127            if {[string index $str 0] == "-"} {
128                error "bad option \"$str\": should be -now"
129            } else {
130                error "bad event \"$str\": should be [join [lsort [array names _event2clients]] {, }]"
131            }
132        }
133    }
134
135    #
136    # If the callback is "", then remove the callback for this
137    # caller.  Otherwise, set the callback for this caller.
138    #
139    foreach name $events {
140        cancel $name
141
142        if {"" == $callback} {
143            catch {unset _dispatch($caller-$name)}
144            if {"" == [array names _dispatch $caller-*]} {
145                set i [lsearch $_event2clients($name) $caller]
146                if {$i >= 0} {
147                    set _event2clients($name) [lreplace $_event2clients($name) $i $i]
148                }
149            }
150        } else {
151            set _dispatch($caller-$name) $callback
152            set i [lsearch $_event2clients($name) $caller]
153            if {$i < 0} { lappend _event2clients($name) $caller }
154
155            if {$now} {
156                _send $name $caller
157            }
158        }
159    }
160}
161
162# ----------------------------------------------------------------------
163# USAGE: event ?-now? ?-later? ?-idle? ?-after time? !event ?args...?
164#
165# Clients use this to dispatch an event to any callers who have
166# registered their interest.
167# ----------------------------------------------------------------------
168itcl::body Rappture::DispatchObj::event {args} {
169    set when "-now"
170    set first [lindex $args 0]
171    if {[string index $first 0] == "-"} {
172        switch -- $first {
173            -now   {
174                set when "-now"
175                set args [lrange $args 1 end]
176            }
177            -later {
178                set when 1
179                set args [lrange $args 1 end]
180            }
181            -idle  {
182                set when "-idle"
183                set args [lrange $args 1 end]
184            }
185            -after {
186                set when [lindex $args 1]
187                if {![string is int $when]} {
188                    error "bad value \"$when\": should be int (time in ms)"
189                }
190                set args [lrange $args 2 end]
191            }
192            default {
193                error "bad option \"$first\": should be -now, -later, -idle, or -after"
194            }
195        }
196    }
197
198    if {[llength $args] < 1} {
199        error "wrong # args: should be \"event ?switches? !event ?args...?\""
200    }
201    set event [lindex $args 0]
202    set args [lrange $args 1 end]
203    if {![info exists _event2clients($event)]} {
204        error "bad event \"$event\": should be [join [lsort [array names _event2clients]] {, }]"
205    }
206
207    switch -- $when {
208        -now {
209            _send $event all $args
210        }
211        -idle {
212            set _extraargs($event) $args
213            after cancel [itcl::code $this _send $event all @extra]
214            after idle [itcl::code $this _send $event all @extra]
215        }
216        default {
217            set _extraargs($event) $args
218            after cancel [itcl::code $this _send $event all @extra]
219            after $when [itcl::code $this _send $event all @extra]
220        }
221    }
222}
223
224# ----------------------------------------------------------------------
225# USAGE: cancel ?!event ...?
226#        cancel all
227#
228# Used to cancel any event notifications pending for the specified
229# list of events.  Notifications may be pending if a particular
230# event was raised with the -idle or -after flags.
231# ----------------------------------------------------------------------
232itcl::body Rappture::DispatchObj::cancel {args} {
233    if { $args == "all" } {
234        foreach event [array names _event2clients] {
235            after cancel [itcl::code $this _send $event all @extra]
236        }
237        return
238    }
239    foreach event $args {
240        if {![info exists _event2clients($event)]} {
241            error "bad event \"$event\": should be [join [lsort [array names _event2clients]] {, }]"
242        }
243        after cancel [itcl::code $this _send $event all @extra]
244    }
245}
246
247# ----------------------------------------------------------------------
248# USAGE: ispending !event
249#
250# Returns 1 if the specified !event is pending, and 0 otherwise.
251# Notifications may be pending if a particular event was raised
252# with the -idle or -after flags.
253# ----------------------------------------------------------------------
254itcl::body Rappture::DispatchObj::ispending {event} {
255    set cmd [itcl::code $this _send $event all @extra]
256    foreach id [after info] {
257        set cmd2 [lindex [after info $id] 0]
258        if {[string equal $cmd $cmd2]} {
259            return 1
260        }
261    }
262    return 0
263}
264
265# ----------------------------------------------------------------------
266# USAGE: _send !event caller ?arglist?
267#
268# Used internally to send an event to one or all callers.  The event
269# is sent by first invoking a callback to generate any data associated
270# with the event.  This data, along with any extra args passed in,
271# are added to the callback for the caller.
272# ----------------------------------------------------------------------
273itcl::body Rappture::DispatchObj::_send {event caller {arglist ""}} {
274    if {$caller == "all"} {
275        set caller $_event2clients($event)
276    }
277    if {$arglist == "@extra" && [info exists _extraargs($event)]} {
278        set arglist $_extraargs($event)
279    }
280
281    # if there are any clients for this event, get the arguments ready
282    set any 0
283    foreach cname $caller {
284        if {[info exists _dispatch($cname-$event)]} {
285            set any 1
286            break
287        }
288    }
289
290    set dargs ""
291    if {$any && [string length $_event2datacb($event)] > 0} {
292        # get the args from the data callback
293        set status [catch {
294            uplevel #0 $_event2datacb($event) $event
295        } dargs]
296
297        if {$status != 0} {
298            # anything go wrong? then throw a background error
299            bgerror "$dargs\n(while dispatching $event)"
300            set dargs ""
301        }
302    }
303    # add any arguments added from command line
304    eval lappend dargs $arglist
305
306    foreach cname $caller {
307        if {[info exists _dispatch($cname-$event)]} {
308            set status [catch {
309                uplevel #0 $_dispatch($cname-$event) event $event $dargs
310            } result]
311
312            if {$status != 0} {
313                # anything go wrong? then throw a background error
314                bgerror "$result\n(while dispatching $event to $cname)"
315            }
316        }
317    }
318}
319
320# ----------------------------------------------------------------------
321# BASE CLASS: Dispatcher
322# ----------------------------------------------------------------------
323itcl::class Rappture::Dispatcher {
324    constructor {args} {
325        Rappture::dispatcher _dispatcher
326        $_dispatcher register !destroy
327
328        eval configure $args
329    }
330
331    destructor {
332        event !destroy
333    }
334
335    public method dispatch {args} {
336        eval $_dispatcher dispatch $args
337    }
338
339    protected method register {args} {
340        eval $_dispatcher register $args
341    }
342    protected method event {args} {
343        eval $_dispatcher event $args object $this
344    }
345
346    private variable _dispatcher ""  ;# dispatcher for events
347}
Note: See TracBrowser for help on using the repository browser.