new version of fancytext module, added foldmenu

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_4_BRANCH@25191 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2004-01-15 17:45:10 +00:00
parent b640ef4662
commit 48c1c764af
3 changed files with 371 additions and 138 deletions

View File

@@ -1,62 +1,109 @@
"""wxFancyText -- methods for rendering XML specified text """<font weight="bold" size="16">FancyText</font> -- <font style="italic" size="16">methods for rendering XML specified text</font>
<font family="swiss" size="12">
This module exports four main methods::
<font family="fixed" style="slant">
def GetExtent(str, dc=None, enclose=True)
def GetFullExtent(str, dc=None, enclose=True)
def RenderToBitmap(str, background=None, enclose=True)
def RenderToDC(str, dc, x, y, enclose=True)
</font>
In all cases, 'str' is an XML string. Note that start and end tags
are only required if *enclose* is set to False. In this case the
text should be wrapped in FancyText tags.
This module has four main methods: In addition, the module exports one class::
<font family="fixed" style="slant">
class StaticFancyText(self, window, id, text, background, ...)
</font>
This class works similar to StaticText except it interprets its text
as FancyText.
def getExtent(str, dc=None, enclose=1): The text can support<sup>superscripts</sup> and <sub>subscripts</sub>, text
def renderToBitmap(str, background=None, enclose=1) in different <font size="20">sizes</font>, <font color="blue">colors</font>, <font style="italic">styles</font>, <font weight="bold">weights</font> and
def renderToDC(str, dc, x, y, enclose=1) <font family="script">families</font>. It also supports a limited set of symbols,
currently <times/>, <infinity/>, <angle/> as well as greek letters in both
upper case (<Alpha/><Beta/>...<Omega/>) and lower case (<alpha/><beta/>...<omega/>).
In all cases, 'str' is an XML string. The tags in the string can We can use doctest/guitest to display this string in all its marked up glory.
currently specify the font, subscripts, superscripts, and the angle <font family="fixed">
sign. The allowable properties of font are size, family, style, weght, >>> frame = wx.Frame(wx.NULL, -1, "FancyText demo", wx.DefaultPosition)
encoding, and color. See the example on the bottom for a better idea >>> sft = StaticFancyText(frame, -1, __doc__, wx.Brush("light grey", wx.SOLID))
of how this works. >>> frame.SetClientSize(sft.GetSize())
>>> didit = frame.Show()
>>> from guitest import PauseTests; PauseTests()
Note that start and end tags for the string are provided if enclose is </font></font>
True, so for instance, renderToBitmap("X<sub>1</sub>") will work. The End"""
# Copyright 2001-2003 Timothy Hochberg
# Use as you see fit. No warantees, I cannot be help responsible, etc.
import copy
import math
import sys
import wx
import xml.parsers.expat
""" __all__ = "GetExtent", "GetFullExtent", "RenderToBitmap", "RenderToDC", "StaticFancyText"
# Copyright 2001 Timothy Hochberg
# Use as you see fit. No warantees, I cannot be held responsible, etc.
if sys.platform == "win32":
_greekEncoding = str(wx.FONTENCODING_CP1253)
else:
_greekEncoding = str(wx.FONTENCODING_ISO8859_7)
_families = {"fixed" : wx.FIXED, "default" : wx.DEFAULT, "decorative" : wx.DECORATIVE, "roman" : wx.ROMAN,
# TODO: Make a wxFancyTextCtrl class that derives from wxControl. "script" : wx.SCRIPT, "swiss" : wx.SWISS, "modern" : wx.MODERN}
# Add support for line breaks _styles = {"normal" : wx.NORMAL, "slant" : wx.SLANT, "italic" : wx.ITALIC}
# etc. _weights = {"normal" : wx.NORMAL, "light" : wx.LIGHT, "bold" : wx.BOLD}
# - Robin
from wxPython.wx import *
import xml.parsers.expat, copy
_families = {"default" : wxDEFAULT, "decorative" : wxDECORATIVE, "roman" : wxROMAN,
"swiss" : wxSWISS, "modern" : wxMODERN}
_styles = {"normal" : wxNORMAL, "slant" : wxSLANT, "italic" : wxITALIC}
_weights = {"normal" : wxNORMAL, "light" : wxLIGHT, "bold" : wxBOLD}
# The next three classes: Renderer, SizeRenderer and DCRenderer are # The next three classes: Renderer, SizeRenderer and DCRenderer are
# what you will need to override to extend the XML language. All of # what you will need to override to extend the XML language. All of
# the font stuff as well as the subscript and superscript stuff are in # the font stuff as well as the subscript and superscript stuff are in
# Renderer. # Renderer.
class Renderer: _greek_letters = ("alpha", "beta", "gamma", "delta", "epsilon", "zeta",
"eta", "theta", "iota", "kappa", "lambda", "mu", "nu",
"xi", "omnikron", "pi", "rho", "altsigma", "sigma", "tau", "upsilon",
"phi", "chi", "psi", "omega")
defaultSize = wxNORMAL_FONT.GetPointSize() def iround(number):
defaultFamily = wxDEFAULT return int(round(number))
defaultStyle = wxNORMAL
defaultWeight = wxNORMAL def iceil(number):
defaultEncoding = wxFont_GetDefaultEncoding() return int(math.ceil(number))
class Renderer:
"""Class for rendering XML marked up text.
See the module docstring for a description of the markup.
This class must be subclassed and the methods the methods that do
the drawing overridden for a particular output device.
"""
defaultSize = wx.NORMAL_FONT.GetPointSize()
defaultFamily = wx.DEFAULT
defaultStyle = wx.NORMAL
defaultWeight = wx.NORMAL
defaultEncoding = wx.Font_GetDefaultEncoding()
defaultColor = "black" defaultColor = "black"
def __init__(self, dc=None): def __init__(self, dc=None, x=0, y=None):
if dc == None: if dc == None:
dc = wxMemoryDC() dc = wx.MemoryDC()
self.dc = dc self.dc = dc
self.offsets = [0] self.offsets = [0]
self.fonts = [{}] self.fonts = [{}]
self.width = self.height = 0
self.x = x
self.minY = self.maxY = self._y = y
def getY(self):
if self._y is None:
self.minY = self.maxY = self._y = self.dc.GetTextExtent("M")[1]
return self._y
def setY(self, value):
self._y = y
y = property(getY, setY)
def startElement(self, name, attrs): def startElement(self, name, attrs):
method = "start_" + name method = "start_" + name
if not hasattr(self, method): if not hasattr(self, method):
@@ -64,16 +111,37 @@ class Renderer:
getattr(self, method)(attrs) getattr(self, method)(attrs)
def endElement(self, name): def endElement(self, name):
method = "end_" + name methname = "end_" + name
if not hasattr(self, method): if hasattr(self, methname):
raise ValueError("XML tag '%s' not supported" % name) getattr(self, methname)()
getattr(self, method)() elif hasattr(self, "start_" + name):
pass
else:
raise ValueError("XML tag '%s' not supported" % methname)
def characterData(self, data):
self.dc.SetFont(self.getCurrentFont())
for i, chunk in enumerate(data.split('\n')):
if i:
self.x = 0
self.y = self.mayY = self.maxY + self.dc.GetTextExtent("M")[1]
if chunk:
width, height, descent, extl = self.dc.GetFullTextExtent(chunk)
self.renderCharacterData(data, iround(self.x), iround(self.y + self.offsets[-1] - height + descent))
else:
width = height = descent = extl = 0
self.updateDims(width, height, descent, extl)
def start_wxFancyString(self, attrs): def updateDims(self, width, height, descent, externalLeading):
pass self.x += width
self.width = max(self.x, self.width)
self.minY = min(self.minY, self.y+self.offsets[-1]-height+descent)
self.maxY = max(self.maxY, self.y+self.offsets[-1]+descent)
self.height = self.maxY - self.minY
def end_wxFancyString(self): def start_FancyText(self, attrs):
pass pass
start_wxFancyText = start_FancyText # For backward compatibility
def start_font(self, attrs): def start_font(self, attrs):
for key, value in attrs.items(): for key, value in attrs.items():
@@ -86,7 +154,7 @@ class Renderer:
elif key == "weight": elif key == "weight":
value = _weights[value] value = _weights[value]
elif key == "encoding": elif key == "encoding":
pass value = int(value)
elif key == "color": elif key == "color":
pass pass
else: else:
@@ -119,11 +187,11 @@ class Renderer:
def end_sup(self): def end_sup(self):
self.fonts.pop() self.fonts.pop()
self.offsets.pop() self.offsets.pop()
def getCurrentFont(self): def getCurrentFont(self):
font = self.fonts[-1] font = self.fonts[-1]
return wxFont(font.get("size", self.defaultSize), return wx.TheFontList.FindOrCreateFont(font.get("size", self.defaultSize),
font.get("family", self.defaultFamily), font.get("family", self.defaultFamily),
font.get("style", self.defaultStyle), font.get("style", self.defaultStyle),
font.get("weight", self.defaultWeight), font.get("weight", self.defaultWeight),
@@ -131,119 +199,212 @@ class Renderer:
def getCurrentColor(self): def getCurrentColor(self):
font = self.fonts[-1] font = self.fonts[-1]
return wxNamedColour(font.get("color", self.defaultColor)) return wx.TheColourDatabase.FindColour(font.get("color", self.defaultColor))
def getCurrentPen(self):
return wx.ThePenList.FindOrCreatePen(self.getCurrentColor(), 1, wx.SOLID)
def renderCharacterData(self, data, x, y):
raise NotImplementedError()
def _addGreek():
alpha = 0xE1
Alpha = 0xC1
def end(self):
pass
for i, name in enumerate(_greek_letters):
def start(self, attrs, code=chr(alpha+i)):
self.start_font({"encoding" : _greekEncoding})
self.characterData(code)
self.end_font()
setattr(Renderer, "start_%s" % name, start)
setattr(Renderer, "end_%s" % name, end)
if name == "altsigma":
continue # There is no capital for altsigma
def start(self, attrs, code=chr(Alpha+i)):
self.start_font({"encoding" : _greekEncoding})
self.characterData(code)
self.end_font()
setattr(Renderer, "start_%s" % name.capitalize(), start)
setattr(Renderer, "end_%s" % name.capitalize(), end)
_addGreek()
class SizeRenderer(Renderer): class SizeRenderer(Renderer):
"""Processes text as if rendering it, but just computes the size."""
def __init__(self, dc=None): def __init__(self, dc=None):
Renderer.__init__(self, dc) Renderer.__init__(self, dc, 0, 0)
self.width = self.height = 0
self.minY = self.maxY = 0 def renderCharacterData(self, data, x, y):
pass
def characterData(self, data):
self.dc.SetFont(self.getCurrentFont())
width, height = self.dc.GetTextExtent(data)
self.width = self.width + width
self.minY = min(self.minY, self.offsets[-1])
self.maxY = max(self.maxY, self.offsets[-1] + height)
self.height = self.maxY - self.minY
def start_angle(self, attrs): def start_angle(self, attrs):
self.characterData("M") self.characterData("M")
def end_angle(self): def start_infinity(self, attrs):
pass width, height = self.dc.GetTextExtent("M")
width = max(width, 10)
height = max(height, width / 2)
self.updateDims(width, height, 0, 0)
def start_times(self, attrs):
self.characterData("M")
def start_in(self, attrs):
self.characterData("M")
def start_times(self, attrs):
self.characterData("M")
class DCRenderer(Renderer): class DCRenderer(Renderer):
"""Renders text to a wxPython device context DC."""
def __init__(self, dc=None, x=0, y=0): def renderCharacterData(self, data, x, y):
Renderer.__init__(self, dc)
self.x = x
self.y = y
def characterData(self, data):
self.dc.SetFont(self.getCurrentFont())
self.dc.SetTextForeground(self.getCurrentColor()) self.dc.SetTextForeground(self.getCurrentColor())
width, height = self.dc.GetTextExtent(data) self.dc.DrawText(data, x, y)
self.dc.DrawText(data, self.x, self.y + self.offsets[-1])
self.x = self.x + width
def start_angle(self, attrs): def start_angle(self, attrs):
self.dc.SetFont(self.getCurrentFont()) self.dc.SetFont(self.getCurrentFont())
self.dc.SetPen(wxPen(self.getCurrentColor(), 1)) self.dc.SetPen(self.getCurrentPen())
width, height, descent, leading = self.dc.GetFullTextExtent("M") width, height, descent, leading = self.dc.GetFullTextExtent("M")
y = self.y + self.offsets[-1] + height - descent y = self.y + self.offsets[-1]
self.dc.DrawLine(self.x, y, self.x+width, y) self.dc.DrawLine(iround(self.x), iround(y),iround( self.x+width), iround(y))
self.dc.DrawLine(self.x, y, self.x+width, y-width) self.dc.DrawLine(iround(self.x), iround(y), iround(self.x+width), iround(y-width))
self.x = self.x + width self.updateDims(width, height, descent, leading)
def end_angle(self): def start_infinity(self, attrs):
pass self.dc.SetFont(self.getCurrentFont())
self.dc.SetPen(self.getCurrentPen())
width, height, descent, leading = self.dc.GetFullTextExtent("M")
width = max(width, 10)
height = max(height, width / 2)
self.dc.SetPen(wx.Pen(self.getCurrentColor(), max(1, width/10)))
self.dc.SetBrush(wx.TRANSPARENT_BRUSH)
y = self.y + self.offsets[-1]
r = iround( 0.95 * width / 4)
xc = (2*self.x + width) / 2
yc = iround(y-1.5*r)
self.dc.DrawCircle(xc - r, yc, r)
self.dc.DrawCircle(xc + r, yc, r)
self.updateDims(width, height, 0, 0)
# This is a rendering function that is primarily used internally, def start_times(self, attrs):
# although it could be used externally if one had overridden the self.dc.SetFont(self.getCurrentFont())
# Renderer classes. self.dc.SetPen(self.getCurrentPen())
width, height, descent, leading = self.dc.GetFullTextExtent("M")
def renderToRenderer(str, renderer, enclose=1): y = self.y + self.offsets[-1]
if enclose: width *= 0.8
str = '<?xml version="1.0"?><wxFancyString>%s</wxFancyString>' % str width = iround(width+.5)
p = xml.parsers.expat.ParserCreate() self.dc.SetPen(wx.Pen(self.getCurrentColor(), 1))
p.returns_unicode = 0 self.dc.DrawLine(iround(self.x), iround(y-width), iround(self.x+width-1), iround(y-1))
p.StartElementHandler = renderer.startElement self.dc.DrawLine(iround(self.x), iround(y-2), iround(self.x+width-1), iround(y-width-1))
p.EndElementHandler = renderer.endElement self.updateDims(width, height, 0, 0)
p.CharacterDataHandler = renderer.characterData
p.Parse(str, 1)
def getExtent(str, dc=None, enclose=1): def RenderToRenderer(str, renderer, enclose=True):
try:
if enclose:
str = '<?xml version="1.0"?><FancyText>%s</FancyText>' % str
p = xml.parsers.expat.ParserCreate()
p.returns_unicode = 0
p.StartElementHandler = renderer.startElement
p.EndElementHandler = renderer.endElement
p.CharacterDataHandler = renderer.characterData
p.Parse(str, 1)
except xml.parsers.expat.error, err:
raise ValueError('error parsing text text "%s": %s' % (str, err))
# Public interface
def GetExtent(str, dc=None, enclose=True):
"Return the extent of str" "Return the extent of str"
renderer = SizeRenderer(dc) renderer = SizeRenderer(dc)
renderToRenderer(str, renderer, enclose) RenderToRenderer(str, renderer, enclose)
return wxSize(renderer.width, renderer.height) return iceil(renderer.width), iceil(renderer.height) # XXX round up
# This should probably only be used internally....
def getFullExtent(str, dc=None, enclose=1): def GetFullExtent(str, dc=None, enclose=True):
renderer = SizeRenderer(dc) renderer = SizeRenderer(dc)
renderToRenderer(str, renderer, enclose) RenderToRenderer(str, renderer, enclose)
return renderer.width, renderer.height, -renderer.minY return iceil(renderer.width), iceil(renderer.height), -iceil(renderer.minY) # XXX round up
def renderToBitmap(str, background=None, enclose=1):
def RenderToBitmap(str, background=None, enclose=1):
"Return str rendered on a minumum size bitmap" "Return str rendered on a minumum size bitmap"
dc = wxMemoryDC() dc = wx.MemoryDC()
width, height, dy = getFullExtent(str, dc) width, height, dy = GetFullExtent(str, dc, enclose)
bmp = wxEmptyBitmap(width, height) bmp = wx.EmptyBitmap(width, height)
dc.SelectObject(bmp) dc.SelectObject(bmp)
if background is not None: if background is None:
dc.SetBackground(background) dc.SetBackground(wx.WHITE_BRUSH)
else:
dc.SetBackground(background)
dc.Clear() dc.Clear()
renderer = DCRenderer(dc, y=dy) renderer = DCRenderer(dc, y=dy)
dc.BeginDrawing() dc.BeginDrawing()
renderToRenderer(str, renderer, enclose) RenderToRenderer(str, renderer, enclose)
dc.EndDrawing() dc.EndDrawing()
dc.SelectObject(wxNullBitmap) dc.SelectObject(wx.NullBitmap)
if background is None:
img = wx.ImageFromBitmap(bmp)
bg = dc.GetBackground().GetColour()
img.SetMaskColour(bg.Red(), bg.Green(), bg.Blue())
bmp = img.ConvertToBitmap()
return bmp return bmp
def renderToDC(str, dc, x, y, enclose=1):
def RenderToDC(str, dc, x, y, enclose=1):
"Render str onto a wxDC at (x,y)" "Render str onto a wxDC at (x,y)"
width, height, dy = getFullExtent(str, dc) width, height, dy = GetFullExtent(str, dc)
renderer = DCRenderer(dc, x, y+dy) renderer = DCRenderer(dc, x, y+dy)
renderToRenderer(str, renderer, enclose) RenderToRenderer(str, renderer, enclose)
class StaticFancyText(wx.StaticBitmap):
def __init__(self, window, id, text, *args, **kargs):
args = list(args)
kargs.setdefault('name', 'staticFancyText')
if 'background' in kargs:
background = kargs.pop('background')
elif args:
background = args.pop(0)
else:
background = wx.Brush(window.GetBackgroundColour(), wx.SOLID)
bmp = RenderToBitmap(text, background)
wx.StaticBitmap.__init__(self, window, id, bmp, *args, **kargs)
if __name__ == "__main__": # Old names for backward compatibiliry
str = ('<font style="italic" family="swiss" color="red" weight="bold" >some |<sup>23</sup> <angle/>text<sub>with <angle/> subscript</sub> </font> some other text' getExtent = GetExtent
'<font family="swiss" color="green" size="40">big green text</font>') renderToBitmap = RenderToBitmap
ID_EXIT = 102 renderToDC = RenderToDC
class myApp(wxApp):
def OnInit(self):
return 1 # Test Driver
app = myApp()
frame = wxFrame(NULL, -1, "wxFancyText demo", wxDefaultPosition) def test():
frame.SetClientSize(getExtent(str)) app = wx.PyApp()
bmp = renderToBitmap(str, wxCYAN_BRUSH) box = wx.BoxSizer(wx.VERTICAL)
sb = wxStaticBitmap(frame, -1, bmp) frame = wx.Frame(wx.NULL, -1, "FancyText demo", wx.DefaultPosition)
EVT_MENU(frame, ID_EXIT, frame.Destroy) frame.SetBackgroundColour("light grey")
frame.Show(1) sft = StaticFancyText(frame, -1, __doc__)
box.Add(sft, 1, wx.EXPAND)
frame.SetSizer(box)
frame.SetAutoLayout(True)
box.Fit(frame)
box.SetSizeHints(frame)
frame.Show()
app.MainLoop() app.MainLoop()
if __name__ == "__main__":
test()

View File

@@ -1,10 +1,79 @@
## This file imports items from the wx package into the wxPython package for
## backwards compatibility. Some names will also have a 'wx' added on if
## that is how they used to be named in the old wxPython package.
import wx.lib.foldmenu import wx
from wx.lib.evtmgr import eventManager
__doc__ = wx.lib.foldmenu.__doc__ class FoldOutWindow(wx.PopupWindow):
def __init__(self,parent,style=0):
wx.PopupWindow.__init__(self,parent,style)
self.SetAutoLayout(True)
self.sizer=wx.BoxSizer(wx.HORIZONTAL)
self.SetSizer(self.sizer, deleteOld=False)
self.handlers={}
self.InitColors()
self.inWindow=False
wx.EVT_ENTER_WINDOW(self,self.evEnter)
wx.EVT_LEAVE_WINDOW(self,self.evLeave)
def InitColors(self):
faceClr = wx.SystemSettings_GetSystemColour(wx.SYS_COLOUR_WINDOW)
self.SetBackgroundColour(faceClr)
FoldOutMenu = wx.lib.foldmenu.FoldOutMenu def AddButton(self,bitmap,handler=None):
FoldOutWindow = wx.lib.foldmenu.FoldOutWindow id=wx.NewId()
btn=wx.BitmapButton(self,id,bitmap)
self.sizer.Add(btn, 1, wx.ALIGN_CENTER|wx.ALL|wx.EXPAND, 2)
wx.EVT_BUTTON(self,id,self.OnBtnClick)
self.sizer.Fit(self)
self.Layout()
if handler:
self.handlers[id]=handler
return id
def Popup(self):
if not self.IsShown():
self.Show()
def OnBtnClick(self,event):
id=event.GetEventObject().GetId()
if self.handlers.has_key(id):
self.handlers[id](event)
self.Hide()
self.inWindow=False
event.Skip()
def evEnter(self,event):
self.inWindow=True
self.rect=self.GetRect()
event.Skip()
def evLeave(self,event):
if self.inWindow:
if not self.rect.Inside(self.ClientToScreen(event.GetPosition())):
self.Hide()
event.Skip()
class FoldOutMenu(wx.BitmapButton):
def __init__(self,parent,id,bitmap,pos = wx.DefaultPosition,
size = wx.DefaultSize, style = wx.BU_AUTODRAW,
validator = wx.DefaultValidator, name = "button"):
wx.BitmapButton.__init__(self,parent,id,bitmap,pos = wx.DefaultPosition,
size = wx.DefaultSize, style = wx.BU_AUTODRAW,
validator = wx.DefaultValidator, name = "button")
self.parent=parent
wx.EVT_BUTTON(self.parent, self.GetId(), self.click)
self.popwin=FoldOutWindow(self.parent)
def AddButton(self,bitmap,handler=None):
return self.popwin.AddButton(bitmap,handler=handler)
def click(self,event):
pos=self.GetPosition()
sz=self.GetSize()
pos.x=pos.x+sz.width
pos.y=pos.y+sz.height/2
self.popwin.Position(pos,sz)
self.popwin.Popup()

View File

@@ -40,15 +40,18 @@ class wxColumnSorterMixin:
""" """
def __init__(self, numColumns): def __init__(self, numColumns):
self._colSortFlag = [0] * numColumns self.SetColumnCount(numColumns)
self._col = -1
list = self.GetListCtrl() list = self.GetListCtrl()
if not list: if not list:
raise ValueError, "No wxListCtrl available" raise ValueError, "No wxListCtrl available"
EVT_LIST_COL_CLICK(list, list.GetId(), self.__OnColClick) EVT_LIST_COL_CLICK(list, list.GetId(), self.__OnColClick)
def SetColumnCount(self, newNumColumns):
self._colSortFlag = [0] * newNumColumns
self._col = -1
def SortListItems(self, col=-1, ascending=1): def SortListItems(self, col=-1, ascending=1):
"""Sort the list on demand. Can also be used to set the sort column and order.""" """Sort the list on demand. Can also be used to set the sort column and order."""
oldCol = self._col oldCol = self._col