# Display a message in a window when the mouse enters a widget and
# remains there for a set time (default 1 second). The window closes
# when the mouse leaves the widget, or any buttons are pressed while
# over the widget.
# This class has one method 'bind', which takes a widget and a message
# to display for that widget.
import os
import string
import Tkinter
import Pmw
class Balloon(Pmw.MegaToplevel):
def __init__(self, parent=None, **kw):
# Define the megawidget options.
optiondefs = (
('initwait', 500, None), # milliseconds
('label_background', 'lightyellow', None),
('label_justify', 'left', None),
('master', 'parent', None),
('state', 'both', self._state),
('statuscommand', None, None),
('xoffset', 20, None), # pixels
('yoffset', 1, None), # pixels
)
self.defineoptions(kw, optiondefs)
# Initialise the base class (after defining the options).
Pmw.MegaToplevel.__init__(self, parent)
self.withdraw()
self.overrideredirect(1)
self.configure(hull_borderwidth=1, hull_background="black")
# Create the components.
interior = self.interior()
self._label = self.createcomponent('label',
(), None,
Tkinter.Label, (interior,))
self._label.pack()
# Initialise instance variables.
self._timer = None
# Check keywords and initialise options.
self.initialiseoptions(Balloon)
def destroy(self):
if self._timer is not None:
self.after_cancel(self._timer)
self._timer = None
Pmw.MegaToplevel.destroy(self)
def bind(self, widget, balloonHelp, statusHelp = None):
if balloonHelp is None and statusHelp is None:
self.unbind(widget)
return
if statusHelp is None:
statusHelp = balloonHelp
statusHelp = string.replace(statusHelp, '\n', ' ')
widget.bind('<Enter>',
lambda event=None, self=self, w=widget,
sHelp=statusHelp, bHelp=balloonHelp:
self._enter(w, sHelp, bHelp, 0))
# Note: The Motion binding only works for basic widgets,
# not megawidgets.
widget.bind('<Motion>',
lambda event=None, self=self, statusHelp=statusHelp:
self.showstatus(statusHelp))
widget.bind('<Leave>', self._leave)
widget.bind('<ButtonPress>', self._buttonpress)
def unbind(self, widget):
widget.unbind('<Motion>')
widget.unbind('<Enter>')
widget.unbind('<Leave>')
widget.unbind('<ButtonPress>')
def tagbind(self, widget, tagOrItem, balloonHelp, statusHelp = None):
if balloonHelp is None and statusHelp is None:
self.tagunbind(widget)
return
if statusHelp is None:
statusHelp = balloonHelp
statusHelp = string.replace(statusHelp, '\n', ' ')
widget.tag_bind(tagOrItem, '<Enter>',
lambda event=None, self=self, w=widget,
sHelp=statusHelp, bHelp=balloonHelp:
self._enter(w, sHelp, bHelp, 1))
widget.tag_bind(tagOrItem, '<Motion>',
lambda event=None, self=self, statusHelp=statusHelp:
self.showstatus(statusHelp))
widget.tag_bind(tagOrItem, '<Leave>', self._leave)
widget.tag_bind(tagOrItem, '<ButtonPress>', self._buttonpress)
def tagunbind(self, widget, tagOrItem):
widget.tag_unbind(tagOrItem, '<Motion>')
widget.tag_unbind(tagOrItem, '<Enter>')
widget.tag_unbind(tagOrItem, '<Leave>')
widget.tag_unbind(tagOrItem, '<ButtonPress>')
def showstatus(self, statusHelp):
if self['state'] in ('status', 'both'):
cmd = self['statuscommand']
if callable(cmd):
cmd(statusHelp)
def clearstatus(self):
if self['state'] in ('status', 'both'):
cmd = self['statuscommand']
if callable(cmd):
cmd(None)
def _state(self):
if self['state'] not in ('both', 'balloon', 'status', 'none'):
raise ValueError, 'bad state option ' + repr(self['state']) + \
': should be one of \'both\', \'balloon\', ' + \
'\'status\' or \'none\''
def _enter(self, widget, statusHelp, balloonHelp, isItem):
if balloonHelp is not None and self['state'] in ('balloon', 'both'):
if self._timer is not None:
self.after_cancel(self._timer)
self._timer = None
self._timer = self.after(self['initwait'],
lambda self=self, widget=widget, help=balloonHelp,
isItem=isItem:
self._showBalloon(widget, help, isItem))
self.showstatus(statusHelp)
def _leave(self, event):
if self._timer is not None:
self.after_cancel(self._timer)
self._timer = None
self.withdraw()
self.clearstatus()
def _buttonpress(self, event):
if self._timer is not None:
self.after_cancel(self._timer)
self._timer = None
self.withdraw()
def _showBalloon(self, widget, balloonHelp, isItem):
if isItem:
# The widget is either a text or canvas. The meaning of
# the values returned by the bbox method is different for
# each, so use the existence of the 'canvasx' method to
# distinguish between them.
if hasattr(widget, 'canvasx'):
# The widget is a canvas. Place balloon under canvas
# item. The positions returned by bbox are relative
# to the entire canvas, not just the visible part, so
# need to convert to window coordinates.
bbox = widget.bbox('current')
windowx = bbox[0] - widget.canvasx(0)
windowy = bbox[3] - widget.canvasy(0)
else:
# The widget is a text widget. Place balloon under
# the character closest to the mouse. The positions
# returned by bbox are relative to the text widget
# window (ie the visible part of the text only).
bbox = widget.bbox('current')
windowx = bbox[0]
windowy = bbox[1] + bbox[3]
else:
windowx = 0
windowy = widget.winfo_height()
x = windowx + widget.winfo_rootx() + self['xoffset']
y = windowy + widget.winfo_rooty() + self['yoffset']
self._label.configure(text=balloonHelp)
# To avoid flashes on X and to position the window
# correctly on Win95 (caused by Tk bugs):
if os.name != "nt":
self.geometry('%+d%+d' % (x, y))
self.deiconify()
self.tkraise()
if os.name == "nt":
self.geometry('%+d%+d' % (x, y))