RichTextCtrl
provides a generic implementation of a
rich text editor that can handle different character styles, paragraph
formatting, and images.
It’s aimed at editing ‘natural’ language text - if you need an editor
that supports code editing, wx.stc.StyledTextCtrl
is a better choice.
Despite its name, it cannot currently read or write RTF (rich text format) files. Instead, it uses its own XML format, and can also read and write plain text. In future we expect to provide RTF or OpenDocument file capabilities. Custom file formats can be supported by creating additional file handlers and registering them with the control.
RichTextCtrl
is largely compatible with the
wx.TextCtrl
API, but extends it where necessary. The control can
be used where the native rich text capabilities of wx.TextCtrl
are not adequate (this is particularly true on Windows) and where more
direct access to the content representation is required. It is
difficult and inefficient to read the style information in a
wx.TextCtrl
, whereas this information is readily available in
RichTextCtrl
. Since it’s written in pure wxWidgets,
any customizations you make to RichTextCtrl
will be
reflected on all platforms.
RichTextCtrl
supports basic printing via the
easy-to-use RichTextPrinting
class. Creating
applications with simple word processing features is simplified with
the inclusion of RichTextFormattingDialog
, a tabbed
dialog allowing interactive tailoring of paragraph and character
styling. Also provided is the multi-purpose dialog
RichTextStyleOrganiserDialog
that can be used for
managing style definitions, browsing styles and applying them, or
selecting list styles with a renumber option.
There are a few disadvantages to using
RichTextCtrl
. It is not native, so does not behave
exactly as a native wx.TextCtrl
, although common editing
conventions are followed. Users may miss the built-in spelling
correction on Mac OS X, or any special character input that may be
provided by the native control. It would also be a poor choice if
intended users rely on screen readers that would be not work well with
non-native text input implementation. You might mitigate this by
providing the choice between wx.TextCtrl
and
RichTextCtrl
, with fewer features in the former
case.
A good way to understand RichTextCtrl
’s
capabilities is to run the sample in the wxPython demo, and browse the
code.
This is taken from the wxPython demo:
import wx
import wx.richtext as rt
import images
#----------------------------------------------------------------------
class RichTextFrame(wx.Frame):
def __init__(self, *args, **kw):
wx.Frame.__init__(self, *args, **kw)
self.MakeMenuBar()
self.MakeToolBar()
self.CreateStatusBar()
self.SetStatusText("Welcome to wx.richtext.RichTextCtrl!")
self.rtc = rt.RichTextCtrl(self, style=wx.VSCROLL|wx.HSCROLL|wx.NO_BORDER);
wx.CallAfter(self.rtc.SetFocus)
self.rtc.Freeze()
self.rtc.BeginSuppressUndo()
self.rtc.BeginParagraphSpacing(0, 20)
self.rtc.BeginAlignment(rt.TEXT_ALIGNMENT_CENTRE)
self.rtc.BeginBold()
self.rtc.BeginFontSize(14)
self.rtc.WriteText("Welcome to wxRichTextCtrl, a wxWidgets control for editing and presenting " \
"styled text and images")
self.rtc.EndFontSize()
self.rtc.Newline()
self.rtc.BeginItalic()
self.rtc.WriteText("by Julian Smart")
self.rtc.EndItalic()
self.rtc.EndBold()
self.rtc.Newline()
self.rtc.WriteImage(images._rt_zebra.GetImage())
self.rtc.EndAlignment()
self.rtc.Newline()
self.rtc.Newline()
self.rtc.WriteText("What can you do with this thing? ")
self.rtc.WriteImage(images._rt_smiley.GetImage())
self.rtc.WriteText(" Well, you can change text ")
self.rtc.BeginTextColour((255, 0, 0))
self.rtc.WriteText("colour, like this red bit.")
self.rtc.EndTextColour()
self.rtc.BeginTextColour((0, 0, 255))
self.rtc.WriteText(" And this blue bit.")
self.rtc.EndTextColour()
self.rtc.WriteText(" Naturally you can make things ")
self.rtc.BeginBold()
self.rtc.WriteText("bold ")
self.rtc.EndBold()
self.rtc.BeginItalic()
self.rtc.WriteText("or italic ")
self.rtc.EndItalic()
self.rtc.BeginUnderline()
self.rtc.WriteText("or underlined.")
self.rtc.EndUnderline()
self.rtc.BeginFontSize(14)
self.rtc.WriteText(" Different font sizes on the same line is allowed, too.")
self.rtc.EndFontSize()
self.rtc.WriteText(" Next we'll show an indented paragraph.")
self.rtc.BeginLeftIndent(60)
self.rtc.Newline()
self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
"An attractive woman came into the cafe, which is nothing remarkable.")
self.rtc.EndLeftIndent()
self.rtc.Newline()
self.rtc.WriteText("Next, we'll show a first-line indent, achieved using BeginLeftIndent(100, -40).")
self.rtc.BeginLeftIndent(100, -40)
self.rtc.Newline()
self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
"An attractive woman came into the cafe, which is nothing remarkable.")
self.rtc.EndLeftIndent()
self.rtc.Newline()
self.rtc.WriteText("Numbered bullets are possible, again using sub-indents:")
self.rtc.BeginNumberedBullet(1, 100, 60)
self.rtc.Newline()
self.rtc.WriteText("This is my first item. Note that wxRichTextCtrl doesn't automatically do numbering, " \
"but this will be added later.")
self.rtc.EndNumberedBullet()
self.rtc.BeginNumberedBullet(2, 100, 60)
self.rtc.Newline()
self.rtc.WriteText("This is my second item.")
self.rtc.EndNumberedBullet()
self.rtc.Newline()
self.rtc.WriteText("The following paragraph is right-indented:")
self.rtc.BeginRightIndent(200)
self.rtc.Newline()
self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
"An attractive woman came into the cafe, which is nothing remarkable.")
self.rtc.EndRightIndent()
self.rtc.Newline()
self.rtc.WriteText("The following paragraph is right-aligned with 1.5 line spacing:")
self.rtc.BeginAlignment(rt.TEXT_ALIGNMENT_RIGHT)
self.rtc.BeginLineSpacing(rt.TEXT_ATTR_LINE_SPACING_HALF)
self.rtc.Newline()
self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
"An attractive woman came into the cafe, which is nothing remarkable.")
self.rtc.EndLineSpacing()
self.rtc.EndAlignment()
self.rtc.Newline()
self.rtc.WriteText("Other notable features of wxRichTextCtrl include:")
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
self.rtc.WriteText("Compatibility with wxTextCtrl API")
self.rtc.EndSymbolBullet()
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
self.rtc.WriteText("Easy stack-based BeginXXX()...EndXXX() style setting in addition to SetStyle()")
self.rtc.EndSymbolBullet()
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
self.rtc.WriteText("XML loading and saving")
self.rtc.EndSymbolBullet()
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
self.rtc.WriteText("Undo/Redo, with batching option and Undo suppressing")
self.rtc.EndSymbolBullet()
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
self.rtc.WriteText("Clipboard copy and paste")
self.rtc.EndSymbolBullet()
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
self.rtc.WriteText("wxRichTextStyleSheet with named character and paragraph styles, and control for " \
"applying named styles")
self.rtc.EndSymbolBullet()
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
self.rtc.WriteText("A design that can easily be extended to other content types, ultimately with text " \
"boxes, tables, controls, and so on")
self.rtc.EndSymbolBullet()
self.rtc.BeginSymbolBullet('*', 100, 60)
self.rtc.Newline()
# Make a style suitable for showing a URL
urlStyle = rt.TextAttrEx()
urlStyle.SetTextColour(wx.BLUE)
urlStyle.SetFontUnderlined(True)
self.rtc.WriteText("RichTextCtrl can also display URLs, such as this one: ")
self.rtc.BeginStyle(urlStyle)
self.rtc.BeginURL("http://wxPython.org/")
self.rtc.WriteText("The wxPython Web Site")
self.rtc.EndURL();
self.rtc.EndStyle();
self.rtc.WriteText(". Click on the URL to generate an event.")
self.rtc.Bind(wx.EVT_TEXT_URL, self.OnURL)
self.rtc.Newline()
self.rtc.WriteText("Note: this sample content was generated programmatically from within the " \
"MyFrame constructor " \
"in the demo. The images were loaded from inline XPMs. Enjoy wxRichTextCtrl!")
self.rtc.EndParagraphSpacing()
self.rtc.EndSuppressUndo()
self.rtc.Thaw()
def OnURL(self, evt):
wx.MessageBox(evt.GetString(), "URL Clicked")
def OnFileOpen(self, evt):
# This gives us a string suitable for the file dialog based on
# the file handlers that are loaded
wildcard, types = rt.RichTextBuffer.GetExtWildcard(save=False)
dlg = wx.FileDialog(self, "Choose a filename",
wildcard=wildcard,
style=wx.OPEN)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
if path:
fileType = types[dlg.GetFilterIndex()]
self.rtc.LoadFile(path, fileType)
dlg.Destroy()
def OnFileSave(self, evt):
if not self.rtc.GetFilename():
self.OnFileSaveAs(evt)
return
self.rtc.SaveFile()
def OnFileSaveAs(self, evt):
wildcard, types = rt.RichTextBuffer.GetExtWildcard(save=True)
dlg = wx.FileDialog(self, "Choose a filename",
wildcard=wildcard,
style=wx.SAVE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
if path:
fileType = types[dlg.GetFilterIndex()]
ext = rt.RichTextBuffer.FindHandlerByType(fileType).GetExtension()
if not path.endswith(ext):
path += '.' + ext
self.rtc.SaveFile(path, fileType)
dlg.Destroy()
def OnFileViewHTML(self, evt):
# Get an instance of the html file handler, use it to save the
# document to a StringIO stream, and then display the
# resulting html text in a dialog with a HtmlWindow.
handler = rt.RichTextHTMLHandler()
handler.SetFlags(rt.RICHTEXT_HANDLER_SAVE_IMAGES_TO_MEMORY)
handler.SetFontSizeMapping([7,9,11,12,14,22,100])
import cStringIO
stream = cStringIO.StringIO()
if not handler.SaveStream(self.rtc.GetBuffer(), stream):
return
import wx.html
dlg = wx.Dialog(self, title="HTML", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
html = wx.html.HtmlWindow(dlg, size=(500,400), style=wx.BORDER_SUNKEN)
html.SetPage(stream.getvalue())
btn = wx.Button(dlg, wx.ID_CANCEL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(html, 1, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 10)
dlg.SetSizer(sizer)
sizer.Fit(dlg)
dlg.ShowModal()
handler.DeleteTemporaryImages()
def OnFileExit(self, evt):
self.Close(True)
def OnBold(self, evt):
self.rtc.ApplyBoldToSelection()
def OnItalic(self, evt):
self.rtc.ApplyItalicToSelection()
def OnUnderline(self, evt):
self.rtc.ApplyUnderlineToSelection()
def OnAlignLeft(self, evt):
self.rtc.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_LEFT)
def OnAlignRight(self, evt):
self.rtc.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_RIGHT)
def OnAlignCenter(self, evt):
self.rtc.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_CENTRE)
def OnIndentMore(self, evt):
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
ip = self.rtc.GetInsertionPoint()
if self.rtc.GetStyle(ip, attr):
r = rt.RichTextRange(ip, ip)
if self.rtc.HasSelection():
r = self.rtc.GetSelectionRange()
attr.SetLeftIndent(attr.GetLeftIndent() + 100)
attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
self.rtc.SetStyle(r, attr)
def OnIndentLess(self, evt):
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
ip = self.rtc.GetInsertionPoint()
if self.rtc.GetStyle(ip, attr):
r = rt.RichTextRange(ip, ip)
if self.rtc.HasSelection():
r = self.rtc.GetSelectionRange()
if attr.GetLeftIndent() >= 100:
attr.SetLeftIndent(attr.GetLeftIndent() - 100)
attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
self.rtc.SetStyle(r, attr)
def OnParagraphSpacingMore(self, evt):
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
ip = self.rtc.GetInsertionPoint()
if self.rtc.GetStyle(ip, attr):
r = rt.RichTextRange(ip, ip)
if self.rtc.HasSelection():
r = self.rtc.GetSelectionRange()
attr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter() + 20);
attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
self.rtc.SetStyle(r, attr)
def OnParagraphSpacingLess(self, evt):
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
ip = self.rtc.GetInsertionPoint()
if self.rtc.GetStyle(ip, attr):
r = rt.RichTextRange(ip, ip)
if self.rtc.HasSelection():
r = self.rtc.GetSelectionRange()
if attr.GetParagraphSpacingAfter() >= 20:
attr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter() - 20);
attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
self.rtc.SetStyle(r, attr)
def OnLineSpacingSingle(self, evt):
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
ip = self.rtc.GetInsertionPoint()
if self.rtc.GetStyle(ip, attr):
r = rt.RichTextRange(ip, ip)
if self.rtc.HasSelection():
r = self.rtc.GetSelectionRange()
attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
attr.SetLineSpacing(10)
self.rtc.SetStyle(r, attr)
def OnLineSpacingHalf(self, evt):
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
ip = self.rtc.GetInsertionPoint()
if self.rtc.GetStyle(ip, attr):
r = rt.RichTextRange(ip, ip)
if self.rtc.HasSelection():
r = self.rtc.GetSelectionRange()
attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
attr.SetLineSpacing(15)
self.rtc.SetStyle(r, attr)
def OnLineSpacingDouble(self, evt):
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
ip = self.rtc.GetInsertionPoint()
if self.rtc.GetStyle(ip, attr):
r = rt.RichTextRange(ip, ip)
if self.rtc.HasSelection():
r = self.rtc.GetSelectionRange()
attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
attr.SetLineSpacing(20)
self.rtc.SetStyle(r, attr)
def OnFont(self, evt):
if not self.rtc.HasSelection():
return
r = self.rtc.GetSelectionRange()
fontData = wx.FontData()
fontData.EnableEffects(False)
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_FONT)
if self.rtc.GetStyle(self.rtc.GetInsertionPoint(), attr):
fontData.SetInitialFont(attr.GetFont())
dlg = wx.FontDialog(self, fontData)
if dlg.ShowModal() == wx.ID_OK:
fontData = dlg.GetFontData()
font = fontData.GetChosenFont()
if font:
attr.SetFlags(rt.TEXT_ATTR_FONT)
attr.SetFont(font)
self.rtc.SetStyle(r, attr)
dlg.Destroy()
def OnColour(self, evt):
colourData = wx.ColourData()
attr = rt.TextAttrEx()
attr.SetFlags(rt.TEXT_ATTR_TEXT_COLOUR)
if self.rtc.GetStyle(self.rtc.GetInsertionPoint(), attr):
colourData.SetColour(attr.GetTextColour())
dlg = wx.ColourDialog(self, colourData)
if dlg.ShowModal() == wx.ID_OK:
colourData = dlg.GetColourData()
colour = colourData.GetColour()
if colour:
if not self.rtc.HasSelection():
self.rtc.BeginTextColour(colour)
else:
r = self.rtc.GetSelectionRange()
attr.SetFlags(rt.TEXT_ATTR_TEXT_COLOUR)
attr.SetTextColour(colour)
self.rtc.SetStyle(r, attr)
dlg.Destroy()
def OnUpdateBold(self, evt):
evt.Check(self.rtc.IsSelectionBold())
def OnUpdateItalic(self, evt):
evt.Check(self.rtc.IsSelectionItalics())
def OnUpdateUnderline(self, evt):
evt.Check(self.rtc.IsSelectionUnderlined())
def OnUpdateAlignLeft(self, evt):
evt.Check(self.rtc.IsSelectionAligned(rt.TEXT_ALIGNMENT_LEFT))
def OnUpdateAlignCenter(self, evt):
evt.Check(self.rtc.IsSelectionAligned(rt.TEXT_ALIGNMENT_CENTRE))
def OnUpdateAlignRight(self, evt):
evt.Check(self.rtc.IsSelectionAligned(rt.TEXT_ALIGNMENT_RIGHT))
def ForwardEvent(self, evt):
# The RichTextCtrl can handle menu and update events for undo,
# redo, cut, copy, paste, delete, and select all, so just
# forward the event to it.
self.rtc.ProcessEvent(evt)
def MakeMenuBar(self):
def doBind(item, handler, updateUI=None):
self.Bind(wx.EVT_MENU, handler, item)
if updateUI is not None:
self.Bind(wx.EVT_UPDATE_UI, updateUI, item)
fileMenu = wx.Menu()
doBind( fileMenu.Append(-1, "&Open\tCtrl+O", "Open a file"),
self.OnFileOpen )
doBind( fileMenu.Append(-1, "&Save\tCtrl+S", "Save a file"),
self.OnFileSave )
doBind( fileMenu.Append(-1, "&Save As...\tF12", "Save to a new file"),
self.OnFileSaveAs )
fileMenu.AppendSeparator()
doBind( fileMenu.Append(-1, "&View as HTML", "View HTML"),
self.OnFileViewHTML)
fileMenu.AppendSeparator()
doBind( fileMenu.Append(-1, "E&xit\tCtrl+Q", "Quit this program"),
self.OnFileExit )
editMenu = wx.Menu()
doBind( editMenu.Append(wx.ID_UNDO, "&Undo\tCtrl+Z"),
self.ForwardEvent, self.ForwardEvent)
doBind( editMenu.Append(wx.ID_REDO, "&Redo\tCtrl+Y"),
self.ForwardEvent, self.ForwardEvent )
editMenu.AppendSeparator()
doBind( editMenu.Append(wx.ID_CUT, "Cu&t\tCtrl+X"),
self.ForwardEvent, self.ForwardEvent )
doBind( editMenu.Append(wx.ID_COPY, "&Copy\tCtrl+C"),
self.ForwardEvent, self.ForwardEvent)
doBind( editMenu.Append(wx.ID_PASTE, "&Paste\tCtrl+V"),
self.ForwardEvent, self.ForwardEvent)
doBind( editMenu.Append(wx.ID_CLEAR, "&Delete\tDel"),
self.ForwardEvent, self.ForwardEvent)
editMenu.AppendSeparator()
doBind( editMenu.Append(wx.ID_SELECTALL, "Select A&ll\tCtrl+A"),
self.ForwardEvent, self.ForwardEvent )
formatMenu = wx.Menu()
doBind( formatMenu.AppendCheckItem(-1, "&Bold\tCtrl+B"),
self.OnBold, self.OnUpdateBold)
doBind( formatMenu.AppendCheckItem(-1, "&Italic\tCtrl+I"),
self.OnItalic, self.OnUpdateItalic)
doBind( formatMenu.AppendCheckItem(-1, "&Underline\tCtrl+U"),
self.OnUnderline, self.OnUpdateUnderline)
formatMenu.AppendSeparator()
doBind( formatMenu.AppendCheckItem(-1, "L&eft Align"),
self.OnAlignLeft, self.OnUpdateAlignLeft)
doBind( formatMenu.AppendCheckItem(-1, "&Centre"),
self.OnAlignCenter, self.OnUpdateAlignCenter)
doBind( formatMenu.AppendCheckItem(-1, "&Right Align"),
self.OnAlignRight, self.OnUpdateAlignRight)
formatMenu.AppendSeparator()
doBind( formatMenu.Append(-1, "Indent &More"), self.OnIndentMore)
doBind( formatMenu.Append(-1, "Indent &Less"), self.OnIndentLess)
formatMenu.AppendSeparator()
doBind( formatMenu.Append(-1, "Increase Paragraph &Spacing"), self.OnParagraphSpacingMore)
doBind( formatMenu.Append(-1, "Decrease &Paragraph Spacing"), self.OnParagraphSpacingLess)
formatMenu.AppendSeparator()
doBind( formatMenu.Append(-1, "Normal Line Spacing"), self.OnLineSpacingSingle)
doBind( formatMenu.Append(-1, "1.5 Line Spacing"), self.OnLineSpacingHalf)
doBind( formatMenu.Append(-1, "Double Line Spacing"), self.OnLineSpacingDouble)
formatMenu.AppendSeparator()
doBind( formatMenu.Append(-1, "&Font..."), self.OnFont)
mb = wx.MenuBar()
mb.Append(fileMenu, "&File")
mb.Append(editMenu, "&Edit")
mb.Append(formatMenu, "F&ormat")
self.SetMenuBar(mb)
def MakeToolBar(self):
def doBind(item, handler, updateUI=None):
self.Bind(wx.EVT_TOOL, handler, item)
if updateUI is not None:
self.Bind(wx.EVT_UPDATE_UI, updateUI, item)
tbar = self.CreateToolBar()
doBind( tbar.AddTool(-1, images._rt_open.GetBitmap(),
shortHelpString="Open"), self.OnFileOpen)
doBind( tbar.AddTool(-1, images._rt_save.GetBitmap(),
shortHelpString="Save"), self.OnFileSave)
tbar.AddSeparator()
doBind( tbar.AddTool(wx.ID_CUT, images._rt_cut.GetBitmap(),
shortHelpString="Cut"), self.ForwardEvent, self.ForwardEvent)
doBind( tbar.AddTool(wx.ID_COPY, images._rt_copy.GetBitmap(),
shortHelpString="Copy"), self.ForwardEvent, self.ForwardEvent)
doBind( tbar.AddTool(wx.ID_PASTE, images._rt_paste.GetBitmap(),
shortHelpString="Paste"), self.ForwardEvent, self.ForwardEvent)
tbar.AddSeparator()
doBind( tbar.AddTool(wx.ID_UNDO, images._rt_undo.GetBitmap(),
shortHelpString="Undo"), self.ForwardEvent, self.ForwardEvent)
doBind( tbar.AddTool(wx.ID_REDO, images._rt_redo.GetBitmap(),
shortHelpString="Redo"), self.ForwardEvent, self.ForwardEvent)
tbar.AddSeparator()
doBind( tbar.AddTool(-1, images._rt_bold.GetBitmap(), isToggle=True,
shortHelpString="Bold"), self.OnBold, self.OnUpdateBold)
doBind( tbar.AddTool(-1, images._rt_italic.GetBitmap(), isToggle=True,
shortHelpString="Italic"), self.OnItalic, self.OnUpdateItalic)
doBind( tbar.AddTool(-1, images._rt_underline.GetBitmap(), isToggle=True,
shortHelpString="Underline"), self.OnUnderline, self.OnUpdateUnderline)
tbar.AddSeparator()
doBind( tbar.AddTool(-1, images._rt_alignleft.GetBitmap(), isToggle=True,
shortHelpString="Align Left"), self.OnAlignLeft, self.OnUpdateAlignLeft)
doBind( tbar.AddTool(-1, images._rt_centre.GetBitmap(), isToggle=True,
shortHelpString="Center"), self.OnAlignCenter, self.OnUpdateAlignCenter)
doBind( tbar.AddTool(-1, images._rt_alignright.GetBitmap(), isToggle=True,
shortHelpString="Align Right"), self.OnAlignRight, self.OnUpdateAlignRight)
tbar.AddSeparator()
doBind( tbar.AddTool(-1, images._rt_indentless.GetBitmap(),
shortHelpString="Indent Less"), self.OnIndentLess)
doBind( tbar.AddTool(-1, images._rt_indentmore.GetBitmap(),
shortHelpString="Indent More"), self.OnIndentMore)
tbar.AddSeparator()
doBind( tbar.AddTool(-1, images._rt_font.GetBitmap(),
shortHelpString="Font"), self.OnFont)
doBind( tbar.AddTool(-1, images._rt_colour.GetBitmap(),
shortHelpString="Font Colour"), self.OnColour)
tbar.Realize()
#----------------------------------------------------------------------
class TestPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
b = wx.Button(self, -1, "Show the RichTextCtrl sample", (50,50))
self.Bind(wx.EVT_BUTTON, self.OnButton, b)
self.AddRTCHandlers()
def AddRTCHandlers(self):
# make sure we haven't already added them.
if rt.RichTextBuffer.FindHandlerByType(rt.RICHTEXT_TYPE_HTML) is not None:
return
# This would normally go in your app's OnInit method. I'm
# not sure why these file handlers are not loaded by
# default by the C++ richtext code, I guess it's so you
# can change the name or extension if you wanted...
rt.RichTextBuffer.AddHandler(rt.RichTextHTMLHandler())
rt.RichTextBuffer.AddHandler(rt.RichTextXMLHandler())
# ...like this
rt.RichTextBuffer.AddHandler(rt.RichTextXMLHandler(name="Other XML",
ext="ox",
type=99))
# This is needed for the view as HTML option since we tell it
# to store the images in the memory file system.
wx.FileSystem.AddHandler(wx.MemoryFSHandler())
def OnButton(self, evt):
win = RichTextFrame(self, -1, "wx.richtext.RichTextCtrl",
size=(700, 500),
style = wx.DEFAULT_FRAME_STYLE)
win.Show(True)
# give easy access to the demo's PyShell if it's running
self.rtfrm = win
self.rtc = win.rtc
app = wx.App(0)
frame = wx.Frame(None)
panel = TestPanel(frame)
frame.Show()
app.MainLoop()
Styling attributes are represented by wx.TextAttr
, or for more
control over attributes such as margins and size, the derived class
RichTextAttr
.
When setting a style, the flags of the attribute object determine which attributes are applied. When querying a style, the passed flags are ignored except (optionally) to determine whether attributes should be retrieved from character content or from the paragraph object.
RichTextCtrl
takes a layered approach to styles, so that
different parts of the content may be responsible for contributing
different attributes to the final style you see on the screen.
There are four main notions of style within a control:
Basic style: The fundamental style of a control, onto which any
other styles are layered. It provides default attributes, and
changing the basic style may immediately change the look of the
content depending on what other styles the content uses. Calling
SetFont
changes the font for the
basic style. The basic style is set with
SetBasicStyle
.
Paragraph style: Each paragraph has attributes that are set
independently from other paragraphs and independently from the
content within the paragraph. Normally, these attributes are
paragraph- related, such as alignment and indentation, but it is
possible to set character attributes too. The paragraph style can be
set independently of its content by passing
RICHTEXT_SETSTYLE_PARAGRAPHS_ONLY
to
SetStyleEx
.
Character style: Characters within each paragraph can have
attributes. A single character, or a run of characters, can have a
particular set of attributes. The character style can be with
SetStyle
or
SetStyleEx
.
Default style: This is the ‘current’ style that determines the
style of content that is subsequently typed, pasted or
programmatically inserted. The default style is set with
SetDefaultStyle
.
What you see on the screen is the dynamically combined style, found by merging the first three of the above style types (the fourth is only a guide for future content insertion and therefore does not affect the currently displayed content).
To make all this more concrete, here are examples of where you might set these different styles:
You might set the basic style to have a Times Roman font in 12 point, left-aligned, with two millimetres of spacing after each paragraph.
You might set the paragraph style (for one particular paragraph) to be centred.
You might set the character style of one particular word to bold.
You might set the default style to be underlined, for subsequent inserted text.
Naturally you can do any of these things either using your own UI, or programmatically.
The basic wx.TextCtrl
doesn’t make the same distinctions as
RichTextCtrl
regarding attribute storage. So we need finer
control when setting and retrieving attributes.
SetStyleEx
takes a flags parameter:
RICHTEXT_SETSTYLE_OPTIMIZE
specifies that the style should be
changed only if the combined attributes are different from the
attributes for the current object. This is important when applying
styling that has been edited by the user, because he has just edited
the combined (visible) style, and RichTextCtrl
wants to leave
unchanged attributes associated with their original objects instead of
applying them to both paragraph and content objects.
RICHTEXT_SETSTYLE_PARAGRAPHS_ONLY
specifies that only paragraph
objects within the given range should take on the attributes.
RICHTEXT_SETSTYLE_CHARACTERS_ONLY
specifies that only content
objects (text or images) within the given range should take on the
attributes.
RICHTEXT_SETSTYLE_WITH_UNDO
specifies that the operation should be
undoable.
It’s great to be able to change arbitrary attributes in a
RichTextCtrl
, but it can be unwieldy for the
user or programmer to set attributes separately. Word processors have
collections of styles that you can tailor or use as-is, and this means
that you can set a heading with one click instead of marking text in
bold, specifying a large font size, and applying a certain paragraph
spacing and alignment for every such heading. Similarly, wxPython
provides a class called RichTextStyleSheet
which
manages style definitions
(RichTextParagraphStyleDefinition
,
RichTextListStyleDefinition
and
RichTextCharacterStyleDefinition
). Once you have
added definitions to a style sheet and associated it with a
RichTextCtrl
, you can apply a named definition
to a range of text. The classes
RichTextStyleComboCtrl
and
RichTextStyleListBox
can be used to present the
user with a list of styles in a sheet, and apply them to the selected
text.
You can reapply a style sheet to the contents of the control, by
calling ApplyStyleSheet
. This is
useful if the style definitions have changed, and you want the content
to reflect this. It relies on the fact that when you apply a named
style, the style definition name is recorded in the content. So
ApplyStyleSheet works by finding the paragraph attributes with style
names and re- applying the definition’s attributes to the
paragraph. Currently, this works with paragraph and list style
definitions only.
RichTextCtrl
comes with standard dialogs to make
it easier to implement text editing functionality.
RichTextFormattingDialog
can be used for
character or paragraph formatting, or a combination of both. It’s a
PropertySheetDialog with the following available tabs: Font, Indents
& Spacing, Tabs, Bullets, Style, Borders, Margins, Background, Size,
and List Style. You can select which pages will be shown by supplying
flags to the dialog constructor. In a character formatting dialog,
typically only the Font page will be shown. In a paragraph formatting
dialog, you’ll show the Indents & Spacing, Tabs and Bullets pages. The
Style tab is useful when editing a style definition.
You can customize this dialog by providing your own
RichTextFormattingDialogFactory
object, which
tells the formatting dialog how many pages are supported, what their
identifiers are, and how to creates the pages.
RichTextStyleOrganiserDialog
is a multi-purpose
dialog that can be used for managing style definitions, browsing
styles and applying them, or selecting list styles with a renumber
option. See the sample for usage - it is used for the “Manage Styles”
and “Bullets and Numbering” menu commands.
SymbolPickerDialog
lets the user insert a symbol
from a specified font. It has no RichTextCtrl
dependencies besides being included in the rich text library.
Data representation is handled by
RichTextBuffer
, and a
RichTextCtrl
always has one such buffer.
The content is represented by a hierarchy of objects, all derived from
RichTextObject
. An object might be an image, a
fragment of text, a paragraph, or a further composite object. Objects
store a RichTextAttr
containing style
information; a paragraph object can contain both paragraph and
character information, but content objects such as text can only store
character information. The final style displayed in the control or in
a printout is a combination of base style, paragraph style and content
(character) style.
The top of the hierarchy is the buffer, a kind of
RichTextParagraphLayoutBox
, containing further
RichTextParagraph
objects, each of which can
include text, images and potentially other types of object.
Each object maintains a range (start and end position) measured from the start of the main parent object.
When Layout is called on an object, it is given a size which the object must limit itself to, or one or more flexible directions (vertical or horizontal). So, for example, a centred paragraph is given the page width to play with (minus any margins), but can extend indefinitely in the vertical direction. The implementation of Layout caches the calculated size and position.
When the buffer is modified, a range is invalidated (marked as requiring layout), so that only the minimum amount of layout is performed.
A paragraph of pure text with the same style contains just one further
object, a RichTextPlainText
object. When styling
is applied to part of this object, the object is decomposed into
separate objects, one object for each different character style. So
each object within a paragraph always has just one wx.TextAttr
object to denote its character style. Of course, this can lead to
fragmentation after a lot of edit operations, potentially leading to
several objects with the same style where just one would do. So a
Defragment function is called when updating the control’s display, to
ensure that the minimum number of objects is used.
RichTextCtrl
supports nested objects such as
text boxes and tables. To achieve compatibility with the existing API,
there is the concept of object focus. When the user clicks on a
nested text box, the object focus is set to that container object so
all keyboard input and API functions apply to that container. The
application can change the focus using
SetObjectFocus
. Call this function
with a None
parameter to set the focus back to the top-level
object.
An event will be sent to the control when the focus changes.
When the user clicks on the control,
RichTextCtrl
determines which container to set
as the current object focus by calling the found container’s overridden
AcceptsFocus
function. For
example, although a table is a container, it must not itself be the
object focus because there is no text editing at the table
level. Instead, a cell within the table must accept the focus.
Since with nested objects it is not possible to represent a section
with merely a start position and an end position, the class
RichTextSelection
is provided which stores
multiple ranges (for non-contiguous selections such as table cells)
and a pointer to the container object in question. You can pass
RichTextSelection
to
SetSelection
or get an instance of
it from GetSelection
.
When selecting multiple objects, such as cell tables, the
RichTextCtrl
dragging handler code calls the
function HandlesChildSelections
to
determine whether the children can be individual selections. Currently
only table cells can be multiply-selected in this way.
This is an incomplete list of bugs.
Moving the caret up at the beginning of a line sometimes incorrectly positions the caret.
As the selection is expanded, the text jumps slightly due to kerning
differences between drawing a single text string versus drawing
several fragments separately. This could be improved by using
wx.DC.GetPartialTextExtents
to calculate exactly where the separate
fragments should be drawn. Note that this problem also applies to
separation of text fragments due to difference in their attributes.
This is a list of some of the features that have yet to be implemented. Help with them will be appreciated.
Support for composite objects in some functions where it’s not yet implemented, for example ApplyStyleSheet
Table API enhancements and dialogs; improved table layout especially row spans and fitting
Conversion from HTML, and a rewrite of the HTML output handler that includes CSS, tables, text boxes, and floating images, in addition to a simplified-HTML mode for wxHTML compatibility
Open Office input and output
RTF input and output
A ruler control
Standard editing toolbars
Bitmap bullets
Justified text, in print/preview at least
Scaling: either everything scaled, or rendering using a custom reference point size and an optional dimension scale
There are also things that could be done to take advantage of the
underlying text capabilities of the platform; higher-level text
formatting APIs are available on some platforms, such as Mac OS X, and
some of translation from high level to low level wx.DC
API is
unnecessary. However this would require additions to the wxPython API.