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

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