586 lines
13 KiB
Tcl
586 lines
13 KiB
Tcl
|
# -*- mode: TCL; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*-
|
||
|
#
|
||
|
# $Id: Balloon.tcl,v 1.7 2008/02/27 22:17:28 hobbs Exp $
|
||
|
#
|
||
|
# Balloon.tcl --
|
||
|
#
|
||
|
# The help widget. It provides both "balloon" type of help
|
||
|
# message and "status bar" type of help message. You can use
|
||
|
# this widget to indicate the function of the widgets inside
|
||
|
# your application.
|
||
|
#
|
||
|
# Copyright (c) 1993-1999 Ioi Kim Lam.
|
||
|
# Copyright (c) 2000-2001 Tix Project Group.
|
||
|
# Copyright (c) 2004 ActiveState
|
||
|
#
|
||
|
# See the file "license.terms" for information on usage and redistribution
|
||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||
|
#
|
||
|
|
||
|
|
||
|
tixWidgetClass tixBalloon {
|
||
|
-classname TixBalloon
|
||
|
-superclass tixShell
|
||
|
-method {
|
||
|
bind post unbind
|
||
|
}
|
||
|
-flag {
|
||
|
-installcolormap -initwait -state -statusbar -cursor
|
||
|
}
|
||
|
-configspec {
|
||
|
{-installcolormap installColormap InstallColormap false}
|
||
|
{-initwait initWait InitWait 1000}
|
||
|
{-state state State both}
|
||
|
{-statusbar statusBar StatusBar ""}
|
||
|
{-cursor cursor Cursor {}}
|
||
|
}
|
||
|
-default {
|
||
|
{*background #ffff60}
|
||
|
{*foreground black}
|
||
|
{*borderWidth 0}
|
||
|
{.borderWidth 1}
|
||
|
{.background black}
|
||
|
{*Label.anchor w}
|
||
|
{*Label.justify left}
|
||
|
}
|
||
|
}
|
||
|
# static seem to be -installcolormap -initwait -statusbar -cursor
|
||
|
|
||
|
# Class Record
|
||
|
#
|
||
|
global tixBalloon
|
||
|
set tixBalloon(bals) ""
|
||
|
|
||
|
proc tixBalloon:InitWidgetRec {w} {
|
||
|
upvar #0 $w data
|
||
|
global tixBalloon
|
||
|
|
||
|
tixChainMethod $w InitWidgetRec
|
||
|
|
||
|
set data(isActive) 0
|
||
|
set data(client) ""
|
||
|
|
||
|
lappend tixBalloon(bals) $w
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:ConstructWidget {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
tixChainMethod $w ConstructWidget
|
||
|
|
||
|
if {[tk windowingsystem] eq "aqua"} {
|
||
|
::tk::unsupported::MacWindowStyle style $w help none
|
||
|
} else {
|
||
|
wm overrideredirect $w 1
|
||
|
}
|
||
|
catch {wm attributes $w -topmost 1}
|
||
|
wm positionfrom $w program
|
||
|
wm withdraw $w
|
||
|
|
||
|
# Frame 1 : arrow
|
||
|
frame $w.f1 -bd 0
|
||
|
set data(w:label) [label $w.f1.lab -bd 0 -relief flat \
|
||
|
-bitmap [tix getbitmap balarrow]]
|
||
|
pack $data(w:label) -side left -padx 1 -pady 1
|
||
|
|
||
|
# Frame 2 : Message
|
||
|
frame $w.f2 -bd 0
|
||
|
set data(w:message) [label $w.f2.message -padx 0 -pady 0 -bd 0]
|
||
|
pack $data(w:message) -side left -expand yes -fill both -padx 10 -pady 1
|
||
|
|
||
|
# Pack all
|
||
|
pack $w.f1 -fill both
|
||
|
pack $w.f2 -fill both
|
||
|
|
||
|
# This is an event tag used by the clients
|
||
|
#
|
||
|
bind TixBal$w <Destroy> [list tixBalloon:ClientDestroy $w %W]
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:Destructor {w} {
|
||
|
global tixBalloon
|
||
|
|
||
|
set bals ""
|
||
|
foreach b $tixBalloon(bals) {
|
||
|
if {$w != $b} {
|
||
|
lappend bals $b
|
||
|
}
|
||
|
}
|
||
|
set tixBalloon(bals) $bals
|
||
|
|
||
|
tixChainMethod $w Destructor
|
||
|
}
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
# Config:
|
||
|
#----------------------------------------------------------------------
|
||
|
proc tixBalloon:config-state {w value} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
set re {^(none|balloon|status|both)$}
|
||
|
if {![regexp -- $re $value]} {
|
||
|
error "invalid value $value, must be none, balloon, status, or both"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
# "RAW" event bindings:
|
||
|
#----------------------------------------------------------------------
|
||
|
|
||
|
bind all <B1-Motion> "+tixBalloon_XXMotion %X %Y 1"
|
||
|
bind all <B2-Motion> "+tixBalloon_XXMotion %X %Y 2"
|
||
|
bind all <B3-Motion> "+tixBalloon_XXMotion %X %Y 3"
|
||
|
bind all <B4-Motion> "+tixBalloon_XXMotion %X %Y 4"
|
||
|
bind all <B5-Motion> "+tixBalloon_XXMotion %X %Y 5"
|
||
|
bind all <Any-Motion> "+tixBalloon_XXMotion %X %Y 0"
|
||
|
# Should %b be 0? %b is illegal
|
||
|
bind all <Leave> "+tixBalloon_XXMotion %X %Y 0"
|
||
|
bind all <Button> "+tixBalloon_XXButton %X %Y %b"
|
||
|
bind all <ButtonRelease> "+tixBalloon_XXButtonUp %X %Y %b"
|
||
|
|
||
|
proc tixBalloon_XXMotion {rootX rootY b} {
|
||
|
global tixBalloon
|
||
|
|
||
|
foreach w $tixBalloon(bals) {
|
||
|
tixBalloon:XXMotion $w $rootX $rootY $b
|
||
|
}
|
||
|
}
|
||
|
|
||
|
proc tixBalloon_XXButton {rootX rootY b} {
|
||
|
global tixBalloon
|
||
|
|
||
|
foreach w $tixBalloon(bals) {
|
||
|
tixBalloon:XXButton $w $rootX $rootY $b
|
||
|
}
|
||
|
}
|
||
|
|
||
|
proc tixBalloon_XXButtonUp {rootX rootY b} {
|
||
|
global tixBalloon
|
||
|
|
||
|
foreach w $tixBalloon(bals) {
|
||
|
tixBalloon:XXButtonUp $w $rootX $rootY $b
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# return true if d is a descendant of w
|
||
|
#
|
||
|
proc tixIsDescendant {w d} {
|
||
|
return [expr {$w eq "." || [string match $w.* $d]}]
|
||
|
}
|
||
|
|
||
|
# All the button events are fine if the ballooned widget is
|
||
|
# a descendant of the grabbing widget
|
||
|
#
|
||
|
proc tixBalloon:GrabBad {w cw} {
|
||
|
global tixBalloon
|
||
|
|
||
|
set g [grab current $w]
|
||
|
if {$g == ""} {
|
||
|
return 0
|
||
|
}
|
||
|
if {[info exists tixBalloon(g_ignore,$g)]} {
|
||
|
return 1
|
||
|
}
|
||
|
if {[info exists tixBalloon(g_ignore,[winfo class $g])]} {
|
||
|
return 1
|
||
|
}
|
||
|
if {$g == $cw || [tixIsDescendant $g $cw]} {
|
||
|
return 0
|
||
|
}
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:XXMotion {w rootX rootY b} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {![info exists data(-state)]} {
|
||
|
# puts "tixBalloon:XXMotion called without a state\n$w"
|
||
|
set data(state) none
|
||
|
return
|
||
|
}
|
||
|
if {$data(-state) eq "none"} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if {$b == 0} {
|
||
|
if {[info exists data(b:1)]} {unset data(b:1)}
|
||
|
if {[info exists data(b:2)]} {unset data(b:2)}
|
||
|
if {[info exists data(b:3)]} {unset data(b:3)}
|
||
|
if {[info exists data(b:4)]} {unset data(b:4)}
|
||
|
if {[info exists data(b:5)]} {unset data(b:5)}
|
||
|
}
|
||
|
|
||
|
if {[llength [array names data b:*]]} {
|
||
|
# Some buttons are down. Do nothing
|
||
|
#
|
||
|
return
|
||
|
}
|
||
|
|
||
|
set cw [winfo containing -displayof $w $rootX $rootY]
|
||
|
if {[tixBalloon:GrabBad $w $cw]} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
# Find the a client window that matches
|
||
|
#
|
||
|
if {$w eq $cw || [string match $w.* $cw]} {
|
||
|
# Cursor moved over the balloon -- Ignore
|
||
|
return
|
||
|
}
|
||
|
|
||
|
while {$cw != ""} {
|
||
|
if {[info exists data(m:$cw)]} {
|
||
|
set client $cw
|
||
|
break
|
||
|
} else {
|
||
|
set cw [winfo parent $cw]
|
||
|
}
|
||
|
}
|
||
|
if {![info exists client]} {
|
||
|
# The cursor is at a position covered by a non-client
|
||
|
# Popdown the balloon if it is up
|
||
|
if {$data(isActive)} {
|
||
|
tixBalloon:Deactivate $w
|
||
|
}
|
||
|
set data(client) ""
|
||
|
if {[info exists data(cancel)]} {
|
||
|
unset data(cancel)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if {$data(client) ne $client} {
|
||
|
if {$data(isActive)} {
|
||
|
tixBalloon:Deactivate $w
|
||
|
}
|
||
|
set data(client) $client
|
||
|
after $data(-initwait) tixBalloon:SwitchToClient $w $client
|
||
|
}
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:XXButton {w rootX rootY b} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
tixBalloon:XXMotion $w $rootX $rootY $b
|
||
|
|
||
|
set data(b:$b) 1
|
||
|
|
||
|
if {$data(isActive)} {
|
||
|
tixBalloon:Deactivate $w
|
||
|
} else {
|
||
|
set data(cancel) 1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:XXButtonUp {w rootX rootY b} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
tixBalloon:XXMotion $w $rootX $rootY $b
|
||
|
if {[info exists data(b:$b)]} {
|
||
|
unset data(b:$b)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
# "COOKED" event bindings:
|
||
|
#----------------------------------------------------------------------
|
||
|
|
||
|
# switch the balloon to a new client
|
||
|
#
|
||
|
proc tixBalloon:SwitchToClient {w client} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {![winfo exists $w]} {
|
||
|
return
|
||
|
}
|
||
|
if {![winfo exists $client]} {
|
||
|
return
|
||
|
}
|
||
|
if {$client ne $data(client)} {
|
||
|
return
|
||
|
}
|
||
|
if {[info exists data(cancel)]} {
|
||
|
unset data(cancel)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if {[tixBalloon:GrabBad $w $w]} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
tixBalloon:Activate $w
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:ClientDestroy {w client} {
|
||
|
if {![winfo exists $w]} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {$data(client) eq $client} {
|
||
|
tixBalloon:Deactivate $w
|
||
|
set data(client) ""
|
||
|
}
|
||
|
|
||
|
# Maybe thses have already been unset by the Destroy method
|
||
|
#
|
||
|
if {[info exists data(m:$client)]} {unset data(m:$client)}
|
||
|
if {[info exists data(s:$client)]} {unset data(s:$client)}
|
||
|
}
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
# Popping up balloon:
|
||
|
#----------------------------------------------------------------------
|
||
|
proc tixBalloon:Activate {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {[tixBalloon:GrabBad $w $w]} {
|
||
|
return
|
||
|
}
|
||
|
if {[winfo containing -displayof $w \
|
||
|
[winfo pointerx $w] [winfo pointery $w]] == ""} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if {![info exists data(-state)]} {
|
||
|
# puts "tixBalloon:Activate called without a state\n$w"
|
||
|
set data(state) none
|
||
|
return
|
||
|
}
|
||
|
if {$data(-state) eq "none"} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch -exact -- $data(-state) {
|
||
|
"both" {
|
||
|
tixBalloon:PopUp $w
|
||
|
tixBalloon:SetStatus $w
|
||
|
}
|
||
|
"balloon" {
|
||
|
tixBalloon:PopUp $w
|
||
|
}
|
||
|
"status" {
|
||
|
tixBalloon:SetStatus $w
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set data(isActive) 1
|
||
|
|
||
|
after 200 tixBalloon:Verify $w
|
||
|
}
|
||
|
|
||
|
|
||
|
# %% Perhaps this is no more needed
|
||
|
#
|
||
|
proc tixBalloon:Verify {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {![winfo exists $w]} {
|
||
|
return
|
||
|
}
|
||
|
if {!$data(isActive)} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if {[tixBalloon:GrabBad $w $w]} {
|
||
|
tixBalloon:Deactivate $w
|
||
|
return
|
||
|
}
|
||
|
if {[winfo containing -displayof $w \
|
||
|
[winfo pointerx $w] [winfo pointery $w]] == ""} {
|
||
|
tixBalloon:Deactivate $w
|
||
|
return
|
||
|
}
|
||
|
after 200 tixBalloon:Verify $w
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:Deactivate {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
tixBalloon:PopDown $w
|
||
|
tixBalloon:ClearStatus $w
|
||
|
set data(isActive) 0
|
||
|
if {[info exists data(cancel)]} {
|
||
|
unset data(cancel)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:PopUp {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {[string is true -strict $data(-installcolormap)]} {
|
||
|
wm colormapwindows [winfo toplevel $data(client)] $w
|
||
|
}
|
||
|
|
||
|
# trick: the following lines allow the balloon window to
|
||
|
# acquire a stable width and height when it is finally
|
||
|
# put on the visible screen
|
||
|
#
|
||
|
set client $data(client)
|
||
|
if {$data(m:$client) == ""} {return ""}
|
||
|
|
||
|
$data(w:message) config -text $data(m:$client)
|
||
|
wm geometry $w +10000+10000
|
||
|
wm deiconify $w
|
||
|
raise $w
|
||
|
update
|
||
|
|
||
|
# The windows may become destroyed as a result of the "update" command
|
||
|
#
|
||
|
if {![winfo exists $w]} {
|
||
|
return
|
||
|
}
|
||
|
if {![winfo exists $client]} {
|
||
|
return
|
||
|
}
|
||
|
# Put it on the visible screen
|
||
|
#
|
||
|
set x [expr {[winfo rootx $client]+[winfo width $client]/2}]
|
||
|
set y [expr {int([winfo rooty $client]+[winfo height $client]/1.3)}]
|
||
|
|
||
|
set width [winfo reqwidth $w]
|
||
|
set height [winfo reqheight $w]
|
||
|
set scrwidth [winfo vrootwidth $w]
|
||
|
set scrheight [winfo vrootheight $w]
|
||
|
|
||
|
# If the balloon is too far right, pull it back to the left
|
||
|
#
|
||
|
if {($x + $width) > $scrwidth} {
|
||
|
set x [expr {$scrwidth - $width}]
|
||
|
}
|
||
|
|
||
|
# If the balloon is too far left, pull it back to the right
|
||
|
#
|
||
|
if {$x < 0} {
|
||
|
set x 0
|
||
|
}
|
||
|
|
||
|
# If the listbox is below bottom of screen, put it upwards
|
||
|
#
|
||
|
if {($y + $height) > $scrheight} {
|
||
|
set y [expr {$scrheight-$height}]
|
||
|
}
|
||
|
if {$y < 0} {
|
||
|
set y 0
|
||
|
}
|
||
|
|
||
|
wm geometry $w +$x+$y
|
||
|
after idle raise $w
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:PopDown {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
# Close the balloon
|
||
|
#
|
||
|
wm withdraw $w
|
||
|
|
||
|
# We don't set the data(client) to be zero, so that the balloon
|
||
|
# will re-appear only if you move out then in the client window
|
||
|
# set data(client) ""
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:SetStatus {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {![winfo exists $data(-statusbar)]
|
||
|
|| ![info exists data(s:$data(client))]} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
set vv [$data(-statusbar) cget -textvariable]
|
||
|
if {$vv == ""} {
|
||
|
$data(-statusbar) config -text $data(s:$data(client))
|
||
|
} else {
|
||
|
uplevel #0 set $vv [list $data(s:$data(client))]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:ClearStatus {w} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {![winfo exists $data(-statusbar)]} {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
# Clear the StatusBar widget
|
||
|
#
|
||
|
set vv [$data(-statusbar) cget -textvariable]
|
||
|
if {$vv == ""} {
|
||
|
$data(-statusbar) config -text ""
|
||
|
} else {
|
||
|
uplevel #0 set $vv [list ""]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
# PublicMethods:
|
||
|
#----------------------------------------------------------------------
|
||
|
|
||
|
# %% if balloon is already popped-up for this client, change mesage
|
||
|
#
|
||
|
proc tixBalloon:bind {w client args} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
set alreadyBound [info exists data(m:$client)]
|
||
|
|
||
|
set opt(-balloonmsg) ""
|
||
|
set opt(-statusmsg) ""
|
||
|
set opt(-msg) ""
|
||
|
|
||
|
tixHandleOptions opt {-balloonmsg -msg -statusmsg} $args
|
||
|
|
||
|
if {$opt(-balloonmsg) != ""} {
|
||
|
set data(m:$client) $opt(-balloonmsg)
|
||
|
} else {
|
||
|
set data(m:$client) $opt(-msg)
|
||
|
}
|
||
|
if {$opt(-statusmsg) != ""} {
|
||
|
set data(s:$client) $opt(-statusmsg)
|
||
|
} else {
|
||
|
set data(s:$client) $opt(-msg)
|
||
|
}
|
||
|
|
||
|
tixAppendBindTag $client TixBal$w
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:post {w client} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {![info exists data(m:$client)] || $data(m:$client) == ""} {
|
||
|
return
|
||
|
}
|
||
|
tixBalloon:Enter $w $client
|
||
|
incr data(fakeEnter)
|
||
|
}
|
||
|
|
||
|
proc tixBalloon:unbind {w client} {
|
||
|
upvar #0 $w data
|
||
|
|
||
|
if {[info exists data(m:$client)]} {
|
||
|
if {[info exists data(m:$client)]} {unset data(m:$client)}
|
||
|
if {[info exists data(s:$client)]} {unset data(s:$client)}
|
||
|
|
||
|
if {[winfo exists $client]} {
|
||
|
catch {tixDeleteBindTag $client TixBal$w}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
#
|
||
|
# Utility function
|
||
|
#
|
||
|
#----------------------------------------------------------------------
|
||
|
#
|
||
|
# $w can be a widget name or a classs name
|
||
|
proc tixBalIgnoreWhenGrabbed {wc} {
|
||
|
global tixBalloon
|
||
|
set tixBalloon(g_ignore,$wc) ""
|
||
|
}
|
||
|
|
||
|
tixBalIgnoreWhenGrabbed TixComboBox
|
||
|
tixBalIgnoreWhenGrabbed Menu
|
||
|
tixBalIgnoreWhenGrabbed Menubutton
|