source: branches/blt4/gui/scripts/balloon.tcl @ 1695

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