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

Last change on this file since 193 was 193, checked in by mmc, 18 years ago
  • Added "Upload..." capability to the <loader> widget. You can now add an <upload> directive containing the path of the control that will receive data. A file is uploaded from the desktop and saved in the specified control.
  • Fixed download capability to work properly for all result types, including contour plots and molecules.
  • Added rappture::signal so we can catch SIGHUP and clean up the upload/download spool directory.
File size: 17.1 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: imageresult - picture image in a ResultSet
3#
4#  This widget displays an image found in the output of a Rappture
5#  tool run.  Use the "add" and "delete" methods to control the images
6#  showing in the widget.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005  Purdue Research Foundation
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 *ImageResult.width 3i widgetDefault
19option add *ImageResult.height 3i widgetDefault
20option add *ImageResult.controlBackground gray widgetDefault
21option add *ImageResult.font \
22    -*-helvetica-medium-r-normal-*-*-120-* widgetDefault
23
24blt::bitmap define ImageResult-reset {
25#define reset_width 12
26#define reset_height 12
27static unsigned char reset_bits[] = {
28   0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02,
29   0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00};
30}
31
32blt::bitmap define ImageResult-zoomin {
33#define zoomin_width 12
34#define zoomin_height 12
35static unsigned char zoomin_bits[] = {
36   0x7c, 0x00, 0x82, 0x00, 0x11, 0x01, 0x11, 0x01, 0x7d, 0x01, 0x11, 0x01,
37   0x11, 0x01, 0x82, 0x03, 0xfc, 0x07, 0x80, 0x0f, 0x00, 0x0f, 0x00, 0x06};
38}
39
40blt::bitmap define ImageResult-zoomout {
41#define zoomout_width 12
42#define zoomout_height 12
43static unsigned char zoomout_bits[] = {
44   0x7c, 0x00, 0x82, 0x00, 0x01, 0x01, 0x01, 0x01, 0x7d, 0x01, 0x01, 0x01,
45   0x01, 0x01, 0x82, 0x03, 0xfc, 0x07, 0x80, 0x0f, 0x00, 0x0f, 0x00, 0x06};
46}
47
48
49itcl::class Rappture::ImageResult {
50    inherit itk::Widget
51
52    constructor {args} { # defined below }
53    destructor { # defined below }
54
55    public method add {image {settings ""}}
56    public method get {}
57    public method delete {args}
58    public method scale {args}
59    public method download {option}
60
61    protected method _rebuild {args}
62    protected method _topimage {}
63    protected method _zoom {option args}
64    protected method _move {option args}
65
66    private variable _dispatcher "" ;# dispatcher for !events
67    private variable _dlist ""      ;# list of data objects
68    private variable _topmost ""    ;# topmost image in _dlist
69    private variable _max           ;# max size of all images
70    private variable _scale         ;# info related to zoom
71    private variable _image         ;# image buffers used for scaling
72}
73                                                                               
74itk::usual ImageResult {
75    keep -background -foreground -cursor -font
76}
77
78# ----------------------------------------------------------------------
79# CONSTRUCTOR
80# ----------------------------------------------------------------------
81itcl::body Rappture::ImageResult::constructor {args} {
82    Rappture::dispatcher _dispatcher
83    $_dispatcher register !rebuild
84    $_dispatcher dispatch $this !rebuild [itcl::code $this _rebuild]
85
86    array set _scale {
87        max 1.0
88        current 1.0
89        x 0
90        y 0
91    }
92
93    option add hull.width hull.height
94    pack propagate $itk_component(hull) no
95
96    itk_component add controls {
97        frame $itk_interior.cntls
98    } {
99        usual
100        rename -background -controlbackground controlBackground Background
101    }
102    pack $itk_component(controls) -side right -fill y
103
104    itk_component add reset {
105        button $itk_component(controls).reset \
106            -borderwidth 1 -padx 1 -pady 1 \
107            -bitmap ImageResult-reset \
108            -command [itcl::code $this _zoom reset]
109    } {
110        usual
111        ignore -borderwidth
112        rename -highlightbackground -controlbackground controlBackground Background
113    }
114    pack $itk_component(reset) -padx 4 -pady 4
115    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
116
117    itk_component add zoomin {
118        button $itk_component(controls).zin \
119            -borderwidth 1 -padx 1 -pady 1 \
120            -bitmap ImageResult-zoomin \
121            -command [itcl::code $this _zoom in]
122    } {
123        usual
124        ignore -borderwidth
125        rename -highlightbackground -controlbackground controlBackground Background
126    }
127    pack $itk_component(zoomin) -padx 4 -pady 4
128    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
129
130    itk_component add zoomout {
131        button $itk_component(controls).zout \
132            -borderwidth 1 -padx 1 -pady 1 \
133            -bitmap ImageResult-zoomout \
134            -command [itcl::code $this _zoom out]
135    } {
136        usual
137        ignore -borderwidth
138        rename -highlightbackground -controlbackground controlBackground Background
139    }
140    pack $itk_component(zoomout) -padx 4 -pady 4
141    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
142
143
144    set _image(zoom) [image create photo]
145    set _image(final) [image create photo]
146
147    itk_component add image {
148        label $itk_interior.image -image $_image(final)
149    } {
150        keep -background -foreground -cursor -font
151    }
152    pack $itk_component(image) -expand yes -fill both
153
154    #
155    # Add bindings for resize/move
156    #
157    bind $itk_component(image) <Configure> \
158        [list $_dispatcher event -idle !rebuild resize 1]
159
160    bind $itk_component(image) <ButtonPress-1> \
161        [itcl::code $this _move click %x %y]
162    bind $itk_component(image) <B1-Motion> \
163        [itcl::code $this _move drag %x %y]
164    bind $itk_component(image) <ButtonRelease-1> \
165        [itcl::code $this _move release %x %y]
166
167    eval itk_initialize $args
168}
169
170# ----------------------------------------------------------------------
171# DESTRUCTOR
172# ----------------------------------------------------------------------
173itcl::body Rappture::ImageResult::destructor {} {
174    foreach name [array names _image] {
175        image delete $_image($name)
176    }
177}
178
179# ----------------------------------------------------------------------
180# USAGE: add <image> ?<settings>?
181#
182# Clients use this to add an image to the plot.  The optional <settings>
183# are used to configure the image.  Allowed settings are -color,
184# -brightness, -width, -linestyle and -raise.
185# ----------------------------------------------------------------------
186itcl::body Rappture::ImageResult::add {image {settings ""}} {
187    array set params {
188        -color auto
189        -brightness 0
190        -width 1
191        -raise 0
192        -linestyle solid
193    }
194    foreach {opt val} $settings {
195        if {![info exists params($opt)]} {
196            error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]"
197        }
198        set params($opt) $val
199    }
200
201    if {$params(-raise)} {
202        set _topmost $image
203        $_dispatcher event -idle !rebuild
204    }
205
206    set pos [lsearch -exact $image $_dlist]
207    if {$pos < 0} {
208        lappend _dlist $image
209        $_dispatcher event -idle !rebuild
210    }
211}
212
213# ----------------------------------------------------------------------
214# USAGE: get
215#
216# Clients use this to query the list of images being displayed, in
217# order from bottom to top of this result.
218# ----------------------------------------------------------------------
219itcl::body Rappture::ImageResult::get {} {
220    # put the dataobj list in order according to -raise options
221    set dlist $_dlist
222
223    set i [lsearch $_dlist $_topmost]
224    if {$i >= 0} {
225        set dlist [lreplace $dlist $i $i]
226        set dlist [linsert $dlist 0 $_topmost]
227    }
228    return $dlist
229}
230
231# ----------------------------------------------------------------------
232# USAGE: delete ?<image1> <image2> ...?
233#
234# Clients use this to delete an image from the plot.  If no images
235# are specified, then all images are deleted.
236# ----------------------------------------------------------------------
237itcl::body Rappture::ImageResult::delete {args} {
238    if {[llength $args] == 0} {
239        set args $_dlist
240    }
241
242    # delete all specified curves
243    set changed 0
244    foreach image $args {
245        set pos [lsearch -exact $_dlist $image]
246        if {$pos >= 0} {
247            set _dlist [lreplace $_dlist $pos $pos]
248            set changed 1
249
250            if {$image == $_topmost} {
251                set _topmost ""
252            }
253        }
254    }
255
256    # if anything changed, then rebuild the plot
257    if {$changed} {
258        $_dispatcher event -idle !rebuild
259    }
260}
261
262# ----------------------------------------------------------------------
263# USAGE: scale ?<image1> <image2> ...?
264#
265# Sets the default limits for the overall plot according to the
266# limits of the data for all of the given <image> objects.  This
267# accounts for all images--even those not showing on the screen.
268# Because of this, the limits are appropriate for all images as
269# the user scans through data in the ResultSet viewer.
270# ----------------------------------------------------------------------
271itcl::body Rappture::ImageResult::scale {args} {
272    set _max(w) 0
273    set _max(h) 0
274    foreach image $args {
275        set imh [$image tkimage]
276
277        set w [image width $imh]
278        if {$w > $_max(w)} { set _max(w) $w }
279
280        set h [image height $imh]
281        if {$h > $_max(h)} { set _max(h) $h }
282    }
283
284    # scale is unknown for now... scale later at next _rebuild
285    set _scale(max) "?"
286    set _scale(current) "?"
287
288    $_dispatcher event -idle !rebuild
289}
290
291# ----------------------------------------------------------------------
292# USAGE: download coming
293# USAGE: download now
294#
295# Clients use this method to create a downloadable representation
296# of the plot.  Returns a list of the form {ext string}, where
297# "ext" is the file extension (indicating the type of data) and
298# "string" is the data itself.
299# ----------------------------------------------------------------------
300itcl::body Rappture::ImageResult::download {option} {
301    switch $option {
302        coming {
303            # nothing to do
304        }
305        now {
306            set top [_topimage]
307            if {$top == ""} {
308                return ""
309            }
310
311            #
312            # Hack alert!  Need data in binary format,
313            # so we'll save to a file and read it back.
314            #
315            set tmpfile /tmp/image[pid].jpg
316            $top write $tmpfile -format jpeg
317            set fid [open $tmpfile r]
318            fconfigure $fid -encoding binary -translation binary
319            set bytes [read $fid]
320            close $fid
321            file delete -force $tmpfile
322
323            return [list .jpg $bytes]
324        }
325        default {
326            error "bad option \"$option\": should be coming, now"
327        }
328    }
329}
330
331# ----------------------------------------------------------------------
332# USAGE: _rebuild ?<eventData>...?
333#
334# Called automatically whenever something changes that affects the
335# data in the widget.  Clears any existing data and rebuilds the
336# widget to display new data.
337# ----------------------------------------------------------------------
338itcl::body Rappture::ImageResult::_rebuild {args} {
339    array set event $args
340    if {[info exists event(resize)] && $event(resize)} {
341        # window changed size -- recompute max scale below
342        set _scale(max) "?"
343    }
344
345    if {$_scale(max) == "?"} {
346        if {![_zoom rescale]} {
347            return
348        }
349    }
350    if {$_scale(current) == "?"} {
351        _zoom reset
352    }
353
354    set w [winfo width $itk_component(image)]
355    set h [winfo height $itk_component(image)]
356    $_image(final) configure -width $w -height $h
357    set bg [$itk_component(image) cget -background]
358    $_image(final) put $bg -to 0 0 $w $h
359
360    set imh [_topimage]
361    if {$imh != ""} {
362        if {$_scale(current) <= 1.0} {
363            set wz [expr {round($_scale(current)*$w)}]
364            set hz [expr {round($_scale(current)*$h)}]
365            if {$wz > 1 && $hz > 1} {
366                $_image(zoom) configure -width $wz -height $hz
367                $_image(zoom) put $bg -to 0 0 $wz $hz
368                set sx [expr {round($_scale(x)*$_scale(current))}]
369                set sy [expr {round($_scale(y)*$_scale(current))}]
370                $_image(zoom) copy $imh -from $sx $sy
371                blt::winop resample $_image(zoom) $_image(final) sinc
372            }
373        } else {
374            set iw [image width $imh]
375            set ih [image height $imh]
376            set wz [expr {round(double($iw)/$_scale(current))}]
377            set hz [expr {round(double($ih)/$_scale(current))}]
378            if {$wz > 1 && $hz > 1} {
379                $_image(zoom) configure -width $wz -height $hz
380                $_image(zoom) put $bg -to 0 0 $wz $hz
381                blt::winop resample $imh $_image(zoom) sinc
382                $_image(final) copy $_image(zoom) -from $_scale(x) $_scale(y)
383            }
384        }
385    }
386}
387
388# ----------------------------------------------------------------------
389# USAGE: _topimage
390#
391# Used internally to get the topmost image currently being displayed.
392# ----------------------------------------------------------------------
393itcl::body Rappture::ImageResult::_topimage {} {
394    set top $_topmost
395    if {"" == $top} {
396        set top [lindex $_dlist 0]
397    }
398    if {"" != $top} {
399        return [$top tkimage]
400    }
401    return ""
402}
403
404# ----------------------------------------------------------------------
405# USAGE: _zoom reset
406# USAGE: _zoom in
407# USAGE: _zoom out
408#
409# Called automatically when the user clicks on one of the zoom
410# controls for this widget.  Changes the zoom for the current view.
411# ----------------------------------------------------------------------
412itcl::body Rappture::ImageResult::_zoom {option args} {
413    switch -- $option {
414        rescale {
415            # empty list? then reset w/h max size
416            if {[llength $_dlist] == 0} {
417                set _max(w) 0
418                set _max(h) 0
419                set _scale(max) 1.0
420            } else {
421                set w [winfo width $itk_component(image)]
422                set h [winfo height $itk_component(image)]
423                if {$w == 1 && $h == 1} {
424                    return 0
425                }
426
427                set wfac [expr {$_max(w)/double($w)}]
428                set hfac [expr {$_max(h)/double($h)}]
429                set _scale(max) [expr {($wfac > $hfac) ? $wfac : $hfac}]
430            }
431            return 1
432        }
433        reset {
434            set _scale(current) $_scale(max)
435            set _scale(x) 0
436            set _scale(y) 0
437        }
438        in {
439            set _scale(current) [expr {$_scale(current)*0.5}]
440        }
441        out {
442            set w [winfo width $itk_component(image)]
443            set h [winfo height $itk_component(image)]
444            if {$_max(w)/$_scale(current) > $w
445                  || $_max(h)/$_scale(current) > $h} {
446                # must be room left to zoom -- zoom out, but not beyond max
447                set _scale(current) [expr {$_scale(current)*2.0}]
448                if {$_scale(current) < $_scale(max)} {
449                    set _scale(current) $_scale(max)
450                }
451            } else {
452                # no room left to zoom -- zoom out max
453                if {$_scale(max) < 1} {
454                    set _scale(current) 1
455                } else {
456                    set _scale(current) $_scale(max)
457                }
458            }
459        }
460    }
461    $_dispatcher event -idle !rebuild
462}
463
464# ----------------------------------------------------------------------
465# USAGE: _move click <x> <y>
466# USAGE: _move drag <x> <y>
467# USAGE: _move release <x> <y>
468#
469# Called automatically when the user clicks and drags on the image
470# to pan the view.  Adjusts the (x,y) offset for the scaling info
471# and redraws the widget.
472# ----------------------------------------------------------------------
473itcl::body Rappture::ImageResult::_move {option args} {
474    switch -- $option {
475        click {
476            foreach {x y} $args break
477            $itk_component(image) configure -cursor fleur
478            set _scale(x0) $_scale(x)
479            set _scale(y0) $_scale(y)
480            set _scale(xclick) $x
481            set _scale(yclick) $y
482        }
483        drag {
484            foreach {x y} $args break
485            if {[info exists _scale(xclick)] && [info exists _scale(yclick)]} {
486                set w [winfo width $itk_component(image)]
487                set h [winfo height $itk_component(image)]
488                set wx [expr {round($_max(w)/$_scale(current))}]
489                set hy [expr {round($_max(h)/$_scale(current))}]
490                if {$wx > $w || $hy > $h} {
491                    set x [expr {$_scale(x0)-$x+$_scale(xclick)}]
492                    if {$x > $wx-$w} {set x [expr {$wx-$w}]}
493                    if {$x < 0} {set x 0}
494
495                    set y [expr {$_scale(y0)-$y+$_scale(yclick)}]
496                    if {$y > $hy-$h} {set y [expr {$hy-$h}]}
497                    if {$y < 0} {set y 0}
498
499                    set _scale(x) $x
500                    set _scale(y) $y
501                } else {
502                    set _scale(x) 0
503                    set _scale(y) 0
504                }
505                $_dispatcher event -idle !rebuild
506            }
507        }
508        release {
509            eval _move drag $args
510            $itk_component(image) configure -cursor ""
511            catch {unset _scale(xclick)}
512            catch {unset _scale(yclick)}
513        }
514        default {
515            error "bad option \"$option\": should be click, drag, release"
516        }
517    }
518}
Note: See TracBrowser for help on using the repository browser.