source: trunk/gui/scripts/imageentry.tcl @ 3511

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

merge (by hand) with Rappture1.2 branch

File size: 14.3 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: ImageEntry - widget for displaying images
4#
5#  This widget represents an <image> entry on a control panel.
6#  It displays images which help explain the input.
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 Img
16
17itcl::class Rappture::ImageEntry {
18    inherit itk::Widget
19
20    itk_option define -state state State "normal"
21
22    constructor {owner path args} { # defined below }
23    destructor { # defined below }
24
25    public method value {args}
26
27    public method label {}
28    public method tooltip {}
29
30    protected method _redraw {}
31    protected method _outline {imh color}
32    protected method _uploadValue {args}
33    protected method _downloadValue {}
34
35    private variable _owner ""    ;# thing managing this control
36    private variable _path ""     ;# path in XML to this image
37    private variable _data ""     ;# current image data
38    private variable _imh ""      ;# image handle for current value
39    private variable _resize ""   ;# image for resize operations
40
41    private common _thumbsize 100 ;# std size for thumbnail images
42}
43
44itk::usual ImageEntry {
45    keep -cursor -font
46    keep -foreground -background
47    keep -textbackground
48    keep -selectbackground -selectforeground -selectborderwidth
49}
50
51# ----------------------------------------------------------------------
52# CONSTRUCTOR
53# ----------------------------------------------------------------------
54itcl::body Rappture::ImageEntry::constructor {owner path args} {
55    if {[catch {$owner isa Rappture::ControlOwner} valid] != 0 || !$valid} {
56        error "bad object \"$owner\": should be Rappture::ControlOwner"
57    }
58    set _owner $owner
59    set _path $path
60    set _resize [image create photo]
61
62    #
63    # Create the widget and configure it properly based on other
64    # hints in the XML.  Two ways to display:  Old apps use images
65    # without labels as decorations.  In that case, show the image
66    # alone, probably full size.  Newer apps use images as inputs.
67    # In that case, show a thumbnail of the image with some extra
68    # facts about image type, file size, etc.
69    #
70    itk_component add image {
71        ::label $itk_interior.image -borderwidth 0
72    }
73
74    itk_component add info {
75        ::label $itk_interior.info -borderwidth 0 -width 5 \
76            -anchor w -justify left
77    }
78
79    itk_component add rmenu {
80        menu $itk_interior.menu -tearoff 0
81    } {
82        usual
83        ignore -tearoff
84    }
85    $itk_component(rmenu) add command \
86        -label [Rappture::filexfer::label upload] \
87        -command [itcl::code $this _uploadValue -start]
88    $itk_component(rmenu) add command \
89        -label [Rappture::filexfer::label download] \
90        -command [itcl::code $this _downloadValue]
91
92
93    if {[string length [label]] == 0} {
94        # old mode -- big image
95        pack $itk_component(image) -expand yes -fill both
96        bind $itk_component(image) <Configure> [itcl::code $this _redraw]
97    } else {
98        # new mode -- thumbnail and details
99        pack $itk_component(image) -side left
100        pack $itk_component(info) -side left -expand yes -fill both -padx 4
101
102        bind $itk_component(image) <<PopupMenu>> \
103            [list tk_popup $itk_component(rmenu) %X %Y]
104        bind $itk_component(info) <<PopupMenu>> \
105            [list tk_popup $itk_component(rmenu) %X %Y]
106
107        _redraw  ;# draw Empty image/info
108    }
109
110    set str [$_owner xml get $path.current]
111    if {[string length $str] == 0} {
112        set str [$_owner xml get $path.default]
113    }
114    if {[string length $str] > 0} {
115        value $str
116    }
117
118    eval itk_initialize $args
119}
120
121# ----------------------------------------------------------------------
122# DESTRUCTOR
123# ----------------------------------------------------------------------
124itcl::body Rappture::ImageEntry::destructor {} {
125    if {"" != $_imh} { image delete $_imh }
126    if {"" != $_resize} { image delete $_resize }
127}
128
129# ----------------------------------------------------------------------
130# USAGE: value ?-check? ?<newval>?
131#
132# Clients use this to query/set the value for this widget.  With
133# no args, it returns the current value for the widget.  If the
134# <newval> is specified, it sets the value of the widget and
135# sends a <<Value>> event.  If the -check flag is included, the
136# new value is not actually applied, but just checked for correctness.
137# ----------------------------------------------------------------------
138itcl::body Rappture::ImageEntry::value {args} {
139    set onlycheck 0
140    set i [lsearch -exact $args -check]
141    if {$i >= 0} {
142        set onlycheck 1
143        set args [lreplace $args $i $i]
144    }
145
146    if {[llength $args] == 1} {
147        if {$onlycheck} {
148            # someday we may add validation...
149            return
150        }
151        set newval [lindex $args 0]
152        if {[string length $newval] > 0} {
153            set imh [image create photo -data $newval]
154        } else {
155            set imh ""
156        }
157
158        if {$_imh != ""} {
159            image delete $_imh
160        }
161        set _imh $imh
162        set _data $newval
163
164        _redraw
165
166        return $newval
167
168    } elseif {[llength $args] != 0} {
169        error "wrong # args: should be \"value ?-check? ?newval?\""
170    }
171
172    #
173    # Query the value and return.
174    #
175    set bytes $_data
176    set fmt [$_owner xml get $_path.convert]
177    if {"" != $fmt && "" != $_imh} {
178        if {"pgm" == $fmt} { set fmt "ppm -grayscale" }
179        set bytes [eval $_imh data -format $fmt]
180        set bytes [Rappture::encoding::decode -as b64 $bytes]
181    }
182    return $bytes
183}
184
185# ----------------------------------------------------------------------
186# USAGE: label
187#
188# Clients use this to query the label associated with this widget.
189# Reaches into the XML and pulls out the appropriate label string.
190# ----------------------------------------------------------------------
191itcl::body Rappture::ImageEntry::label {} {
192    set label [$_owner xml get $_path.about.label]
193    return [string trim $label]
194}
195
196# ----------------------------------------------------------------------
197# USAGE: tooltip
198#
199# Clients use this to query the tooltip associated with this widget.
200# Reaches into the XML and pulls out the appropriate description
201# string.  Returns the string that should be used with the
202# Rappture::Tooltip facility.
203# ----------------------------------------------------------------------
204itcl::body Rappture::ImageEntry::tooltip {} {
205    set str [$_owner xml get $_path.about.description]
206    return [string trim $str]
207}
208
209# ----------------------------------------------------------------------
210# USAGE: _redraw
211#
212# Used internally to update the image displayed in this entry.
213# If the <resize> parameter is set, then the image is resized.
214# The "auto" value resizes to the current area.  The "width=XX" or
215# "height=xx" form resizes to a particular size.  The "none" value
216# leaves the image as-is; this is the default.
217# ----------------------------------------------------------------------
218itcl::body Rappture::ImageEntry::_redraw {} {
219    if {"" == $_imh} {
220        # generate a big diagonal cross-hatch image
221        set diag [Rappture::icon diag]
222        set dw [image width $diag]
223        set dh [image height $diag]
224        $_resize configure -width $_thumbsize -height $_thumbsize
225        for {set i 0} {$i < $_thumbsize/$dw+1} {incr i} {
226            for {set j 0} {$j < $_thumbsize/$dh+1} {incr j} {
227                set x [expr {$i*$dw}]
228                set y [expr {$j*$dh}]
229                $_resize copy $diag -to $x $y
230            }
231        }
232        _outline $_resize black
233        $itk_component(image) configure -image $_resize
234        $itk_component(info) configure -text "Empty"
235        return
236    }
237
238    set iw [image width $_imh]
239    set ih [image height $_imh]
240    $itk_component(image) configure -image "" -width $iw -height $ih
241
242    #
243    # Build a description of the image if the info is showing.
244    #
245    set desc ""
246    if {[string length [label]] != 0} {
247        # if data is base64-encoded, try to decode it
248        if {![regexp {^[a-zA-Z0-9+/=]+(\n[a-zA-Z0-9+/=]+)*$} $_data]
249              || [catch {Rappture::encoding::decode -as b64 $_data} bytes]} {
250            # oops! not base64 -- use data directly
251            set bytes $_data
252        }
253        set desc [Rappture::utils::datatype $bytes]
254        if {[string equal $desc "Binary data"]} {
255            # generic description -- we can do a little better
256            set iw [image width $_imh]
257            set ih [image height $_imh]
258            set desc "Image, ${iw} x ${ih}"
259        }
260        append desc "\n[Rappture::utils::binsize [string length $_data]]"
261    }
262    $itk_component(info) configure -text $desc
263
264    #
265    # Put up the preview image, resizing if necessary.
266    #
267    set str [string trim [$_owner xml get $_path.resize]]
268    if {"" == $str} {
269        set str "none"
270    }
271    switch -glob -- $str {
272        width=* - height=* {
273            if {[regexp {^width=([0-9]+)$} $str match size]} {
274                set w $size
275                set h [expr {round($w*$ih/double($iw))}]
276                $_resize configure -width $w -height $h
277                $_resize blank
278                blt::winop resample $_imh $_resize box
279                _outline $_resize black
280                $itk_component(image) configure -image $_resize \
281                    -width $w -height $h
282            } elseif {[regexp {^height=([0-9]+)$} $str match size]} {
283                set h $size
284                set w [expr {round($h*$iw/double($ih))}]
285                $_resize configure -width $w -height $h
286                $_resize blank
287                blt::winop resample $_imh $_resize box
288                _outline $_resize black
289                $itk_component(image) configure -image $_resize \
290                    -width $w -height $h
291            } else {
292                $itk_component(image) configure -image $_imh
293            }
294        }
295        auto - none - default {
296            if {[string length [label]] == 0} {
297                # old mode -- big image with no label
298                $itk_component(image) configure -image $_imh
299            } else {
300                # new mode -- thumbnail and image info
301                set w $_thumbsize
302                set h $_thumbsize
303                $itk_component(image) configure -width $w -height $h
304
305                if {$iw <= $_thumbsize && $ih <= $_thumbsize} {
306                    $_resize configure -width $iw -height $ih
307                    $_resize copy $_imh
308                    _outline $_resize black
309                } else {
310                    # large image -- scale it down
311                    if {$iw > $ih} {
312                        set h [expr {round($w/double($iw)*$ih)}]
313                    } else {
314                        set w [expr {round($h/double($ih)*$iw)}]
315                    }
316                    $_resize configure -width $w -height $h
317                    $_resize blank
318                    blt::winop resample $_imh $_resize box
319                    _outline $_resize black
320                }
321                $itk_component(image) configure -image $_resize
322            }
323        }
324    }
325}
326
327# ----------------------------------------------------------------------
328# USAGE: _outline <image> <color>
329#
330# Used internally to outline the given <image> with a single-pixel
331# line of the specified <color>.  Updates the image in place.
332# ----------------------------------------------------------------------
333itcl::body Rappture::ImageEntry::_outline {im color} {
334    if {"" != $im} {
335        set w [image width $im]
336        set h [image height $im]
337        $im put $color -to 0 0 $w 1
338        $im put $color -to 0 0 1 $h
339        $im put $color -to 0 [expr {$h-1}] $w $h
340        $im put $color -to [expr {$w-1}] 0 $w $h
341    }
342}
343
344# ----------------------------------------------------------------------
345# USAGE: _uploadValue -start
346# USAGE: _uploadValue -assign <key> <value> <key> <value> ...
347#
348# Used internally to initiate an upload operation.  Prompts the
349# user to upload into the image area of this widget.
350# ----------------------------------------------------------------------
351itcl::body Rappture::ImageEntry::_uploadValue {args} {
352    set opt [lindex $args 0]
353    switch -- $opt {
354        -start {
355            set tool [Rappture::Tool::resources -appname]
356            set cntls [list $_path [label] [tooltip]]
357            Rappture::filexfer::upload \
358                $tool $cntls [itcl::code $this _uploadValue -assign]
359        }
360        -assign {
361            array set data [lrange $args 1 end] ;# skip option
362            if {[info exists data(error)]} {
363                Rappture::Tooltip::cue $itk_component(image) $data(error)
364            }
365            if {[info exists data(data)]} {
366                Rappture::Tooltip::cue hide  ;# take down note about the popup
367                if {[catch {value $data(data)} err]} {
368                    Rappture::Tooltip::cue $itk_component(image) "Upload failed:\n$err"
369                }
370            }
371        }
372        default {
373            error "bad option \"$opt\": should be -start or -assign"
374        }
375    }
376}
377
378# ----------------------------------------------------------------------
379# USAGE: _downloadValue
380#
381# Used internally to initiate a download operation.  Takes the current
382# value and downloads it to the user in a new browser window.
383# ----------------------------------------------------------------------
384itcl::body Rappture::ImageEntry::_downloadValue {} {
385    set bytes [Rappture::encoding::decode -as b64 [$_imh data -format png]]
386    set mesg [Rappture::filexfer::download $bytes image.png]
387
388    if {"" != $mesg} {
389        Rappture::Tooltip::cue $itk_component(image) $mesg
390    }
391}
392
393# ----------------------------------------------------------------------
394# CONFIGURATION OPTION: -state
395# ----------------------------------------------------------------------
396itcl::configbody Rappture::ImageEntry::state {
397    set valid {normal disabled}
398    if {[lsearch -exact $valid $itk_option(-state)] < 0} {
399        error "bad value \"$itk_option(-state)\": should be [join $valid {, }]"
400    }
401    $itk_component(image) configure -state $itk_option(-state)
402}
Note: See TracBrowser for help on using the repository browser.