source: branches/uq/gui/scripts/balloon.tcl @ 5245

Last change on this file since 5245 was 5245, checked in by mmh, 9 years ago

if there is no space to place a bolloon to the right, put it above

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