source: trunk/gui/scripts/controlOwner.tcl @ 3093

Last change on this file since 3093 was 2977, checked in by gah, 13 years ago

fix multiple for about icons for 2 page tools with manual-resim set

File size: 19.0 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: owner - manages Rappture controls
3#
4#  This object represents an entity managing Rappture controls.
5#  It is typically a Tool, a DeviceEditor, or some other large entity
6#  that manages a Rappture XML tree.  All controlling widgets are
7#  registered with an owner, and the owner propagates notifications
8#  out to clients who have an interest in a particular control.
9# ======================================================================
10#  AUTHOR:  Michael McLennan, Purdue University
11#  Copyright (c) 2004-2005  Purdue Research Foundation
12#
13#  See the file "license.terms" for information on usage and
14#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15# ======================================================================
16package require Itcl
17
18itcl::class Rappture::ControlOwner {
19    constructor {owner} { # defined below }
20
21    public method xml {args}
22
23    public method load {newobj}
24    public method widgetfor {path args}
25    public method valuefor {path args}
26    public method dependenciesfor {path args}
27    public method ownerfor {path {skip ""}}
28    public method changed {path}
29    public method regularize {path}
30    public method notify {option owner args}
31    public method sync {}
32    public method tool {}
33
34    protected method _slave {option args}
35
36    protected variable _owner ""     ;# ControlOwner containing this one
37    protected variable _path ""      ;# paths within are relative to this
38    protected variable _slaves ""    ;# this owner has these slaves
39    protected variable _xmlobj ""    ;# Rappture XML description
40    private variable _path2widget    ;# maps path => widget on this page
41    private variable _owner2paths    ;# for notify: maps owner => interests
42    private variable _type2curpath   ;# maps type(path) => path's current value
43    private variable _callbacks      ;# for notify: maps owner/path => callback
44    private variable _dependencies   ;# maps path => other paths dep on this
45}
46
47# ----------------------------------------------------------------------
48# CONSTRUCTOR
49# ----------------------------------------------------------------------
50itcl::body Rappture::ControlOwner::constructor {owner} {
51    if {"" != $owner} {
52        set parts [split $owner @]
53        set _owner [lindex $parts 0]
54        set _path [lindex $parts 1]
55        $_owner _slave add $this
56    }
57
58    # we are adding this so notes can be used
59    # in coordination with loaders inside the load function
60    array set _type2curpath {
61        drawing components
62        choice current
63        boolean current
64        image current
65        integer current
66        loader current
67        note contents
68        number current
69        periodicelement current
70        string current
71    }
72}
73
74# ----------------------------------------------------------------------
75# USAGE: xml <subcommand> ?<arg> <arg> ...?
76# USAGE: xml object
77#
78# Used by clients to manipulate the underlying XML data for this
79# tool.  The <subcommand> can be any operation supported by a
80# Rappture::library object.  Clients can also request the XML object
81# directly by using the "object" subcommand.
82# ----------------------------------------------------------------------
83itcl::body Rappture::ControlOwner::xml {args} {
84    if {"object" == $args} {
85        return $_xmlobj
86    }
87    return [eval $_xmlobj $args]
88}
89
90# ----------------------------------------------------------------------
91# USAGE: widgetfor <path> ?<widget>?
92#
93# Used by embedded widgets such as a Controls panel to register the
94# various controls associated with this page.  That way, this
95# ControlOwner knows what widgets to look at when syncing itself
96# to the underlying XML data.
97# ----------------------------------------------------------------------
98itcl::body Rappture::ControlOwner::widgetfor {path args} {
99    # if this is a query operation, then look for the path
100    if {[llength $args] == 0} {
101        set owner [ownerfor $path]
102        if {$owner != $this && $owner != ""} {
103            return [$owner widgetfor $path]
104        }
105        if {[info exists _path2widget($path)]} {
106            return $_path2widget($path)
107        }
108        return ""
109    }
110
111    # otherwise, associate the path with the given widget
112    set widget [lindex $args 0]
113    if {"" != $widget} {
114        if {[info exists _path2widget($path)]} {
115            error "$path already associated with widget $_path2widget($path)"
116        }
117        set _path2widget($path) $widget
118    } else {
119        catch {unset _path2widget($path)}
120    }
121}
122
123# ----------------------------------------------------------------------
124# USAGE: valuefor <path> ?<newValue>?
125#
126# Used by embedded widgets such as a Loader to query or set the
127# value of another control.  With no extra args, it returns the
128# value of the widget at the <path> in the XML.  Otherwise, it
129# sets the value of the widget to <newValue>.
130# ----------------------------------------------------------------------
131itcl::body Rappture::ControlOwner::valuefor {path args} {
132    set owner [ownerfor $path]
133
134    # if this is a query operation, then look for the path
135    if {[llength $args] == 0} {
136        if {$owner != $this && $owner != ""} {
137            return [$owner valuefor $path]
138        }
139        if {[info exists _path2widget($path)]} {
140            return [$_path2widget($path) value]
141        }
142        # can't find the path? try removing the prefix for this owner
143        set plen [string length $_path]
144        if {[string equal -length $plen $_path $path]} {
145            set relpath [string range $path [expr {$plen+1}] end]
146            if {[info exists _path2widget($relpath)]} {
147                return [$_path2widget($relpath) value]
148            }
149        }
150        return ""
151    }
152
153    # otherwise, set the value
154    if {$owner != $this && $owner != ""} {
155        return [eval $owner valuefor $path $args]
156    }
157    if {[llength $args] > 1} {
158        error "wrong # args: should be \"valuefor path ?newValue?\""
159    }
160
161    if {[info exists _path2widget($path)]} {
162        $_path2widget($path) value [lindex $args 0]
163    } else {
164        error "bad path \"$path\": should be one of [join [lsort [array names _path2widget]] {, }]"
165    }
166}
167
168# ----------------------------------------------------------------------
169# USAGE: dependenciesfor <path> ?<path>...?
170#
171# Used by embedded widgets such as a Controls panel to register the
172# various controls that are dependent on another one.  If only one
173# path is specified, then this method returns all known dependencies
174# for the specified <path>.  Otherwise, the additional <path>'s are
175# noted as being dependent on the first <path>.
176# ----------------------------------------------------------------------
177itcl::body Rappture::ControlOwner::dependenciesfor {path args} {
178    if {"" != $_owner} {
179        #
180        # Keep all dependencies at the highest level.
181        # That way, a structure can come and go, but the
182        # dependencies remain fixed in the topmost tool.
183        #
184        set plen [string length $_path]
185        if {"" != $_path && ![string equal -length $plen $_path $path]} {
186            set path $_path.$path
187        }
188        return [eval $_owner dependenciesfor $path $args]
189    }
190
191    # if this is a query operation, then look for the path
192    if {[llength $args] == 0} {
193        if {[info exists _dependencies($path)]} {
194            return $_dependencies($path)
195        }
196        return ""
197    }
198
199    # add new dependencies
200    if {![info exists _dependencies($path)]} {
201        set _dependencies($path) ""
202    }
203    foreach dpath $args {
204        set i [lsearch -exact $_dependencies($path) $dpath]
205        if {$i < 0} {
206            lappend _dependencies($path) $dpath
207        }
208    }
209}
210
211# ----------------------------------------------------------------------
212# USAGE: ownerfor <path> ?<skip>?
213#
214# Returns the ControlOwner that directly controls the specified <path>.
215# ----------------------------------------------------------------------
216itcl::body Rappture::ControlOwner::ownerfor {path {skip ""}} {
217    if {[info exists _path2widget($path)]} {
218        return $this
219    }
220
221    # can't find the path? try removing the prefix for this owner
222    set plen [string length $_path]
223    if {[string equal -length $plen $_path $path]} {
224        set relpath [string range $path [expr {$plen+1}] end]
225        if {[info exists _path2widget($relpath)]} {
226            return $this
227        }
228    }
229
230    # couldn't find this path?  then check all subordinates
231    foreach slave $_slaves {
232        if {$slave == $skip} {
233            continue  ;# skip this slave if it's already been searched
234        }
235        set rval [$slave ownerfor $path $this]
236        if {"" != $rval} {
237            return $rval
238        }
239    }
240
241    # check the owner as a last resort
242    if {"" != $_owner && $_owner != $skip} {
243        set rval [$_owner ownerfor $path $this]
244        if {"" != $rval} {
245            return $rval
246        }
247    }
248
249    return ""
250}
251
252# ----------------------------------------------------------------------
253# USAGE: load <xmlobj>
254#
255# Loads the contents of a Rappture <xmlobj> into the controls
256# associated with this tool.
257# ----------------------------------------------------------------------
258itcl::body Rappture::ControlOwner::load {newobj} {
259    if {![Rappture::library isvalid $newobj]} {
260        error "\"$newobj\" is not a Rappture::library"
261    }
262
263    foreach path [array names _path2widget] {
264        # the following elements do not accept "current" tags, skip them
265        set type [[tool] xml element -as type $path]
266        if {[lsearch {group separator control} $type] >= 0} {
267            continue
268        }
269
270        set type [[tool] xml element -as type $path]
271        if {[info exists _type2curpath($type)]} {
272            set currentpath $path.$_type2curpath($type)
273        } else {
274            # default incase i forgot an input type in _type2curpath
275            set currentpath $path.current
276        }
277
278        # copy new value to the XML tree
279        # also copy to the widget associated with the tree
280        #
281        # we only copy the values if they existed in newobj
282        # so we don't overwrite values that were set in previous loads.
283        # this is needed for when the users specify copy.from and copy.to
284        # in a loader. in this case, _path2widget holds a list of all
285        # widgets. if there are two loaders loading two different widgets,
286        # and each loader uses the copy from/to functionality,
287        # the second load could wipe out the values set in the first load
288        # because on the second load, the copied paths from the first load no
289        # longer exist in newobj and blanks are copied to the paths
290        # in [tool] xml set by the first loader. the solution is to check
291        # newobj and see if the path exists. if the path exists, then we copy
292        # it over to [tool] xml, otherwise we ignore it.
293        if {"" != [$newobj element -as type $currentpath]} {
294            [tool] xml copy $currentpath from $newobj $currentpath
295            set val [$newobj get $currentpath]
296            if {[string length $val] > 0
297                  || [llength [$newobj children $currentpath]] == 0} {
298                $_path2widget($path) value $val
299            } else {
300                set obj [$newobj element -as object $currentpath]
301                $_path2widget($path) value $obj
302            }
303        }
304    }
305}
306
307# ----------------------------------------------------------------------
308# USAGE: changed <path>
309#
310# Invoked automatically by the various widgets associated with this
311# tool whenever their value changes.  Sends notifications to any
312# client that has registered an interest via "notify add".
313# ----------------------------------------------------------------------
314itcl::body Rappture::ControlOwner::changed {path} {
315    if {"" != $_owner} {
316        set plen [string length $_path]
317        if {"" != $_path && ![string equal -length $plen $_path $path]} {
318            set path $_path.$path
319        }
320        $_owner changed $path
321    } else {
322        # send out any callback notifications
323        foreach owner [array names _owner2paths] {
324            foreach pattern $_owner2paths($owner) {
325                if {[string match $pattern $path]} {
326                    uplevel #0 $_callbacks($owner/$pattern)
327                    break
328                }
329            }
330        }
331
332        # find the control panel for each dependency, and tell it
333        # to update its layout.
334        foreach cpath [dependenciesfor $path] {
335            set wv [widgetfor $cpath]
336            while {"" != $wv} {
337                set wv [winfo parent $wv]
338                if {[winfo class $wv] == "Controls"} {
339                    $wv refresh
340                    break
341                }
342            }
343        }
344    }
345}
346
347# ----------------------------------------------------------------------
348# USAGE: regularize <path>
349#
350# Clients use this to get a full, regularized path for the specified
351# <path>, which may be relative to the current owner.
352# ----------------------------------------------------------------------
353itcl::body Rappture::ControlOwner::regularize {path} {
354    set owner [ownerfor $path]
355    if {$owner != $this && $owner != ""} {
356        return [$owner regularize $path]
357    }
358    set rpath ""
359    if {"" != $_xmlobj} {
360        set rpath [$_xmlobj element -as path $path]
361
362        # can't find the path? try removing the prefix for this owner
363        if {"" == $rpath} {
364            set plen [string length $_path]
365            if {[string equal -length $plen $_path $path]} {
366                set relpath [string range $path [expr {$plen+2}] end]
367                set rpath [$_xmlobj element -as path $relpath]
368            }
369        }
370
371        if {"" != $rpath && "" != $_path} {
372            # return a full name for the path
373            set rpath "$_path.$rpath"
374        }
375    }
376    return $rpath
377}
378
379# ----------------------------------------------------------------------
380# USAGE: notify add <owner> <path> <callback>
381# USAGE: notify info ?<owner>? ?<path>?
382# USAGE: notify remove <owner> ?<path> ...?
383#
384# Clients use this to request notifications about changes to a
385# particular <path> for a control under this tool.  Whenever the
386# value associated with <path> changes, the client identified by
387# <owner> is sent a message by invoking its <callback> routine.
388#
389# Notifications can be silenced by calling the "notify remove"
390# function.
391# ----------------------------------------------------------------------
392itcl::body Rappture::ControlOwner::notify {option args} {
393    switch -- $option {
394        add {
395            if {[llength $args] != 3} {
396                error "wrong # args: should be \"notify add owner path callback\""
397            }
398            set owner [lindex $args 0]
399            set path [lindex $args 1]
400            set cb [lindex $args 2]
401
402            if {[info exists _owner2paths($owner)]} {
403                set plist $_owner2paths($owner)
404            } else {
405                set plist ""
406            }
407
408            set i [lsearch -exact $plist $path]
409            if {$i < 0} { lappend _owner2paths($owner) $path }
410            set _callbacks($owner/$path) $cb
411        }
412        info {
413            if {[llength $args] == 0} {
414                # no args? then return all owners
415                return [array names _owner2paths]
416            } else {
417                set owner [lindex $args 0]
418                if {[info exists _owner2paths($owner)]} {
419                    set plist $_owner2paths($owner)
420                } else {
421                    set plist ""
422                }
423                if {[llength $args] == 1} {
424                    # 1 arg? then return paths for this owner
425                    return $plist
426                } elseif {[llength $args] == 2} {
427                    # 2 args? then return callback for this path
428                    set path [lindex $args 1]
429                    if {[info exists _callbacks($owner/$path)]} {
430                        return $_callbacks($owner/$path)
431                    }
432                    return ""
433                } else {
434                    error "wrong # args: should be \"notify info ?owner? ?path?\""
435                }
436            }
437        }
438        remove {
439            if {[llength $args] < 1} {
440                error "wrong # args: should be \"notify remove owner ?path ...?\""
441            }
442            set owner [lindex $args 0]
443
444            if {[llength $args] == 1} {
445                # no args? then delete all paths for this owner
446                if {[info exists _owner2paths($owner)]} {
447                    set plist $_owner2paths($owner)
448                } else {
449                    set plist ""
450                }
451            } else {
452                set plist [lrange $args 1 end]
453            }
454
455            # forget about the callback for each path
456            foreach path $plist {
457                catch {unset _callbacks($owner/$path)}
458
459                if {[info exists _owner2paths($owner)]} {
460                    set i [lsearch -exact $_owner2paths($owner) $path]
461                    if {$i >= 0} {
462                        set _owner2paths($owner) \
463                            [lreplace $_owner2paths($owner) $i $i]
464                    }
465                }
466            }
467        }
468    }
469}
470
471# ----------------------------------------------------------------------
472# USAGE: sync
473#
474# Used by descendents such as a Controls panel to register the
475# various controls associated with this page.  That way, this
476# ControlOwner knows what widgets to look at when syncing itself
477# to the underlying XML data.
478# ----------------------------------------------------------------------
479itcl::body Rappture::ControlOwner::sync {} {
480    # sync all of the widgets under control of this owner
481    if {"" != $_xmlobj} {
482        foreach path [lsort [array names _path2widget]] {
483            set type [$_xmlobj element -as type $path]
484            if {[lsearch {group separator control note} $type] >= 0} {
485                continue
486            }
487            $_xmlobj put $path.current [$_path2widget($path) value]
488        }
489    }
490
491    # sync all subordinate slaves as well
492    foreach slave $_slaves {
493        $slave sync
494    }
495}
496
497# ----------------------------------------------------------------------
498# USAGE: tool
499#
500# Clients use this to figure out which tool is associated with
501# this object.  If there is no parent ControlOwner, then this
502# must be the top-level tool.
503# ----------------------------------------------------------------------
504itcl::body Rappture::ControlOwner::tool {} {
505    if {"" != $_owner} {
506        return [$_owner tool]
507    }
508    return $this
509}
510
511# ----------------------------------------------------------------------
512# USAGE: _slave add <newobj>...
513#
514# Used internally to register the parent-child relationship whenever
515# one ControlOwner is registered to another.  When the parent syncs,
516# it causes all of its children to sync.  When a name is being
517# resolved, it is resolved locally first, then up to the parent for
518# further resolution.
519# ----------------------------------------------------------------------
520itcl::body Rappture::ControlOwner::_slave {option args} {
521    switch -- $option {
522        add {
523            eval lappend _slaves $args
524        }
525        default {
526            error "bad option \"$option\": should be add"
527        }
528    }
529}
Note: See TracBrowser for help on using the repository browser.