# 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. # TO DO: # If the window appears off the screen, put it somewhere else! import os import regsub 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), ('state', 'both', None), ('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 = regsub.gsub('\n', ' ', statusHelp) widget.bind('<Enter>', lambda event=None, self=self, w=widget, sHelp=statusHelp, bHelp=balloonHelp: self._enter(w, sHelp, bHelp, None)) # 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 canvasbind(self, widget, item, balloonHelp, statusHelp = None): if balloonHelp is None and statusHelp is None: self.canvasunbind(widget) return if statusHelp is None: statusHelp = balloonHelp statusHelp = regsub.gsub('\n', ' ', statusHelp) widget.tag_bind(item, '<Enter>', lambda event=None, self=self, w=widget, item=item, sHelp=statusHelp, bHelp=balloonHelp: self._enter(w, sHelp, bHelp, item)) widget.tag_bind(item, '<Motion>', lambda event=None, self=self, statusHelp=statusHelp: self.showstatus(statusHelp)) widget.tag_bind(item, '<Leave>', self._leave) widget.tag_bind(item, '<ButtonPress>', self._buttonpress) def canvasunbind(self, widget, item): widget.tag_unbind(item, '<Motion>') widget.tag_unbind(item, '<Enter>') widget.tag_unbind(item, '<Leave>') widget.tag_unbind(item, '<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 _enter(self, widget, statusHelp, balloonHelp, item): if 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, item=item: self._showBalloon(widget, help, item)) 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, item = None): if item is None: x = widget.winfo_rootx() y = widget.winfo_rooty() + widget.winfo_height() else: # The widget is a canvas. Place balloon under canvas item. bbox = widget.bbox(item) x = widget.winfo_rootx() + bbox[0] y = widget.winfo_rooty() + bbox[3] x = x + self['xoffset'] y = y + 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))