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

Last change on this file since 676 was 676, checked in by mmc, 17 years ago

Fixed all fonts to set pixelsize instead of pointsize, so that fonts in
the latest X distribution look right.

Added initial Rappture::bugreport::submit command for submitting bug
reports to nanoHUB.org. This isn't tied in yet, but it's a start.

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-*-12-* 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    set rgb [winfo rgb . $bg]
340    set bg [format "#%03x%03x%03x" [lindex $rgb 0] [lindex $rgb 1] [lindex $rgb 2]]
341    $_image(final) put $bg -to 0 0 $w $h
342
343    set imh [_topimage]
344    if {$imh != ""} {
345        if {$_scale(current) <= 1.0} {
346            set wz [expr {round($_scale(current)*$w)}]
347            set hz [expr {round($_scale(current)*$h)}]
348            if {$wz > 1 && $hz > 1} {
349                $_image(zoom) configure -width $wz -height $hz
350                $_image(zoom) put $bg -to 0 0 $wz $hz
351                set sx [expr {round($_scale(x)*$_scale(current))}]
352                set sy [expr {round($_scale(y)*$_scale(current))}]
353                $_image(zoom) copy $imh -from $sx $sy
354                blt::winop resample $_image(zoom) $_image(final) sinc
355            }
356        } else {
357            set iw [image width $imh]
358            set ih [image height $imh]
359            set wz [expr {round(double($iw)/$_scale(current))}]
360            set hz [expr {round(double($ih)/$_scale(current))}]
361            if {$wz > 1 && $hz > 1} {
362                $_image(zoom) configure -width $wz -height $hz
363                $_image(zoom) put $bg -to 0 0 $wz $hz
364                blt::winop resample $imh $_image(zoom) sinc
365                $_image(final) copy $_image(zoom) -from $_scale(x) $_scale(y)
366            }
367        }
368    }
369}
370
371# ----------------------------------------------------------------------
372# USAGE: _topimage
373#
374# Used internally to get the topmost image currently being displayed.
375# ----------------------------------------------------------------------
376itcl::body Rappture::ImageResult::_topimage {} {
377    set top $_topmost
378    if {"" == $top} {
379        set top [lindex $_dlist 0]
380    }
381    if {"" != $top} {
382        return [$top tkimage]
383    }
384    return ""
385}
386
387# ----------------------------------------------------------------------
388# USAGE: _zoom reset
389# USAGE: _zoom in
390# USAGE: _zoom out
391#
392# Called automatically when the user clicks on one of the zoom
393# controls for this widget.  Changes the zoom for the current view.
394# ----------------------------------------------------------------------
395itcl::body Rappture::ImageResult::_zoom {option args} {
396    switch -- $option {
397        rescale {
398            # empty list? then reset w/h max size
399            if {[llength $_dlist] == 0} {
400                set _max(w) 0
401                set _max(h) 0
402                set _scale(max) 1.0
403            } else {
404                set w [winfo width $itk_component(image)]
405                set h [winfo height $itk_component(image)]
406                if {$w == 1 && $h == 1} {
407                    return 0
408                }
409
410                set wfac [expr {$_max(w)/double($w)}]
411                set hfac [expr {$_max(h)/double($h)}]
412                set _scale(max) [expr {($wfac > $hfac) ? $wfac : $hfac}]
413            }
414            return 1
415        }
416        reset {
417            set _scale(current) $_scale(max)
418            set _scale(x) 0
419            set _scale(y) 0
420        }
421        in {
422            set _scale(current) [expr {$_scale(current)*0.5}]
423        }
424        out {
425            set w [winfo width $itk_component(image)]
426            set h [winfo height $itk_component(image)]
427            if {$_max(w)/$_scale(current) > $w
428                  || $_max(h)/$_scale(current) > $h} {
429                # must be room left to zoom -- zoom out, but not beyond max
430                set _scale(current) [expr {$_scale(current)*2.0}]
431                if {$_scale(current) < $_scale(max)} {
432                    set _scale(current) $_scale(max)
433                }
434            } else {
435                # no room left to zoom -- zoom out max
436                if {$_scale(max) < 1} {
437                    set _scale(current) 1
438                } else {
439                    set _scale(current) $_scale(max)
440                }
441            }
442        }
443    }
444    $_dispatcher event -idle !rebuild
445}
446
447# ----------------------------------------------------------------------
448# USAGE: _move click <x> <y>
449# USAGE: _move drag <x> <y>
450# USAGE: _move release <x> <y>
451#
452# Called automatically when the user clicks and drags on the image
453# to pan the view.  Adjusts the (x,y) offset for the scaling info
454# and redraws the widget.
455# ----------------------------------------------------------------------
456itcl::body Rappture::ImageResult::_move {option args} {
457    switch -- $option {
458        click {
459            foreach {x y} $args break
460            $itk_component(image) configure -cursor fleur
461            set _scale(x0) $_scale(x)
462            set _scale(y0) $_scale(y)
463            set _scale(xclick) $x
464            set _scale(yclick) $y
465        }
466        drag {
467            foreach {x y} $args break
468            if {[info exists _scale(xclick)] && [info exists _scale(yclick)]} {
469                set w [winfo width $itk_component(image)]
470                set h [winfo height $itk_component(image)]
471                set wx [expr {round($_max(w)/$_scale(current))}]
472                set hy [expr {round($_max(h)/$_scale(current))}]
473                if {$wx > $w || $hy > $h} {
474                    set x [expr {$_scale(x0)-$x+$_scale(xclick)}]
475                    if {$x > $wx-$w} {set x [expr {$wx-$w}]}
476                    if {$x < 0} {set x 0}
477
478                    set y [expr {$_scale(y0)-$y+$_scale(yclick)}]
479                    if {$y > $hy-$h} {set y [expr {$hy-$h}]}
480                    if {$y < 0} {set y 0}
481
482                    set _scale(x) $x
483                    set _scale(y) $y
484                } else {
485                    set _scale(x) 0
486                    set _scale(y) 0
487                }
488                $_dispatcher event -idle !rebuild
489            }
490        }
491        release {
492            eval _move drag $args
493            $itk_component(image) configure -cursor ""
494            catch {unset _scale(xclick)}
495            catch {unset _scale(yclick)}
496        }
497        default {
498            error "bad option \"$option\": should be click, drag, release"
499        }
500    }
501}
Note: See TracBrowser for help on using the repository browser.