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

Last change on this file since 2417 was 1929, checked in by gah, 14 years ago
File size: 20.4 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    if { $pw < 1 || $ph < 1 }  {
215        # I really don't know why this is happenning.  I believe this occurs
216        # when in a work space (i.e the main window is smaller than the root
217        # window). So for now, better to place the balloon window somewhere
218        # than to fail with a bad geometry.
219        wm geometry $p +$px+$py
220    } else {
221        wm geometry $p ${pw}x${ph}+$px+$py
222    }
223    wm deiconify $p
224    raise $p
225
226    wm geometry $s +$sx+$sy
227    wm deiconify $s
228    raise $s
229
230    # grab the mouse pointer
231    update
232    while {[catch {grab set -global $itk_component(hull)}]} {
233        after 100
234    }
235    focus $itk_component(hull)
236}
237
238# ----------------------------------------------------------------------
239# USAGE: deactivate
240#
241# Clients use this to take down the balloon panel if it is on screen.
242# ----------------------------------------------------------------------
243itcl::body Rappture::Balloon::deactivate {} {
244    if {[string length $itk_option(-deactivatecommand)] > 0} {
245        uplevel #0 $itk_option(-deactivatecommand)
246    }
247
248    grab release $itk_component(hull)
249
250    wm withdraw $itk_component(hull)
251    foreach dir {left right above below} {
252        wm withdraw $_stems($dir)
253    }
254}
255
256# ----------------------------------------------------------------------
257# USAGE: _createStems
258#
259# Used internally to create the stems that connect a balloon panel
260# to its anchor point, in all four possible directions:  left, right,
261# above, and below.
262# ----------------------------------------------------------------------
263itcl::body Rappture::Balloon::_createStems {} {
264    # destroy any existing stems
265    foreach dir [array names _stems] {
266        destroy $_stems($dir)
267        unset _stems($dir)
268    }
269    foreach dir [array names _masks] {
270        image delete $_masks($dir)
271        unset _masks($dir)
272    }
273    foreach dir [array names _fills] {
274        image delete $_fills($dir)
275        unset _fills($dir)
276    }
277
278    if {[catch {package require Shape}] == 0} {
279        #
280        # We have the Shape extension.  Use it to create nice
281        # looking (triangle-shaped) stems.
282        #
283        set s $itk_option(-stemlength)
284        foreach dir {left right above below} {
285            switch -- $dir {
286                left - right {
287                    set sw [expr {$s+2}]
288                    set sh $s
289                }
290                above - below {
291                    set sw $s
292                    set sh [expr {$s+2}]
293                }
294            }
295
296            set _stems($dir) [toplevel $itk_interior.s$dir -borderwidth 0]
297            label $_stems($dir).l \
298                -width $sw -height $sh -borderwidth 0
299            pack $_stems($dir).l -expand yes -fill both
300
301            wm withdraw $_stems($dir)
302            wm overrideredirect $_stems($dir) yes
303
304            #
305            # Draw the triangle part of the stem, with a black outline
306            # and light/dark highlights:
307            #
308            #     --------  ---       LEFT STEM
309            #    |..##    |  ^ 
310            #    |  ..##  |  |        . = light color
311            #    |    ..##|  | s      @ = dark color
312            #    |    @@##|  |        # = black
313            #    |  @@##  |  |
314            #    |@@##    |  v
315            #     --------  ---
316            #    |<------>|
317            #        s+2
318            #
319            set _masks($dir) [image create photo -width $sw -height $sh]
320            set _fills($dir) [image create photo -width $sw -height $sh]
321
322            set bg $itk_option(-background)
323            set light [Rappture::color::brightness $bg 0.4]
324            set dark [Rappture::color::brightness $bg -0.4]
325            set rgb [winfo rgb . $bg]
326            set bg [format "#%03x%03x%03x" [lindex $rgb 0] [lindex $rgb 1] [lindex $rgb 2]]
327
328            $_fills($dir) put $bg -to 0 0 $sw $sh
329
330            switch -- $dir {
331              left {
332                set i 0
333                for {set j 0} {$j < $s/2} {incr j} {
334                    set ybtm [expr {$s-$j-1}]
335                    $_fills($dir) put $dark \
336                        -to $i [expr {$ybtm-1}] [expr {$i+2}] [expr {$ybtm+1}]
337                    $_fills($dir) put black \
338                        -to [expr {$i+2}] $ybtm [expr {$i+4}] [expr {$ybtm+1}]
339
340                    set ytop $j
341                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
342                    $_fills($dir) put $light \
343                        -to $i $ytop [expr {$i+2}] [expr {$ytop+$ytoffs}]
344                    $_fills($dir) put black \
345                        -to [expr {$i+2}] $ytop [expr {$i+4}] [expr {$ytop+1}]
346                    incr i 2
347                }
348                $_stems($dir).l configure -image $_fills($dir)
349
350                $_masks($dir) put black -to 0 0 $sw $sh
351                set i 0
352                for {set j 0} {$j < $s/2} {incr j} {
353                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
354                        $_masks($dir) transparency set $k $j yes
355                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
356                    }
357                    incr i 2
358                }
359              }
360              right {
361                set i $sw
362                for {set j 0} {$j < $s/2} {incr j} {
363                    set ybtm [expr {$s-$j-1}]
364                    $_fills($dir) put $dark \
365                        -to [expr {$i-2}] [expr {$ybtm-1}] $i [expr {$ybtm+1}]
366                    $_fills($dir) put black \
367                        -to [expr {$i-4}] $ybtm [expr {$i-2}] [expr {$ybtm+1}]
368
369                    set ytop $j
370                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
371                    $_fills($dir) put $light \
372                        -to [expr {$i-2}] $ytop $i [expr {$ytop+$ytoffs}]
373                    $_fills($dir) put black \
374                        -to [expr {$i-4}] $ytop [expr {$i-2}] [expr {$ytop+1}]
375                    incr i -2
376                }
377                $_stems($dir).l configure -image $_fills($dir)
378
379                $_masks($dir) put black -to 0 0 $sw $sh
380                set i $sw
381                for {set j 0} {$j < $s/2} {incr j} {
382                    for {set k 0} {$k < $i-4} {incr k} {
383                        $_masks($dir) transparency set $k $j yes
384                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
385                    }
386                    incr i -2
387                }
388              }
389              above {
390                set i 0
391                for {set j 0} {$j < $s/2} {incr j} {
392                    set xrhs [expr {$s-$j-1}]
393                    $_fills($dir) put $dark \
394                        -to [expr {$xrhs-1}] $i [expr {$xrhs+1}] [expr {$i+2}]
395                    $_fills($dir) put black \
396                        -to $xrhs [expr {$i+2}] [expr {$xrhs+1}] [expr {$i+4}]
397
398                    set xlhs $j
399                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
400                    $_fills($dir) put $light \
401                        -to $xlhs $i [expr {$xlhs+$xloffs}] [expr {$i+2}]
402                    $_fills($dir) put black \
403                        -to $xlhs [expr {$i+2}] [expr {$xlhs+1}] [expr {$i+4}]
404                    incr i 2
405                }
406                $_stems($dir).l configure -image $_fills($dir)
407
408                $_masks($dir) put black -to 0 0 $sw $sh
409                set i 0
410                for {set j 0} {$j < $s/2} {incr j} {
411                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
412                        $_masks($dir) transparency set $j $k yes
413                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
414                    }
415                    incr i 2
416                }
417              }
418              below {
419                set i $sh
420                for {set j 0} {$j < $s/2} {incr j} {
421                    set xrhs [expr {$s-$j-1}]
422                    $_fills($dir) put $dark \
423                        -to [expr {$xrhs-1}] [expr {$i-2}] [expr {$xrhs+1}] $i
424                    $_fills($dir) put black \
425                        -to $xrhs [expr {$i-4}] [expr {$xrhs+1}] [expr {$i-2}]
426
427                    set xlhs $j
428                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
429                    $_fills($dir) put $light \
430                        -to $xlhs [expr {$i-2}] [expr {$xlhs+$xloffs}] $i
431                    $_fills($dir) put black \
432                        -to $xlhs [expr {$i-4}] [expr {$xlhs+1}] [expr {$i-2}]
433                    incr i -2
434                }
435                $_stems($dir).l configure -image $_fills($dir)
436
437                $_masks($dir) put black -to 0 0 $sw $sh
438                set i $sh
439                for {set j 0} {$j < $s/2} {incr j} {
440                    for {set k 0} {$k < $i-4} {incr k} {
441                        $_masks($dir) transparency set $j $k yes
442                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
443                    }
444                    incr i -2
445                }
446              }
447            }
448        }
449    } else {
450        #
451        # No shape extension.  Do the best we can by creating a
452        # black line for all directions.
453        #
454        foreach {dir w h} [list \
455            left   $itk_option(-stemlength) 3 \
456            right  $itk_option(-stemlength) 3 \
457            above  3 $itk_option(-stemlength) \
458            below  3 $itk_option(-stemlength) \
459        ] {
460            set _stems($dir) [toplevel $itk_interior.s$dir \
461                -width $w -height $h \
462                -borderwidth 1 -relief solid -background black]
463            wm withdraw $_stems($dir)
464            wm overrideredirect $_stems($dir) yes
465
466            # create this for size, even though we don't really use it
467            set _fills($dir) [image create photo -width $w -height $h]
468        }
469    }
470}
471
472# ----------------------------------------------------------------------
473# USAGE: outside <widget> <x> <y>
474#
475# Used internally to see if the click point <x>,<y> is outside of
476# this widget.  If so, the widget usually releases is grab and
477# deactivates.
478# ----------------------------------------------------------------------
479itcl::body Rappture::Balloon::outside {widget x y} {
480    return [expr {$x < [winfo rootx $widget]
481             || $x > [winfo rootx $widget]+[winfo width $widget]
482             || $y < [winfo rooty $widget]
483             || $y > [winfo rooty $widget]+[winfo height $widget]}]
484}
485
486# ----------------------------------------------------------------------
487# CONFIGURATION OPTION: -stemlength
488#
489# Used internally to create the stems that connect a balloon panel
490# to its anchor point, in all four possible directions:  left, right,
491# above, and below.
492# ----------------------------------------------------------------------
493itcl::configbody Rappture::Balloon::stemlength {
494    if {$itk_option(-stemlength) % 2 != 0} {
495        error "stem length should be an even number of pixels"
496    }
497}
498
499# ----------------------------------------------------------------------
500# CONFIGURATION OPTION: -dismissbutton
501# ----------------------------------------------------------------------
502itcl::configbody Rappture::Balloon::dismissbutton {
503    if {![string is boolean $itk_option(-dismissbutton)]} {
504        error "bad value \"$itk_option(-dismissbutton)\": should be on/off, 1/0, true/false, yes/no"
505    }
506    if {$itk_option(-dismissbutton)} {
507        pack $itk_component(titlebar) -before $itk_component(inner) \
508            -side top -fill x
509        pack $itk_component(dismiss) -side right -padx 4
510    } elseif {"" != $itk_option(-title)} {
511        pack $itk_component(titlebar) -before $itk_component(inner) \
512            -side top -fill x
513        pack forget $itk_component(dismiss)
514    } else {
515        pack forget $itk_component(titlebar)
516    }
517}
518
519# ----------------------------------------------------------------------
520# CONFIGURATION OPTION: -padx
521# ----------------------------------------------------------------------
522itcl::configbody Rappture::Balloon::padx {
523    pack $itk_component(inner) -padx $itk_option(-padx)
524}
525
526# ----------------------------------------------------------------------
527# CONFIGURATION OPTION: -pady
528# ----------------------------------------------------------------------
529itcl::configbody Rappture::Balloon::pady {
530    pack $itk_component(inner) -pady $itk_option(-pady)
531}
532
533# ----------------------------------------------------------------------
534# CONFIGURATION OPTION: -title
535# ----------------------------------------------------------------------
536itcl::configbody Rappture::Balloon::title {
537    if {"" != $itk_option(-title) || $itk_option(-dismissbutton)} {
538        pack $itk_component(titlebar) -before $itk_component(inner) \
539            -side top -fill x
540        if {$itk_option(-dismissbutton)} {
541            pack $itk_component(dismiss) -side right -padx 4
542        } else {
543            pack forget $itk_component(dismiss)
544        }
545    } else {
546        pack forget $itk_component(titlebar)
547    }
548}
Note: See TracBrowser for help on using the repository browser.