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

Last change on this file since 1342 was 1342, checked in by gah, 16 years ago

preliminary HQ output from molvisviewer; unexpand tabs; all jpeg generation at 100%

File size: 17.2 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            set rgb [winfo rgb . $bg]
319            set bg [format "#%03x%03x%03x" [lindex $rgb 0] [lindex $rgb 1] [lindex $rgb 2]]
320
321            $_fills($dir) put $bg -to 0 0 $sw $sh
322
323            switch -- $dir {
324              left {
325                set i 0
326                for {set j 0} {$j < $s/2} {incr j} {
327                    set ybtm [expr {$s-$j-1}]
328                    $_fills($dir) put $dark \
329                        -to $i [expr {$ybtm-1}] [expr {$i+2}] [expr {$ybtm+1}]
330                    $_fills($dir) put black \
331                        -to [expr {$i+2}] $ybtm [expr {$i+4}] [expr {$ybtm+1}]
332
333                    set ytop $j
334                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
335                    $_fills($dir) put $light \
336                        -to $i $ytop [expr {$i+2}] [expr {$ytop+$ytoffs}]
337                    $_fills($dir) put black \
338                        -to [expr {$i+2}] $ytop [expr {$i+4}] [expr {$ytop+1}]
339                    incr i 2
340                }
341                $_stems($dir).l configure -image $_fills($dir)
342
343                $_masks($dir) put black -to 0 0 $sw $sh
344                set i 0
345                for {set j 0} {$j < $s/2} {incr j} {
346                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
347                        $_masks($dir) transparency set $k $j yes
348                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
349                    }
350                    incr i 2
351                }
352              }
353              right {
354                set i $sw
355                for {set j 0} {$j < $s/2} {incr j} {
356                    set ybtm [expr {$s-$j-1}]
357                    $_fills($dir) put $dark \
358                        -to [expr {$i-2}] [expr {$ybtm-1}] $i [expr {$ybtm+1}]
359                    $_fills($dir) put black \
360                        -to [expr {$i-4}] $ybtm [expr {$i-2}] [expr {$ybtm+1}]
361
362                    set ytop $j
363                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
364                    $_fills($dir) put $light \
365                        -to [expr {$i-2}] $ytop $i [expr {$ytop+$ytoffs}]
366                    $_fills($dir) put black \
367                        -to [expr {$i-4}] $ytop [expr {$i-2}] [expr {$ytop+1}]
368                    incr i -2
369                }
370                $_stems($dir).l configure -image $_fills($dir)
371
372                $_masks($dir) put black -to 0 0 $sw $sh
373                set i $sw
374                for {set j 0} {$j < $s/2} {incr j} {
375                    for {set k 0} {$k < $i-4} {incr k} {
376                        $_masks($dir) transparency set $k $j yes
377                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
378                    }
379                    incr i -2
380                }
381              }
382              above {
383                set i 0
384                for {set j 0} {$j < $s/2} {incr j} {
385                    set xrhs [expr {$s-$j-1}]
386                    $_fills($dir) put $dark \
387                        -to [expr {$xrhs-1}] $i [expr {$xrhs+1}] [expr {$i+2}]
388                    $_fills($dir) put black \
389                        -to $xrhs [expr {$i+2}] [expr {$xrhs+1}] [expr {$i+4}]
390
391                    set xlhs $j
392                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
393                    $_fills($dir) put $light \
394                        -to $xlhs $i [expr {$xlhs+$xloffs}] [expr {$i+2}]
395                    $_fills($dir) put black \
396                        -to $xlhs [expr {$i+2}] [expr {$xlhs+1}] [expr {$i+4}]
397                    incr i 2
398                }
399                $_stems($dir).l configure -image $_fills($dir)
400
401                $_masks($dir) put black -to 0 0 $sw $sh
402                set i 0
403                for {set j 0} {$j < $s/2} {incr j} {
404                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
405                        $_masks($dir) transparency set $j $k yes
406                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
407                    }
408                    incr i 2
409                }
410              }
411              below {
412                set i $sh
413                for {set j 0} {$j < $s/2} {incr j} {
414                    set xrhs [expr {$s-$j-1}]
415                    $_fills($dir) put $dark \
416                        -to [expr {$xrhs-1}] [expr {$i-2}] [expr {$xrhs+1}] $i
417                    $_fills($dir) put black \
418                        -to $xrhs [expr {$i-4}] [expr {$xrhs+1}] [expr {$i-2}]
419
420                    set xlhs $j
421                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
422                    $_fills($dir) put $light \
423                        -to $xlhs [expr {$i-2}] [expr {$xlhs+$xloffs}] $i
424                    $_fills($dir) put black \
425                        -to $xlhs [expr {$i-4}] [expr {$xlhs+1}] [expr {$i-2}]
426                    incr i -2
427                }
428                $_stems($dir).l configure -image $_fills($dir)
429
430                $_masks($dir) put black -to 0 0 $sw $sh
431                set i $sh
432                for {set j 0} {$j < $s/2} {incr j} {
433                    for {set k 0} {$k < $i-4} {incr k} {
434                        $_masks($dir) transparency set $j $k yes
435                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
436                    }
437                    incr i -2
438                }
439              }
440            }
441        }
442    } else {
443        #
444        # No shape extension.  Do the best we can by creating a
445        # black line for all directions.
446        #
447        foreach {dir w h} [list \
448            left   $itk_option(-stemlength) 3 \
449            right  $itk_option(-stemlength) 3 \
450            above  3 $itk_option(-stemlength) \
451            below  3 $itk_option(-stemlength) \
452        ] {
453            set _stems($dir) [toplevel $itk_interior.s$dir \
454                -width $w -height $h \
455                -borderwidth 1 -relief solid -background black]
456            wm withdraw $_stems($dir)
457            wm overrideredirect $_stems($dir) yes
458
459            # create this for size, even though we don't really use it
460            set _fills($dir) [image create photo -width $w -height $h]
461        }
462    }
463}
464
465# ----------------------------------------------------------------------
466# USAGE: outside <widget> <x> <y>
467#
468# Used internally to see if the click point <x>,<y> is outside of
469# this widget.  If so, the widget usually releases is grab and
470# deactivates.
471# ----------------------------------------------------------------------
472itcl::body Rappture::Balloon::outside {widget x y} {
473    return [expr {$x < [winfo rootx $widget]
474             || $x > [winfo rootx $widget]+[winfo width $widget]
475             || $y < [winfo rooty $widget]
476             || $y > [winfo rooty $widget]+[winfo height $widget]}]
477}
478
479# ----------------------------------------------------------------------
480# CONFIGURATION OPTION: -stemlength
481#
482# Used internally to create the stems that connect a balloon panel
483# to its anchor point, in all four possible directions:  left, right,
484# above, and below.
485# ----------------------------------------------------------------------
486itcl::configbody Rappture::Balloon::stemlength {
487    if {$itk_option(-stemlength) % 2 != 0} {
488        error "stem length should be an even number of pixels"
489    }
490}
491
492# ----------------------------------------------------------------------
493# CONFIGURATION OPTION: -dismissbutton
494# ----------------------------------------------------------------------
495itcl::configbody Rappture::Balloon::dismissbutton {
496    if {![string is boolean $itk_option(-dismissbutton)]} {
497        error "bad value \"$itk_option(-dismissbutton)\": should be on/off, 1/0, true/false, yes/no"
498    }
499    if {$itk_option(-dismissbutton)} {
500        pack $itk_component(titlebar) -before $itk_component(inner) \
501            -side top -fill x
502        pack $itk_component(dismiss) -side right -padx 4
503    } elseif {"" != $itk_option(-title)} {
504        pack $itk_component(titlebar) -before $itk_component(inner) \
505            -side top -fill x
506        pack forget $itk_component(dismiss)
507    } else {
508        pack forget $itk_component(titlebar)
509    }
510}
511
512# ----------------------------------------------------------------------
513# CONFIGURATION OPTION: -padx
514# ----------------------------------------------------------------------
515itcl::configbody Rappture::Balloon::padx {
516    pack $itk_component(inner) -padx $itk_option(-padx)
517}
518
519# ----------------------------------------------------------------------
520# CONFIGURATION OPTION: -pady
521# ----------------------------------------------------------------------
522itcl::configbody Rappture::Balloon::pady {
523    pack $itk_component(inner) -pady $itk_option(-pady)
524}
525
526# ----------------------------------------------------------------------
527# CONFIGURATION OPTION: -title
528# ----------------------------------------------------------------------
529itcl::configbody Rappture::Balloon::title {
530    if {"" != $itk_option(-title) || $itk_option(-dismissbutton)} {
531        pack $itk_component(titlebar) -before $itk_component(inner) \
532            -side top -fill x
533        if {$itk_option(-dismissbutton)} {
534            pack $itk_component(dismiss) -side right -padx 4
535        } else {
536            pack forget $itk_component(dismiss)
537        }
538    } else {
539        pack forget $itk_component(titlebar)
540    }
541}
Note: See TracBrowser for help on using the repository browser.