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

Last change on this file since 2586 was 1929, checked in by gah, 14 years ago
File size: 20.3 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 parameters {title args} { # do nothing }
35    public method download {option args}
36
37    protected method _rebuild {args}
38    protected method _top {what}
39    protected method _zoom {option args}
40    protected method _move {option args}
41
42    private variable _dispatcher "" ;# dispatcher for !events
43    private variable _dlist ""      ;# list of data objects
44    private variable _topmost ""    ;# topmost image in _dlist
45    private variable _max           ;# max size of all images
46    private variable _scale         ;# info related to zoom
47    private variable _image         ;# image buffers used for scaling
48}
49                                                                               
50itk::usual ImageResult {
51    keep -background -foreground -cursor -font
52}
53
54# ----------------------------------------------------------------------
55# CONSTRUCTOR
56# ----------------------------------------------------------------------
57itcl::body Rappture::ImageResult::constructor {args} {
58    Rappture::dispatcher _dispatcher
59    $_dispatcher register !rebuild
60    $_dispatcher dispatch $this !rebuild [itcl::code $this _rebuild]
61
62    array set _scale {
63        max 1.0
64        current 1.0
65        default 1
66        x 0
67        y 0
68    }
69
70    option add hull.width hull.height
71    pack propagate $itk_component(hull) no
72
73    Rappture::Panes $itk_interior.panes -sashwidth 1 -sashrelief solid -sashpadding 2
74    pack $itk_interior.panes -expand yes -fill both
75    set main [$itk_interior.panes pane 0]
76    $itk_interior.panes fraction 0 1
77
78    itk_component add controls {
79        frame $main.cntls
80    } {
81        usual
82        rename -background -controlbackground controlBackground Background
83    }
84    pack $itk_component(controls) -side right -fill y
85
86    itk_component add reset {
87        button $itk_component(controls).reset \
88            -borderwidth 1 -padx 1 -pady 1 \
89            -bitmap [Rappture::icon reset] \
90            -command [itcl::code $this _zoom reset]
91    } {
92        usual
93        ignore -borderwidth
94        rename -highlightbackground -controlbackground controlBackground Background
95    }
96    pack $itk_component(reset) -padx 4 -pady 4
97    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
98
99    itk_component add zoomin {
100        button $itk_component(controls).zin \
101            -borderwidth 1 -padx 1 -pady 1 \
102            -bitmap [Rappture::icon zoomin] \
103            -command [itcl::code $this _zoom in]
104    } {
105        usual
106        ignore -borderwidth
107        rename -highlightbackground -controlbackground controlBackground Background
108    }
109    pack $itk_component(zoomin) -padx 4 -pady 4
110    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
111
112    itk_component add zoomout {
113        button $itk_component(controls).zout \
114            -borderwidth 1 -padx 1 -pady 1 \
115            -bitmap [Rappture::icon zoomout] \
116            -command [itcl::code $this _zoom out]
117    } {
118        usual
119        ignore -borderwidth
120        rename -highlightbackground -controlbackground controlBackground Background
121    }
122    pack $itk_component(zoomout) -padx 4 -pady 4
123    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
124
125
126    set _image(zoom) [image create photo]
127    set _image(final) [image create photo]
128
129    itk_component add image {
130        label $main.image -image $_image(final) -anchor c
131    } {
132        keep -background -foreground -cursor -font
133    }
134    pack $itk_component(image) -expand yes -fill both
135
136    #
137    # Add bindings for resize/move
138    #
139    bind $itk_component(image) <Configure> \
140        [list $_dispatcher event -idle !rebuild resize 1]
141
142    bind $itk_component(image) <ButtonPress-1> \
143        [itcl::code $this _move click %x %y]
144    bind $itk_component(image) <B1-Motion> \
145        [itcl::code $this _move drag %x %y]
146    bind $itk_component(image) <ButtonRelease-1> \
147        [itcl::code $this _move release %x %y]
148
149    #
150    # Add area at the bottom for notes.
151    #
152    set notes [$itk_interior.panes insert end -fraction 0.15]
153    $itk_interior.panes visibility 1 off
154    Rappture::Scroller $notes.scr -xscrollmode auto -yscrollmode auto
155    pack $notes.scr -expand yes -fill both
156    itk_component add notes {
157        Rappture::HTMLviewer $notes.scr.html
158    }
159    $notes.scr contents $notes.scr.html
160
161    eval itk_initialize $args
162}
163
164# ----------------------------------------------------------------------
165# DESTRUCTOR
166# ----------------------------------------------------------------------
167itcl::body Rappture::ImageResult::destructor {} {
168    foreach name [array names _image] {
169        image delete $_image($name)
170    }
171}
172
173# ----------------------------------------------------------------------
174# USAGE: add <image> ?<settings>?
175#
176# Clients use this to add an image to the plot.  The optional <settings>
177# are used to configure the image.  Allowed settings are -color,
178# -brightness, -width, -linestyle and -raise.
179# ----------------------------------------------------------------------
180itcl::body Rappture::ImageResult::add {image {settings ""}} {
181    array set params {
182        -color auto
183        -brightness 0
184        -width 1
185        -raise 0
186        -linestyle solid
187        -description ""
188        -param ""
189    }
190    foreach {opt val} $settings {
191        if {![info exists params($opt)]} {
192            error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]"
193        }
194        set params($opt) $val
195    }
196
197    if {$params(-raise)} {
198        set _topmost $image
199        $_dispatcher event -idle !rebuild
200    }
201
202    set pos [lsearch -exact $image $_dlist]
203    if {$pos < 0} {
204        lappend _dlist $image
205        $_dispatcher event -idle !rebuild
206    }
207}
208
209# ----------------------------------------------------------------------
210# USAGE: get
211#
212# Clients use this to query the list of images being displayed, in
213# order from bottom to top of this result.
214# ----------------------------------------------------------------------
215itcl::body Rappture::ImageResult::get {} {
216    # put the dataobj list in order according to -raise options
217    set dlist $_dlist
218
219    set i [lsearch $_dlist $_topmost]
220    if {$i >= 0} {
221        set dlist [lreplace $dlist $i $i]
222        set dlist [linsert $dlist 0 $_topmost]
223    }
224    return $dlist
225}
226
227# ----------------------------------------------------------------------
228# USAGE: delete ?<image1> <image2> ...?
229#
230# Clients use this to delete an image from the plot.  If no images
231# are specified, then all images are deleted.
232# ----------------------------------------------------------------------
233itcl::body Rappture::ImageResult::delete {args} {
234    if {[llength $args] == 0} {
235        set args $_dlist
236    }
237
238    # delete all specified curves
239    set changed 0
240    foreach image $args {
241        set pos [lsearch -exact $_dlist $image]
242        if {$pos >= 0} {
243            set _dlist [lreplace $_dlist $pos $pos]
244            set changed 1
245
246            if {$image == $_topmost} {
247                set _topmost ""
248            }
249        }
250    }
251
252    # if anything changed, then rebuild the plot
253    if {$changed} {
254        $_dispatcher event -idle !rebuild
255    }
256}
257
258# ----------------------------------------------------------------------
259# USAGE: scale ?<image1> <image2> ...?
260#
261# Sets the default limits for the overall plot according to the
262# limits of the data for all of the given <image> objects.  This
263# accounts for all images--even those not showing on the screen.
264# Because of this, the limits are appropriate for all images as
265# the user scans through data in the ResultSet viewer.
266# ----------------------------------------------------------------------
267itcl::body Rappture::ImageResult::scale {args} {
268    set _max(w) 0
269    set _max(h) 0
270    foreach image $args {
271        set imh [$image tkimage]
272
273        set w [image width $imh]
274        if {$w > $_max(w)} { set _max(w) $w }
275
276        set h [image height $imh]
277        if {$h > $_max(h)} { set _max(h) $h }
278    }
279
280    # scale is unknown for now... scale later at next _rebuild
281    set _scale(max) "?"
282    set _scale(current) "?"
283
284    $_dispatcher event -idle !rebuild
285}
286
287# ----------------------------------------------------------------------
288# USAGE: download coming
289# USAGE: download controls <downloadCommand>
290# USAGE: download now
291#
292# Clients use this method to create a downloadable representation
293# of the plot.  Returns a list of the form {ext string}, where
294# "ext" is the file extension (indicating the type of data) and
295# "string" is the data itself.
296# ----------------------------------------------------------------------
297itcl::body Rappture::ImageResult::download {option args} {
298    switch $option {
299        coming {
300            # nothing to do
301        }
302        controls {
303            # no controls for this download yet
304            return ""
305        }
306        now {
307            set top [_top image]
308            if {$top == ""} {
309                return ""
310            }
311            # Get the image data (as base64) and decode it back to binary.
312            # This is better than writing to temporary files.  When we switch
313            # to the BLT picture image it won't be necessary to decode the
314            # image data.
315            set bytes [$top data -format "jpeg -quality 100"]
316            set bytes [Rappture::encoding::decode -as b64 $bytes]
317            return [list .jpg $bytes]
318        }
319        default {
320            error "bad option \"$option\": should be coming, controls, now"
321        }
322    }
323}
324
325# ----------------------------------------------------------------------
326# USAGE: _rebuild ?<eventData>...?
327#
328# Called automatically whenever something changes that affects the
329# data in the widget.  Clears any existing data and rebuilds the
330# widget to display new data.
331# ----------------------------------------------------------------------
332itcl::body Rappture::ImageResult::_rebuild {args} {
333    array set event $args
334    if {[info exists event(resize)] && $event(resize)} {
335        # window changed size -- recompute max scale below
336        set _scale(max) "?"
337    }
338
339    if {$_scale(max) == "?"} {
340        if {![_zoom rescale]} {
341            return
342        }
343    }
344    if {$_scale(current) == "?" || $_scale(default)} {
345        set _scale(current) $_scale(max)
346        set _scale(x) 0.5
347        set _scale(y) 0.5
348    }
349
350    set w [winfo width $itk_component(image)]
351    set h [winfo height $itk_component(image)]
352    set bg [$itk_component(image) cget -background]
353
354    set imh [_top image]
355    if {$imh != ""} {
356        set iw [image width $imh]
357        set ih [image height $imh]
358        set wz [expr {round($w*$_scale(current))}]
359        set hz [expr {round($h*$_scale(current))}]
360
361        if {$wz < $iw || $hz < $ih} {
362            #
363            # Scale the image up by creating a "zoom" image which
364            # is smaller than the current image.  Sample a small
365            # part of the original image by copying into the "zoom"
366            # image, then scale that part up to the full "view" area.
367            #
368            if {$wz > $iw} {
369                set wz $iw
370            }
371            if {$hz > $ih} {
372                set hz $ih
373            }
374
375            set sx [expr {round($_scale(x)*$_max(w)-0.5*$wz)}]
376            if {$sx+$wz > $iw} {
377                set sx [expr {$iw-$wz}]
378            }
379            if {$sx < 0} {
380                set sx 0
381            }
382
383            set sy [expr {round($_scale(y)*$_max(h)-0.5*$hz)}]
384            if {$sy+$hz > $ih} {
385                set sy [expr {$ih-$hz}]
386            }
387            if {$sy < 0} {
388                set sy 0
389            }
390
391            if {$wz > 1 && $hz > 1} {
392                $_image(zoom) configure -width $wz -height $hz
393                set wf [expr {round(double($wz)/$_scale(current))}]
394                set hf [expr {round(double($hz)/$_scale(current))}]
395                $_image(final) configure -width $wf -height $hf
396                $_image(zoom) copy $imh -from $sx $sy
397                blt::winop resample $_image(zoom) $_image(final) sinc
398            }
399        } else {
400            #
401            # Scale the image down by creating a "zoom" image which
402            # is smaller than the current image.  Resize the original
403            # image to the smaller size, then copy into the current
404            # view.
405            #
406            set wz [expr {round(double($iw)/$_scale(current))}]
407            set hz [expr {round(double($ih)/$_scale(current))}]
408            if {$wz > 1 && $hz > 1} {
409                $_image(zoom) configure -width $wz -height $hz
410                $_image(zoom) put $bg -to 0 0 $wz $hz
411                blt::winop resample $imh $_image(zoom) sinc
412
413                $_image(final) configure -width $wz -height $hz
414                $_image(final) copy $_image(zoom) -from 0 0
415            }
416        }
417    }
418
419    set note [_top note]
420    if {[string length $note] > 0} {
421        if {[regexp {^html://} $note]} {
422            set note [string range $note 7 end]
423        } else {
424            regexp {&} $note {\007} note
425            regexp {<} $note {\&lt;} note
426            regexp {>} $note {\&gt;} note
427            regexp {\007} $note {\&amp;} note
428            regexp "\n\n" $note {<br/>} note
429            set note "<html><body>$note</body></html>"
430        }
431        set notes [$itk_interior.panes pane 1]
432        $itk_component(notes) load $note -in [file join [_top tooldir] docs]
433        $itk_interior.panes visibility 1 on
434    } else {
435        $itk_interior.panes visibility 1 off
436    }
437}
438
439# ----------------------------------------------------------------------
440# USAGE: _top image|note|tooldir
441#
442# Used internally to get the topmost image currently being displayed.
443# ----------------------------------------------------------------------
444itcl::body Rappture::ImageResult::_top {option} {
445    set top $_topmost
446    if {"" == $top} {
447        set top [lindex $_dlist 0]
448    }
449    if {"" != $top} {
450        switch -- $option {
451            image   { return [$top tkimage] }
452            note    { return [$top hints note] }
453            tooldir { return [$top hints tooldir] }
454            default { error "bad option \"$option\": should be image, note, tooldir" }
455        }
456    }
457    return ""
458}
459
460# ----------------------------------------------------------------------
461# USAGE: _zoom reset
462# USAGE: _zoom in
463# USAGE: _zoom out
464#
465# Called automatically when the user clicks on one of the zoom
466# controls for this widget.  Changes the zoom for the current view.
467# ----------------------------------------------------------------------
468itcl::body Rappture::ImageResult::_zoom {option args} {
469    switch -- $option {
470        rescale {
471            # empty list? then reset w/h max size
472            if {[llength $_dlist] == 0} {
473                set _max(w) 0
474                set _max(h) 0
475                set _scale(max) 1.0
476            } else {
477                set w [winfo width $itk_component(image)]
478                set h [winfo height $itk_component(image)]
479                if {$w == 1 && $h == 1} {
480                    return 0
481                }
482
483                if {$w < $h} {
484                    if {$_max(w)/double($_max(h)) > $w/double($h)} {
485                        set _scale(max) [expr {$_max(w)/double($w)}]
486                    } else {
487                        set _scale(max) [expr {$_max(h)/double($h)}]
488                    }
489                } else {
490                    if {$_max(w)/double($_max(h)) < $w/double($h)} {
491                        set _scale(max) [expr {$_max(h)/double($h)}]
492                    } else {
493                        set _scale(max) [expr {$_max(w)/double($w)}]
494                    }
495                }
496            }
497            return 1
498        }
499        reset {
500            set _scale(current) $_scale(max)
501            set _scale(default) 1
502            set _scale(x) 0.5
503            set _scale(y) 0.5
504        }
505        in {
506            set _scale(current) [expr {$_scale(current)*0.8}]
507            set _scale(default) 0
508        }
509        out {
510            set w [winfo width $itk_component(image)]
511            set h [winfo height $itk_component(image)]
512            if {$_max(w)/$_scale(current) > $w
513                  || $_max(h)/$_scale(current) > $h} {
514                # must be room left to zoom -- zoom out, but not beyond max
515                set _scale(current) [expr {$_scale(current)*1.25}]
516                if {$_scale(current) > $_scale(max)} {
517                    set _scale(current) $_scale(max)
518                }
519            } else {
520                # no room left to zoom -- zoom out max
521                set _scale(current) $_scale(max)
522            }
523
524            # fix the center of view, in case it is now out of bounds
525            if {$_scale(current) > 1.0} {
526                set _scale(x) 0.5
527                set _scale(y) 0.5
528            }
529
530            # keep this zoom setting now that we've zoomed out
531            set _scale(default) 0
532        }
533    }
534    $_dispatcher event -idle !rebuild
535}
536
537# ----------------------------------------------------------------------
538# USAGE: _move click <x> <y>
539# USAGE: _move drag <x> <y>
540# USAGE: _move release <x> <y>
541#
542# Called automatically when the user clicks and drags on the image
543# to pan the view.  Adjusts the (x,y) offset for the scaling info
544# and redraws the widget.
545# ----------------------------------------------------------------------
546itcl::body Rappture::ImageResult::_move {option args} {
547    switch -- $option {
548        click {
549            foreach {x y} $args break
550            $itk_component(image) configure -cursor fleur
551            set _scale(x0) $_scale(x)
552            set _scale(y0) $_scale(y)
553            set _scale(xclick) $x
554            set _scale(yclick) $y
555        }
556        drag {
557            foreach {x y} $args break
558            if {[info exists _scale(xclick)] && [info exists _scale(yclick)]} {
559                set w [winfo width $itk_component(image)]
560                set h [winfo height $itk_component(image)]
561                set wx [expr {round($_max(w)/$_scale(current))}]
562                set hy [expr {round($_max(h)/$_scale(current))}]
563                if {$wx > $w || $hy > $h} {
564                    set x [expr {$_scale(x0)-($x-$_scale(xclick))/double($wx)}]
565                    set y [expr {$_scale(y0)-($y-$_scale(yclick))/double($hy)}]
566                    if {$x*$_max(w) < 0.5*$w*$_scale(current)} {
567                        set x [expr {0.5*$w*$_scale(current)/$_max(w)}]
568                    }
569                    if {$x*$_max(w) > $_max(w) - 0.5*$w*$_scale(current)} {
570                        set x [expr {1 - 0.5*$w*$_scale(current)/$_max(w)}]
571                    }
572                    if {$y*$_max(h) < 0.5*$h*$_scale(current)} {
573                        set y [expr {0.5*$h*$_scale(current)/$_max(h)}]
574                    }
575                    if {$y*$_max(h) > $_max(h) - 0.5*$h*$_scale(current)} {
576                        set y [expr {1 - 0.5*$h*$_scale(current)/$_max(h)}]
577                    }
578                    set _scale(x) $x
579                    set _scale(y) $y
580                } else {
581                    set _scale(x) 0.5
582                    set _scale(y) 0.5
583                }
584                $_dispatcher event -idle !rebuild
585            }
586        }
587        release {
588            eval _move drag $args
589            $itk_component(image) configure -cursor ""
590            catch {unset _scale(xclick)}
591            catch {unset _scale(yclick)}
592        }
593        default {
594            error "bad option \"$option\": should be click, drag, release"
595        }
596    }
597}
Note: See TracBrowser for help on using the repository browser.