source: trunk/gui/scripts/dispatcher.tcl @ 924

Last change on this file since 924 was 115, checked in by mmc, 19 years ago

Updated all copyright notices.

File size: 11.8 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#
227# Used to cancel any event notifications pending for the specified
228# list of events.  Notifications may be pending if a particular
229# event was raised with the -idle or -after flags.
230# ----------------------------------------------------------------------
231itcl::body Rappture::DispatchObj::cancel {args} {
232    foreach event $args {
233        if {![info exists _event2clients($event)]} {
234            error "bad event \"$str\": should be [join [lsort [array names _event2clients]] {, }]"
235        }
236        after cancel [itcl::code $this _send $event all @extra]
237    }
238}
239
240# ----------------------------------------------------------------------
241# USAGE: ispending !event
242#
243# Returns 1 if the specified !event is pending, and 0 otherwise.
244# Notifications may be pending if a particular event was raised
245# with the -idle or -after flags.
246# ----------------------------------------------------------------------
247itcl::body Rappture::DispatchObj::ispending {event} {
248    set cmd [itcl::code $this _send $event all @extra]
249    foreach id [after info] {
250        set cmd2 [lindex [after info $id] 0]
251        if {[string equal $cmd $cmd2]} {
252            return 1
253        }
254    }
255    return 0
256}
257
258# ----------------------------------------------------------------------
259# USAGE: _send !event caller ?arglist?
260#
261# Used internally to send an event to one or all callers.  The event
262# is sent by first invoking a callback to generate any data associated
263# with the event.  This data, along with any extra args passed in,
264# are added to the callback for the caller.
265# ----------------------------------------------------------------------
266itcl::body Rappture::DispatchObj::_send {event caller {arglist ""}} {
267    if {$caller == "all"} {
268        set caller $_event2clients($event)
269    }
270    if {$arglist == "@extra" && [info exists _extraargs($event)]} {
271        set arglist $_extraargs($event)
272    }
273
274    # if there are any clients for this event, get the arguments ready
275    set any 0
276    foreach cname $caller {
277        if {[info exists _dispatch($cname-$event)]} {
278            set any 1
279            break
280        }
281    }
282
283    set dargs ""
284    if {$any && [string length $_event2datacb($event)] > 0} {
285        # get the args from the data callback
286        set status [catch {
287            uplevel #0 $_event2datacb($event) $event
288        } dargs]
289
290        if {$status != 0} {
291            # anything go wrong? then throw a background error
292            bgerror "$dargs\n(while dispatching $event)"
293            set dargs ""
294        }
295    }
296    # add any arguments added from command line
297    eval lappend dargs $arglist
298
299    foreach cname $caller {
300        if {[info exists _dispatch($cname-$event)]} {
301            set status [catch {
302                uplevel #0 $_dispatch($cname-$event) event $event $dargs
303            } result]
304
305            if {$status != 0} {
306                # anything go wrong? then throw a background error
307                bgerror "$result\n(while dispatching $event to $cname)"
308            }
309        }
310    }
311}
312
313# ----------------------------------------------------------------------
314# BASE CLASS: Dispatcher
315# ----------------------------------------------------------------------
316itcl::class Rappture::Dispatcher {
317    constructor {args} {
318        Rappture::dispatcher _dispatcher
319        $_dispatcher register !destroy
320
321        eval configure $args
322    }
323
324    destructor {
325        event !destroy
326    }
327
328    public method dispatch {args} {
329        eval $_dispatcher dispatch $args
330    }
331
332    protected method register {args} {
333        eval $_dispatcher register $args
334    }
335    protected method event {args} {
336        eval $_dispatcher event $args object $this
337    }
338
339    private variable _dispatcher ""  ;# dispatcher for events
340}
Note: See TracBrowser for help on using the repository browser.