source: trunk/gui/scripts/deviceViewer1D.tcl @ 9

Last change on this file since 9 was 9, checked in by mmc, 16 years ago

Massive changes across the entire toolkit. Rearranged the
XML description to agree better with new documentation and
conventions.

Added a small start of Rappture.interface and Rappture.number
in the python directory. This is the new way of doing Rappture--
by declaring variables directly in the program, not using XML
directly at all.

File size: 21.8 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: deviceViewer1D - visualizer for 1D device geometries
3#
4#  This widget is a simple visualizer for 1D devices.  It takes the
5#  Rappture XML representation for a 1D device and draws various
6#  facets of the data.  Each facet shows the physical layout along
7#  with some other quantity.  The "Electrical" facet shows electrical
8#  contacts.  The "Doping" facet shows the doping profile, and so
9#  forth.
10# ======================================================================
11#  AUTHOR:  Michael McLennan, Purdue University
12#  Copyright (c) 2004  Purdue Research Foundation, West Lafayette, IN
13# ======================================================================
14package require Itk
15package require BLT
16
17option add *DeviceViewer1D.width 4i widgetDefault
18option add *DeviceViewer1D.height 4i widgetDefault
19option add *DeviceViewer1D.padding 4 widgetDefault
20option add *DeviceViewer1D.deviceSize 0.25i widgetDefault
21option add *DeviceViewer1D.deviceOutline black widgetDefault
22
23itcl::class Rappture::DeviceViewer1D {
24    inherit itk::Widget
25
26    itk_option define -device device Device ""
27    itk_option define -tool tool Tool ""
28
29    constructor {args} { # defined below }
30    destructor { # defined below }
31
32    public method controls {option args}
33                                                                               
34    protected method _fixTabs {}
35    protected method _changeTabs {}
36    protected method _fixAxes {}
37    protected method _align {}
38
39    protected method _marker {option {name ""} {path ""}}
40
41    protected method _controlCreate {container libObj path}
42    protected method _controlSet {widget libObj path}
43
44    private variable _device ""     ;# LibraryObj for device rep
45    private variable _tool ""       ;# LibraryObj for tool parameters
46    private variable _tab2fields    ;# maps tab name => list of fields
47    private variable _units ""      ;# units for field being edited
48    private variable _restrict ""   ;# restriction expr for field being edited
49    private variable _marker        ;# marker currently being edited
50}
51                                                                               
52itk::usual DeviceViewer1D {
53}
54
55# ----------------------------------------------------------------------
56# CONSTRUCTOR
57# ----------------------------------------------------------------------
58itcl::body Rappture::DeviceViewer1D::constructor {args} {
59    itk_option add hull.width hull.height
60    pack propagate $itk_component(hull) no
61
62    itk_component add tabs {
63        blt::tabset $itk_interior.tabs -borderwidth 0 -relief flat \
64            -side bottom -tearoff 0 \
65            -selectcommand [itcl::code $this _changeTabs]
66    } {
67        keep -activebackground -activeforeground
68        keep -background -cursor -font
69        rename -highlightbackground -background background Background
70        keep -highlightcolor -highlightthickness
71        keep -tabbackground -tabforeground
72        rename -selectbackground -background background Background
73        rename -selectforeground -foreground foreground Foreground
74    }
75    pack $itk_component(tabs) -expand yes -fill both
76
77    itk_component add -protected inner {
78        frame $itk_component(tabs).inner
79    }
80
81    itk_component add ambient {
82        frame $itk_component(inner).ambient
83    }
84    pack $itk_component(ambient) -side top -fill x
85
86    itk_component add layout {
87        Rappture::DeviceLayout1D $itk_component(inner).layout
88    }
89    pack $itk_component(layout) -side top -fill x -pady 4
90
91    itk_component add graph {
92        blt::graph $itk_component(inner).graph \
93            -highlightthickness 0 -plotpadx 0 -plotpady 0 \
94            -width 3i -height 3i
95    } {
96        keep -background -foreground -cursor -font
97    }
98    pack $itk_component(graph) -expand yes -fill both
99    $itk_component(graph) legend configure -hide yes
100
101    bind $itk_component(graph) <Configure> "
102        after cancel [itcl::code $this _fixAxes]
103        after idle [itcl::code $this _fixAxes]
104    "
105
106    itk_component add geditor {
107        Rappture::Editor $itk_component(graph).editor \
108            -activatecommand [itcl::code $this _marker activate] \
109            -validatecommand [itcl::code $this _marker validate] \
110            -applycommand [itcl::code $this _marker apply]
111    }
112
113    itk_component add devcntls {
114        Rappture::Notebook $itk_component(inner).devcntls
115    }
116    pack $itk_component(devcntls) -side bottom -fill x
117
118    eval itk_initialize $args
119}
120
121# ----------------------------------------------------------------------
122# DESTRUCTOR
123# ----------------------------------------------------------------------
124itcl::body Rappture::DeviceViewer1D::destructor {} {
125    set _device ""
126    foreach name [array names _tab2fields] {
127        eval itcl::delete object $_tab2fields($name)
128    }
129    after cancel [list catch [itcl::code $this _fixAxes]]
130    after cancel [list catch [itcl::code $this _align]]
131}
132
133# ----------------------------------------------------------------------
134# USAGE: controls add <parameter>
135# USAGE: controls remove <parameter>|all
136#
137# Clients use this to add a control to the internal panels of this
138# widget.  If the <parameter> is ambient*, then the control is added
139# to the top, so it goes along with the layout of the device.  If
140# it is structure.fields.field*, then it goes in one of the field
141# panels.
142# ----------------------------------------------------------------------
143itcl::body Rappture::DeviceViewer1D::controls {option args} {
144    switch -- $option {
145        add {
146            if {[llength $args] != 1} {
147                error "wrong # args: should be \"controls add parameter\""
148            }
149            set path [lindex $args 0]
150            if {[string match structure.fields.field* $path]} {
151            } elseif {[string match structure.components* $path]} {
152                $itk_component(layout) controls add $path
153            } else {
154                _controlCreate $itk_component(ambient) $_tool $path
155            }
156        }
157        remove {
158            error "not yet implemented"
159        }
160        default {
161            error "bad option \"$option\": should be add or remove"
162        }
163    }
164}
165
166# ----------------------------------------------------------------------
167# USAGE: _fixTabs
168#
169# Used internally to search for fields and create corresponding
170# tabs whenever a device is installed into this viewer.
171#
172# If there are no tabs, then the widget is packed so that it appears
173# directly.  Otherwise, the interior reconfigured and assigned to
174# the current tab.
175# ----------------------------------------------------------------------
176itcl::body Rappture::DeviceViewer1D::_fixTabs {} {
177    #
178    # Release any info left over from the last device.
179    #
180    foreach name [array names _tab2fields] {
181        eval itcl::delete object $_tab2fields($name)
182    }
183    catch {unset _tab2fields}
184
185    #
186    # Scan through the current device and extract the list of
187    # fields.  Create a tab for each field.
188    #
189    if {$_device != ""} {
190        foreach nn [$_device children fields] {
191            if {[string match field* $nn]} {
192                set name [$_device get $nn.label]
193                if {$name == ""} {
194                    set name $nn
195                }
196
197                set fobj [Rappture::Field ::#auto $_device $_device $nn]
198                lappend _tab2fields($name) $fobj
199            }
200        }
201    }
202    set tabs [lsort [array names _tab2fields]]
203
204    if {[$itk_component(tabs) size] > 0} {
205        $itk_component(tabs) delete 0 end
206    }
207
208    if {[llength $tabs] <= 0} {
209        #
210        # No fields or one field?  Then we don't need to bother
211        # with tabs.  Just pack the inner frame directly.  If
212        # there are no fields, get rid of the graph.
213        #
214        pack $itk_component(inner) -expand yes -fill both
215        if {[llength $tabs] > 0} {
216            pack $itk_component(graph) -expand yes -fill both
217        } else {
218            pack forget $itk_component(graph)
219            $itk_component(layout) configure -leftmargin 0 -rightmargin 0
220        }
221    } else {
222        #
223        # Two or more fields?  Then create a tab for each field
224        # and select the first one by default.  Make sure the
225        # graph is packed.
226        #
227        pack forget $itk_component(inner)
228        pack $itk_component(graph) -expand yes -fill both
229
230        foreach name $tabs {
231            $itk_component(tabs) insert end $name \
232                -activebackground $itk_option(-background)
233        }
234        $itk_component(tabs) select 0
235    }
236    _changeTabs
237
238    #
239    # Fix the right margin of the graph so that it has enough room
240    # to display the right-hand edge of the device.
241    #
242    $itk_component(graph) configure \
243        -rightmargin [$itk_component(layout) extents bar3D]
244}
245
246# ----------------------------------------------------------------------
247# USAGE: _changeTabs
248#
249# Used internally to change the field being displayed whenever a new
250# tab is selected.
251# ----------------------------------------------------------------------
252itcl::body Rappture::DeviceViewer1D::_changeTabs {} {
253    set graph $itk_component(graph)
254
255    #
256    # Figure out which tab is selected and make the inner frame
257    # visible in that tab.
258    #
259    set i [$itk_component(tabs) index select]
260    if {$i != ""} {
261        set name [$itk_component(tabs) get $i]
262        $itk_component(tabs) tab configure $name \
263            -window $itk_component(inner) -fill both
264    } else {
265        set name [lindex [array names _tab2fields] 0]
266    }
267
268    #
269    # Update the graph to show the current field.
270    #
271    eval $graph element delete [$graph element names]
272    eval $graph marker delete [$graph marker names]
273
274    foreach {zmin zmax} [$itk_component(layout) limits] { break }
275    if {$zmax > $zmin} {
276        $graph axis configure x -min $zmin -max $zmax -title "Position (um)"
277    }
278
279    # turn on auto limits
280    $graph axis configure y -min "" -max ""
281
282    set flist ""
283    if {[info exists _tab2fields($name)]} {
284        set flist $_tab2fields($name)
285    }
286
287    set n 0
288    foreach fobj $flist {
289        catch {unset hints}
290        array set hints [$fobj hints]
291
292        if {[info exists hints(units)]} {
293            set _units $hints(units)
294            $graph axis configure y -title "$name ($hints(units))"
295        } else {
296            set _units ""
297            $graph axis configure y -title $name
298        }
299
300        if {[info exists hints(restrict)]} {
301            set _restrict $hints(restrict)
302        } else {
303            set _restrict ""
304        }
305
306        if {[info exists hints(scale)]
307              && [string match log* $hints(scale)]} {
308            $graph axis configure y -logscale yes
309        } else {
310            $graph axis configure y -logscale no
311        }
312
313        foreach comp [$fobj components] {
314            set elem "elem[incr n]"
315            foreach {xv yv} [$fobj vectors $comp] { break }
316            $graph element create $elem -x $xv -y $yv -symbol "" -linewidth 2
317
318            if {[info exists hints(color)]} {
319                $graph element configure $elem -color $hints(color)
320            }
321
322            foreach {path x y val} [$fobj controls get $comp] {
323                $graph marker create text -coords [list $x $y] \
324                    -text $val -anchor s -name $comp.$x -background ""
325                $graph marker bind $comp.$x <Enter> \
326                    [itcl::code $this _marker enter $comp.$x]
327                $graph marker bind $comp.$x <Leave> \
328                    [itcl::code $this _marker leave $comp.$x]
329                $graph marker bind $comp.$x <ButtonPress> \
330                    [itcl::code $this _marker edit $comp.$x $fobj/$path]
331            }
332        }
333    }
334
335    # let the widget settle, then fix the axes to "nice" values
336    after cancel [itcl::code $this _fixAxes]
337    after 20 [itcl::code $this _fixAxes]
338}
339
340# ----------------------------------------------------------------------
341# USAGE: _fixAxes
342#
343# Used internally to adjust the y-axis limits of the graph to "nice"
344# values, so that any control marker associated with the value,
345# for example, remains on screen.
346# ----------------------------------------------------------------------
347itcl::body Rappture::DeviceViewer1D::_fixAxes {} {
348    set graph $itk_component(graph)
349
350    #
351    # HACK ALERT!
352    # Use this code to fix up the y-axis limits for the BLT graph.
353    # The auto-limits don't always work well.  We want them to be
354    # set to a "nice" number slightly above or below the min/max
355    # limits.
356    #
357    set log [$graph axis cget y -logscale]
358    foreach {min max} [$graph axis limits y] { break }
359
360    if {$log} {
361        set min [expr {0.9*$min}]
362        set max [expr {1.1*$max}]
363    } else {
364        if {$min > 0} {
365            set min [expr {0.95*$min}]
366        } else {
367            set min [expr {1.05*$min}]
368        }
369        if {$max > 0} {
370            set max [expr {1.05*$max}]
371        } else {
372            set max [expr {0.95*$max}]
373        }
374    }
375
376    # bump up the max so that it's big enough to show control markers
377    set fnt $itk_option(-font)
378    set h [expr {[font metrics $fnt -linespace] + 5}]
379    foreach mname [$graph marker names] {
380        set xy [$graph marker cget $mname -coord]
381        foreach {x y} [eval $graph transform $xy] { break }
382        set y [expr {$y-$h}]  ;# find top of text in pixels
383        foreach {x y} [eval $graph invtransform [list 0 $y]] { break }
384        if {$y > $max} { set max $y }
385    }
386
387    if {$log} {
388        set min [expr {pow(10.0,floor(log10($min)))}]
389        set max [expr {pow(10.0,ceil(log10($max)))}]
390    } else {
391        set min [expr {0.1*floor(10*$min)}]
392        set max [expr {0.1*ceil(10*$max)}]
393    }
394
395    $graph axis configure y -min $min -max $max
396
397    after cancel [list catch [itcl::code $this _align]]
398    after 100 [list catch [itcl::code $this _align]]
399}
400
401# ----------------------------------------------------------------------
402# USAGE: _align
403#
404# Used internally to align the margins of the device layout and the
405# graph, so that two areas line up.
406# ----------------------------------------------------------------------
407itcl::body Rappture::DeviceViewer1D::_align {} {
408    set graph $itk_component(graph)
409
410    #
411    # Set the left/right margins of the layout so that it aligns
412    # with the graph.  Set the right margin of the graph so it
413    # it is big enough to show the 3D edge of the device that
414    # hangs over on the right-hand side.
415    #
416    update
417    foreach {xmin xmax} [$graph axis limits x] { break }
418    set lm [$graph xaxis transform $xmin]
419    $itk_component(layout) configure -leftmargin $lm
420
421    set w [winfo width $graph]
422    set rm [expr {$w-[$graph xaxis transform $xmax]}]
423    $itk_component(layout) configure -rightmargin $rm
424}
425
426# ----------------------------------------------------------------------
427# USAGE: _marker enter <name>
428# USAGE: _marker leave <name>
429# USAGE: _marker edit <name> <path>
430# USAGE: _marker activate
431# USAGE: _marker validate <value>
432# USAGE: _marker apply <value>
433#
434# Used internally to manipulate the control markers draw on the
435# graph for a field.
436# ----------------------------------------------------------------------
437itcl::body Rappture::DeviceViewer1D::_marker {option {name ""} {path ""}} {
438    switch -- $option {
439        enter {
440            $itk_component(graph) marker configure $name -background #e5e5e5
441        }
442        leave {
443            $itk_component(graph) marker configure $name -background ""
444        }
445        edit {
446            set _marker(name) $name
447            set _marker(fobj) [lindex [split $path /] 0]
448            set _marker(path) [lindex [split $path /] 1]
449            $itk_component(geditor) activate
450        }
451        activate {
452            set g $itk_component(graph)
453            set val [$g marker cget $_marker(name) -text]
454            foreach {x y} [$g marker cget $_marker(name) -coords] { break }
455            foreach {x y} [$g transform $x $y] { break }
456            set x [expr {$x + [winfo rootx $g] - 4}]
457            set y [expr {$y + [winfo rooty $g] - 5}]
458
459            set fnt $itk_option(-font)
460            set h [expr {[font metrics $fnt -linespace] + 2}]
461            set w [expr {[font measure $fnt $val] + 5}]
462
463            return [list text $val \
464                x [expr {$x-$w/2}] \
465                y [expr {$y-$h}] \
466                w $w \
467                h $h]
468        }
469        validate {
470            if {$_units != ""} {
471                if {[catch {Rappture::Units::convert $name \
472                        -context $_units -to $_units} result] != 0} {
473                    if {[regexp {^bad.*: +(.)(.+)} $result match first tail]
474                          || [regexp {(.)(.+)} $result match first tail]} {
475                        set result "[string toupper $first]$tail"
476                    }
477                    bell
478                    Rappture::Tooltip::cue $itk_component(geditor) $result
479                    return 0
480                }
481                if {"" != $_restrict
482                      && [catch {Rappture::Units::convert $result \
483                        -context $_units -to $_units -units off} value] == 0} {
484
485                    set rexpr $_restrict
486                    regsub -all value $rexpr {$value} rexpr
487                    if {[catch {expr $rexpr} result] == 0 && !$result} {
488                        bell
489                        Rappture::Tooltip::cue $itk_component(geditor) "Should satisfy the condition: $_restrict"
490                        return 0
491                    }
492                }
493            }
494            return 1
495        }
496        apply {
497            if {$_units != ""} {
498                catch {Rappture::Units::convert $name \
499                    -context $_units -to $_units} value
500            } else {
501                set value $name
502            }
503
504            $_marker(fobj) controls put $_marker(path) $value
505            event generate $itk_component(hull) <<Edit>>
506
507            _changeTabs
508        }
509    }
510}
511
512# ----------------------------------------------------------------------
513# USAGE: _controlCreate <container> <libObj> <path>
514#
515# Used internally to create a gauge widget and pack it into the
516# given <container>.  When the gauge is set, it updates the value
517# for the <path> in the <libObj>.
518# ----------------------------------------------------------------------
519itcl::body Rappture::DeviceViewer1D::_controlCreate {container libObj path} {
520    set presets ""
521    foreach pre [$libObj children -type preset $path] {
522        lappend presets \
523            [$libObj get $path.$pre.value] \
524            [$libObj get $path.$pre.label]
525    }
526
527    set type Rappture::Gauge
528    set units [$libObj get $path.units]
529    if {$units != ""} {
530        set desc [Rappture::Units::description $units]
531        if {[string match temperature* $desc]} {
532            set type Rappture::TemperatureGauge
533        }
534    }
535
536    set counter 0
537    set w "$container.gauge[incr counter]"
538    while {[winfo exists $w]} {
539        set w "$container.gauge[incr counter]"
540    }
541
542    # create the widget
543    $type $w -units $units -presets $presets
544    pack $w -side top -anchor w
545    bind $w <<Value>> [itcl::code $this _controlSet $w $libObj $path]
546
547    set min [$libObj get $path.min]
548    if {"" != $min} { $w configure -minvalue $min }
549
550    set max [$libObj get $path.max]
551    if {"" != $max} { $w configure -maxvalue $max }
552
553    set str [$libObj get $path.default]
554    if {$str != ""} { $w value $str }
555
556    if {$type == "Rappture::Gauge" && "" != $min && "" != $max} {
557        set color [$libObj get $path.color]
558        if {$color == ""} {
559            set color blue
560        }
561        if {$units != ""} {
562            set min [Rappture::Units::convert $min -to $units -units off]
563            set max [Rappture::Units::convert $max -to $units -units off]
564        }
565        $w configure -spectrum [Rappture::Spectrum ::#auto [list \
566            $min white $max $color] -units $units]
567    }
568
569    set str [$libObj get $path.label]
570    if {$str != ""} {
571        set help [$libObj get $path.help]
572        if {"" != $help} {
573            append str "\n$help"
574        }
575        if {$units != ""} {
576            set desc [Rappture::Units::description $units]
577            append str "\n(units of $desc)"
578        }
579        Rappture::Tooltip::for $w $str
580    }
581
582    set str [$libObj get $path.icon]
583    if {$str != ""} {
584        $w configure -image [image create photo -data $str]
585    }
586}
587
588# ----------------------------------------------------------------------
589# USAGE: _controlSet <widget> <libObj> <path>
590#
591# Invoked automatically whenever an internal control changes value.
592# Queries the new value for the control and assigns the value to the
593# given <path> on the XML object <libObj>.
594# ----------------------------------------------------------------------
595itcl::body Rappture::DeviceViewer1D::_controlSet {widget libObj path} {
596    set newval [$widget value]
597    $libObj put $path.current $newval
598    event generate $itk_component(hull) <<Edit>>
599}
600
601# ----------------------------------------------------------------------
602# CONFIGURATION OPTION: -device
603#
604# Set to the Rappture::Library object representing the device being
605# displayed in the viewer.  If set to "", the viewer is cleared to
606# display nothing.
607# ----------------------------------------------------------------------
608itcl::configbody Rappture::DeviceViewer1D::device {
609    if {$itk_option(-device) != ""} {
610        if {![Rappture::library isvalid $itk_option(-device)]} {
611            error "bad value \"$itk_option(-device)\": should be Rappture::Library"
612        }
613    }
614    set _device $itk_option(-device)
615    _fixTabs
616}
617
618# ----------------------------------------------------------------------
619# CONFIGURATION OPTION: -tool
620#
621# Set to the Rappture::Library object containing tool parameters.
622# Needed only if controls are added to the widget, so the controls
623# can update the tool parameters.
624# ----------------------------------------------------------------------
625itcl::configbody Rappture::DeviceViewer1D::tool {
626    if {$itk_option(-tool) != ""} {
627        if {![Rappture::library isvalid $itk_option(-tool)]} {
628            error "bad value \"$itk_option(-tool)\": should be Rappture::Library"
629        }
630    }
631    set _tool $itk_option(-tool)
632}
Note: See TracBrowser for help on using the repository browser.