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

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

Major reorganization of the entire package. The config.xml file
is now irrelevant. All the action is in the tool.xml file. The
main program now organizes all input into 1) side-by-side pages,
2) input/result (wizard-style) pages, or 3) a series of wizard-
style pages. The <input> can have <phase> parts representing
the various pages.

Added a new ContourResult? widget based on Swaroop's vtk plotting
code.

Also, added easymesh and showmesh to the "tools" directory.
We need these for Eric Polizzi's code.

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