source: trunk/gui/scripts/balloon.tcl @ 479

Last change on this file since 479 was 428, checked in by mmc, 18 years ago
  • Added <sequence> for playing movie outputs and other sequences of related results.
  • Added <resize> option to <image> elements. This can be used to resize input items to a smaller size, so they don't take up so much real estate on the form.
  • Fixed a bug in right/below cases for popup balloons.
  • Reduced the tooltip delay time to 750ms to interact better with Rick's attention span.
  • Fixed the sash between grips to light up when you touch it, so it's easier to see.
File size: 19.9 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: Balloon - toplevel popup window, like a cartoon balloon
3#
4#  This widget is used as a container for pop-up panels in Rappture.
5#  If shape extensions are supported, then the window is drawn as
6#  a cartoon balloon.  Otherwise, it is a raised panel, with a little
7#  line connecting it to the widget that brought it up.  When active,
8#  the panel has a global grab and focus, so the user must interact
9#  with it or dismiss it.
10# ======================================================================
11#  AUTHOR:  Michael McLennan, Purdue University
12#  Copyright (c) 2004-2005  Purdue Research Foundation
13#
14#  See the file "license.terms" for information on usage and
15#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
16# ======================================================================
17package require Itk
18
19namespace eval Rappture { # forward declaration }
20
21option add *Balloon.dismissButton on widgetDefault
22option add *Balloon.padX 4 widgetDefault
23option add *Balloon.padY 4 widgetDefault
24option add *Balloon.titleBackground #999999 widgetDefault
25option add *Balloon.titleForeground white widgetDefault
26option add *Balloon.relief raised widgetDefault
27option add *Balloon.stemLength 16 widgetDefault
28
29itcl::class Rappture::Balloon {
30    inherit itk::Toplevel
31
32    itk_option define -deactivatecommand deactivateCommand DeactivateCommand ""
33    itk_option define -dismissbutton dismissButton DismissButton "on"
34    itk_option define -padx padX Pad 0
35    itk_option define -pady padY Pad 0
36    itk_option define -title title Title ""
37    itk_option define -titlebackground titleBackground Background ""
38    itk_option define -titleforeground titleForeground Foreground ""
39    itk_option define -stemlength stemLength StemLength 20
40
41    constructor {args} { # defined below }
42
43    public method activate {where placement}
44    public method deactivate {}
45
46    protected method _createStems {}
47
48    protected variable _stems   ;# windows for cartoon balloon stems
49    protected variable _masks   ;# masks for cartoon balloon stems
50    protected variable _fills   ;# lines for cartoon balloon stems
51
52    public proc outside {widget x y}
53
54    bind RapptureBalloon <ButtonPress> \
55        {if {[Rappture::Balloon::outside %W %X %Y]} {%W deactivate}}
56}
57
58itk::usual Balloon {
59}
60
61# ----------------------------------------------------------------------
62# CONSTRUCTOR
63# ----------------------------------------------------------------------
64itcl::body Rappture::Balloon::constructor {args} {
65    wm overrideredirect $itk_component(hull) yes
66    wm withdraw $itk_component(hull)
67    component hull configure -borderwidth 1 -relief solid -padx 0 -pady 0
68
69    itk_component add border {
70        frame $itk_interior.border -borderwidth 2
71    } {
72        usual
73        keep -relief
74    }
75    pack $itk_component(border) -expand yes -fill both
76
77    itk_component add titlebar {
78        frame $itk_component(border).tbar
79    } {
80        usual
81        rename -background -titlebackground titleBackground Background
82    }
83
84    itk_component add title {
85        label $itk_component(titlebar).title -width 1 -anchor w
86    } {
87        usual
88        rename -background -titlebackground titleBackground Background
89        rename -foreground -titleforeground titleForeground Foreground
90        rename -highlightbackground -titlebackground titleBackground Background
91        rename -text -title title Title
92    }
93    pack $itk_component(title) -side left -expand yes -fill both -padx 2
94
95    itk_component add dismiss {
96        button $itk_component(titlebar).dismiss \
97            -bitmap [Rappture::icon dismiss] \
98            -relief flat -overrelief raised -command "
99              Rappture::Tooltip::cue hide
100              [list $itk_component(hull) deactivate]
101            "
102    } {
103        usual
104        rename -background -titlebackground titleBackground Background
105        rename -foreground -titleforeground titleForeground Foreground
106        rename -highlightbackground -titlebackground titleBackground Background
107    }
108
109    itk_component add inner {
110        frame $itk_component(border).inner
111    }
112    pack $itk_component(inner) -expand yes -fill both
113
114    # add bindings to release the grab
115    set btags [bindtags $itk_component(hull)]
116    bindtags $itk_component(hull) [linsert $btags 1 RapptureBalloon]
117
118    eval itk_initialize $args
119
120    _createStems
121}
122
123# ----------------------------------------------------------------------
124# USAGE: activate <where> <placement>
125#
126# Clients use this to pop up this balloon panel pointing to the
127# <where> location, which should be a widget name or @X,Y.  The
128# <placement> indicates whether the panel should be left, right,
129# above, or below the <where> coordinate.
130# ----------------------------------------------------------------------
131itcl::body Rappture::Balloon::activate {where placement} {
132    if {![info exists _stems($placement)]} {
133        error "bad placement \"$placement\": should be [join [lsort [array names _stems]] {, }]"
134    }
135    set s $_stems($placement)
136    set sw [image width $_fills($placement)]
137    set sh [image height $_fills($placement)]
138    set p $itk_component(hull)
139    set screenw [winfo screenwidth $p]
140    set screenh [winfo screenheight $p]
141
142    if {[winfo exists $where]} {
143        set x [expr {[winfo rootx $where]+[winfo width $where]/2}]
144        set y [expr {[winfo rooty $where]+[winfo height $where]/2}]
145        switch -- $placement {
146            left { set x [expr {[winfo rootx $where]+5}] }
147            right { set x [expr {[winfo rootx $where]+[winfo width $where]-5}] }
148            above { set y [expr {[winfo rooty $where]+5}] }
149            below { set y [expr {[winfo rooty $where]+[winfo height $where]-5}] }
150        }
151    } elseif {[regexp {^@([0-9]+),([0-9]+)$} $where match x y]} {
152        # got x and y
153    } else {
154        error "bad location \"$where\": should be widget or @x,y"
155    }
156
157    # if the panel is already up, take it down
158    deactivate
159
160    set pw [winfo reqwidth $p]
161    if {$pw > $screenw} { set pw [expr {$screenw-10}] }
162    set ph [winfo reqheight $p]
163    if {$ph > $screenh} { set ph [expr {$screenh-10}] }
164
165    switch -- $placement {
166        left {
167            set sx [expr {$x-$sw+3}]
168            set sy [expr {$y-$sh/2}]
169            set px [expr {$sx-$pw+3}]
170            set py [expr {$y-$ph/2}]
171
172            # make sure that the panel doesn't go off-screen
173            if {$py < 0} { set py 0 }
174            if {$py+$ph > $screenh} { set py [expr {$screenh-$ph}] }
175            if {$px < 0} { set pw [expr {$pw+$px}]; set px 0 }
176        }
177        right {
178            set sx $x
179            set sy [expr {$y-$sh/2}]
180            set px [expr {$x+$sw-3}]
181            set py [expr {$y-$ph/2}]
182
183            # make sure that the panel doesn't go off-screen
184            if {$py < 0} { set py 0 }
185            if {$py+$ph > $screenh} { set py [expr {$screenh-$ph}] }
186            if {$px+$pw > $screenw} { set pw [expr {$screenw-$px}] }
187        }
188        above {
189            set sx [expr {$x-$sw/2}]
190            set sy [expr {$y-$sh+3}]
191            set px [expr {$x-$pw/2}]
192            set py [expr {$sy-$ph+3}]
193
194            # make sure that the panel doesn't go off-screen
195            if {$px < 0} { set px 0 }
196            if {$px+$pw > $screenw} { set px [expr {$screenw-$pw}] }
197            if {$py < 0} { set ph [expr {$ph+$py}]; set py 0 }
198        }
199        below {
200            set sx [expr {$x-$sw/2}]
201            set sy $y
202            set px [expr {$x-$pw/2}]
203            set py [expr {$y+$sh-3}]
204
205            # make sure that the panel doesn't go off-screen
206            if {$px < 0} { set px 0 }
207            if {$px+$pw > $screenw} { set px [expr {$screenw-$pw}] }
208            if {$py+$ph > $screenh} { set ph [expr {$screenh-$py}] }
209        }
210    }
211    if {[info exists _masks($placement)]} {
212        shape set $s -bound photo $_masks($placement)
213    }
214
215    wm geometry $p ${pw}x${ph}+$px+$py
216    wm deiconify $p
217    raise $p
218
219    wm geometry $s +$sx+$sy
220    wm deiconify $s
221    raise $s
222
223    # grab the mouse pointer
224    update
225    while {[catch {grab set -global $itk_component(hull)}]} {
226        after 100
227    }
228    focus $itk_component(hull)
229}
230
231# ----------------------------------------------------------------------
232# USAGE: deactivate
233#
234# Clients use this to take down the balloon panel if it is on screen.
235# ----------------------------------------------------------------------
236itcl::body Rappture::Balloon::deactivate {} {
237    if {[string length $itk_option(-deactivatecommand)] > 0} {
238        uplevel #0 $itk_option(-deactivatecommand)
239    }
240
241    grab release $itk_component(hull)
242
243    wm withdraw $itk_component(hull)
244    foreach dir {left right above below} {
245        wm withdraw $_stems($dir)
246    }
247}
248
249# ----------------------------------------------------------------------
250# USAGE: _createStems
251#
252# Used internally to create the stems that connect a balloon panel
253# to its anchor point, in all four possible directions:  left, right,
254# above, and below.
255# ----------------------------------------------------------------------
256itcl::body Rappture::Balloon::_createStems {} {
257    # destroy any existing stems
258    foreach dir [array names _stems] {
259        destroy $_stems($dir)
260        unset _stems($dir)
261    }
262    foreach dir [array names _masks] {
263        image delete $_masks($dir)
264        unset _masks($dir)
265    }
266    foreach dir [array names _fills] {
267        image delete $_fills($dir)
268        unset _fills($dir)
269    }
270
271    if {[catch {package require Shape}] == 0} {
272        #
273        # We have the Shape extension.  Use it to create nice
274        # looking (triangle-shaped) stems.
275        #
276        set s $itk_option(-stemlength)
277        foreach dir {left right above below} {
278            switch -- $dir {
279                left - right {
280                    set sw [expr {$s+2}]
281                    set sh $s
282                }
283                above - below {
284                    set sw $s
285                    set sh [expr {$s+2}]
286                }
287            }
288
289            set _stems($dir) [toplevel $itk_interior.s$dir -borderwidth 0]
290            label $_stems($dir).l \
291                -width $sw -height $sh -borderwidth 0
292            pack $_stems($dir).l -expand yes -fill both
293
294            wm withdraw $_stems($dir)
295            wm overrideredirect $_stems($dir) yes
296
297            #
298            # Draw the triangle part of the stem, with a black outline
299            # and light/dark highlights:
300            #
301            #     --------  ---       LEFT STEM
302            #    |..##    |  ^ 
303            #    |  ..##  |  |        . = light color
304            #    |    ..##|  | s      @ = dark color
305            #    |    @@##|  |        # = black
306            #    |  @@##  |  |
307            #    |@@##    |  v
308            #     --------  ---
309            #    |<------>|
310            #        s+2
311            #
312            set _masks($dir) [image create photo -width $sw -height $sh]
313            set _fills($dir) [image create photo -width $sw -height $sh]
314
315            set bg $itk_option(-background)
316            set light [Rappture::color::brightness $bg 0.4]
317            set dark [Rappture::color::brightness $bg -0.4]
318
319            $_fills($dir) put $bg -to 0 0 $sw $sh
320
321            switch -- $dir {
322              left {
323                set i 0
324                for {set j 0} {$j < $s/2} {incr j} {
325                    set ybtm [expr {$s-$j-1}]
326                    $_fills($dir) put $dark \
327                        -to $i [expr {$ybtm-1}] [expr {$i+2}] [expr {$ybtm+1}]
328                    $_fills($dir) put black \
329                        -to [expr {$i+2}] $ybtm [expr {$i+4}] [expr {$ybtm+1}]
330
331                    set ytop $j
332                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
333                    $_fills($dir) put $light \
334                        -to $i $ytop [expr {$i+2}] [expr {$ytop+$ytoffs}]
335                    $_fills($dir) put black \
336                        -to [expr {$i+2}] $ytop [expr {$i+4}] [expr {$ytop+1}]
337                    incr i 2
338                }
339                $_stems($dir).l configure -image $_fills($dir)
340
341                $_masks($dir) put black -to 0 0 $sw $sh
342                set i 0
343                for {set j 0} {$j < $s/2} {incr j} {
344                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
345                        $_masks($dir) transparency set $k $j yes
346                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
347                    }
348                    incr i 2
349                }
350              }
351              right {
352                set i $sw
353                for {set j 0} {$j < $s/2} {incr j} {
354                    set ybtm [expr {$s-$j-1}]
355                    $_fills($dir) put $dark \
356                        -to [expr {$i-2}] [expr {$ybtm-1}] $i [expr {$ybtm+1}]
357                    $_fills($dir) put black \
358                        -to [expr {$i-4}] $ybtm [expr {$i-2}] [expr {$ybtm+1}]
359
360                    set ytop $j
361                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
362                    $_fills($dir) put $light \
363                        -to [expr {$i-2}] $ytop $i [expr {$ytop+$ytoffs}]
364                    $_fills($dir) put black \
365                        -to [expr {$i-4}] $ytop [expr {$i-2}] [expr {$ytop+1}]
366                    incr i -2
367                }
368                $_stems($dir).l configure -image $_fills($dir)
369
370                $_masks($dir) put black -to 0 0 $sw $sh
371                set i $sw
372                for {set j 0} {$j < $s/2} {incr j} {
373                    for {set k 0} {$k < $i-4} {incr k} {
374                        $_masks($dir) transparency set $k $j yes
375                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
376                    }
377                    incr i -2
378                }
379              }
380              above {
381                set i 0
382                for {set j 0} {$j < $s/2} {incr j} {
383                    set xrhs [expr {$s-$j-1}]
384                    $_fills($dir) put $dark \
385                        -to [expr {$xrhs-1}] $i [expr {$xrhs+1}] [expr {$i+2}]
386                    $_fills($dir) put black \
387                        -to $xrhs [expr {$i+2}] [expr {$xrhs+1}] [expr {$i+4}]
388
389                    set xlhs $j
390                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
391                    $_fills($dir) put $light \
392                        -to $xlhs $i [expr {$xlhs+$xloffs}] [expr {$i+2}]
393                    $_fills($dir) put black \
394                        -to $xlhs [expr {$i+2}] [expr {$xlhs+1}] [expr {$i+4}]
395                    incr i 2
396                }
397                $_stems($dir).l configure -image $_fills($dir)
398
399                $_masks($dir) put black -to 0 0 $sw $sh
400                set i 0
401                for {set j 0} {$j < $s/2} {incr j} {
402                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
403                        $_masks($dir) transparency set $j $k yes
404                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
405                    }
406                    incr i 2
407                }
408              }
409              below {
410                set i $sh
411                for {set j 0} {$j < $s/2} {incr j} {
412                    set xrhs [expr {$s-$j-1}]
413                    $_fills($dir) put $dark \
414                        -to [expr {$xrhs-1}] [expr {$i-2}] [expr {$xrhs+1}] $i
415                    $_fills($dir) put black \
416                        -to $xrhs [expr {$i-4}] [expr {$xrhs+1}] [expr {$i-2}]
417
418                    set xlhs $j
419                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
420                    $_fills($dir) put $light \
421                        -to $xlhs [expr {$i-2}] [expr {$xlhs+$xloffs}] $i
422                    $_fills($dir) put black \
423                        -to $xlhs [expr {$i-4}] [expr {$xlhs+1}] [expr {$i-2}]
424                    incr i -2
425                }
426                $_stems($dir).l configure -image $_fills($dir)
427
428                $_masks($dir) put black -to 0 0 $sw $sh
429                set i $sh
430                for {set j 0} {$j < $s/2} {incr j} {
431                    for {set k 0} {$k < $i-4} {incr k} {
432                        $_masks($dir) transparency set $j $k yes
433                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
434                    }
435                    incr i -2
436                }
437              }
438            }
439        }
440    } else {
441        #
442        # No shape extension.  Do the best we can by creating a
443        # black line for all directions.
444        #
445        foreach {dir w h} [list \
446            left   $itk_option(-stemlength) 3 \
447            right  $itk_option(-stemlength) 3 \
448            above  3 $itk_option(-stemlength) \
449            below  3 $itk_option(-stemlength) \
450        ] {
451            set _stems($dir) [toplevel $itk_interior.s$dir \
452                -width $w -height $h \
453                -borderwidth 1 -relief solid -background black]
454            wm withdraw $_stems($dir)
455            wm overrideredirect $_stems($dir) yes
456
457            # create this for size, even though we don't really use it
458            set _fills($dir) [image create photo -width $w -height $h]
459        }
460    }
461}
462
463# ----------------------------------------------------------------------
464# USAGE: outside <widget> <x> <y>
465#
466# Used internally to see if the click point <x>,<y> is outside of
467# this widget.  If so, the widget usually releases is grab and
468# deactivates.
469# ----------------------------------------------------------------------
470itcl::body Rappture::Balloon::outside {widget x y} {
471    return [expr {$x < [winfo rootx $widget]
472             || $x > [winfo rootx $widget]+[winfo width $widget]
473             || $y < [winfo rooty $widget]
474             || $y > [winfo rooty $widget]+[winfo height $widget]}]
475}
476
477# ----------------------------------------------------------------------
478# CONFIGURATION OPTION: -stemlength
479#
480# Used internally to create the stems that connect a balloon panel
481# to its anchor point, in all four possible directions:  left, right,
482# above, and below.
483# ----------------------------------------------------------------------
484itcl::configbody Rappture::Balloon::stemlength {
485    if {$itk_option(-stemlength) % 2 != 0} {
486        error "stem length should be an even number of pixels"
487    }
488}
489
490# ----------------------------------------------------------------------
491# CONFIGURATION OPTION: -dismissbutton
492# ----------------------------------------------------------------------
493itcl::configbody Rappture::Balloon::dismissbutton {
494    if {![string is boolean $itk_option(-dismissbutton)]} {
495        error "bad value \"$itk_option(-dismissbutton)\": should be on/off, 1/0, true/false, yes/no"
496    }
497    if {$itk_option(-dismissbutton)} {
498        pack $itk_component(titlebar) -before $itk_component(inner) \
499            -side top -fill x
500        pack $itk_component(dismiss) -side right -padx 4
501    } elseif {"" != $itk_option(-title)} {
502        pack $itk_component(titlebar) -before $itk_component(inner) \
503            -side top -fill x
504        pack forget $itk_component(dismiss)
505    } else {
506        pack forget $itk_component(titlebar)
507    }
508}
509
510# ----------------------------------------------------------------------
511# CONFIGURATION OPTION: -padx
512# ----------------------------------------------------------------------
513itcl::configbody Rappture::Balloon::padx {
514    pack $itk_component(inner) -padx $itk_option(-padx)
515}
516
517# ----------------------------------------------------------------------
518# CONFIGURATION OPTION: -pady
519# ----------------------------------------------------------------------
520itcl::configbody Rappture::Balloon::pady {
521    pack $itk_component(inner) -pady $itk_option(-pady)
522}
523
524# ----------------------------------------------------------------------
525# CONFIGURATION OPTION: -title
526# ----------------------------------------------------------------------
527itcl::configbody Rappture::Balloon::title {
528    if {"" != $itk_option(-title) || $itk_option(-dismissbutton)} {
529        pack $itk_component(titlebar) -before $itk_component(inner) \
530            -side top -fill x
531        if {$itk_option(-dismissbutton)} {
532            pack $itk_component(dismiss) -side right -padx 4
533        } else {
534            pack forget $itk_component(dismiss)
535        }
536    } else {
537        pack forget $itk_component(titlebar)
538    }
539}
Note: See TracBrowser for help on using the repository browser.