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

Last change on this file since 464 was 464, checked in by mmc, 18 years ago

Added popup options for the "download" button. Right now this works
only for <curve> objects. You can select between CSV and PDF output.
Will add other formats later.

Fixed a few "after cancel" errors that were happening when you switch
between inputs in the structure demo.

Fixed the colors and fonts for the new bug report window.

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