source: trunk/gui/scripts/controls.tcl @ 82

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

Fixed the device viewer to work properly even with
<units>arbitrary</units>. Also, fixed Controls so
that you can use a <separator> to break up a group
of groups and have it render as a flat list of groups
rather than a series of tabs.

File size: 15.4 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: controls - a container for various Rappture controls
3#
4#  This widget is a smart frame acting as a container for controls.
5#  Controls are added to this panel, and the panel itself decides
6#  how to arrange them given available space.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005
10#  Purdue Research Foundation, West Lafayette, IN
11# ======================================================================
12package require Itk
13package require BLT
14
15option add *Controls.padding 4 widgetDefault
16option add *Controls.labelFont \
17    -*-helvetica-medium-r-normal-*-*-120-* widgetDefault
18
19itcl::class Rappture::Controls {
20    inherit itk::Widget
21
22    itk_option define -padding padding Padding 0
23
24    constructor {owner args} { # defined below }
25
26    public method insert {pos path}
27    public method delete {first {last ""}}
28    public method index {name}
29    public method control {args}
30
31    protected method _layout {}
32    protected method _controlChanged {path}
33    protected method _formatLabel {str}
34    protected method _changeTabs {}
35
36    private variable _owner ""       ;# controls belong to this owner
37    private variable _tabs ""        ;# optional tabset for groups
38    private variable _frame ""       ;# pack controls into this frame
39    private variable _counter 0      ;# counter for control names
40    private variable _dispatcher ""  ;# dispatcher for !events
41    private variable _controls ""    ;# list of known controls
42    private variable _name2info      ;# maps control name => info
43}
44                                                                               
45itk::usual Controls {
46}
47
48# ----------------------------------------------------------------------
49# CONSTRUCTOR
50# ----------------------------------------------------------------------
51itcl::body Rappture::Controls::constructor {owner args} {
52    Rappture::dispatcher _dispatcher
53    $_dispatcher register !layout
54    $_dispatcher dispatch $this !layout "[itcl::code $this _layout]; list"
55
56    set _owner $owner
57
58    Rappture::Scroller $itk_interior.sc -xscrollmode none -yscrollmode auto
59    pack $itk_interior.sc -expand yes -fill both
60    set f [$itk_interior.sc contents frame]
61
62    set _tabs [blt::tabset $f.tabs -borderwidth 0 -relief flat \
63        -side top -tearoff 0 -highlightthickness 0 \
64        -selectbackground $itk_option(-background) \
65        -selectcommand [itcl::code $this _changeTabs]]
66
67    set _frame [frame $f.inner]
68    pack $_frame -expand yes -fill both
69
70    #
71    # Put this frame in whenever the control frame is empty.
72    # It forces the size to contract back now when controls are deleted.
73    #
74    frame $_frame.empty -width 1 -height 1
75
76    eval itk_initialize $args
77}
78
79# ----------------------------------------------------------------------
80# USAGE: insert <pos> <path>
81#
82# Clients use this to insert a control into this panel.  The control
83# is inserted into the list at position <pos>, which can be an integer
84# starting from 0 or the keyword "end".  Information about the control
85# is taken from the specified <path>.
86#
87# Returns a name that can be used to identify the control in other
88# methods.
89# ----------------------------------------------------------------------
90itcl::body Rappture::Controls::insert {pos path} {
91    if {"end" == $pos} {
92        set pos [llength $_controls]
93    } elseif {![string is integer $pos]} {
94        error "bad index \"$pos\": should be integer or \"end\""
95    }
96
97    incr _counter
98    set name "control$_counter"
99
100    set _name2info($name-path) $path
101    set _name2info($name-label) ""
102    set _name2info($name-value) [set w $_frame.v$name]
103
104    set type [$_owner xml element -as type $path]
105    switch -- $type {
106        choice {
107            Rappture::ChoiceEntry $w $_owner $path
108            bind $w <<Value>> [itcl::code $this _controlChanged $path]
109        }
110        group {
111            Rappture::GroupEntry $w $_owner $path
112        }
113        loader {
114            Rappture::Loader $w $_owner $path -tool [$_owner tool]
115            bind $w <<Value>> [itcl::code $this _controlChanged $path]
116        }
117        number {
118            Rappture::NumberEntry $w $_owner $path
119            bind $w <<Value>> [itcl::code $this _controlChanged $path]
120        }
121        integer {
122            Rappture::IntegerEntry $w $_owner $path
123            bind $w <<Value>> [itcl::code $this _controlChanged $path]
124        }
125        boolean {
126            Rappture::BooleanEntry $w $_owner $path
127            bind $w <<Value>> [itcl::code $this _controlChanged $path]
128        }
129        string {
130            Rappture::TextEntry $w $_owner $path
131            bind $w <<Value>> [itcl::code $this _controlChanged $path]
132        }
133        control {
134            set label [$_owner xml get $path.label]
135            if {"" == $label} { set label "Simulate" }
136            set service [$_owner xml get $path.service]
137            button $w -text $label -command [list $service run]
138        }
139        separator {
140            # no widget to create
141            set _name2info($name-value) "--"
142        }
143        default {
144            error "don't know how to add control type \"$type\""
145        }
146    }
147
148    if {$type != "control" && $type != "separator"} {
149        $_owner widgetfor $path $w
150
151        # make a label for this control
152        set label [$w label]
153        if {"" != $label} {
154            set _name2info($name-label) $_frame.l$name
155            set font [option get $itk_component(hull) labelFont Font]
156            label $_name2info($name-label) -text [_formatLabel $label] \
157                -font $font
158        }
159
160        # register the tooltip for this control
161        set tip [$w tooltip]
162        if {"" != $tip} {
163            Rappture::Tooltip::for $w $tip
164
165            # add the tooltip to the label too, if there is one
166            if {$_name2info($name-label) != ""} {
167                Rappture::Tooltip::for $_name2info($name-label) $tip
168            }
169        }
170    }
171
172    # insert the new control onto the known list
173    set _controls [linsert $_controls $pos $name]
174
175    # now that we have a new control, we should fix the layout
176    $_dispatcher event -idle !layout
177
178    return $name
179}
180
181# ----------------------------------------------------------------------
182# USAGE: delete <first> ?<last>?
183#
184# Clients use this to delete one or more controls from this widget.
185# The <first> and <last> represent the integer index of the desired
186# control.  You can use the "index" method to convert a control name to
187# its integer index.  If only <first> is specified, then that one
188# control is deleted.  If <last> is specified, then all controls in the
189# range <first> to <last> are deleted.
190# ----------------------------------------------------------------------
191itcl::body Rappture::Controls::delete {first {last ""}} {
192    if {$last == ""} {
193        set last $first
194    }
195    if {![regexp {^[0-9]+|end$} $first]} {
196        error "bad index \"$first\": should be integer or \"end\""
197    }
198    if {![regexp {^[0-9]+|end$} $last]} {
199        error "bad index \"$last\": should be integer or \"end\""
200    }
201
202    foreach name [lrange $_controls $first $last] {
203        if {"" != $_name2info($name-label)} {
204            destroy $_name2info($name-label)
205        }
206        if {"" != $_name2info($name-value)} {
207            destroy $_name2info($name-value)
208        }
209        $_owner widgetfor $_name2info($name-path) ""
210
211        unset _name2info($name-path)
212        unset _name2info($name-label)
213        unset _name2info($name-value)
214    }
215    set _controls [lreplace $_controls $first $last]
216
217    $_dispatcher event -idle !layout
218}
219
220# ----------------------------------------------------------------------
221# USAGE: index <name>|@n
222#
223# Clients use this to convert a control <name> into its corresponding
224# integer index.  Returns an error if the <name> is not recognized.
225# ----------------------------------------------------------------------
226itcl::body Rappture::Controls::index {name} {
227    set i [lsearch $_controls $name]
228    if {$i >= 0} {
229        return $i
230    }
231    if {[regexp {^@([0-9]+)$} $name match i]} {
232        return $i
233    }
234    error "bad control name \"$name\": should be @int or one of [join [lsort $_controls] {, }]"
235}
236
237# ----------------------------------------------------------------------
238# USAGE: control ?-label|-value|-path? ?<name>|@n?
239#
240# Clients use this to get information about controls.  With no args, it
241# returns a list of all control names.  Otherwise, it returns the frame
242# associated with a control name.  The -label option requests the label
243# widget instead of the value widget.  The -path option requests the
244# path within the XML that the control affects.
245# ----------------------------------------------------------------------
246itcl::body Rappture::Controls::control {args} {
247    if {[llength $args] == 0} {
248        return $_controls
249    }
250    Rappture::getopts args params {
251        flag switch -value default
252        flag switch -label
253        flag switch -path
254    }
255    if {[llength $args] == 0} {
256        error "missing control name"
257    }
258    set i [index [lindex $args 0]]
259    set name [lindex $_controls $i]
260
261    set opt $params(switch)
262    return $_name2info($name$opt)
263}
264
265# ----------------------------------------------------------------------
266# USAGE: _layout
267#
268# Used internally to fix the layout of controls whenever controls
269# are added or deleted, or when the control arrangement changes.
270# There are a lot of heuristics here trying to achieve a "good"
271# arrangement of controls.
272# ----------------------------------------------------------------------
273itcl::body Rappture::Controls::_layout {} {
274    #
275    # Clear any existing layout
276    #
277    foreach name $_controls {
278        foreach elem {label value} {
279            set w $_name2info($name-$elem)
280            if {$w != "" && [winfo exists $w]} {
281                grid forget $w
282            }
283        }
284    }
285    if {[$_tabs size] > 0} {
286        $_tabs delete 0 end
287    }
288    grid forget $_frame.empty
289
290    #
291    # Decide on a layout scheme:
292    #   tabs ...... best if all elements within are groups
293    #   hlabels ... horizontal labels (label: value)
294    #
295    if {[llength $_controls] >= 2} {
296        # assume tabs for multiple groups
297        set scheme tabs
298        foreach name $_controls {
299            set w $_name2info($name-value)
300
301            if {$w == "--" || [winfo class $w] != "GroupEntry"} {
302                # something other than a group? then fall back on hlabels
303                set scheme hlabels
304                break
305            }
306        }
307    } else {
308        set scheme hlabels
309    }
310
311    switch -- $scheme {
312      tabs {
313        #
314        # SCHEME: tabs
315        # put a series of groups into a tabbed notebook
316        #
317
318        # use inner frame within tabs to show current group
319        pack $_tabs -before $_frame -fill x
320
321        set gn 1
322        foreach name $_controls {
323            set wv $_name2info($name-value)
324            $wv configure -heading no
325
326            set label [$wv component heading cget -text]
327            if {"" == $label} {
328                set label "Group #$gn"
329            }
330            set _name2info($name-label) $label
331
332            $_tabs insert end $label \
333                -activebackground $itk_option(-background)
334
335            incr gn
336        }
337
338        # compute the overall size
339        # BE CAREFUL: do this after setting "-heading no" above
340        set maxw 0
341        set maxh 0
342        update idletasks
343        foreach name $_controls {
344            set wv $_name2info($name-value)
345            set w [winfo reqwidth $wv]
346            if {$w > $maxw} { set maxw $w }
347            set h [winfo reqheight $wv]
348            if {$h > $maxh} { set maxh $h }
349        }
350        $_frame configure -width $maxw -height $maxh
351
352        grid propagate $_frame off
353        grid columnconfigure $_frame 0 -weight 1
354        grid rowconfigure $_frame 0 -weight 1
355
356        $_tabs select 0; _changeTabs
357      }
358
359      hlabels {
360        #
361        # SCHEME: hlabels
362        # simple "Label: Value" layout
363        #
364        pack forget $_tabs
365        grid propagate $_frame on
366        grid columnconfigure $_frame 0 -weight 0
367        grid rowconfigure $_frame 0 -weight 0
368
369        set row 0
370        foreach name $_controls {
371            set wl $_name2info($name-label)
372            if {$wl != "" && [winfo exists $wl]} {
373                grid $wl -row $row -column 0 -sticky e
374            }
375
376            set wv $_name2info($name-value)
377            if {$wv != "" && [winfo exists $wv]} {
378                if {$wl != ""} {
379                    grid $wv -row $row -column 1 -sticky ew
380                } else {
381                    grid $wv -row $row -column 0 -columnspan 2 -sticky ew
382                }
383
384                grid rowconfigure $_frame $row -weight 0
385                grid rowconfigure $_frame $row -weight 0
386
387                switch -- [winfo class $wv] {
388                    TextEntry {
389                        if {[regexp {[0-9]+x[0-9]+} [$wv size]]} {
390                            grid $wl -sticky n -pady 4
391                            grid $wv -sticky nsew
392                            grid rowconfigure $_frame $row -weight 1
393                            grid columnconfigure $_frame 1 -weight 1
394                        }
395                    }
396                    GroupEntry {
397                        $wv configure -heading yes
398                    }
399                }
400                grid columnconfigure $_frame 1 -weight 1
401            } elseif {$wv == "--"} {
402                grid rowconfigure $_frame $row -minsize 10
403            }
404
405            incr row
406            grid rowconfigure $_frame $row -minsize $itk_option(-padding)
407            incr row
408        }
409        grid $_frame.empty -row $row
410      }
411    }
412}
413
414# ----------------------------------------------------------------------
415# USAGE: _controlChanged <path>
416#
417# Invoked automatically whenever the value for the control with the
418# XML <path> changes.  Sends a notification along to the tool
419# controlling this panel.
420# ----------------------------------------------------------------------
421itcl::body Rappture::Controls::_controlChanged {path} {
422    if {"" != $_owner} {
423        $_owner changed $path
424    }
425}
426
427# ----------------------------------------------------------------------
428# USAGE: _formatLabel <string>
429#
430# Used internally to format a label <string>.  Trims any excess
431# white space and adds a ":" to the end.  That way, all labels
432# have a uniform look.
433# ----------------------------------------------------------------------
434itcl::body Rappture::Controls::_formatLabel {str} {
435    set str [string trim $str]
436    if {"" != $str && [string index $str end] != ":"} {
437        append str ":"
438    }
439    return $str
440}
441
442# ----------------------------------------------------------------------
443# USAGE: _changeTabs
444#
445# Used internally to change tabs when the user clicks on a tab
446# in the "tabs" layout mode.  This mode is used when the widget
447# contains nothing but groups, as a compact way of representing
448# the groups.
449# ----------------------------------------------------------------------
450itcl::body Rappture::Controls::_changeTabs {} {
451    set i [$_tabs index select]
452    set name [lindex $_controls $i]
453    if {"" != $name} {
454        foreach w [grid slaves $_frame] {
455            grid forget $w
456        }
457
458        set wv $_name2info($name-value)
459        grid $wv -row 0 -column 0 -sticky new
460    }
461}
462
463# ----------------------------------------------------------------------
464# OPTION: -padding
465# ----------------------------------------------------------------------
466itcl::configbody Rappture::Controls::padding {
467    $_dispatcher event -idle !layout
468}
Note: See TracBrowser for help on using the repository browser.