# --------------------------------------------------------------------------------- ## SUPERTOOLTIP wxPython IMPLEMENTATION## Andrea Gavana, @ 07 October 2008# Latest Revision: 14 Sep 2011, 21.00 GMT### TODO List## 1) Maybe add some more customization like multiline text# in the header and footer;# 2) Check whether it's possible to use rounded corners and# shadows on the Mac### For all kind of problems, requests of enhancements and bug reports, please# write to me at:## andrea.gavana@gmail.com# andrea.gavana@maerskoil.com## Or, obviously, to the wxPython mailing list!!!### End Of Comments# --------------------------------------------------------------------------------- #"""L{SuperToolTip} is a class that mimics the behaviour of `wx.TipWindow` and generic tooltipwindows, although it is a custom-drawn widget.Description===========L{SuperToolTip} is a class that mimics the behaviour of `wx.TipWindow` and generic tooltipwindows, although it is a custom-drawn widget.This class supports:* Blended triple-gradient for the tooltip background;* Header text and header image, with possibility to set the header font indipendently;* Footer text and footer image, with possibility to set the footer font indipendently;* Multiline text message in the tooltip body, plus an optional image as "body image";* Bold lines and hyperlink lines in the tooltip body;* A wide set of predefined drawing styles for the tooltip background;* Drawing of separator lines after the header and/or before the footer;* Rounded corners and shadows below the tooltip window (Windows XP only);* Fade in/fade out effects (Windows XP only);* User-settable delays for the delay after which the tooltip appears and the delay after which the tooltip is destroyed.And a lot more. Check the demo for an almost complete review of the functionalities.Usage=====Usage example:: import wx import wx.lib.agw.supertooltip as STT class MyFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "SuperToolTip Demo") panel = wx.Panel(self) button = wx.Button(panel, -1, "I am the SuperToolTip target", pos=(100, 50)) tip = STT.SuperToolTip("A nice tooltip message") tip.SetHeader("Hello World") tip.SetTarget(button) tip.SetDrawHeaderLine(True) tip.ApplyStyle("Office 2007 Blue") tip.SetDropShadow(True) # our normal wxApp-derived class, as usual app = wx.PySimpleApp() frame = MyFrame(None) app.SetTopWindow(frame) frame.Show() app.MainLoop()Supported Platforms===================L{SuperToolTip} has been tested on the following platforms: * Windows (Windows XP).Window Styles=============`No particular window styles are available for this class.`Events Processing=================`No custom events are available for this class.`License And Version===================L{SuperToolTip} is distributed under the wxPython license.Latest Revision: Andrea Gavana @ 14 Sep 2011, 21.00 GMTVersion 0.4"""importwximportwebbrowser# Let's see if we can add few nice shadows to our tooltips (Windows only)_libimported=Noneifwx.Platform=="__WXMSW__":osVersion=wx.GetOsVersion()# Shadows behind menus are supported only in XPifosVersion[1]==5andosVersion[2]==1:try:# Try Mark Hammond's win32all extensionsimportwin32apiimportwin32conimportwin32guiimportwinxpgui_libimported="MH"exceptImportError:_libimported=Noneelse:_libimported=None# Define a bunch of predefined colour schemes..._colourSchemes={"Beige":(wx.Colour(255,255,255),wx.Colour(242,242,223),wx.Colour(198,195,160),wx.Colour(0,0,0)),"Blue":(wx.Colour(255,255,255),wx.Colour(202,220,246),wx.Colour(150,180,222),wx.Colour(0,0,0)),"Blue 2":(wx.Colour(255,255,255),wx.Colour(228,236,248),wx.Colour(198,214,235),wx.Colour(0,0,0)),"Blue 3":(wx.Colour(255,255,255),wx.Colour(213,233,243),wx.Colour(151,195,216),wx.Colour(0,0,0)),"Blue 4":(wx.Colour(255,255,255),wx.Colour(227,235,255),wx.Colour(102,153,255),wx.Colour(0,0,0)),"Blue Glass":(wx.Colour(182,226,253),wx.Colour(137,185,232),wx.Colour(188,244,253),wx.Colour(0,0,0)),"Blue Glass 2":(wx.Colour(192,236,255),wx.Colour(147,195,242),wx.Colour(198,254,255),wx.Colour(0,0,0)),"Blue Glass 3":(wx.Colour(212,255,255),wx.Colour(167,215,255),wx.Colour(218,255,255),wx.Colour(0,0,0)),"Blue Inverted":(wx.Colour(117,160,222),wx.Colour(167,210,240),wx.Colour(233,243,255),wx.Colour(0,0,0)),"Blue Shift":(wx.Colour(124,178,190),wx.Colour(13,122,153),wx.Colour(0,89,116),wx.Colour(255,255,255)),"CodeProject":(wx.Colour(255,250,172),wx.Colour(255,207,157),wx.Colour(255,153,0),wx.Colour(0,0,0)),"Dark Gray":(wx.Colour(195,195,195),wx.Colour(168,168,168),wx.Colour(134,134,134),wx.Colour(255,255,255)),"Deep Purple":(wx.Colour(131,128,164),wx.Colour(112,110,143),wx.Colour(90,88,117),wx.Colour(255,255,255)),"Electric Blue":(wx.Colour(224,233,255),wx.Colour(135,146,251),wx.Colour(99,109,233),wx.Colour(0,0,0)),"Firefox":(wx.Colour(255,254,207),wx.Colour(254,248,125),wx.Colour(225,119,24),wx.Colour(0,0,0)),"Gold":(wx.Colour(255,202,0),wx.Colour(255,202,0),wx.Colour(255,202,0),wx.Colour(0,0,0)),"Gold Shift":(wx.Colour(178,170,107),wx.Colour(202,180,32),wx.Colour(162,139,1),wx.Colour(255,255,255)),"Gray":(wx.Colour(255,255,255),wx.Colour(228,228,228),wx.Colour(194,194,194),wx.Colour(0,0,0)),"Green":(wx.Colour(234,241,223),wx.Colour(211,224,180),wx.Colour(182,200,150),wx.Colour(0,0,0)),"Green Shift":(wx.Colour(129,184,129),wx.Colour(13,185,15),wx.Colour(1,125,1),wx.Colour(255,255,255)),"Light Green":(wx.Colour(174,251,171),wx.Colour(145,221,146),wx.Colour(90,176,89),wx.Colour(0,0,0)),"NASA Blue":(wx.Colour(0,91,134),wx.Colour(0,100,150),wx.Colour(0,105,160),wx.Colour(255,255,255)),"Office 2007 Blue":(wx.Colour(255,255,255),wx.Colour(242,246,251),wx.Colour(202,218,239),wx.Colour(76,76,76)),"Orange Shift":(wx.Colour(179,120,80),wx.Colour(183,92,19),wx.Colour(157,73,1),wx.Colour(255,255,255)),"Outlook Green":(wx.Colour(236,242,208),wx.Colour(219,230,187),wx.Colour(195,210,155),wx.Colour(0,0,0)),"Pale Green":(wx.Colour(249,255,248),wx.Colour(206,246,209),wx.Colour(148,225,155),wx.Colour(0,0,0)),"Pink Blush":(wx.Colour(255,254,255),wx.Colour(255,231,242),wx.Colour(255,213,233),wx.Colour(0,0,0)),"Pink Shift":(wx.Colour(202,135,188),wx.Colour(186,8,158),wx.Colour(146,2,116),wx.Colour(255,255,255)),"Pretty Pink":(wx.Colour(255,240,249),wx.Colour(253,205,217),wx.Colour(255,150,177),wx.Colour(0,0,0)),"Red":(wx.Colour(255,183,176),wx.Colour(253,157,143),wx.Colour(206,88,78),wx.Colour(0,0,0)),"Red Shift":(wx.Colour(186,102,102),wx.Colour(229,23,9),wx.Colour(182,11,1),wx.Colour(255,255,255)),"Silver":(wx.Colour(255,255,255),wx.Colour(242,242,246),wx.Colour(212,212,224),wx.Colour(0,0,0)),"Silver 2":(wx.Colour(255,255,255),wx.Colour(242,242,248),wx.Colour(222,222,228),wx.Colour(0,0,0)),"Silver Glass":(wx.Colour(158,158,158),wx.Colour(255,255,255),wx.Colour(105,105,105),wx.Colour(0,0,0)),"Silver Inverted":(wx.Colour(161,160,186),wx.Colour(199,201,213),wx.Colour(255,255,255),wx.Colour(0,0,0)),"Silver Inverted 2":(wx.Colour(181,180,206),wx.Colour(219,221,233),wx.Colour(255,255,255),wx.Colour(0,0,0)),"Soylent Green":(wx.Colour(134,211,131),wx.Colour(105,181,106),wx.Colour(50,136,49),wx.Colour(255,255,255)),"Spring Green":(wx.Colour(154,231,151),wx.Colour(125,201,126),wx.Colour(70,156,69),wx.Colour(255,255,255)),"Too Blue":(wx.Colour(255,255,255),wx.Colour(225,235,244),wx.Colour(188,209,226),wx.Colour(0,0,0)),"Totally Green":(wx.Colour(190,230,160),wx.Colour(190,230,160),wx.Colour(190,230,160),wx.Colour(0,0,0)),"XP Blue":(wx.Colour(119,185,236),wx.Colour(81,144,223),wx.Colour(36,76,171),wx.Colour(255,255,255)),"Yellow":(wx.Colour(255,255,220),wx.Colour(255,231,161),wx.Colour(254,218,108),wx.Colour(0,0,0))}defGetStyleKeys():""" Returns the predefined styles keywords. """schemes=_colourSchemes.keys()schemes.sort()returnschemesdefMakeBold(font):""" Makes a font bold. Utility method. :param `font`: the font to be made bold. """newFont=wx.Font(font.GetPointSize(),font.GetFamily(),font.GetStyle(),wx.BOLD,font.GetUnderlined(),font.GetFaceName())returnnewFontdefExtractLink(line):""" Extract the link from an hyperlink line. :param `line`: the line of text to be processed. """line=line[4:]indxStart=line.find("{")indxEnd=line.find("}")hl=line[indxStart+1:indxEnd].strip()line=line[0:indxStart].strip()returnline,hlclassToolTipWindowBase(object):""" Base class for the different Windows and Mac implementation. """def__init__(self,parent,classParent):""" Default class constructor. :param `parent`: the L{SuperToolTip} parent widget; :param `classParent`: the L{SuperToolTip} class object. """self._spacing=6self._wasOnLink=Falseself._hyperlinkRect,self._hyperlinkWeb=[],[]self._classParent=classParentself._alphaTimer=wx.Timer(self,wx.ID_ANY)# Bind the eventsself.Bind(wx.EVT_PAINT,self.OnPaint)self.Bind(wx.EVT_SIZE,self.OnSize)self.Bind(wx.EVT_ERASE_BACKGROUND,self.OnEraseBackground)self.Bind(wx.EVT_MOTION,self.OnMouseMotion)self.Bind(wx.EVT_TIMER,self.AlphaCycle)self.Bind(wx.EVT_KILL_FOCUS,self.OnDestroy)self.Bind(wx.EVT_LEFT_DOWN,self.OnDestroy)self.Bind(wx.EVT_LEFT_DCLICK,self.OnDestroy)defOnPaint(self,event):""" Handles the ``wx.EVT_PAINT`` event for L{SuperToolTip}. :param `event`: a `wx.PaintEvent` event to be processed. """# Go with double buffering...dc=wx.BufferedPaintDC(self)frameRect=self.GetClientRect()x,y,width,height=frameRect# Store the rects for the hyperlink linesself._hyperlinkRect,self._hyperlinkWeb=[],[]classParent=self._classParent# Retrieve the colours for the blended triple-gradient backgroundtopColour,middleColour,bottomColour=classParent.GetTopGradientColour(), \
classParent.GetMiddleGradientColour(), \
classParent.GetBottomGradientColour()# Get the user options for header, bitmaps etc...drawHeader,drawFooter=classParent.GetDrawHeaderLine(),classParent.GetDrawFooterLine()topRect=wx.Rect(frameRect.x,frameRect.y,frameRect.width,frameRect.height/2)bottomRect=wx.Rect(frameRect.x,frameRect.y+frameRect.height/2,frameRect.width,frameRect.height/2+1)# Fill the triple-gradientdc.GradientFillLinear(topRect,topColour,middleColour,wx.SOUTH)dc.GradientFillLinear(bottomRect,middleColour,bottomColour,wx.SOUTH)header,headerBmp=classParent.GetHeader(),classParent.GetHeaderBitmap()headerFont,messageFont,footerFont,hyperlinkFont=classParent.GetHeaderFont(),classParent.GetMessageFont(), \
classParent.GetFooterFont(),classParent.GetHyperlinkFont()xPos,yPos=self._spacing,0bmpXPos=bmpYPos=0bmpHeight=textHeight=bmpWidth=0ifheaderBmpandheaderBmp.IsOk():# We got the header bitmapbmpHeight,bmpWidth=headerBmp.GetHeight(),headerBmp.GetWidth()bmpXPos=self._spacingifheader:# We got the header textdc.SetFont(headerFont)textWidth,textHeight=dc.GetTextExtent(header)# Calculate the header heightheight=max(textHeight,bmpHeight)ifheader:dc.DrawText(header,bmpXPos+bmpWidth+self._spacing,(height-textHeight+self._spacing)/2)ifheaderBmpandheaderBmp.IsOk():dc.DrawBitmap(headerBmp,bmpXPos,(height-bmpHeight+self._spacing)/2,True)ifheaderor(headerBmpandheaderBmp.IsOk()):yPos+=heightifdrawHeader:# Draw the separator line after the headerdc.SetPen(wx.GREY_PEN)dc.DrawLine(self._spacing,yPos+self._spacing,width-self._spacing,yPos+self._spacing)# Get the big body image (if any)embeddedImage=classParent.GetBodyImage()bmpWidth=bmpHeight=-1ifembeddedImageandembeddedImage.IsOk():bmpWidth,bmpHeight=embeddedImage.GetWidth(),embeddedImage.GetHeight()# A bunch of calculations to draw the main body messagemessageHeight=0textSpacing=(bmpWidth>0and[3*self._spacing]or[2*self._spacing])[0]lines=classParent.GetMessage().split("\n")yText=yPosnormalText=wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUTEXT)hyperLinkText=wx.BLUEforindx,lineinenumerate(lines):# Loop over all the lines in the messageisLink=Falsedc.SetTextForeground(normalText)ifline.startswith("</b>"):# is a bold lineline=line[4:]font=MakeBold(messageFont)dc.SetFont(font)elifline.startswith("</l>"):# is a linkdc.SetFont(hyperlinkFont)isLink=Trueline,hl=ExtractLink(line)dc.SetTextForeground(hyperLinkText)else:# Is a normal linedc.SetFont(messageFont)textWidth,textHeight=dc.GetTextExtent(line)iftextHeight==0:textWidth,textHeight=dc.GetTextExtent("a")messageHeight+=textHeightxText=(bmpWidth>0and[bmpWidth+2*self._spacing]or[self._spacing])[0]yText+=textHeight/2+self._spacingdc.DrawText(line,xText,yText)ifisLink:# Store the hyperlink rectangle and linkself._hyperlinkRect.append(wx.Rect(xText,yText,textWidth,textHeight))self._hyperlinkWeb.append(hl)ifindx==0:messagePos=yTexttoAdd=0ifbmpHeight>textHeight:yPos+=2*self._spacing+bmpHeighttoAdd=self._spacingelse:yPos+=messageHeight+2*self._spacingyText=max(messageHeight,bmpHeight+2*self._spacing)ifembeddedImageandembeddedImage.IsOk():# Draw the main body imagedc.DrawBitmap(embeddedImage,self._spacing,messagePos,True)footer,footerBmp=classParent.GetFooter(),classParent.GetFooterBitmap()bmpHeight=bmpWidth=textHeight=textWidth=0bmpXPos=bmpYPos=0iffooterBmpandfooterBmp.IsOk():# Got the footer bitmapbmpHeight,bmpWidth=footerBmp.GetHeight(),footerBmp.GetWidth()bmpXPos=self._spacingiffooter:# Got the footer textdc.SetFont(footerFont)textWidth,textHeight=dc.GetTextExtent(footer)iftextHeightorbmpHeight:ifdrawFooter:# Draw the separator line before the footerdc.SetPen(wx.GREY_PEN)dc.DrawLine(self._spacing,yPos-self._spacing/2+toAdd,width-self._spacing,yPos-self._spacing/2+toAdd)# Draw the footer and footer bitmap (if any)dc.SetTextForeground(normalText)height=max(textHeight,bmpHeight)yPos+=toAddiffooter:dc.DrawText(footer,bmpXPos+bmpWidth+self._spacing,yPos+(height-textHeight+self._spacing)/2)iffooterBmpandfooterBmp.IsOk():dc.DrawBitmap(footerBmp,bmpXPos,yPos+(height-bmpHeight+self._spacing)/2,True)defOnEraseBackground(self,event):""" Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{SuperToolTip}. :param `event`: a `wx.EraseEvent` event to be processed. :note: This method is intentionally empty to reduce flicker. """# This is intentionally empty to reduce flickerpassdefOnSize(self,event):""" Handles the ``wx.EVT_SIZE`` event for L{SuperToolTip}. :param `event`: a `wx.SizeEvent` event to be processed. """self.Refresh()event.Skip()defOnMouseMotion(self,event):""" Handles the ``wx.EVT_MOTION`` event for L{SuperToolTip}. :param `event`: a `wx.MouseEvent` event to be processed. """x,y=event.GetPosition()forrectinself._hyperlinkRect:ifrect.Contains((x,y)):# We are over one hyperlink...self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))self._wasOnLink=Truereturnifself._wasOnLink:# Restore the normal cursorself._wasOnLink=Falseself.SetCursor(wx.NullCursor)defOnDestroy(self,event):""" Handles the ``wx.EVT_LEFT_DOWN``, ``wx.EVT_LEFT_DCLICK`` and ``wx.EVT_KILL_FOCUS`` events for L{SuperToolTip}. All these events destroy the L{SuperToolTip}, unless the user clicked on one hyperlink. :param `event`: a `wx.MouseEvent` or a `wx.FocusEvent` event to be processed. """ifnotisinstance(event,wx.MouseEvent):# We haven't clicked a linkself.Destroy()returnx,y=event.GetPosition()forindx,rectinenumerate(self._hyperlinkRect):ifrect.Contains((x,y)):# Run the webbrowser with the clicked linkwebbrowser.open_new_tab(self._hyperlinkWeb[indx])returnifself._classParent.GetUseFade():# Fade out...self.StartAlpha(False)else:self.Destroy()defStartAlpha(self,isShow):""" Start the timer which set the alpha channel for L{SuperToolTip}. :param `isShow`: whether L{SuperToolTip} is being shown or deleted. :note: This method is available only on Windows and requires Mark Hammond's pywin32 package. """ifself._alphaTimer.IsRunning():return# Calculate starting alpha value and its stepself.amount=(isShowand[0]or[255])[0]self.delta=(isShowand[5]or[-5])[0]# Start the timerself._alphaTimer.Start(30)defSetFont(self,font):""" Sets the L{SuperToolTip} font globally. :param `font`: the font to set. """wx.PopupWindow.SetFont(self,font)self._classParent.InitFont()self.Invalidate()defInvalidate(self):""" Invalidate L{SuperToolTip} size and repaint it. """ifnotself._classParent.GetMessage():# No message yet...returnself.CalculateBestSize()self.Refresh()defDropShadow(self,drop=True):""" Adds a shadow under the window. :param `drop`: whether to drop a shadow or not. :note: This method is available only on Windows and requires Mark Hammond's pywin32 package. """ifnot_libimported:# No Mark Hammond's win32all extensionreturnifwx.Platform!="__WXMSW__":# This works only on Windows XPreturnhwnd=self.GetHandle()# Create a rounded rectangle regionsize=self.GetSize()ifdrop:ifhasattr(win32gui,"CreateRoundRectRgn"):rgn=win32gui.CreateRoundRectRgn(0,0,size.x,size.y,9,9)win32gui.SetWindowRgn(hwnd,rgn,True)CS_DROPSHADOW=0x00020000# Load the user32 libraryifnothasattr(self,"_winlib"):self._winlib=win32api.LoadLibrary("user32")csstyle=win32api.GetWindowLong(hwnd,win32con.GCL_STYLE)ifdrop:ifcsstyle&CS_DROPSHADOW:returnelse:csstyle|=CS_DROPSHADOW#Nothing to be doneelse:csstyle&=~CS_DROPSHADOW# Drop the shadow underneath the windowGCL_STYLE=-26cstyle=win32gui.GetClassLong(hwnd,GCL_STYLE)ifdrop:ifcstyle&CS_DROPSHADOW==0:win32api.SetClassLong(hwnd,GCL_STYLE,cstyle|CS_DROPSHADOW)else:win32api.SetClassLong(hwnd,GCL_STYLE,cstyle&~CS_DROPSHADOW)defAlphaCycle(self,event):""" Handles the ``wx.EVT_TIMER`` event for L{SuperToolTip}. :param `event`: a `wx.TimerEvent` event to be processed. """# Increase (or decrease) the alpha channelself.amount+=self.deltaifself.amount>255orself.amount<0:# We're done, stop the timerself._alphaTimer.Stop()ifself.amount<0:# Destroy the SuperToolTip, we are fading outself.Destroy()return# Make the SuperToolTip more or less transparentself.MakeWindowTransparent(self.amount)ifnotself.IsShown():self.Show()defMakeWindowTransparent(self,amount):""" Makes the L{SuperToolTip} window transparent. :param `amount`: the alpha channel value. :note: This method is available only on Windows and requires Mark Hammond's pywin32 package. """ifnot_libimported:# No way, only Windows XP with Mark Hammond's win32allreturn# this API call is not in all SDKs, only the newer ones, so# we will runtime bind thisifwx.Platform!="__WXMSW__":returnhwnd=self.GetHandle()ifnothasattr(self,"_winlib"):self._winlib=win32api.LoadLibrary("user32")pSetLayeredWindowAttributes=win32api.GetProcAddress(self._winlib,"SetLayeredWindowAttributes")ifpSetLayeredWindowAttributes==None:returnexstyle=win32api.GetWindowLong(hwnd,win32con.GWL_EXSTYLE)if0==(exstyle&0x80000):win32api.SetWindowLong(hwnd,win32con.GWL_EXSTYLE,exstyle|0x80000)winxpgui.SetLayeredWindowAttributes(hwnd,0,amount,2)defCalculateBestSize(self):""" Calculates the L{SuperToolTip} window best size. """# See the OnPaint method for explanations...maxWidth=maxHeight=0dc=wx.ClientDC(self)classParent=self._classParentheader,headerBmp=classParent.GetHeader(),classParent.GetHeaderBitmap()textHeight,bmpHeight=0,0headerFont,messageFont,footerFont,hyperlinkFont=classParent.GetHeaderFont(),classParent.GetMessageFont(), \
classParent.GetFooterFont(),classParent.GetHyperlinkFont()ifheader:dc.SetFont(headerFont)textWidth,textHeight=dc.GetTextExtent(header)maxWidth=max(maxWidth,textWidth+2*self._spacing)maxHeight+=self._spacing/2ifheaderBmpandheaderBmp.IsOk():maxWidth+=headerBmp.GetWidth()+2*self._spacingbmpHeight=headerBmp.GetHeight()ifnotheader:maxHeight+=self._spacing/2maxHeight+=max(textHeight,bmpHeight)iftextHeightorbmpHeight:maxHeight+=self._spacing/2# See the OnPaint method for explanations...bmpWidth=bmpHeight=-1embeddedImage=classParent.GetBodyImage()ifembeddedImageandembeddedImage.IsOk():bmpWidth,bmpHeight=embeddedImage.GetWidth(),embeddedImage.GetHeight()messageHeight=0textSpacing=(bmpWidthand[3*self._spacing]or[2*self._spacing])[0]lines=classParent.GetMessage().split("\n")forlineinlines:ifline.startswith("</b>"):# is a bold linefont=MakeBold(messageFont)dc.SetFont(font)line=line[4:]elifline.startswith("</l>"):# is a linkdc.SetFont(hyperlinkFont)line,hl=ExtractLink(line)else:dc.SetFont(messageFont)textWidth,textHeight=dc.GetTextExtent(line)iftextHeight==0:textWidth,textHeight=dc.GetTextExtent("a")maxWidth=max(maxWidth,textWidth+textSpacing+bmpWidth)messageHeight+=textHeight# See the OnPaint method for explanations...messageHeight=max(messageHeight,bmpHeight)maxHeight+=messageHeighttoAdd=0ifbmpHeight>textHeight:maxHeight+=2*self._spacingtoAdd=self._spacingelse:maxHeight+=2*self._spacingfooter,footerBmp=classParent.GetFooter(),classParent.GetFooterBitmap()textHeight,bmpHeight=0,0# See the OnPaint method for explanations...iffooter:dc.SetFont(footerFont)textWidth,textHeight=dc.GetTextExtent(footer)maxWidth=max(maxWidth,textWidth+2*self._spacing)maxHeight+=self._spacing/2iffooterBmpandfooterBmp.IsOk():bmpWidth,bmpHeight=footerBmp.GetWidth(),footerBmp.GetHeight()maxWidth=max(maxWidth,textWidth+3*self._spacing+bmpWidth)ifnotfooter:maxHeight+=self._spacing/2iftextHeightorbmpHeight:maxHeight+=self._spacing/2+max(textHeight,bmpHeight)maxHeight+=toAddself.SetSize((maxWidth,maxHeight))defCalculateBestPosition(self,widget):screen=wx.ClientDisplayRect()[2:]left,top=widget.ClientToScreenXY(0,0)right,bottom=widget.ClientToScreenXY(*widget.GetClientRect()[2:])size=self.GetSize()ifright+size[0]>screen[0]:xpos=left-size[0]else:xpos=rightifbottom+size[1]>screen[1]:ypos=top-size[1]else:ypos=bottomself.SetPosition((xpos,ypos))# Handle Mac and Windows/GTK differences...ifwx.Platform=="__WXMAC__":classToolTipWindow(wx.Frame,ToolTipWindowBase):""" Popup window that works on wxMac. """def__init__(self,parent,classParent):""" Default class constructor. :param `parent`: the L{SuperToolTip} parent widget; :param `classParent`: the L{SuperToolTip} class object. """wx.Frame.__init__(self,parent,style=wx.NO_BORDER|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.POPUP_WINDOW)# Call the base classToolTipWindowBase.__init__(self,parent,classParent)else:classToolTipWindow(ToolTipWindowBase,wx.PopupWindow):""" A simple `wx.PopupWindow` that holds fancy tooltips. Not available on Mac as `wx.PopupWindow` is not implemented there. """def__init__(self,parent,classParent):""" Default class constructor. :param `parent`: the L{SuperToolTip} parent widget; :param `classParent`: the L{SuperToolTip} class object. """wx.PopupWindow.__init__(self,parent)# Call the base classToolTipWindowBase.__init__(self,parent,classParent)classSuperToolTip(object):""" The main class for L{SuperToolTip}, which holds all the methods and setters/getters available to the user. """def__init__(self,message,bodyImage=wx.NullBitmap,header="",headerBmp=wx.NullBitmap,footer="",footerBmp=wx.NullBitmap):""" Default class constructor. :param `message`: the main message in L{SuperToolTip} body; :param `bodyImage`: the image in the L{SuperToolTip} body; :param `header`: the header text; :param `headerBmp`: the header bitmap; :param `footer`: the footer text; :param `footerBmp`: the footer bitmap. """self._superToolTip=None# Set all the initial optionsself.SetMessage(message)self.SetBodyImage(bodyImage)self.SetHeader(header)self.SetHeaderBitmap(headerBmp)self.SetFooter(footer)self.SetFooterBitmap(footerBmp)self._dropShadow=Falseself._useFade=Falseself._topLine=Falseself._bottomLine=Falseself.InitFont()# Get the running applicationsself._runningApp=wx.GetApp()self._runningApp.__superToolTip=True# Build a couple of timers...self._startTimer=wx.PyTimer(self.OnStartTimer)self._endTimer=wx.PyTimer(self.OnEndTimer)self.SetStartDelay()self.SetEndDelay()self.ApplyStyle("XP Blue")defSetTarget(self,widget):""" Sets the target window for L{SuperToolTip}. :param `widget`: the widget to which L{SuperToolTip} is associated. """self._widget=widgetself._widget.Bind(wx.EVT_ENTER_WINDOW,self.OnWidgetEnter)self._widget.Bind(wx.EVT_LEAVE_WINDOW,self.OnWidgetLeave)defGetTarget(self):""" Returns the target window for L{SuperToolTip}. """ifnothasattr(self,"_widget"):raiseException("\nError: the widget target for L{SuperToolTip} has not been set.")returnself._widgetdefSetStartDelay(self,delay=1):""" Sets the time delay (in seconds) after which the L{SuperToolTip} is created. :param `delay`: the delay in seconds. """self._startDelayTime=float(delay)defGetStartDelay(self):""" Returns the tim delay (in seconds) after which the L{SuperToolTip} is created."""returnself._startDelayTimedefSetEndDelay(self,delay=1e6):""" Sets the delay time (in seconds) after which the L{SuperToolTip} is destroyed. :param `delay`: the delay in seconds. """self._endDelayTime=float(delay)defGetEndDelay(self):""" Returns the delay time (in seconds) after which the L{SuperToolTip} is destroyed."""returnself._endDelayTimedefOnWidgetEnter(self,event):""" Starts the L{SuperToolTip} timer for creation, handles the ``wx.EVT_ENTER_WINDOW`` event. :param `event`: a `wx.MouseEvent` event to be processed. """ifself._superToolTip:# Not yet createdreturnifnotself._runningApp.__superToolTip:# The running app doesn't want tooltips...returnifself._startTimer.IsRunning():# We are already runningevent.Skip()returnself._startTimer.Start(self._startDelayTime*1000)event.Skip()defOnWidgetLeave(self,event):""" Handles the ``wx.EVT_LEAVE_WINDOW`` event for the target widgets. :param `event`: a `wx.MouseEvent` event to be processed. """pos=wx.GetMousePosition()realPos=self._widget.ScreenToClient(pos)rect=self._widget.GetClientRect()ifrect.Contains(realPos):# We get fake leave events...event.Skip()returnifself._superToolTip:ifself.GetUseFade():# Fade out...self._superToolTip.StartAlpha(False)else:self._superToolTip.Destroy()self._startTimer.Stop()self._endTimer.Stop()event.Skip()defGetTipWindow(self):""" Return the TipWindow, will return None if not yet created """returnself._superToolTipdefOnStartTimer(self):""" The creation time has expired, create the L{SuperToolTip}. """# target widget might already be destroyedifnotself._widget:self._startTimer.Stop()returntip=ToolTipWindow(self._widget,self)self._superToolTip=tipself._superToolTip.CalculateBestSize()self._superToolTip.CalculateBestPosition(self._widget)self._superToolTip.DropShadow(self.GetDropShadow())ifself.GetUseFade():self._superToolTip.StartAlpha(True)else:self._superToolTip.Show()self._startTimer.Stop()self._endTimer.Start(self._endDelayTime*1000)defOnEndTimer(self):""" The show time for L{SuperToolTip} has expired, destroy the L{SuperToolTip}. """ifself._superToolTip:ifself.GetUseFade():self._superToolTip.StartAlpha(False)else:self._superToolTip.Destroy()self._endTimer.Stop()defDoShowNow(self):""" Create the L{SuperToolTip} immediately. """ifself._superToolTip:# need to destroy it if already exists,# otherwise we might end up with many of themself._superToolTip.Destroy()tip=ToolTipWindow(self._widget,self)self._superToolTip=tipself._superToolTip.CalculateBestSize()self._superToolTip.CalculateBestPosition(self._widget)self._superToolTip.DropShadow(self.GetDropShadow())# need to stop this, otherwise we get into trouble when leaving the windowself._startTimer.Stop()ifself.GetUseFade():self._superToolTip.StartAlpha(True)else:self._superToolTip.Show()self._endTimer.Start(self._endDelayTime*1000)defOnDestroy(self,event):""" Handles the L{SuperToolTip} target destruction. """ifself._superToolTip:# Unbind the events!self._widget.Unbind(wx.EVT_LEAVE_WINDOW)self._widget.Unbind(wx.EVT_ENTER_WINDOW)self._superToolTip.Destroy()delself._superToolTipself._superToolTip=NonedefSetHeaderBitmap(self,bmp):""" Sets the header bitmap for L{SuperToolTip}. :param `bmp`: the header bitmap, a valid `wx.Bitmap` object. """self._headerBmp=bmpifself._superToolTip:self._superToolTip.Invalidate()defGetHeaderBitmap(self):""" Returns the header bitmap. """returnself._headerBmpdefSetHeader(self,header):""" Sets the header text. :param `header`: the header text to display. """self._header=headerifself._superToolTip:self._superToolTip.Invalidate()defGetHeader(self):""" Returns the header text. """returnself._headerdefSetDrawHeaderLine(self,draw):""" Sets whether to draw a separator line after the header or not. :param `draw`: ``True`` to draw a separator line after the header, ``False`` otherwise. """self._topLine=drawifself._superToolTip:self._superToolTip.Refresh()defGetDrawHeaderLine(self):""" Returns whether the separator line after the header is drawn or not. """returnself._topLinedefSetBodyImage(self,bmp):""" Sets the main body bitmap for L{SuperToolTip}. :param `bmp`: the body bitmap, a valid `wx.Bitmap` object. """self._embeddedImage=bmpifself._superToolTip:self._superToolTip.Invalidate()defGetBodyImage(self):""" Returns the main body bitmap used in L{SuperToolTip}. """returnself._embeddedImagedefSetDrawFooterLine(self,draw):""" Sets whether to draw a separator line before the footer or not. :param `draw`: ``True`` to draw a separator line before the footer, ``False`` otherwise. """self._bottomLine=drawifself._superToolTip:self._superToolTip.Refresh()defGetDrawFooterLine(self):""" Returns whether the separator line before the footer is drawn or not. """returnself._bottomLinedefSetFooterBitmap(self,bmp):""" Sets the footer bitmap for L{SuperToolTip}. :param `bmp`: the footer bitmap, a valid `wx.Bitmap` object. """self._footerBmp=bmpifself._superToolTip:self._superToolTip.Invalidate()defGetFooterBitmap(self):""" Returns the footer bitmap. """returnself._footerBmpdefSetFooter(self,footer):""" Sets the footer text. :param `footer`: the footer text to display. """self._footer=footerifself._superToolTip:self._superToolTip.Invalidate()defGetFooter(self):""" Returns the footer text. """returnself._footerdefSetMessage(self,message):""" Sets the main body message for L{SuperToolTip}. :param `message`: the message to display in the body. """self._message=messageifself._superToolTip:self._superToolTip.Invalidate()defGetMessage(self):""" Returns the main body message in L{SuperToolTip}. """returnself._messagedefSetTopGradientColour(self,colour):""" Sets the top gradient colour for L{SuperToolTip}. :param `colour`: the colour to use as top colour, a valid `wx.Colour` object. """self._topColour=colourifself._superToolTip:self._superToolTip.Refresh()defSetMiddleGradientColour(self,colour):""" Sets the middle gradient colour for L{SuperToolTip}. :param `colour`: the colour to use as middle colour, a valid `wx.Colour` object. """self._middleColour=colourifself._superToolTip:self._superToolTip.Refresh()defSetBottomGradientColour(self,colour):""" Sets the bottom gradient colour for L{SuperToolTip}. :param `colour`: the colour to use as bottom colour, a valid `wx.Colour` object. """self._bottomColour=colourifself._superToolTip:self._superToolTip.Refresh()defSetTextColour(self,colour):""" Sets the text colour for L{SuperToolTip}. :param `colour`: the colour to use as text colour, a valid `wx.Colour` object. """self._textColour=colourifself._superToolTip:self._superToolTip.Refresh()defGetTopGradientColour(self):""" Returns the top gradient colour. """returnself._topColourdefGetMiddleGradientColour(self):""" Returns the middle gradient colour. """returnself._middleColourdefGetBottomGradientColour(self):""" Returns the bottom gradient colour. """returnself._bottomColourdefGetTextColour(self):""" Returns the text colour. """returnself._textColourSetTopGradientColor=SetTopGradientColourSetMiddleGradientColor=SetMiddleGradientColourSetBottomGradientColor=SetBottomGradientColourGetTopGradientColor=GetTopGradientColourGetMiddleGradientColor=GetMiddleGradientColourGetBottomGradientColor=GetBottomGradientColourSetTextColor=SetTextColourGetTextColor=GetTextColourdefInitFont(self):""" Initalizes the fonts for L{SuperToolTip}. """self._messageFont=wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)self._headerFont=wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)self._headerFont.SetWeight(wx.BOLD)self._footerFont=wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)self._footerFont.SetWeight(wx.BOLD)self._hyperlinkFont=wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)self._hyperlinkFont.SetWeight(wx.BOLD)self._hyperlinkFont.SetUnderlined(True)defSetMessageFont(self,font):""" Sets the font for the main body message. :param `font`: the font to use for the main body message, a valid `wx.Font` object. """self._messageFont=fontifself._superToolTip:self._superToolTip.Invalidate()defSetHeaderFont(self,font):""" Sets the font for the header text. :param `font`: the font to use for the header text, a valid `wx.Font` object. """self._headerFont=fontifself._superToolTip:self._superToolTip.Invalidate()defSetFooterFont(self,font):""" Sets the font for the footer text. :param `font`: the font to use for the footer text, a valid `wx.Font` object. """self._footerFont=fontifself._superToolTip:self._superToolTip.Invalidate()defSetHyperlinkFont(self,font):""" Sets the font for the hyperlink text. :param `font`: the font to use for the hyperlink text, a valid `wx.Font` object. """self._hyperlinkFont=fontifself._superToolTip:self._superToolTip.Invalidate()defGetMessageFont(self):""" Returns the font used in the main body message. """returnself._messageFontdefGetHeaderFont(self):""" Returns the font used for the header text. """returnself._headerFontdefGetFooterFont(self):""" Returns the font used for the footer text. """returnself._footerFontdefGetHyperlinkFont(self):""" Returns the font used for the hyperlink text. """returnself._hyperlinkFontdefSetDropShadow(self,drop):""" Whether to draw a shadow below L{SuperToolTip} or not. :param `drop`: ``True`` to drop a shadow below the control, ``False`` otherwise. :note: This method is available only on Windows and requires Mark Hammond's pywin32 package. """self._dropShadow=dropifself._superToolTip:self._superToolTip.Invalidate()defGetDropShadow(self):""" Returns whether a shadow below L{SuperToolTip} is drawn or not. :note: This method is available only on Windows and requires Mark Hammond's pywin32 package. """returnself._dropShadowdefSetUseFade(self,fade):""" Whether to use a fade in/fade out effect or not. :param `fade`: ``True`` to use a fade in/fade out effect, ``False`` otherwise. :note: This method is available only on Windows and requires Mark Hammond's pywin32 package. """self._useFade=fadedefGetUseFade(self):""" Returns whether a fade in/fade out effect is used or not. :note: This method is available only on Windows and requires Mark Hammond's pywin32 package. """returnself._useFadedefApplyStyle(self,style):""" Applies none of the predefined styles. :param `style`: one of the predefined styles available at the beginning of the module. """ifstylenotin_colourSchemes:raiseException("Invalid style '%s' selected"%style)top,middle,bottom,text=_colourSchemes[style]self._topColour=topself._middleColour=middleself._bottomColour=bottomself._textColour=textifself._superToolTip:self._superToolTip.Refresh()defEnableTip(self,enable=True):""" Globally (application-wide) enables/disables L{SuperToolTip}. :param `enable`: ``True`` to enable L{SuperToolTip} globally, ``False`` otherwise. """wx.GetApp().__superToolTip=enableifnotenableandself._superToolTip:self._superToolTip.Destroy()self._superToolTip=Nonedelself._superToolTip