source: trunk/gui/scripts/vtkstreamlinesviewer.tcl @ 3394

Last change on this file since 3394 was 3394, checked in by gah, 11 years ago

fix up stats reporting in servers

File size: 90.3 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: vtkviewer - Vtk drawing object viewer
4#
5#  It connects to the Vtk server running on a rendering farm,
6#  transmits data, and displays the results.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
10#
11#  See the file "license.terms" for information on usage and
12#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13# ======================================================================
14package require Itk
15package require BLT
16package require Img
17
18option add *VtkStreamlinesViewer.width 4i widgetDefault
19option add *VtkStreamlinesViewer*cursor crosshair widgetDefault
20option add *VtkStreamlinesViewer.height 4i widgetDefault
21option add *VtkStreamlinesViewer.foreground black widgetDefault
22option add *VtkStreamlinesViewer.controlBackground gray widgetDefault
23option add *VtkStreamlinesViewer.controlDarkBackground #999999 widgetDefault
24option add *VtkStreamlinesViewer.plotBackground black widgetDefault
25option add *VtkStreamlinesViewer.plotForeground white widgetDefault
26option add *VtkStreamlinesViewer.font \
27    -*-helvetica-medium-r-normal-*-12-* widgetDefault
28
29# must use this name -- plugs into Rappture::resources::load
30proc VtkStreamlinesViewer_init_resources {} {
31    Rappture::resources::register \
32        vtkvis_server Rappture::VtkStreamlinesViewer::SetServerList
33}
34
35itcl::class Rappture::VtkStreamlinesViewer {
36    inherit Rappture::VisViewer
37
38    itk_option define -plotforeground plotForeground Foreground ""
39    itk_option define -plotbackground plotBackground Background ""
40
41    constructor { hostlist args } {
42        Rappture::VisViewer::constructor $hostlist
43    } {
44        # defined below
45    }
46    destructor {
47        # defined below
48    }
49    public proc SetServerList { namelist } {
50        Rappture::VisViewer::SetServerList "vtkvis" $namelist
51    }
52    public method add {dataobj {settings ""}}
53    public method camera {option args}
54    public method delete {args}
55    public method disconnect {}
56    public method download {option args}
57    public method get {args}
58    public method isconnected {}
59    public method limits { colormap }
60    public method sendto { string }
61    public method parameters {title args} {
62        # do nothing
63    }
64    public method scale {args}
65
66    protected method Connect {}
67    protected method CurrentDatasets {args}
68    protected method Disconnect {}
69    protected method DoResize {}
70    protected method DoReseed {}
71    protected method DoRotate {}
72    protected method AdjustSetting {what {value ""}}
73    protected method InitSettings { args  }
74    protected method Pan {option x y}
75    protected method Pick {x y}
76    protected method Rebuild {}
77    protected method ReceiveDataset { args }
78    protected method ReceiveImage { args }
79    protected method ReceiveLegend { colormap title vmin vmax size }
80    protected method Rotate {option x y}
81    protected method SendCmd {string}
82    protected method SendCmdNoSplash {string}
83    protected method Zoom {option}
84
85    # The following methods are only used by this class.
86    private method BuildAxisTab {}
87    private method BuildCameraTab {}
88    private method BuildColormap { name colors }
89    private method BuildCutplaneTab {}
90    private method BuildDownloadPopup { widget command }
91    private method BuildStreamsTab {}
92    private method BuildVolumeTab {}
93    private method ConvertToVtkData { dataobj comp }
94    private method DrawLegend { title }
95    private method Combo { option }
96    private method EnterLegend { x y }
97    private method EventuallyResize { w h }
98    private method EventuallyReseed { numPoints }
99    private method EventuallyRotate { q }
100    private method EventuallySetCutplane { axis args }
101    private method GetImage { args }
102    private method GetVtkData { args }
103    private method IsValidObject { dataobj }
104    private method LeaveLegend {}
105    private method MotionLegend { x y }
106    private method PanCamera {}
107    private method RequestLegend {}
108    private method SetColormap { dataobj comp }
109    private method ChangeColormap { dataobj comp color }
110    private method SetLegendTip { x y }
111    private method SetObjectStyle { dataobj comp }
112    private method Slice {option args}
113
114    private variable _arcball ""
115    private variable _outbuf       ;    # buffer for outgoing commands
116
117    private variable _dlist ""     ;    # list of data objects
118    private variable _obj2datasets
119    private variable _obj2ovride   ;    # maps dataobj => style override
120    private variable _datasets     ;    # contains all the dataobj-component
121                                   ;    # datasets in the server
122    private variable _colormaps    ;    # contains all the colormaps
123                                   ;    # in the server.
124    private variable _dataset2style    ;# maps dataobj-component to transfunc
125
126    private variable _click        ;    # info used for rotate operations
127    private variable _limits       ;    # autoscale min/max for all axes
128    private variable _view         ;    # view params for 3D view
129    private variable _settings
130    private variable _style;            # Array of current component styles.
131    private variable _initialStyle;     # Array of initial component styles.
132    private variable _reset 1;          # indicates if camera needs to be reset
133                                        # to starting position.
134
135    private variable _first ""     ;    # This is the topmost dataset.
136    private variable _start 0
137    private variable _buffering 0
138    private variable _title ""
139    private variable _seeds
140
141    common _downloadPopup;              # download options from popup
142    private common _hardcopy
143    private variable _width 0
144    private variable _height 0
145    private variable _resizePending 0
146    private variable _reseedPending 0
147    private variable _rotatePending 0
148    private variable _cutplanePending 0
149    private variable _legendPending 0
150    private variable _outline
151    private variable _vectorFields
152    private variable _scalarFields
153    private variable _fields
154    private variable _currentField ""
155    private variable _field      ""
156    private variable _numSeeds 200
157    private variable _colorMode "vmag";#  Mode of colormap (vmag or scalar)
158}
159
160itk::usual VtkStreamlinesViewer {
161    keep -background -foreground -cursor -font
162    keep -plotbackground -plotforeground
163}
164
165# ----------------------------------------------------------------------
166# CONSTRUCTOR
167# ----------------------------------------------------------------------
168itcl::body Rappture::VtkStreamlinesViewer::constructor {hostlist args} {
169    package require vtk
170    set _serverType "vtkvis"
171
172    # Rebuild event
173    $_dispatcher register !rebuild
174    $_dispatcher dispatch $this !rebuild "[itcl::code $this Rebuild]; list"
175
176    # Resize event
177    $_dispatcher register !resize
178    $_dispatcher dispatch $this !resize "[itcl::code $this DoResize]; list"
179
180    # Reseed event
181    $_dispatcher register !reseed
182    $_dispatcher dispatch $this !reseed "[itcl::code $this DoReseed]; list"
183
184    # Rotate event
185    $_dispatcher register !rotate
186    $_dispatcher dispatch $this !rotate "[itcl::code $this DoRotate]; list"
187
188    # Legend event
189    $_dispatcher register !legend
190    $_dispatcher dispatch $this !legend "[itcl::code $this RequestLegend]; list"
191
192    # X-Cutplane event
193    $_dispatcher register !xcutplane
194    $_dispatcher dispatch $this !xcutplane \
195        "[itcl::code $this AdjustSetting cutplaneXPosition]; list"
196
197    # Y-Cutplane event
198    $_dispatcher register !ycutplane
199    $_dispatcher dispatch $this !ycutplane \
200        "[itcl::code $this AdjustSetting cutplaneYPosition]; list"
201
202    # Z-Cutplane event
203    $_dispatcher register !zcutplane
204    $_dispatcher dispatch $this !zcutplane \
205        "[itcl::code $this AdjustSetting cutplaneZPosition]; list"
206
207    #
208    # Populate parser with commands handle incoming requests
209    #
210    $_parser alias image [itcl::code $this ReceiveImage]
211    $_parser alias dataset [itcl::code $this ReceiveDataset]
212    $_parser alias legend [itcl::code $this ReceiveLegend]
213
214    array set _outline {
215        id -1
216        afterId -1
217        x1 -1
218        y1 -1
219        x2 -1
220        y2 -1
221    }
222    # Initialize the view to some default parameters.
223    array set _view {
224        qw              0.853553
225        qx              -0.353553
226        qy              0.353553
227        qz              0.146447
228        zoom            1.0
229        xpan            0
230        ypan            0
231        ortho           0
232    }
233    set _arcball [blt::arcball create 100 100]
234    set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)]
235    $_arcball quaternion $q
236
237    set _limits(zmin) 0.0
238    set _limits(zmax) 1.0
239
240    array set _settings [subst {
241        axesVisible             1
242        axisLabelsVisible       1
243        axisXGrid               0
244        axisYGrid               0
245        axisZGrid               0
246        cutplaneEdges           0
247        cutplaneLighting        1
248        cutplaneOpacity         100
249        cutplaneVisible         0
250        cutplaneWireframe       0
251        cutplaneXPosition       50
252        cutplaneXVisible        1
253        cutplaneYPosition       50
254        cutplaneYVisible        1
255        cutplaneZPosition       50
256        cutplaneZVisible        1
257        legendVisible           1
258        streamlinesLighting     1
259        streamlinesMode         lines
260        streamlinesNumSeeds     200
261        streamlinesOpacity      100
262        streamlinesScale        1
263        streamlinesSeedsVisible 0
264        streamlinesVisible      1
265        volumeEdges             0
266        volumeLighting          1
267        volumeOpacity           40
268        volumeVisible           1
269        volumeWireframe         0
270    }]
271
272    itk_component add view {
273        canvas $itk_component(plotarea).view \
274            -highlightthickness 0 -borderwidth 0
275    } {
276        usual
277        ignore -highlightthickness -borderwidth  -background
278    }
279
280    itk_component add fieldmenu {
281        menu $itk_component(plotarea).menu -bg black -fg white -relief flat \
282            -tearoff no
283    } {
284        usual
285        ignore -background -foreground -relief -tearoff
286    }
287    set c $itk_component(view)
288    bind $c <Configure> [itcl::code $this EventuallyResize %w %h]
289    bind $c <4> [itcl::code $this Zoom in 0.25]
290    bind $c <5> [itcl::code $this Zoom out 0.25]
291    bind $c <KeyPress-Left>  [list %W xview scroll 10 units]
292    bind $c <KeyPress-Right> [list %W xview scroll -10 units]
293    bind $c <KeyPress-Up>    [list %W yview scroll 10 units]
294    bind $c <KeyPress-Down>  [list %W yview scroll -10 units]
295    bind $c <Enter> "focus %W"
296    bind $c <Control-F1> [itcl::code $this ToggleConsole]
297
298    # Fix the scrollregion in case we go off screen
299    $c configure -scrollregion [$c bbox all]
300
301    set _map(id) [$c create image 0 0 -anchor nw -image $_image(plot)]
302    set _map(cwidth) -1
303    set _map(cheight) -1
304    set _map(zoom) 1.0
305    set _map(original) ""
306
307    set f [$itk_component(main) component controls]
308    itk_component add reset {
309        button $f.reset -borderwidth 1 -padx 1 -pady 1 \
310            -highlightthickness 0 \
311            -image [Rappture::icon reset-view] \
312            -command [itcl::code $this Zoom reset]
313    } {
314        usual
315        ignore -highlightthickness
316    }
317    pack $itk_component(reset) -side top -padx 2 -pady 2
318    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
319
320    itk_component add zoomin {
321        button $f.zin -borderwidth 1 -padx 1 -pady 1 \
322            -highlightthickness 0 \
323            -image [Rappture::icon zoom-in] \
324            -command [itcl::code $this Zoom in]
325    } {
326        usual
327        ignore -highlightthickness
328    }
329    pack $itk_component(zoomin) -side top -padx 2 -pady 2
330    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
331
332    itk_component add zoomout {
333        button $f.zout -borderwidth 1 -padx 1 -pady 1 \
334            -highlightthickness 0 \
335            -image [Rappture::icon zoom-out] \
336            -command [itcl::code $this Zoom out]
337    } {
338        usual
339        ignore -highlightthickness
340    }
341    pack $itk_component(zoomout) -side top -padx 2 -pady 2
342    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
343
344    itk_component add volume {
345        Rappture::PushButton $f.volume \
346            -onimage [Rappture::icon volume-on] \
347            -offimage [Rappture::icon volume-off] \
348            -variable [itcl::scope _settings(volumeVisible)] \
349            -command [itcl::code $this AdjustSetting volumeVisible]
350    }
351    $itk_component(volume) select
352    Rappture::Tooltip::for $itk_component(volume) \
353        "Don't display the volume"
354    pack $itk_component(volume) -padx 2 -pady 2
355
356    itk_component add streamlines {
357        Rappture::PushButton $f.streamlines \
358            -onimage [Rappture::icon streamlines-on] \
359            -offimage [Rappture::icon streamlines-off] \
360            -variable [itcl::scope _settings(streamlinesVisible)] \
361            -command [itcl::code $this AdjustSetting streamlinesVisible] \
362    }
363    $itk_component(streamlines) select
364    Rappture::Tooltip::for $itk_component(streamlines) \
365        "Toggle the streamlines on/off"
366    pack $itk_component(streamlines) -padx 2 -pady 2
367
368    itk_component add cutplane {
369        Rappture::PushButton $f.cutplane \
370            -onimage [Rappture::icon cutbutton] \
371            -offimage [Rappture::icon cutbutton] \
372            -variable [itcl::scope _settings(cutplaneVisible)] \
373            -command [itcl::code $this AdjustSetting cutplaneVisible]
374    }
375    Rappture::Tooltip::for $itk_component(cutplane) \
376        "Show/Hide cutplanes"
377    pack $itk_component(cutplane) -padx 2 -pady 2
378
379
380    if { [catch {
381        BuildVolumeTab
382        BuildStreamsTab
383        BuildCutplaneTab
384        BuildAxisTab
385        BuildCameraTab
386    } errs] != 0 } {
387        puts stderr errs=$errs
388    }
389    # Legend
390
391    set _image(legend) [image create photo]
392    itk_component add legend {
393        canvas $itk_component(plotarea).legend -width 50 -highlightthickness 0
394    } {
395        usual
396        ignore -highlightthickness
397        rename -background -plotbackground plotBackground Background
398    }
399
400    # Hack around the Tk panewindow.  The problem is that the requested
401    # size of the 3d view isn't set until an image is retrieved from
402    # the server.  So the panewindow uses the tiny size.
403    set w 10000
404    pack forget $itk_component(view)
405    blt::table $itk_component(plotarea) \
406        0,0 $itk_component(view) -fill both -reqwidth $w
407    blt::table configure $itk_component(plotarea) c1 -resize none
408
409    # Bindings for rotation via mouse
410    bind $itk_component(view) <ButtonPress-1> \
411        [itcl::code $this Rotate click %x %y]
412    bind $itk_component(view) <B1-Motion> \
413        [itcl::code $this Rotate drag %x %y]
414    bind $itk_component(view) <ButtonRelease-1> \
415        [itcl::code $this Rotate release %x %y]
416    bind $itk_component(view) <Configure> \
417        [itcl::code $this EventuallyResize %w %h]
418
419    if 0 {
420    bind $itk_component(view) <Configure> \
421        [itcl::code $this EventuallyResize %w %h]
422    }
423    # Bindings for panning via mouse
424    bind $itk_component(view) <ButtonPress-2> \
425        [itcl::code $this Pan click %x %y]
426    bind $itk_component(view) <B2-Motion> \
427        [itcl::code $this Pan drag %x %y]
428    bind $itk_component(view) <ButtonRelease-2> \
429        [itcl::code $this Pan release %x %y]
430
431    bind $itk_component(view) <ButtonRelease-3> \
432        [itcl::code $this Pick %x %y]
433
434    # Bindings for panning via keyboard
435    bind $itk_component(view) <KeyPress-Left> \
436        [itcl::code $this Pan set -10 0]
437    bind $itk_component(view) <KeyPress-Right> \
438        [itcl::code $this Pan set 10 0]
439    bind $itk_component(view) <KeyPress-Up> \
440        [itcl::code $this Pan set 0 -10]
441    bind $itk_component(view) <KeyPress-Down> \
442        [itcl::code $this Pan set 0 10]
443    bind $itk_component(view) <Shift-KeyPress-Left> \
444        [itcl::code $this Pan set -2 0]
445    bind $itk_component(view) <Shift-KeyPress-Right> \
446        [itcl::code $this Pan set 2 0]
447    bind $itk_component(view) <Shift-KeyPress-Up> \
448        [itcl::code $this Pan set 0 -2]
449    bind $itk_component(view) <Shift-KeyPress-Down> \
450        [itcl::code $this Pan set 0 2]
451
452    # Bindings for zoom via keyboard
453    bind $itk_component(view) <KeyPress-Prior> \
454        [itcl::code $this Zoom out]
455    bind $itk_component(view) <KeyPress-Next> \
456        [itcl::code $this Zoom in]
457
458    bind $itk_component(view) <Enter> "focus $itk_component(view)"
459
460    if {[string equal "x11" [tk windowingsystem]]} {
461        # Bindings for zoom via mouse
462        bind $itk_component(view) <4> [itcl::code $this Zoom out]
463        bind $itk_component(view) <5> [itcl::code $this Zoom in]
464    }
465
466    set _image(download) [image create photo]
467
468    eval itk_initialize $args
469    Connect
470    update
471}
472
473# ----------------------------------------------------------------------
474# DESTRUCTOR
475# ----------------------------------------------------------------------
476itcl::body Rappture::VtkStreamlinesViewer::destructor {} {
477    Disconnect
478    image delete $_image(plot)
479    image delete $_image(download)
480    catch { blt::arcball destroy $_arcball }
481}
482
483itcl::body Rappture::VtkStreamlinesViewer::DoResize {} {
484    if { $_width < 2 } {
485        set _width 500
486    }
487    if { $_height < 2 } {
488        set _height 500
489    }
490    set _start [clock clicks -milliseconds]
491    SendCmd "screen size $_width $_height"
492    set _legendPending 1
493
494    #SendCmd "imgflush"
495
496    # Must reset camera to have object scaling to take effect.
497    SendCmd "camera reset"
498    #SendCmd "camera zoom $_view(zoom)"
499    set _resizePending 0
500}
501
502itcl::body Rappture::VtkStreamlinesViewer::DoRotate {} {
503    set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)]
504    SendCmd "camera orient $q"
505    set _rotatePending 0
506}
507
508itcl::body Rappture::VtkStreamlinesViewer::DoReseed {} {
509    foreach dataset [CurrentDatasets -visible] {
510        foreach {dataobj comp} [split $dataset -] break
511        # This command works for either random or fmesh seeds
512        SendCmd "streamlines seed numpts $_numSeeds $dataset"
513    }
514    set _reseedPending 0
515}
516
517itcl::body Rappture::VtkStreamlinesViewer::EventuallyResize { w h } {
518    set _width $w
519    set _height $h
520    $_arcball resize $w $h
521    if { !$_resizePending } {
522        set _resizePending 1
523        $_dispatcher event -after 400 !resize
524    }
525}
526
527itcl::body Rappture::VtkStreamlinesViewer::EventuallyReseed { numPoints } {
528    set _numSeeds $numPoints
529    if { !$_reseedPending } {
530        set _reseedPending 1
531        $_dispatcher event -after 600 !reseed
532    }
533}
534
535set rotate_delay 100
536
537itcl::body Rappture::VtkStreamlinesViewer::EventuallyRotate { q } {
538    foreach { _view(qw) _view(qx) _view(qy) _view(qz) } $q break
539    if { !$_rotatePending } {
540        set _rotatePending 1
541        global rotate_delay
542        $_dispatcher event -after $rotate_delay !rotate
543    }
544}
545
546itcl::body Rappture::VtkStreamlinesViewer::EventuallySetCutplane { axis args } {
547    if { !$_cutplanePending } {
548        set _cutplanePending 1
549        $_dispatcher event -after 100 !${axis}cutplane
550    }
551}
552
553# ----------------------------------------------------------------------
554# USAGE: add <dataobj> ?<settings>?
555#
556# Clients use this to add a data object to the plot.  The optional
557# <settings> are used to configure the plot.  Allowed settings are
558# -color, -brightness, -width, -linestyle, and -raise.
559# ----------------------------------------------------------------------
560itcl::body Rappture::VtkStreamlinesViewer::add {dataobj {settings ""}} {
561    array set params {
562        -color auto
563        -width 1
564        -linestyle solid
565        -brightness 0
566        -raise 0
567        -description ""
568        -param ""
569        -type ""
570    }
571    array set params $settings
572    set params(-description) ""
573    set params(-param) ""
574    foreach {opt val} $settings {
575        if {![info exists params($opt)]} {
576            error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]"
577        }
578        set params($opt) $val
579    }
580    if {$params(-color) == "auto" || $params(-color) == "autoreset"} {
581        # can't handle -autocolors yet
582        set params(-color) black
583    }
584    set pos [lsearch -exact $dataobj $_dlist]
585    if {$pos < 0} {
586        lappend _dlist $dataobj
587    }
588    set _obj2ovride($dataobj-color) $params(-color)
589    set _obj2ovride($dataobj-width) $params(-width)
590    set _obj2ovride($dataobj-raise) $params(-raise)
591    $_dispatcher event -idle !rebuild
592}
593
594
595# ----------------------------------------------------------------------
596# USAGE: delete ?<dataobj1> <dataobj2> ...?
597#
598#       Clients use this to delete a dataobj from the plot.  If no dataobjs
599#       are specified, then all dataobjs are deleted.  No data objects are
600#       deleted.  They are only removed from the display list.
601#
602# ----------------------------------------------------------------------
603itcl::body Rappture::VtkStreamlinesViewer::delete {args} {
604    if { [llength $args] == 0} {
605        set args $_dlist
606    }
607    # Delete all specified dataobjs
608    set changed 0
609    foreach dataobj $args {
610        set pos [lsearch -exact $_dlist $dataobj]
611        if { $pos < 0 } {
612            continue;                   # Don't know anything about it.
613        }
614        # Remove it from the dataobj list.
615        set _dlist [lreplace $_dlist $pos $pos]
616        SendCmd "dataset visible 0"
617        array unset _obj2ovride $dataobj-*
618        array unset _settings $dataobj-*
619        # Append to the end of the dataobj list.
620        lappend _dlist $dataobj
621        set changed 1
622    }
623    # If anything changed, then rebuild the plot
624    if { $changed } {
625        $_dispatcher event -idle !rebuild
626    }
627}
628
629# ----------------------------------------------------------------------
630# USAGE: get ?-objects?
631# USAGE: get ?-visible?
632# USAGE: get ?-image view?
633#
634# Clients use this to query the list of objects being plotted, in
635# order from bottom to top of this result.  The optional "-image"
636# flag can also request the internal images being shown.
637# ----------------------------------------------------------------------
638itcl::body Rappture::VtkStreamlinesViewer::get {args} {
639    if {[llength $args] == 0} {
640        set args "-objects"
641    }
642
643    set op [lindex $args 0]
644    switch -- $op {
645        "-objects" {
646            # put the dataobj list in order according to -raise options
647            set dlist {}
648            foreach dataobj $_dlist {
649                if { ![IsValidObject $dataobj] } {
650                    continue
651                }
652                if {[info exists _obj2ovride($dataobj-raise)] &&
653                    $_obj2ovride($dataobj-raise)} {
654                    set dlist [linsert $dlist 0 $dataobj]
655                } else {
656                    lappend dlist $dataobj
657                }
658            }
659            return $dlist
660        }
661        "-visible" {
662            set dlist {}
663            foreach dataobj $_dlist {
664                if { ![IsValidObject $dataobj] } {
665                    continue
666                }
667                if { ![info exists _obj2ovride($dataobj-raise)] } {
668                    # No setting indicates that the object isn't visible.
669                    continue
670                }
671                # Otherwise use the -raise parameter to put the object to
672                # the front of the list.
673                if { $_obj2ovride($dataobj-raise) } {
674                    set dlist [linsert $dlist 0 $dataobj]
675                } else {
676                    lappend dlist $dataobj
677                }
678            }
679            return $dlist
680        }           
681        -image {
682            if {[llength $args] != 2} {
683                error "wrong # args: should be \"get -image view\""
684            }
685            switch -- [lindex $args end] {
686                view {
687                    return $_image(plot)
688                }
689                default {
690                    error "bad image name \"[lindex $args end]\": should be view"
691                }
692            }
693        }
694        default {
695            error "bad option \"$op\": should be -objects or -image"
696        }
697    }
698}
699
700# ----------------------------------------------------------------------
701# USAGE: scale ?<data1> <data2> ...?
702#
703# Sets the default limits for the overall plot according to the
704# limits of the data for all of the given <data> objects.  This
705# accounts for all objects--even those not showing on the screen.
706# Because of this, the limits are appropriate for all objects as
707# the user scans through data in the ResultSet viewer.
708# ----------------------------------------------------------------------
709itcl::body Rappture::VtkStreamlinesViewer::scale {args} {
710    array unset _limits
711    foreach dataobj $args {
712        set string [limits $dataobj]
713        if { $string == "" } {
714            continue
715        }
716        array set bounds $string
717        if {![info exists _limits(xmin)] || $_limits(xmin) > $bounds(xmin)} {
718            set _limits(xmin) $bounds(xmin)
719        }
720        if {![info exists _limits(xmax)] || $_limits(xmax) < $bounds(xmax)} {
721            set _limits(xmax) $bounds(xmax)
722        }
723
724        if {![info exists _limits(ymin)] || $_limits(ymin) > $bounds(ymin)} {
725            set _limits(ymin) $bounds(ymin)
726        }
727        if {![info exists _limits(ymax)] || $_limits(ymax) < $bounds(ymax)} {
728            set _limits(ymax) $bounds(ymax)
729        }
730
731        if {![info exists _limits(zmin)] || $_limits(zmin) > $bounds(zmin)} {
732            set _limits(zmin) $bounds(zmin)
733        }
734        if {![info exists _limits(zmax)] || $_limits(zmax) < $bounds(zmax)} {
735            set _limits(zmax) $bounds(zmax)
736        }
737    }
738}
739
740# ----------------------------------------------------------------------
741# USAGE: download coming
742# USAGE: download controls <downloadCommand>
743# USAGE: download now
744#
745# Clients use this method to create a downloadable representation
746# of the plot.  Returns a list of the form {ext string}, where
747# "ext" is the file extension (indicating the type of data) and
748# "string" is the data itself.
749# ----------------------------------------------------------------------
750itcl::body Rappture::VtkStreamlinesViewer::download {option args} {
751    switch $option {
752        coming {
753            if {[catch {
754                blt::winop snap $itk_component(plotarea) $_image(download)
755            }]} {
756                $_image(download) configure -width 1 -height 1
757                $_image(download) put #000000
758            }
759        }
760        controls {
761            set popup .vtkviewerdownload
762            if { ![winfo exists .vtkviewerdownload] } {
763                set inner [BuildDownloadPopup $popup [lindex $args 0]]
764            } else {
765                set inner [$popup component inner]
766            }
767            set _downloadPopup(image_controls) $inner.image_frame
768            set num [llength [get]]
769            set num [expr {($num == 1) ? "1 result" : "$num results"}]
770            set word [Rappture::filexfer::label downloadWord]
771            $inner.summary configure -text "$word $num in the following format:"
772            update idletasks            ;# Fix initial sizes
773            return $popup
774        }
775        now {
776            set popup .vtkviewerdownload
777            if {[winfo exists .vtkviewerdownload]} {
778                $popup deactivate
779            }
780            switch -- $_downloadPopup(format) {
781                "image" {
782                    return [$this GetImage [lindex $args 0]]
783                }
784                "vtk" {
785                    return [$this GetVtkData [lindex $args 0]]
786                }
787            }
788            return ""
789        }
790        default {
791            error "bad option \"$option\": should be coming, controls, now"
792        }
793    }
794}
795
796# ----------------------------------------------------------------------
797# USAGE: Connect ?<host:port>,<host:port>...?
798#
799# Clients use this method to establish a connection to a new
800# server, or to reestablish a connection to the previous server.
801# Any existing connection is automatically closed.
802# ----------------------------------------------------------------------
803itcl::body Rappture::VtkStreamlinesViewer::Connect {} {
804    set _hosts [GetServerList "vtkvis"]
805    if { "" == $_hosts } {
806        return 0
807    }
808    set result [VisViewer::Connect $_hosts]
809    if { $result } {
810        set w [winfo width $itk_component(view)]
811        set h [winfo height $itk_component(view)]
812        EventuallyResize $w $h
813    }
814    return $result
815}
816
817#
818# isconnected --
819#
820#       Indicates if we are currently connected to the visualization server.
821#
822itcl::body Rappture::VtkStreamlinesViewer::isconnected {} {
823    return [VisViewer::IsConnected]
824}
825
826#
827# disconnect --
828#
829itcl::body Rappture::VtkStreamlinesViewer::disconnect {} {
830    Disconnect
831    set _reset 1
832}
833
834#
835# Disconnect --
836#
837#       Clients use this method to disconnect from the current rendering
838#       server.
839#
840itcl::body Rappture::VtkStreamlinesViewer::Disconnect {} {
841    VisViewer::Disconnect
842
843    $_dispatcher cancel !rebuild
844    $_dispatcher cancel !resize
845    $_dispatcher cancel !reseed
846    $_dispatcher cancel !rotate
847    $_dispatcher cancel !xcutplane
848    $_dispatcher cancel !ycutplane
849    $_dispatcher cancel !zcutplane
850    $_dispatcher cancel !legend
851    # disconnected -- no more data sitting on server
852    set _outbuf ""
853    array unset _datasets
854    array unset _data
855    array unset _colormaps
856    array unset _seeds
857    array unset _dataset2style
858    array unset _obj2datasets
859}
860
861#
862# sendto --
863#
864itcl::body Rappture::VtkStreamlinesViewer::sendto { bytes } {
865    SendBytes "$bytes\n"
866    StartWaiting
867}
868
869#
870# SendCmd
871#
872#       Send commands off to the rendering server.  If we're currently
873#       sending data objects to the server, buffer the commands to be
874#       sent later.
875#
876itcl::body Rappture::VtkStreamlinesViewer::SendCmd {string} {
877    if { $_buffering } {
878        append _outbuf $string "\n"
879    } else {
880        SendBytes "$string\n"
881        StartWaiting
882    }
883}
884
885#
886# SendCmdNoSplash
887#
888#       Send commands off to the rendering server.  This method
889#       doesn't initiate the slash screen.
890#
891itcl::body Rappture::VtkStreamlinesViewer::SendCmdNoSplash {string} {
892    if { $_buffering } {
893        append _outbuf $string "\n"
894    } else {
895        SendBytes "$string\n"
896    }
897}
898
899# ----------------------------------------------------------------------
900# USAGE: ReceiveImage -bytes <size> -type <type> -token <token>
901#
902# Invoked automatically whenever the "image" command comes in from
903# the rendering server.  Indicates that binary image data with the
904# specified <size> will follow.
905# ----------------------------------------------------------------------
906itcl::body Rappture::VtkStreamlinesViewer::ReceiveImage { args } {
907    array set info {
908        -token "???"
909        -bytes 0
910        -type image
911    }
912    array set info $args
913    set bytes [ReceiveBytes $info(-bytes)]
914    StopWaiting
915    if { $info(-type) == "image" } {
916        if 0 {
917            set f [open "last.ppm" "w"]
918            puts $f $bytes
919            close $f
920        }
921        $_image(plot) configure -data $bytes
922        set time [clock seconds]
923        set date [clock format $time]
924        #puts stderr "$date: received image [image width $_image(plot)]x[image height $_image(plot)] image>"       
925        if { $_start > 0 } {
926            set finish [clock clicks -milliseconds]
927            #puts stderr "round trip time [expr $finish -$_start] milliseconds"
928            set _start 0
929        }
930    } elseif { $info(type) == "print" } {
931        set tag $this-print-$info(-token)
932        set _hardcopy($tag) $bytes
933    }
934    if { $_legendPending } {
935        RequestLegend
936    }
937}
938
939#
940# ReceiveDataset --
941#
942itcl::body Rappture::VtkStreamlinesViewer::ReceiveDataset { args } {
943    if { ![isconnected] } {
944        return
945    }
946    set option [lindex $args 0]
947    switch -- $option {
948        "scalar" {
949            set option [lindex $args 1]
950            switch -- $option {
951                "world" {
952                    foreach { x y z value tag } [lrange $args 2 end] break
953                }
954                "pixel" {
955                    foreach { x y value tag } [lrange $args 2 end] break
956                }
957            }
958        }
959        "vector" {
960            set option [lindex $args 1]
961            switch -- $option {
962                "world" {
963                    foreach { x y z vx vy vz tag } [lrange $args 2 end] break
964                }
965                "pixel" {
966                    foreach { x y vx vy vz tag } [lrange $args 2 end] break
967                }
968            }
969        }
970        "names" {
971            foreach { name } [lindex $args 1] {
972                #puts stderr "Dataset: $name"
973            }
974        }
975        default {
976            error "unknown dataset option \"$option\" from server"
977        }
978    }
979}
980
981# ----------------------------------------------------------------------
982# USAGE: Rebuild
983#
984# Called automatically whenever something changes that affects the
985# data in the widget.  Clears any existing data and rebuilds the
986# widget to display new data.
987# ----------------------------------------------------------------------
988itcl::body Rappture::VtkStreamlinesViewer::Rebuild {} {
989    update
990    set w [winfo width $itk_component(view)]
991    set h [winfo height $itk_component(view)]
992    if { $w < 2 || $h < 2 } {
993        $_dispatcher event -idle !rebuild
994        return
995    }
996
997    set _buffering 1
998    set _legendPending 1
999    # Turn on buffering of commands to the server.  We don't want to
1000    # be preempted by a server disconnect/reconnect (which automatically
1001    # generates a new call to Rebuild).   
1002
1003    set _first ""
1004    if { $_reset } {
1005        if 1 {
1006            # Tell the server the name of the tool, the version, and dataset
1007            # that we are rendering.  Have to do it here because we don't know
1008            # what data objects are using the renderer until be get here.
1009            global env
1010
1011            set info {}
1012            set user "???"
1013            if { [info exists env(USER)] } {
1014                set user $env(USER)
1015            }
1016            set session "???"
1017            if { [info exists env(SESSION)] } {
1018                set session $env(SESSION)
1019            }
1020            lappend info "hub" [exec hostname]
1021            lappend info "client" "vtkstreamlinesviewer"
1022            lappend info "user" $user
1023            lappend info "session" $session
1024            SendCmd "clientinfo [list $info]"
1025        }
1026        set _width $w
1027        set _height $h
1028        $_arcball resize $w $h
1029        DoResize
1030        InitSettings axisXGrid axisYGrid axisZGrid axis-mode \
1031            axesVisible axisLabelsVisible \
1032    }
1033    #SendCmd "imgflush"
1034
1035    set _limits(zmin) ""
1036    set _limits(zmax) ""
1037    set _first ""
1038    foreach dataobj [get -objects] {
1039        if { [info exists _obj2ovride($dataobj-raise)] &&  $_first == "" } {
1040            set _first $dataobj
1041        }
1042        set _obj2datasets($dataobj) ""
1043        foreach comp [$dataobj components] {
1044            set tag $dataobj-$comp
1045            if { ![info exists _datasets($tag)] } {
1046                set bytes [$dataobj vtkdata $comp]
1047                set length [string length $bytes]
1048                if 1 {
1049                    set info {}
1050                    lappend info "tool_id"       [$dataobj hints toolId]
1051                    lappend info "tool_name"     [$dataobj hints toolName]
1052                    lappend info "tool_version"  [$dataobj hints toolRevision]
1053                    lappend info "tool_title"    [$dataobj hints toolTitle]
1054                    lappend info "dataset_label" [$dataobj hints label]
1055                    lappend info "dataset_size"  $length
1056                    lappend info "dataset_tag"   $tag
1057                    SendCmd "clientinfo [list $info]"
1058                }
1059                append _outbuf "dataset add $tag data follows $length\n"
1060                append _outbuf $bytes
1061                set _datasets($tag) 1
1062                SetObjectStyle $dataobj $comp
1063            }
1064            lappend _obj2datasets($dataobj) $tag
1065            if { [info exists _obj2ovride($dataobj-raise)] } {
1066                SendCmd "dataset visible 1 $tag"
1067            } else {
1068                SendCmd "dataset visible 0 $tag"
1069            }
1070        }
1071    }
1072    if {"" != $_first} {
1073        set location [$_first hints camera]
1074        if { $location != "" } {
1075            array set view $location
1076        }
1077        foreach axis { x y z } {
1078            set label [$_first hints ${axis}label]
1079            if { $label != "" } {
1080                SendCmd "axis name $axis $label"
1081            }
1082            set units [$_first hints ${axis}units]
1083            if { $units != "" } {
1084                SendCmd "axis units $axis $units"
1085            }
1086        }
1087        array unset _scalarFields
1088        array unset _vectorFields
1089        set _currentField [$_first hints default]
1090        $itk_component(field) choices delete 0 end
1091        $itk_component(fieldmenu) delete 0 end
1092        array unset _fields
1093        foreach { name title units } [$_first hints vectors] {
1094            set _vectorFields($title) $name
1095            $itk_component(field) choices insert end "$name" "$title"
1096            $itk_component(fieldmenu) add radiobutton -label "$title" \
1097                -value $title -variable [itcl::scope _currentField] \
1098                -selectcolor red \
1099                -activebackground black \
1100                -activeforeground white \
1101                -font "Arial 8" \
1102                -command [itcl::code $this Combo invoke]
1103            set _fields($name) [list $title $units]
1104        }
1105        foreach { name title units } [$_first hints scalars] {
1106            set _scalarFields($title) $name
1107            $itk_component(field) choices insert end "$name" "$title"
1108            $itk_component(fieldmenu) add radiobutton -label "$title" \
1109                -value $title -variable [itcl::scope _currentField] \
1110                -selectcolor red \
1111                -activebackground black \
1112                -activeforeground white \
1113                -font "Arial 8" \
1114                -command [itcl::code $this Combo invoke]
1115            set _fields($name) [list $title $units]
1116        }
1117        $itk_component(field) value $_currentField
1118    }
1119
1120    InitSettings streamlinesVisible streamlines-palette volumeVisible
1121
1122    if { $_reset } {
1123        InitSettings streamlinesSeedsVisible streamlinesOpacity \
1124            streamlinesNumSeeds streamlinesLighting \
1125            streamlines-palette streamlines-field \
1126            volumeEdges volumeLighting volumeOpacity volumeWireframe \
1127            cutplaneVisible \
1128            cutplaneXPosition cutplaneYPosition cutplaneZPosition \
1129            cutplaneXVisible cutplaneYVisible cutplaneZVisible
1130        #
1131        # Reset the camera and other view parameters
1132        #
1133        set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)]
1134        $_arcball quaternion $q
1135        if {$_view(ortho)} {
1136            SendCmd "camera mode ortho"
1137        } else {
1138            SendCmd "camera mode persp"
1139        }
1140        DoRotate
1141        PanCamera
1142        Zoom reset
1143        SendCmd "camera reset"
1144        set _reset 0
1145    }
1146    set _buffering 0;                        # Turn off buffering.
1147
1148    # Actually write the commands to the server socket.  If it fails, we don't
1149    # care.  We're finished here.
1150    blt::busy hold $itk_component(hull)
1151    sendto $_outbuf;                       
1152    blt::busy release $itk_component(hull)
1153    set _outbuf "";                        # Clear the buffer.               
1154}
1155
1156# ----------------------------------------------------------------------
1157# USAGE: CurrentDatasets ?-all -visible? ?dataobjs?
1158#
1159# Returns a list of server IDs for the current datasets being displayed.  This
1160# is normally a single ID, but it might be a list of IDs if the current data
1161# object has multiple components.
1162# ----------------------------------------------------------------------
1163itcl::body Rappture::VtkStreamlinesViewer::CurrentDatasets {args} {
1164    set flag [lindex $args 0]
1165    switch -- $flag {
1166        "-all" {
1167            if { [llength $args] > 1 } {
1168                error "CurrentDatasets: can't specify dataobj after \"-all\""
1169            }
1170            set dlist [get -objects]
1171        }
1172        "-visible" {
1173            if { [llength $args] > 1 } {
1174                set dlist {}
1175                set args [lrange $args 1 end]
1176                foreach dataobj $args {
1177                    if { [info exists _obj2ovride($dataobj-raise)] } {
1178                        lappend dlist $dataobj
1179                    }
1180                }
1181            } else {
1182                set dlist [get -visible]
1183            }
1184        }           
1185        default {
1186            set dlist $args
1187        }
1188    }
1189    set rlist ""
1190    foreach dataobj $dlist {
1191        foreach comp [$dataobj components] {
1192            set tag $dataobj-$comp
1193            if { [info exists _datasets($tag)] && $_datasets($tag) } {
1194                lappend rlist $tag
1195            }
1196        }
1197    }
1198    return $rlist
1199}
1200
1201# ----------------------------------------------------------------------
1202# USAGE: Zoom in
1203# USAGE: Zoom out
1204# USAGE: Zoom reset
1205#
1206# Called automatically when the user clicks on one of the zoom
1207# controls for this widget.  Changes the zoom for the current view.
1208# ----------------------------------------------------------------------
1209itcl::body Rappture::VtkStreamlinesViewer::Zoom {option} {
1210    switch -- $option {
1211        "in" {
1212            set _view(zoom) [expr {$_view(zoom)*1.25}]
1213            SendCmd "camera zoom $_view(zoom)"
1214        }
1215        "out" {
1216            set _view(zoom) [expr {$_view(zoom)*0.8}]
1217            SendCmd "camera zoom $_view(zoom)"
1218        }
1219        "reset" {
1220            array set _view {
1221                qw              0.853553
1222                qx              -0.353553
1223                qy              0.353553
1224                qz              0.146447
1225                zoom    1.0
1226                xpan   0
1227                ypan   0
1228            }
1229            SendCmd "camera reset all"
1230            if { $_first != "" } {
1231                set location [$_first hints camera]
1232                if { $location != "" } {
1233                    array set _view $location
1234                }
1235            }
1236            set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)]
1237            $_arcball quaternion $q
1238            DoRotate
1239        }
1240    }
1241}
1242
1243itcl::body Rappture::VtkStreamlinesViewer::PanCamera {} {
1244    set x $_view(xpan)
1245    set y $_view(ypan)
1246    SendCmd "camera pan $x $y"
1247}
1248
1249
1250# ----------------------------------------------------------------------
1251# USAGE: Rotate click <x> <y>
1252# USAGE: Rotate drag <x> <y>
1253# USAGE: Rotate release <x> <y>
1254#
1255# Called automatically when the user clicks/drags/releases in the
1256# plot area.  Moves the plot according to the user's actions.
1257# ----------------------------------------------------------------------
1258itcl::body Rappture::VtkStreamlinesViewer::Rotate {option x y} {
1259    switch -- $option {
1260        "click" {
1261            $itk_component(view) configure -cursor fleur
1262            set _click(x) $x
1263            set _click(y) $y
1264        }
1265        "drag" {
1266            if {[array size _click] == 0} {
1267                Rotate click $x $y
1268            } else {
1269                set w [winfo width $itk_component(view)]
1270                set h [winfo height $itk_component(view)]
1271                if {$w <= 0 || $h <= 0} {
1272                    return
1273                }
1274
1275                if {[catch {
1276                    # this fails sometimes for no apparent reason
1277                    set dx [expr {double($x-$_click(x))/$w}]
1278                    set dy [expr {double($y-$_click(y))/$h}]
1279                }]} {
1280                    return
1281                }
1282                if { $dx == 0 && $dy == 0 } {
1283                    return
1284                }
1285                set q [$_arcball rotate $x $y $_click(x) $_click(y)]
1286                EventuallyRotate $q
1287                set _click(x) $x
1288                set _click(y) $y
1289            }
1290        }
1291        "release" {
1292            Rotate drag $x $y
1293            $itk_component(view) configure -cursor ""
1294            catch {unset _click}
1295        }
1296        default {
1297            error "bad option \"$option\": should be click, drag, release"
1298        }
1299    }
1300}
1301
1302itcl::body Rappture::VtkStreamlinesViewer::Pick {x y} {
1303    foreach tag [CurrentDatasets -visible] {
1304        SendCmdNoSplash "dataset getscalar pixel $x $y $tag"
1305    }
1306}
1307
1308# ----------------------------------------------------------------------
1309# USAGE: $this Pan click x y
1310#        $this Pan drag x y
1311#        $this Pan release x y
1312#
1313# Called automatically when the user clicks on one of the zoom
1314# controls for this widget.  Changes the zoom for the current view.
1315# ----------------------------------------------------------------------
1316itcl::body Rappture::VtkStreamlinesViewer::Pan {option x y} {
1317    switch -- $option {
1318        "set" {
1319            set w [winfo width $itk_component(view)]
1320            set h [winfo height $itk_component(view)]
1321            set x [expr $x / double($w)]
1322            set y [expr $y / double($h)]
1323            set _view(xpan) [expr $_view(xpan) + $x]
1324            set _view(ypan) [expr $_view(ypan) + $y]
1325            PanCamera
1326            return
1327        }
1328        "click" {
1329            set _click(x) $x
1330            set _click(y) $y
1331            $itk_component(view) configure -cursor hand1
1332        }
1333        "drag" {
1334            if { ![info exists _click(x)] } {
1335                set _click(x) $x
1336            }
1337            if { ![info exists _click(y)] } {
1338                set _click(y) $y
1339            }
1340            set w [winfo width $itk_component(view)]
1341            set h [winfo height $itk_component(view)]
1342            set dx [expr ($_click(x) - $x)/double($w)]
1343            set dy [expr ($_click(y) - $y)/double($h)]
1344            set _click(x) $x
1345            set _click(y) $y
1346            set _view(xpan) [expr $_view(xpan) - $dx]
1347            set _view(ypan) [expr $_view(ypan) - $dy]
1348            PanCamera
1349        }
1350        "release" {
1351            Pan drag $x $y
1352            $itk_component(view) configure -cursor ""
1353        }
1354        default {
1355            error "unknown option \"$option\": should set, click, drag, or release"
1356        }
1357    }
1358}
1359
1360# ----------------------------------------------------------------------
1361# USAGE: InitSettings <what> ?<value>?
1362#
1363# Used internally to update rendering settings whenever parameters
1364# change in the popup settings panel.  Sends the new settings off
1365# to the back end.
1366# ----------------------------------------------------------------------
1367itcl::body Rappture::VtkStreamlinesViewer::InitSettings { args } {
1368    foreach spec $args {
1369        if { [info exists _settings($_first-$spec)] } {
1370            # Reset global setting with dataobj specific setting
1371            set _settings($spec) $_settings($_first-$spec)
1372        }
1373        AdjustSetting $spec
1374    }
1375}
1376
1377#
1378# AdjustSetting --
1379#
1380#       Changes/updates a specific setting in the widget.  There are
1381#       usually user-setable option.  Commands are sent to the render
1382#       server.
1383#
1384itcl::body Rappture::VtkStreamlinesViewer::AdjustSetting {what {value ""}} {
1385    if { ![isconnected] } {
1386        return
1387    }
1388    switch -- $what {
1389        "volumeOpacity" {
1390            set val $_settings(volumeOpacity)
1391            set sval [expr { 0.01 * double($val) }]
1392            foreach dataset [CurrentDatasets -visible] {
1393                SendCmd "polydata opacity $sval $dataset"
1394            }
1395        }
1396        "volumeWireframe" {
1397            set bool $_settings(volumeWireframe)
1398            foreach dataset [CurrentDatasets -visible] {
1399                SendCmd "polydata wireframe $bool $dataset"
1400            }
1401        }
1402        "volumeVisible" {
1403            set bool $_settings(volumeVisible)
1404            foreach dataset [CurrentDatasets -visible] {
1405                SendCmd "polydata visible $bool $dataset"
1406            }
1407            if { $bool } {
1408                Rappture::Tooltip::for $itk_component(volume) \
1409                    "Hide the volume"
1410            } else {
1411                Rappture::Tooltip::for $itk_component(volume) \
1412                    "Show the volume"
1413            }
1414        }
1415        "volumeLighting" {
1416            set bool $_settings(volumeLighting)
1417            foreach dataset [CurrentDatasets -visible] {
1418                SendCmd "polydata lighting $bool $dataset"
1419            }
1420        }
1421        "volumeEdges" {
1422            set bool $_settings(volumeEdges)
1423            foreach dataset [CurrentDatasets -visible] {
1424                SendCmd "polydata edges $bool $dataset"
1425            }
1426        }
1427        "axesVisible" {
1428            set bool $_settings(axesVisible)
1429            SendCmd "axis visible all $bool"
1430        }
1431        "axisLabelsVisible" {
1432            set bool $_settings(axisLabelsVisible)
1433            SendCmd "axis labels all $bool"
1434        }
1435        "axisXGrid" - "axisYGrid" - "axisZGrid" {
1436            set axis [string tolower [string range $what 4 4]]
1437            set bool $_settings($what)
1438            SendCmd "axis grid $axis $bool"
1439        }
1440        "axis-mode" {
1441            set mode [$itk_component(axismode) value]
1442            set mode [$itk_component(axismode) translate $mode]
1443            set _settings($what) $mode
1444            SendCmd "axis flymode $mode"
1445        }
1446        "cutplaneEdges" {
1447            set bool $_settings($what)
1448            foreach dataset [CurrentDatasets -visible] {
1449                SendCmd "cutplane edges $bool $dataset"
1450            }
1451        }
1452        "cutplaneVisible" {
1453            set bool $_settings($what)
1454            foreach dataset [CurrentDatasets -visible] {
1455                SendCmd "cutplane visible $bool $dataset"
1456            }
1457        }
1458        "cutplaneWireframe" {
1459            set bool $_settings($what)
1460            foreach dataset [CurrentDatasets -visible] {
1461                SendCmd "cutplane wireframe $bool $dataset"
1462            }
1463        }
1464        "cutplaneLighting" {
1465            set bool $_settings($what)
1466            foreach dataset [CurrentDatasets -visible] {
1467                SendCmd "cutplane lighting $bool $dataset"
1468            }
1469        }
1470        "cutplaneOpacity" {
1471            set val $_settings($what)
1472            set sval [expr { 0.01 * double($val) }]
1473            foreach dataset [CurrentDatasets -visible] {
1474                SendCmd "cutplane opacity $sval $dataset"
1475            }
1476        }
1477        "cutplaneXVisible" - "cutplaneYVisible" - "cutplaneZVisible" {
1478            set axis [string tolower [string range $what 8 8]]
1479            set bool $_settings($what)
1480            if { $bool } {
1481                $itk_component(${axis}CutScale) configure -state normal \
1482                    -troughcolor white
1483            } else {
1484                $itk_component(${axis}CutScale) configure -state disabled \
1485                    -troughcolor grey82
1486            }
1487            foreach dataset [CurrentDatasets -visible] {
1488                SendCmd "cutplane axis $axis $bool $dataset"
1489            }
1490        }
1491        "cutplaneXPosition" - "cutplaneYPosition" - "cutplaneZPosition" {
1492            set axis [string tolower [string range $what 8 8]]
1493            set pos [expr $_settings($what) * 0.01]
1494            foreach dataset [CurrentDatasets -visible] {
1495                SendCmd "cutplane slice ${axis} ${pos} $dataset"
1496            }
1497            set _cutplanePending 0
1498        }
1499        "streamlinesSeedsVisible" {
1500            set bool $_settings($what)
1501            foreach dataset [CurrentDatasets -visible] {
1502                SendCmd "streamlines seed visible $bool $dataset"
1503            }
1504        }
1505        "streamlinesNumSeeds" {
1506            set density $_settings($what)
1507            EventuallyReseed $density
1508        }
1509        "streamlinesVisible" {
1510            set bool $_settings($what)
1511            foreach dataset [CurrentDatasets -visible] {
1512                SendCmd "streamlines visible $bool $dataset"
1513            }
1514            if { $bool } {
1515                Rappture::Tooltip::for $itk_component(streamlines) \
1516                    "Hide the streamlines"
1517            } else {
1518                Rappture::Tooltip::for $itk_component(streamlines) \
1519                    "Show the streamlines"
1520            }
1521        }
1522        "streamlinesMode" {
1523            set mode [$itk_component(streammode) value]
1524            set _settings(streamlinesMode) $mode
1525            foreach dataset [CurrentDatasets -visible] {
1526                switch -- $mode {
1527                    "lines" {
1528                        SendCmd "streamlines lines $dataset"
1529                    }
1530                    "ribbons" {
1531                        SendCmd "streamlines ribbons 3 0 $dataset"
1532                    }
1533                    "tubes" {
1534                        SendCmd "streamlines tubes 5 3 $dataset"
1535                    }
1536                }
1537            }
1538        }
1539        "streamlines-palette" {
1540            set palette [$itk_component(palette) value]
1541            set _settings(streamlines-palette) $palette
1542            foreach dataset [CurrentDatasets -visible $_first] {
1543                foreach {dataobj comp} [split $dataset -] break
1544                ChangeColormap $dataobj $comp $palette
1545            }
1546            set _legendPending 1
1547        }
1548        "streamlinesOpacity" {
1549            set val $_settings(streamlinesOpacity)
1550            set sval [expr { 0.01 * double($val) }]
1551            foreach dataset [CurrentDatasets -visible $_first] {
1552                SendCmd "streamlines opacity $sval $dataset"
1553            }
1554        }
1555        "streamlinesScale" {
1556            set val $_settings(streamlinesScale)
1557            set sval [expr { 0.01 * double($val) }]
1558            foreach dataset [CurrentDatasets -visible $_first] {
1559                SendCmd "streamlines scale $sval $sval $sval $dataset"
1560            }
1561        }
1562        "streamlinesLighting" {
1563            set bool $_settings(streamlinesLighting)
1564            foreach dataset [CurrentDatasets -visible $_first] {
1565                SendCmd "streamlines lighting $bool $dataset"
1566            }
1567        }
1568        "streamlines-field" {
1569            set new [$itk_component(field) value]
1570            set value [$itk_component(field) translate $new]
1571            set _settings(streamlines-field) $value
1572            if { [info exists _scalarFields($new)] } {
1573                set name $_scalarFields($new)
1574                set _colorMode scalar
1575                set _currentField $new
1576            } elseif { [info exists _vectorFields($new)] } {
1577                set name $_vectorFields($new)
1578                set _colorMode vmag
1579                set _currentField $new
1580            } else {
1581                puts stderr "unknown field \"$new\""
1582                return
1583            }
1584            foreach dataset [CurrentDatasets -visible] {
1585                SendCmd "streamlines colormode $_colorMode ${name} $dataset"
1586                SendCmd "cutplane colormode $_colorMode ${name} $dataset"
1587            }
1588            set _legendPending 1
1589        }
1590        default {
1591            error "don't know how to fix $what"
1592        }
1593    }
1594}
1595
1596#
1597# RequestLegend --
1598#
1599#       Request a new legend from the server.  The size of the legend
1600#       is determined from the height of the canvas.  It will be rotated
1601#       to be vertical when drawn.
1602#
1603itcl::body Rappture::VtkStreamlinesViewer::RequestLegend {} {
1604    set font "Arial 8"
1605    set lineht [font metrics $font -linespace]
1606    set c $itk_component(legend)
1607    set w 12
1608    set h [expr {$_height - 3 * ($lineht + 2)}]
1609    if { $h < 1} {
1610        return
1611    }
1612    if { [info exists _scalarFields($_currentField)] } {
1613        set name $_scalarFields($_currentField)
1614    } elseif { [info exists _vectorFields($_currentField)] } {
1615        set name $_vectorFields($_currentField)
1616    } else {
1617        return
1618    }
1619    # Set the legend on the first streamlines dataset.
1620    foreach dataset [CurrentDatasets -visible $_first] {
1621        foreach {dataobj comp} [split $dataset -] break
1622        if { [info exists _dataset2style($dataset)] } {
1623            SendCmdNoSplash \
1624                "legend $_dataset2style($dataset) $_colorMode $name {} $w $h 0"
1625            break;
1626        }
1627    }
1628}
1629
1630#
1631# ChangeColormap --
1632#
1633itcl::body Rappture::VtkStreamlinesViewer::ChangeColormap {dataobj comp color} {
1634    set tag $dataobj-$comp
1635    if { ![info exist _style($tag)] } {
1636        error "no initial colormap"
1637    }
1638    array set style $_style($tag)
1639    set style(-color) $color
1640    set _style($tag) [array get style]
1641    SetColormap $dataobj $comp
1642}
1643
1644#
1645# SetColormap --
1646#
1647itcl::body Rappture::VtkStreamlinesViewer::SetColormap { dataobj comp } {
1648    array set style {
1649        -color BCGYR
1650        -levels 6
1651        -opacity 1.0
1652    }
1653    set tag $dataobj-$comp
1654    if { ![info exists _initialStyle($tag)] } {
1655        # Save the initial component style.
1656        set _initialStyle($tag) [$dataobj style $comp]
1657    }
1658
1659    # Override defaults with initial style defined in xml.
1660    array set style $_initialStyle($tag)
1661
1662    if { ![info exists _style($tag)] } {
1663        set _style($tag) [array get style]
1664    }
1665    # Override initial style with current style.
1666    array set style $_style($tag)
1667
1668    set name "$style(-color):$style(-levels):$style(-opacity)"
1669    if { ![info exists _colormaps($name)] } {
1670        BuildColormap $name [array get style]
1671        set _colormaps($name) 1
1672    }
1673    if { ![info exists _dataset2style($tag)] ||
1674         $_dataset2style($tag) != $name } {
1675        SendCmd "streamlines colormap $name $tag"
1676        SendCmd "cutplane colormap $name $tag"
1677        set _dataset2style($tag) $name
1678    }
1679}
1680
1681
1682#
1683# BuildColormap --
1684#
1685itcl::body Rappture::VtkStreamlinesViewer::BuildColormap { name styles } {
1686    array set style $styles
1687    set cmap [ColorsToColormap $style(-color)]
1688    if { [llength $cmap] == 0 } {
1689        set cmap "0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0"
1690    }
1691    if { ![info exists _settings(volumeOpacity)] } {
1692        set _settings(volumeOpacity) $style(-opacity)
1693    }
1694    set max $_settings(volumeOpacity)
1695
1696    set wmap "0.0 1.0 1.0 1.0"
1697    SendCmd "colormap add $name { $cmap } { $wmap }"
1698}
1699
1700# ----------------------------------------------------------------------
1701# CONFIGURATION OPTION: -plotbackground
1702# ----------------------------------------------------------------------
1703itcl::configbody Rappture::VtkStreamlinesViewer::plotbackground {
1704    if { [isconnected] } {
1705        foreach {r g b} [Color2RGB $itk_option(-plotbackground)] break
1706        SendCmd "screen bgcolor $r $g $b"
1707    }
1708}
1709
1710# ----------------------------------------------------------------------
1711# CONFIGURATION OPTION: -plotforeground
1712# ----------------------------------------------------------------------
1713itcl::configbody Rappture::VtkStreamlinesViewer::plotforeground {
1714    if { [isconnected] } {
1715        foreach {r g b} [Color2RGB $itk_option(-plotforeground)] break
1716        #fix this!
1717        #SendCmd "color background $r $g $b"
1718    }
1719}
1720
1721itcl::body Rappture::VtkStreamlinesViewer::limits { dataobj } {
1722    return
1723    array unset _limits $dataobj-*
1724    foreach comp [$dataobj components] {
1725        set tag $dataobj-$comp
1726        if { ![info exists _limits($tag)] } {
1727            set data [$dataobj blob $comp]
1728            set tmpfile file[pid].vtk
1729            set f [open "$tmpfile" "w"]
1730            fconfigure $f -translation binary -encoding binary
1731            puts $f $data
1732            close $f
1733            set reader [vtkDataSetReader $tag-xvtkDataSetReader]
1734            $reader SetFileName $tmpfile
1735            $reader ReadAllScalarsOn
1736            $reader ReadAllVectorsOn
1737            $reader ReadAllFieldsOn
1738            $reader Update
1739            set output [$reader GetOutput]
1740            set _limits($tag) [$output GetBounds]
1741            set pointData [$output GetPointData]
1742            puts stderr "\#scalars=[$reader GetNumberOfScalarsInFile]"
1743            puts stderr "\#fielddata=[$reader GetNumberOfFieldDataInFile]"
1744            puts stderr "fielddataname=[$reader GetFieldDataNameInFile 0]"
1745            set fieldData [$output GetFieldData]
1746            set pointData [$output GetPointData]
1747            puts stderr "field \#arrays=[$fieldData GetNumberOfArrays]"
1748            for { set i 0 } { $i < [$fieldData GetNumberOfArrays] } { incr i } {
1749                puts stderr [$fieldData GetArrayName $i]
1750            }
1751            for { set i 0 } { $i < [$pointData GetNumberOfArrays] } { incr i } {
1752                set name [$pointData GetArrayName $i]
1753                if { ![info exists _fields($name)] } {
1754                    $itk_component(field) choices insert end "$name" "$name"
1755                    set _fields($name) 1
1756                }
1757            }
1758            puts stderr "field \#components=[$fieldData GetNumberOfComponents]"
1759            puts stderr "point \#components=[$pointData GetNumberOfComponents]"
1760            puts stderr "field \#tuples=[$fieldData GetNumberOfTuples]"
1761            puts stderr "point \#tuples=[$pointData GetNumberOfTuples]"
1762            puts stderr "point \#scalars=[$pointData GetScalars]"
1763            puts stderr vectors=[$pointData GetVectors]
1764            rename $output ""
1765            rename $reader ""
1766            file delete $tmpfile
1767        }
1768        foreach { xMin xMax yMin yMax zMin zMax} $_limits($tag) break
1769        if {![info exists limits(xmin)] || $limits(xmin) > $xMin} {
1770            set limits(xmin) $xMin
1771        }
1772        if {![info exists limits(xmax)] || $limits(xmax) < $xMax} {
1773            set limits(xmax) $xMax
1774        }
1775        if {![info exists limits(ymin)] || $limits(ymin) > $yMin} {
1776            set limits(ymin) $xMin
1777        }
1778        if {![info exists limits(ymax)] || $limits(ymax) < $yMax} {
1779            set limits(ymax) $yMax
1780        }
1781        if {![info exists limits(zmin)] || $limits(zmin) > $zMin} {
1782            set limits(zmin) $zMin
1783        }
1784        if {![info exists limits(zmax)] || $limits(zmax) < $zMax} {
1785            set limits(zmax) $zMax
1786        }
1787    }
1788    return [array get limits]
1789}
1790
1791itcl::body Rappture::VtkStreamlinesViewer::BuildVolumeTab {} {
1792
1793    set fg [option get $itk_component(hull) font Font]
1794    #set bfg [option get $itk_component(hull) boldFont Font]
1795
1796    set inner [$itk_component(main) insert end \
1797        -title "Volume Settings" \
1798        -icon [Rappture::icon volume-on]]
1799    $inner configure -borderwidth 4
1800
1801    checkbutton $inner.volume \
1802        -text "Show Volume" \
1803        -variable [itcl::scope _settings(volumeVisible)] \
1804        -command [itcl::code $this AdjustSetting volumeVisible] \
1805        -font "Arial 9"
1806
1807    checkbutton $inner.wireframe \
1808        -text "Show Wireframe" \
1809        -variable [itcl::scope _settings(volumeWireframe)] \
1810        -command [itcl::code $this AdjustSetting volumeWireframe] \
1811        -font "Arial 9"
1812
1813    checkbutton $inner.lighting \
1814        -text "Enable Lighting" \
1815        -variable [itcl::scope _settings(volumeLighting)] \
1816        -command [itcl::code $this AdjustSetting volumeLighting] \
1817        -font "Arial 9"
1818
1819    checkbutton $inner.edges \
1820        -text "Show Edges" \
1821        -variable [itcl::scope _settings(volumeEdges)] \
1822        -command [itcl::code $this AdjustSetting volumeEdges] \
1823        -font "Arial 9"
1824
1825    label $inner.opacity_l -text "Opacity" -font "Arial 9"
1826    ::scale $inner.opacity -from 0 -to 100 -orient horizontal \
1827        -variable [itcl::scope _settings(volumeOpacity)] \
1828        -width 10 \
1829        -showvalue off \
1830        -command [itcl::code $this AdjustSetting volumeOpacity]
1831
1832    blt::table $inner \
1833        0,0 $inner.wireframe -anchor w -pady 2 -cspan 2 \
1834        1,0 $inner.lighting  -anchor w -pady 2 -cspan 2 \
1835        2,0 $inner.edges     -anchor w -pady 2 -cspan 3 \
1836        3,0 $inner.opacity_l -anchor w -pady 2 \
1837        3,1 $inner.opacity   -fill x   -pady 2
1838
1839    blt::table configure $inner r* c* -resize none
1840    blt::table configure $inner r4 c1 -resize expand
1841}
1842
1843
1844itcl::body Rappture::VtkStreamlinesViewer::BuildStreamsTab {} {
1845
1846    set fg [option get $itk_component(hull) font Font]
1847    #set bfg [option get $itk_component(hull) boldFont Font]
1848
1849    set inner [$itk_component(main) insert end \
1850        -title "Streams Settings" \
1851        -icon [Rappture::icon streamlines-on]]
1852    $inner configure -borderwidth 4
1853
1854    checkbutton $inner.streamlines \
1855        -text "Show Streamlines" \
1856        -variable [itcl::scope _settings(streamlinesVisible)] \
1857        -command [itcl::code $this AdjustSetting streamlinesVisible] \
1858        -font "Arial 9"
1859   
1860    checkbutton $inner.lighting \
1861        -text "Enable Lighting" \
1862        -variable [itcl::scope _settings(streamlinesLighting)] \
1863        -command [itcl::code $this AdjustSetting streamlinesLighting] \
1864        -font "Arial 9"
1865
1866    checkbutton $inner.seeds \
1867        -text "Show Seeds" \
1868        -variable [itcl::scope _settings(streamlinesSeedsVisible)] \
1869        -command [itcl::code $this AdjustSetting streamlinesSeedsVisible] \
1870        -font "Arial 9"
1871
1872    label $inner.mode_l -text "Mode" -font "Arial 9"
1873    itk_component add streammode {
1874        Rappture::Combobox $inner.mode -width 10 -editable no
1875    }
1876    $inner.mode choices insert end \
1877        "lines"    "lines" \
1878        "ribbons"   "ribbons" \
1879        "tubes"     "tubes"
1880    $itk_component(streammode) value $_settings(streamlinesMode)
1881    bind $inner.mode <<Value>> [itcl::code $this AdjustSetting streamlinesMode]
1882
1883    label $inner.opacity_l -text "Opacity" -font "Arial 9"
1884    ::scale $inner.opacity -from 0 -to 100 -orient horizontal \
1885        -variable [itcl::scope _settings(streamlinesOpacity)] \
1886        -width 10 \
1887        -showvalue off \
1888        -command [itcl::code $this AdjustSetting streamlinesOpacity]
1889
1890    label $inner.density_l -text "No. Seeds" -font "Arial 9"
1891    ::scale $inner.density -from 1 -to 1000 -orient horizontal \
1892        -variable [itcl::scope _settings(streamlinesNumSeeds)] \
1893        -width 10 \
1894        -showvalue on \
1895        -command [itcl::code $this AdjustSetting streamlinesNumSeeds]
1896
1897    label $inner.scale_l -text "Scale" -font "Arial 9"
1898    ::scale $inner.scale -from 1 -to 100 -orient horizontal \
1899        -variable [itcl::scope _settings(streamlinesScale)] \
1900        -width 10 \
1901        -showvalue off \
1902        -command [itcl::code $this AdjustSetting streamlinesScale]
1903
1904    label $inner.field_l -text "Field" -font "Arial 9"
1905    itk_component add field {
1906        Rappture::Combobox $inner.field -width 10 -editable no
1907    }
1908    bind $inner.field <<Value>> \
1909        [itcl::code $this AdjustSetting streamlines-field]
1910
1911    label $inner.palette_l -text "Palette" -font "Arial 9"
1912    itk_component add palette {
1913        Rappture::Combobox $inner.palette -width 10 -editable no
1914    }
1915    $inner.palette choices insert end \
1916        "BCGYR"              "BCGYR"            \
1917        "BGYOR"              "BGYOR"            \
1918        "blue"               "blue"             \
1919        "blue-to-brown"      "blue-to-brown"    \
1920        "blue-to-orange"     "blue-to-orange"   \
1921        "blue-to-grey"       "blue-to-grey"     \
1922        "green-to-magenta"   "green-to-magenta" \
1923        "greyscale"          "greyscale"        \
1924        "nanohub"            "nanohub"          \
1925        "rainbow"            "rainbow"          \
1926        "spectral"           "spectral"         \
1927        "ROYGB"              "ROYGB"            \
1928        "RYGCB"              "RYGCB"            \
1929        "brown-to-blue"      "brown-to-blue"    \
1930        "grey-to-blue"       "grey-to-blue"     \
1931        "orange-to-blue"     "orange-to-blue"   
1932
1933    $itk_component(palette) value "BCGYR"
1934    bind $inner.palette <<Value>> \
1935        [itcl::code $this AdjustSetting streamlines-palette]
1936
1937    blt::table $inner \
1938        0,0 $inner.palette_l   -anchor w -pady 2  \
1939        0,1 $inner.palette     -fill x   -pady 2  \
1940        1,0 $inner.field_l     -anchor w -pady 2  \
1941        1,1 $inner.field       -fill x   -pady 2  \
1942        2,0 $inner.mode_l      -anchor w -pady 2  \
1943        2,1 $inner.mode        -fill x   -pady 2  \
1944        3,0 $inner.opacity_l   -anchor w -pady 2  \
1945        3,1 $inner.opacity     -anchor w -pady 2  \
1946        5,0 $inner.lighting    -anchor w -pady 2 -cspan 2 \
1947        6,0 $inner.seeds       -anchor w -pady 2 -cspan 2 \
1948        7,0 $inner.density_l   -anchor w -pady 2  \
1949        7,1 $inner.density     -fill x   -pady 2  \
1950
1951    blt::table configure $inner r* c* -resize none
1952    blt::table configure $inner r10 c1 c2 -resize expand
1953}
1954
1955itcl::body Rappture::VtkStreamlinesViewer::BuildAxisTab {} {
1956
1957    set fg [option get $itk_component(hull) font Font]
1958    #set bfg [option get $itk_component(hull) boldFont Font]
1959
1960    set inner [$itk_component(main) insert end \
1961        -title "Axis Settings" \
1962        -icon [Rappture::icon axis1]]
1963    $inner configure -borderwidth 4
1964
1965    checkbutton $inner.visible \
1966        -text "Show Axes" \
1967        -variable [itcl::scope _settings(axesVisible)] \
1968        -command [itcl::code $this AdjustSetting axesVisible] \
1969        -font "Arial 9"
1970
1971    checkbutton $inner.labels \
1972        -text "Show Axis Labels" \
1973        -variable [itcl::scope _settings(axisLabelsVisible)] \
1974        -command [itcl::code $this AdjustSetting axisLabelsVisible] \
1975        -font "Arial 9"
1976
1977    checkbutton $inner.xgrid \
1978        -text "Show X Grid" \
1979        -variable [itcl::scope _settings(axisXGrid)] \
1980        -command [itcl::code $this AdjustSetting axisXGrid] \
1981        -font "Arial 9"
1982    checkbutton $inner.ygrid \
1983        -text "Show Y Grid" \
1984        -variable [itcl::scope _settings(axisYGrid)] \
1985        -command [itcl::code $this AdjustSetting axisYGrid] \
1986        -font "Arial 9"
1987    checkbutton $inner.zgrid \
1988        -text "Show Z Grid" \
1989        -variable [itcl::scope _settings(axisZGrid)] \
1990        -command [itcl::code $this AdjustSetting axisZGrid] \
1991        -font "Arial 9"
1992
1993    label $inner.mode_l -text "Mode" -font "Arial 9"
1994
1995    itk_component add axismode {
1996        Rappture::Combobox $inner.mode -width 10 -editable no
1997    }
1998    $inner.mode choices insert end \
1999        "static_triad"    "static" \
2000        "closest_triad"   "closest" \
2001        "furthest_triad"  "furthest" \
2002        "outer_edges"     "outer"         
2003    $itk_component(axismode) value "static"
2004    bind $inner.mode <<Value>> [itcl::code $this AdjustSetting axis-mode]
2005
2006    blt::table $inner \
2007        0,0 $inner.visible -anchor w -cspan 2 \
2008        1,0 $inner.labels  -anchor w -cspan 2 \
2009        2,0 $inner.xgrid   -anchor w -cspan 2 \
2010        3,0 $inner.ygrid   -anchor w -cspan 2 \
2011        4,0 $inner.zgrid   -anchor w -cspan 2 \
2012        5,0 $inner.mode_l  -anchor w -cspan 2 -padx { 2 0 } \
2013        6,0 $inner.mode    -fill x   -cspan 2
2014
2015    blt::table configure $inner r* c* -resize none
2016    blt::table configure $inner r7 c1 -resize expand
2017}
2018
2019
2020itcl::body Rappture::VtkStreamlinesViewer::BuildCameraTab {} {
2021    set inner [$itk_component(main) insert end \
2022        -title "Camera Settings" \
2023        -icon [Rappture::icon camera]]
2024    $inner configure -borderwidth 4
2025
2026    set labels { qx qy qz qw xpan ypan zoom }
2027    set row 0
2028    foreach tag $labels {
2029        label $inner.${tag}label -text $tag -font "Arial 9"
2030        entry $inner.${tag} -font "Arial 9"  -bg white \
2031            -textvariable [itcl::scope _view($tag)]
2032        bind $inner.${tag} <KeyPress-Return> \
2033            [itcl::code $this camera set ${tag}]
2034        blt::table $inner \
2035            $row,0 $inner.${tag}label -anchor e -pady 2 \
2036            $row,1 $inner.${tag} -anchor w -pady 2
2037        blt::table configure $inner r$row -resize none
2038        incr row
2039    }
2040    checkbutton $inner.ortho \
2041        -text "Orthographic Projection" \
2042        -variable [itcl::scope _view(ortho)] \
2043        -command [itcl::code $this camera set ortho] \
2044        -font "Arial 9"
2045    blt::table $inner \
2046            $row,0 $inner.ortho -columnspan 2 -anchor w -pady 2
2047    blt::table configure $inner r$row -resize none
2048    incr row
2049
2050    blt::table configure $inner c0 c1 -resize none
2051    blt::table configure $inner c2 -resize expand
2052    blt::table configure $inner r$row -resize expand
2053}
2054
2055itcl::body Rappture::VtkStreamlinesViewer::BuildCutplaneTab {} {
2056
2057    set fg [option get $itk_component(hull) font Font]
2058   
2059    set inner [$itk_component(main) insert end \
2060        -title "Cutplane Settings" \
2061        -icon [Rappture::icon cutbutton]]
2062
2063    $inner configure -borderwidth 4
2064
2065    checkbutton $inner.visible \
2066        -text "Show Cutplanes" \
2067        -variable [itcl::scope _settings(cutplaneVisible)] \
2068        -command [itcl::code $this AdjustSetting cutplaneVisible] \
2069        -font "Arial 9"
2070
2071    checkbutton $inner.wireframe \
2072        -text "Show Wireframe" \
2073        -variable [itcl::scope _settings(cutplaneWireframe)] \
2074        -command [itcl::code $this AdjustSetting cutplaneWireframe] \
2075        -font "Arial 9"
2076
2077    checkbutton $inner.lighting \
2078        -text "Enable Lighting" \
2079        -variable [itcl::scope _settings(cutplaneLighting)] \
2080        -command [itcl::code $this AdjustSetting cutplaneLighting] \
2081        -font "Arial 9"
2082
2083    checkbutton $inner.edges \
2084        -text "Show Edges" \
2085        -variable [itcl::scope _settings(cutplaneEdges)] \
2086        -command [itcl::code $this AdjustSetting cutplaneEdges] \
2087        -font "Arial 9"
2088
2089    label $inner.opacity_l -text "Opacity" -font "Arial 9"
2090    ::scale $inner.opacity -from 0 -to 100 -orient horizontal \
2091        -variable [itcl::scope _settings(cutplaneOpacity)] \
2092        -width 10 \
2093        -showvalue off \
2094        -command [itcl::code $this AdjustSetting cutplaneOpacity]
2095    $inner.opacity set $_settings(cutplaneOpacity)
2096
2097    # X-value slicer...
2098    itk_component add xCutButton {
2099        Rappture::PushButton $inner.xbutton \
2100            -onimage [Rappture::icon x-cutplane-red] \
2101            -offimage [Rappture::icon x-cutplane-red] \
2102            -command [itcl::code $this AdjustSetting cutplaneXVisible] \
2103            -variable [itcl::scope _settings(cutplaneXVisible)]
2104    }
2105    Rappture::Tooltip::for $itk_component(xCutButton) \
2106        "Toggle the X-axis cutplane on/off"
2107    $itk_component(xCutButton) select
2108
2109    itk_component add xCutScale {
2110        ::scale $inner.xval -from 100 -to 0 \
2111            -width 10 -orient vertical -showvalue yes \
2112            -borderwidth 1 -highlightthickness 0 \
2113            -command [itcl::code $this EventuallySetCutplane x] \
2114            -variable [itcl::scope _settings(cutplaneXPosition)] \
2115            -foreground red3 -font "Arial 9 bold"
2116    } {
2117        usual
2118        ignore -borderwidth -highlightthickness -foreground -font
2119    }
2120    # Set the default cutplane value before disabling the scale.
2121    $itk_component(xCutScale) set 50
2122    $itk_component(xCutScale) configure -state disabled
2123    Rappture::Tooltip::for $itk_component(xCutScale) \
2124        "@[itcl::code $this Slice tooltip x]"
2125
2126    # Y-value slicer...
2127    itk_component add yCutButton {
2128        Rappture::PushButton $inner.ybutton \
2129            -onimage [Rappture::icon y-cutplane-green] \
2130            -offimage [Rappture::icon y-cutplane-green] \
2131            -command [itcl::code $this AdjustSetting cutplaneYVisible] \
2132            -variable [itcl::scope _settings(cutplaneYVisible)]
2133    }
2134    Rappture::Tooltip::for $itk_component(yCutButton) \
2135        "Toggle the Y-axis cutplane on/off"
2136    $itk_component(yCutButton) select
2137
2138    itk_component add yCutScale {
2139        ::scale $inner.yval -from 100 -to 0 \
2140            -width 10 -orient vertical -showvalue yes \
2141            -borderwidth 1 -highlightthickness 0 \
2142            -command [itcl::code $this EventuallySetCutplane y] \
2143            -variable [itcl::scope _settings(cutplaneYPosition)] \
2144            -foreground green3 -font "Arial 9 bold"
2145    } {
2146        usual
2147        ignore -borderwidth -highlightthickness -foreground -font
2148    }
2149    Rappture::Tooltip::for $itk_component(yCutScale) \
2150        "@[itcl::code $this Slice tooltip y]"
2151    # Set the default cutplane value before disabling the scale.
2152    $itk_component(yCutScale) set 50
2153    $itk_component(yCutScale) configure -state disabled
2154
2155    # Z-value slicer...
2156    itk_component add zCutButton {
2157        Rappture::PushButton $inner.zbutton \
2158            -onimage [Rappture::icon z-cutplane-blue] \
2159            -offimage [Rappture::icon z-cutplane-blue] \
2160            -command [itcl::code $this AdjustSetting cutplaneZVisible] \
2161            -variable [itcl::scope _settings(cutplaneZVisible)]
2162    }
2163    Rappture::Tooltip::for $itk_component(zCutButton) \
2164        "Toggle the Z-axis cutplane on/off"
2165    $itk_component(zCutButton) select
2166
2167    itk_component add zCutScale {
2168        ::scale $inner.zval -from 100 -to 0 \
2169            -width 10 -orient vertical -showvalue yes \
2170            -borderwidth 1 -highlightthickness 0 \
2171            -command [itcl::code $this EventuallySetCutplane z] \
2172            -variable [itcl::scope _settings(cutplaneZPosition)] \
2173            -foreground blue3 -font "Arial 9 bold"
2174    } {
2175        usual
2176        ignore -borderwidth -highlightthickness -foreground -font
2177    }
2178    $itk_component(zCutScale) set 50
2179    $itk_component(zCutScale) configure -state disabled
2180    #$itk_component(zCutScale) configure -state disabled
2181    Rappture::Tooltip::for $itk_component(zCutScale) \
2182        "@[itcl::code $this Slice tooltip z]"
2183
2184    blt::table $inner \
2185        0,0 $inner.lighting             -anchor w -pady 2 -cspan 4 \
2186        1,0 $inner.wireframe            -anchor w -pady 2 -cspan 4 \
2187        2,0 $inner.edges                -anchor w -pady 2 -cspan 4 \
2188        3,0 $inner.opacity_l            -anchor w -pady 2 -cspan 1 \
2189        3,1 $inner.opacity              -fill x   -pady 2 -cspan 3 \
2190        4,0 $itk_component(xCutButton)  -anchor w -padx 2 -pady 2 \
2191        5,0 $itk_component(yCutButton)  -anchor w -padx 2 -pady 2 \
2192        6,0 $itk_component(zCutButton)  -anchor w -padx 2 -pady 2 \
2193        4,1 $itk_component(xCutScale)   -fill y -rspan 4 \
2194        4,2 $itk_component(yCutScale)   -fill y -rspan 4 \
2195        4,3 $itk_component(zCutScale)   -fill y -rspan 4 \
2196
2197    blt::table configure $inner r* c* -resize none
2198    blt::table configure $inner r7 c4 -resize expand
2199}
2200
2201
2202
2203#
2204#  camera --
2205#
2206itcl::body Rappture::VtkStreamlinesViewer::camera {option args} {
2207    switch -- $option {
2208        "show" {
2209            puts [array get _view]
2210        }
2211        "set" {
2212            set who [lindex $args 0]
2213            set x $_view($who)
2214            set code [catch { string is double $x } result]
2215            if { $code != 0 || !$result } {
2216                return
2217            }
2218            switch -- $who {
2219                "ortho" {
2220                    if {$_view(ortho)} {
2221                        SendCmd "camera mode ortho"
2222                    } else {
2223                        SendCmd "camera mode persp"
2224                    }
2225                }
2226                "xpan" - "ypan" {
2227                    PanCamera
2228                }
2229                "qx" - "qy" - "qz" - "qw" {
2230                    set q [list $_view(qw) $_view(qx) $_view(qy) $_view(qz)]
2231                    $_arcball quaternion $q
2232                    EventuallyRotate $q
2233                }
2234                "zoom" {
2235                    SendCmd "camera zoom $_view(zoom)"
2236                }
2237            }
2238        }
2239    }
2240}
2241
2242itcl::body Rappture::VtkStreamlinesViewer::ConvertToVtkData { dataobj comp } {
2243    foreach { x1 x2 xN y1 y2 yN } [$dataobj mesh $comp] break
2244    set values [$dataobj values $comp]
2245    append out "# vtk DataFile Version 2.0 \n"
2246    append out "Test data \n"
2247    append out "ASCII \n"
2248    append out "DATASET STRUCTURED_POINTS \n"
2249    append out "DIMENSIONS $xN $yN 1 \n"
2250    append out "ORIGIN 0 0 0 \n"
2251    append out "SPACING 1 1 1 \n"
2252    append out "POINT_DATA [expr $xN * $yN] \n"
2253    append out "SCALARS field float 1 \n"
2254    append out "LOOKUP_TABLE default \n"
2255    append out [join $values "\n"]
2256    append out "\n"
2257    return $out
2258}
2259
2260
2261itcl::body Rappture::VtkStreamlinesViewer::GetVtkData { args } {
2262    set bytes ""
2263    foreach dataobj [get] {
2264        foreach comp [$dataobj components] {
2265            set tag $dataobj-$comp
2266            #set contents [ConvertToVtkData $dataobj $comp]
2267            set contents [$dataobj blob $comp]
2268            append bytes "$contents\n\n"
2269        }
2270    }
2271    return [list .vtk $bytes]
2272}
2273
2274itcl::body Rappture::VtkStreamlinesViewer::GetImage { args } {
2275    if { [image width $_image(download)] > 0 &&
2276         [image height $_image(download)] > 0 } {
2277        set bytes [$_image(download) data -format "jpeg -quality 100"]
2278        set bytes [Rappture::encoding::decode -as b64 $bytes]
2279        return [list .jpg $bytes]
2280    }
2281    return ""
2282}
2283
2284itcl::body Rappture::VtkStreamlinesViewer::BuildDownloadPopup { popup command } {
2285    Rappture::Balloon $popup \
2286        -title "[Rappture::filexfer::label downloadWord] as..."
2287    set inner [$popup component inner]
2288    label $inner.summary -text "" -anchor w
2289    radiobutton $inner.vtk_button -text "VTK data file" \
2290        -variable [itcl::scope _downloadPopup(format)] \
2291        -font "Helvetica 9 " \
2292        -value vtk 
2293    Rappture::Tooltip::for $inner.vtk_button "Save as VTK data file."
2294    radiobutton $inner.image_button -text "Image File" \
2295        -variable [itcl::scope _downloadPopup(format)] \
2296        -value image
2297    Rappture::Tooltip::for $inner.image_button \
2298        "Save as digital image."
2299
2300    button $inner.ok -text "Save" \
2301        -highlightthickness 0 -pady 2 -padx 3 \
2302        -command $command \
2303        -compound left \
2304        -image [Rappture::icon download]
2305
2306    button $inner.cancel -text "Cancel" \
2307        -highlightthickness 0 -pady 2 -padx 3 \
2308        -command [list $popup deactivate] \
2309        -compound left \
2310        -image [Rappture::icon cancel]
2311
2312    blt::table $inner \
2313        0,0 $inner.summary -cspan 2  \
2314        1,0 $inner.vtk_button -anchor w -cspan 2 -padx { 4 0 } \
2315        2,0 $inner.image_button -anchor w -cspan 2 -padx { 4 0 } \
2316        4,1 $inner.cancel -width .9i -fill y \
2317        4,0 $inner.ok -padx 2 -width .9i -fill y
2318    blt::table configure $inner r3 -height 4
2319    blt::table configure $inner r4 -pady 4
2320    raise $inner.image_button
2321    $inner.vtk_button invoke
2322    return $inner
2323}
2324
2325itcl::body Rappture::VtkStreamlinesViewer::SetObjectStyle { dataobj comp } {
2326    # Parse style string.
2327    set tag $dataobj-$comp
2328    set style [$dataobj style $comp]
2329    array set settings {
2330        -color \#808080
2331        -edges 0
2332        -edgecolor black
2333        -linewidth 1.0
2334        -opacity 0.4
2335        -wireframe 0
2336        -lighting 1
2337        -seeds 1
2338        -seedcolor white
2339        -visible 1
2340    }
2341    if { $dataobj != $_first } {
2342        set settings(-opacity) 1
2343    }
2344    array set settings $style
2345    SendCmd "streamlines add $tag"
2346    SendCmd "streamlines seed visible off $tag"
2347    set seeds [$dataobj hints seeds]
2348    if { $seeds != "" && ![info exists _seeds($dataobj)] } {
2349        set length [string length $seeds]
2350        SendCmd "streamlines seed fmesh 200 data follows $length $tag"
2351        SendCmd "$seeds"
2352        set _seeds($dataobj) 1
2353    }
2354    SendCmd "cutplane add $tag"
2355    SendCmd "cutplane edges 0 $tag"
2356    SendCmd "cutplane wireframe 0 $tag"
2357    SendCmd "cutplane lighting 1 $tag"
2358    SendCmd "cutplane linewidth 1 $tag"
2359    #SendCmd "cutplane linecolor 1 1 1 $tag"
2360    #SendCmd "cutplane visible $tag"
2361    foreach axis { x y z } {
2362        SendCmd "cutplane slice $axis 0.5 $tag"
2363        SendCmd "cutplane axis $axis 0 $tag"
2364    }
2365
2366    SendCmd "polydata add $tag"
2367    SendCmd "polydata edges $settings(-edges) $tag"
2368    set _settings(volumeEdges) $settings(-edges)
2369    SendCmd "polydata color [Color2RGB $settings(-color)] $tag"
2370    SendCmd "polydata lighting $settings(-lighting) $tag"
2371    set _settings(volumeLighting) $settings(-lighting)
2372    SendCmd "polydata linecolor [Color2RGB $settings(-edgecolor)] $tag"
2373    SendCmd "polydata linewidth $settings(-linewidth) $tag"
2374    SendCmd "polydata opacity $settings(-opacity) $tag"
2375    set _settings(volumeOpacity) $settings(-opacity)
2376    SendCmd "polydata wireframe $settings(-wireframe) $tag"
2377    set _settings(volumeWireframe) $settings(-wireframe)
2378    set _settings(volumeOpacity) [expr $settings(-opacity) * 100.0]
2379    SetColormap $dataobj $comp
2380}
2381
2382itcl::body Rappture::VtkStreamlinesViewer::IsValidObject { dataobj } {
2383    if {[catch {$dataobj isa Rappture::Field} valid] != 0 || !$valid} {
2384        return 0
2385    }
2386    return 1
2387}
2388
2389# ----------------------------------------------------------------------
2390# USAGE: ReceiveLegend <colormap> <title> <vmin> <vmax> <size>
2391#
2392# Invoked automatically whenever the "legend" command comes in from
2393# the rendering server.  Indicates that binary image data with the
2394# specified <size> will follow.
2395# ----------------------------------------------------------------------
2396itcl::body Rappture::VtkStreamlinesViewer::ReceiveLegend { colormap title vmin vmax size } {
2397    set _legendPending 0
2398    set _limits(vmin) $vmin
2399    set _limits(vmax) $vmax
2400    set _title $title
2401    regsub {\(mag\)} $title "" _title
2402    if { [IsConnected] } {
2403        set bytes [ReceiveBytes $size]
2404        if { ![info exists _image(legend)] } {
2405            set _image(legend) [image create photo]
2406        }
2407        $_image(legend) configure -data $bytes
2408        #puts stderr "read $size bytes for [image width $_image(legend)]x[image height $_image(legend)] legend>"
2409        if { [catch {DrawLegend $_title} errs] != 0 } {
2410            puts stderr errs=$errs
2411        }
2412    }
2413}
2414
2415#
2416# DrawLegend --
2417#
2418#       Draws the legend in it's own canvas which resides to the right
2419#       of the contour plot area.
2420#
2421itcl::body Rappture::VtkStreamlinesViewer::DrawLegend { name } {
2422    set c $itk_component(view)
2423    set w [winfo width $c]
2424    set h [winfo height $c]
2425    set font "Arial 8"
2426    set lineht [font metrics $font -linespace]
2427   
2428    if { [info exists _fields($name)] } {
2429        foreach { title units } $_fields($name) break
2430        if { $units != "" } {
2431            set title [format "%s (%s)" $title $units]
2432        }
2433    } else {
2434        set title $name
2435    }
2436    if { $_settings(legendVisible) } {
2437        set x [expr $w - 2]
2438        if { [$c find withtag "legend"] == "" } {
2439            set y 2
2440            $c create text $x $y \
2441                -anchor ne \
2442                -fill $itk_option(-plotforeground) -tags "title legend" \
2443                -font $font
2444            incr y $lineht
2445            $c create text $x $y \
2446                -anchor ne \
2447                -fill $itk_option(-plotforeground) -tags "vmax legend" \
2448                -font $font
2449            incr y $lineht
2450            $c create image $x $y \
2451                -anchor ne \
2452                -image $_image(legend) -tags "colormap legend"
2453            $c create text $x [expr {$h-2}] \
2454                -anchor se \
2455                -fill $itk_option(-plotforeground) -tags "vmin legend" \
2456                -font $font
2457            #$c bind colormap <Enter> [itcl::code $this EnterLegend %x %y]
2458            $c bind colormap <Leave> [itcl::code $this LeaveLegend]
2459            $c bind colormap <Motion> [itcl::code $this MotionLegend %x %y]
2460        }
2461        $c bind title <ButtonPress> [itcl::code $this Combo post]
2462        $c bind title <Enter> [itcl::code $this Combo activate]
2463        $c bind title <Leave> [itcl::code $this Combo deactivate]
2464        # Reset the item coordinates according the current size of the plot.
2465        $c itemconfigure title -text $title
2466        if { $_limits(vmin) != "" } {
2467            $c itemconfigure vmin -text [format %g $_limits(vmin)]
2468        }
2469        if { $_limits(vmax) != "" } {
2470            $c itemconfigure vmax -text [format %g $_limits(vmax)]
2471        }
2472        set y 2
2473        $c coords title $x $y
2474        incr y $lineht
2475        $c coords vmax $x $y
2476        incr y $lineht
2477        $c coords colormap $x $y
2478        $c coords vmin $x [expr {$h - 2}]
2479    }
2480}
2481
2482#
2483# EnterLegend --
2484#
2485itcl::body Rappture::VtkStreamlinesViewer::EnterLegend { x y } {
2486    SetLegendTip $x $y
2487}
2488
2489#
2490# MotionLegend --
2491#
2492itcl::body Rappture::VtkStreamlinesViewer::MotionLegend { x y } {
2493    Rappture::Tooltip::tooltip cancel
2494    set c $itk_component(view)
2495    SetLegendTip $x $y
2496}
2497
2498#
2499# LeaveLegend --
2500#
2501itcl::body Rappture::VtkStreamlinesViewer::LeaveLegend { } {
2502    Rappture::Tooltip::tooltip cancel
2503    .rappturetooltip configure -icon ""
2504}
2505
2506#
2507# SetLegendTip --
2508#
2509itcl::body Rappture::VtkStreamlinesViewer::SetLegendTip { x y } {
2510    set c $itk_component(view)
2511    set w [winfo width $c]
2512    set h [winfo height $c]
2513    set font "Arial 8"
2514    set lineht [font metrics $font -linespace]
2515   
2516    set imgHeight [image height $_image(legend)]
2517    set coords [$c coords colormap]
2518    set imgX [expr $w - [image width $_image(legend)] - 2]
2519    set imgY [expr $y - 2 * ($lineht + 2)]
2520
2521    if { [info exists _fields($_title)] } {
2522        foreach { title units } $_fields($_title) break
2523        if { $units != "" } {
2524            set title [format "%s (%s)" $title $units]
2525        }
2526    } else {
2527        set title $_title
2528    }
2529    # Make a swatch of the selected color
2530    if { [catch { $_image(legend) get 10 $imgY } pixel] != 0 } {
2531        #puts stderr "out of range: $imgY"
2532        return
2533    }
2534    if { ![info exists _image(swatch)] } {
2535        set _image(swatch) [image create photo -width 24 -height 24]
2536    }
2537    set color [eval format "\#%02x%02x%02x" $pixel]
2538    $_image(swatch) put black  -to 0 0 23 23
2539    $_image(swatch) put $color -to 1 1 22 22
2540    .rappturetooltip configure -icon $_image(swatch)
2541
2542    # Compute the value of the point
2543    if { [info exists _limits(vmax)] && [info exists _limits(vmin)] } {
2544        set t [expr 1.0 - (double($imgY) / double($imgHeight-1))]
2545        set value [expr $t * ($_limits(vmax) - $_limits(vmin)) + $_limits(vmin)]
2546    } else {
2547        set value 0.0
2548    }
2549    set tipx [expr $x + 15]
2550    set tipy [expr $y - 5]
2551    Rappture::Tooltip::text $c "$title $value"
2552    Rappture::Tooltip::tooltip show $c +$tipx,+$tipy   
2553}
2554
2555
2556# ----------------------------------------------------------------------
2557# USAGE: Slice move x|y|z <newval>
2558#
2559# Called automatically when the user drags the slider to move the
2560# cut plane that slices 3D data.  Gets the current value from the
2561# slider and moves the cut plane to the appropriate point in the
2562# data set.
2563# ----------------------------------------------------------------------
2564itcl::body Rappture::VtkStreamlinesViewer::Slice {option args} {
2565    switch -- $option {
2566        "move" {
2567            set axis [lindex $args 0]
2568            set oldval $_settings(axis-${axis}position)
2569            set newval [lindex $args 1]
2570            if {[llength $args] != 2} {
2571                error "wrong # args: should be \"Slice move x|y|z newval\""
2572            }
2573            set newpos [expr {0.01*$newval}]
2574            SendCmd "cutplane slice $axis $newpos"
2575        }
2576        "tooltip" {
2577            set axis [lindex $args 0]
2578            set val [$itk_component(${axis}CutScale) get]
2579            return "Move the [string toupper $axis] cut plane.\nCurrently:  $axis = $val%"
2580        }
2581        default {
2582            error "bad option \"$option\": should be axis, move, or tooltip"
2583        }
2584    }
2585}
2586
2587
2588# ----------------------------------------------------------------------
2589# USAGE: _dropdown post
2590# USAGE: _dropdown unpost
2591# USAGE: _dropdown select
2592#
2593# Used internally to handle the dropdown list for this combobox.  The
2594# post/unpost options are invoked when the list is posted or unposted
2595# to manage the relief of the controlling button.  The select option
2596# is invoked whenever there is a selection from the list, to assign
2597# the value back to the gauge.
2598# ----------------------------------------------------------------------
2599itcl::body Rappture::VtkStreamlinesViewer::Combo {option} {
2600    set c $itk_component(view)
2601    switch -- $option {
2602        post {
2603            foreach { x1 y1 x2 y2 } [$c bbox title] break
2604            set x1 [expr [winfo width $itk_component(view)] - [winfo reqwidth $itk_component(fieldmenu)]]
2605            set x [expr $x1 + [winfo rootx $itk_component(view)]]
2606            set y [expr $y2 + [winfo rooty $itk_component(view)]]
2607            tk_popup $itk_component(fieldmenu) $x $y
2608        }
2609        activate {
2610            $c itemconfigure title -fill red
2611        }
2612        deactivate {
2613            $c itemconfigure title -fill white
2614        }
2615        invoke {
2616            $itk_component(field) value $_currentField
2617            AdjustSetting streamlines-field
2618        }
2619        default {
2620            error "bad option \"$option\": should be post, unpost, select"
2621        }
2622    }
2623}
Note: See TracBrowser for help on using the repository browser.