# Chapter 10: Creating Components and Extending Functionality # Recipe 6: StyledTextCtrl Custom Highlighting # import wx import wx.stc as st import itertools issl_table = ';+-?.#~' if wx.Platform == '__WXMSW__': # for windows OS faces = { 'times': 'Times New Roman', 'mono' : 'Courier New', # try temporary switch to mono 'helv' : 'Courier New', #'helv' : 'Arial', 'other': 'Comic Sans MS', 'size' : 10, 'size2': 8, } else: faces = { 'times': 'Times', 'mono' : 'Courier', 'helv' : 'Helvetica', 'other': 'new century schoolbook', 'size' : 12, 'size2': 10, } class MyMiniFrame(wx.MiniFrame): def __init__( self, parent, title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE ): wx.MiniFrame.__init__(self, parent, -1, title, pos, size, style) panel = wx.Panel(self, -1) button = wx.Button(panel, -1, "Close Me") button.SetPosition((15, 15)) self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) def OnCloseMe(self, event): self.Close(True) def OnCloseWindow(self, event): print "OnCloseWindow" self.Destroy() class VowelLexer(object): """Simple lexer to highlight vowels""" # Define some style IDs STC_STYLE_VOWEL_DEFAULT, \ STC_STYLE_VOWEL_KW = range(2) def __init__(self, lang): self.lang = lang def StyleText(self, event): """Handle the EVT_STC_STYLENEEDED event""" stc = event.GetEventObject() # Last correctly styled character last_styled_pos = stc.GetEndStyled() # Get styling range for this call line = stc.LineFromPosition(last_styled_pos) start_pos = stc.PositionFromLine(line) end_pos = event.GetPosition() word = '' pos = start_pos def displaystyle(lang, stcvar, stvar): # set other options ... stcvar.SetProperty("fold", "1") stcvar.SetMargins(0, 0) stcvar.SetViewWhiteSpace(False) stcvar.SetEdgeMode(stvar.STC_EDGE_BACKGROUND) stcvar.SetEdgeColumn(78) stcvar.SetCaretForeground("blue") # setup a margin to hold the fold markers stcvar.SetMarginType(2, stvar.STC_MARGIN_SYMBOL) stcvar.SetMarginMask(2, stvar.STC_MASK_FOLDERS) stcvar.SetMarginSensitive(2, True) stcvar.SetMarginWidth(2, 12) stcvar.SetMarginType(1, stvar.STC_MARGIN_NUMBER) stcvar.SetMarginWidth(1, 30) # fold markers use square headers stcvar.MarkerDefine(stvar.STC_MARKNUM_FOLDEROPEN, st.STC_MARK_BOXMINUS, "white", "#808080") stcvar.MarkerDefine(stvar.STC_MARKNUM_FOLDER, st.STC_MARK_BOXPLUS, "white", "#808080") stcvar.MarkerDefine(stvar.STC_MARKNUM_FOLDERSUB, st.STC_MARK_VLINE, "white", "#808080") stcvar.MarkerDefine(stvar.STC_MARKNUM_FOLDERTAIL, st.STC_MARK_LCORNER, "white", "#808080") stcvar.MarkerDefine(stvar.STC_MARKNUM_FOLDEREND, st.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") stcvar.MarkerDefine(stvar.STC_MARKNUM_FOLDEROPENMID, st.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") stcvar.MarkerDefine(stvar.STC_MARKNUM_FOLDERMIDTAIL, st.STC_MARK_TCORNER, "white", "#808080") # make some general styles ... # global default styles for all languages # set default font stcvar.StyleSetSpec(stvar.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces) # set default background color beige = '#F5F5DC' stcvar.StyleSetBackground(style=stvar.STC_STYLE_DEFAULT, back=beige) # reset all to be like the default stcvar.StyleClearAll() # more global default styles for all languages stcvar.StyleSetSpec(stvar.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces) stcvar.StyleSetSpec(stvar.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces) stcvar.StyleSetSpec(stvar.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold") stcvar.StyleSetSpec(stvar.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold") stcvar.StyleSetSpec(stvar.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD") # make the Python styles ... # default if lang == 'python': # Python styles # Default stcvar.StyleSetSpec(stvar.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces) # Comments stcvar.StyleSetSpec(stvar.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces) # Number stcvar.StyleSetSpec(stvar.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces) # String stcvar.StyleSetSpec(stvar.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces) # Single quoted string stcvar.StyleSetSpec(stvar.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces) # Keyword stcvar.StyleSetSpec(stvar.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces) # Triple quotes stcvar.StyleSetSpec(stvar.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces) # Triple double quotes stcvar.StyleSetSpec(stvar.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces) # Class name definition stcvar.StyleSetSpec(stvar.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces) # Function or method name definition stcvar.StyleSetSpec(stvar.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces) # Operators stcvar.StyleSetSpec(stvar.STC_P_OPERATOR, "bold,size:%(size)d" % faces) # Identifiers stcvar.StyleSetSpec(stvar.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces) # Comment-blocks stcvar.StyleSetSpec(stvar.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces) # End of line where string is not closed stcvar.StyleSetSpec(stvar.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s, back:#E0C0E0,eol,size:%(size)d" % faces) if self.lang == 'python': pythonlist = [ 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield', ] stc.SetLexer(st.STC_LEX_PYTHON) stc.SetKeyWords(0, " ".join(pythonlist)) displaystyle(self.lang, stc, st) class MyPopupMenu(wx.Menu): def __init__(self, parent): super(MyPopupMenu, self).__init__() self.parent = parent mmi = wx.MenuItem(self, wx.NewId(), 'Remove') self.AppendItem(mmi) self.Bind(wx.EVT_MENU, self.OnMinimize, mmi) # cmi = wx.MenuItem(self, wx.NewId(), 'Close') # self.AppendItem(cmi) # self.Bind(wx.EVT_MENU, self.OnClose, cmi) def OnMinimize(self, e): StcFrame.panel.Autosizer.Remove(self) def OnClose(self, e): self.parent.Close() class STC(st.StyledTextCtrl): def __init__(self, *args, **kwargs): super(STC, self).__init__(*args, **kwargs) # def __init__(self, *args, **kwargs): # super(STC, self).__init__(*args, **kwargs) # Attributes self.custlex = None # Event Handlers self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyle) self.InitUI() def InitUI(self): self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.SetSize((250, 200)) # self.SetTitle('Context menu') self.Centre() self.Show(True) def OnRightDown(self, e): self.PopupMenu(MyPopupMenu(self), e.GetPosition()) def OnStyle(self, event): # Delegate to custom lexer object if one exists if self.custlex: self.custlex.StyleText(event) else: event.Skip() def SetLexer(self, lexerid, lexer=None): """Overrides StyledTextCtrl.SetLexer Adds optional param to pass in custom container lexer object. """ self.custlex = lexer super(STC, self).SetLexer(lexerid) class StyledTextApp(wx.App): def OnInit(self): self.frame = StcFrame(None, title="Custom Lexer") self.frame.Show() return True class TestPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, -1) # Attributes self.stc1 = STC(self) self.stc1.Text = """print 'this is calling python' def main(): pass print 'man' print 'good'""" self.stc1.SetUseHorizontalScrollBar(False) self.stc1.SetUseVerticalScrollBar(False) style = VowelLexer.STC_STYLE_VOWEL_DEFAULT self.stc1.StyleSetSpec(style, "fore:#000000") style = VowelLexer.STC_STYLE_VOWEL_KW self.stc1.StyleSetSpec(style, "fore:#FF0000,bold") self.stc1.SetLexer(st.STC_LEX_CONTAINER, VowelLexer('python')) # python sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.stc1, 0, wx.EXPAND) self.SetSizer(sizer) self.SetInitialSize((300, 300)) self.Autosizer = wx.FlexGridSizer(rows=1, cols=2, hgap = -190, vgap = -1) self.Autosizer.SetFlexibleDirection(wx.VERTICAL) labels = "P".split() for label in labels: t=wx.StaticText(self, label=label) if label == 'P': t.SetForegroundColour((255,0,255)) e=self.stc1 self.Autosizer.Add(t,0) self.Autosizer.Add(e,1, wx.EXPAND) self.Autosizer.AddGrowableCol(1, 1) # 2 col self.SetSizer(self.Autosizer) self.Fit() self.Bind(wx.EVT_SCROLLWIN, self.OnScroll1) self.Bind(wx.EVT_SCROLLWIN, self.OnScroll2) self.Bind(wx.EVT_SCROLLWIN, self.OnScroll3) def OnScroll2(self, event): clone = event.Clone() clone.SetId(wx.NewId()) clone.SetEventObject(self.stc1) self.stc1.GetEventHandler().ProcessEvent(clone) event.Skip() def OnScroll1(self, event): clone = event.Clone() clone.SetId(wx.NewId()) clone.SetEventObject(self.stc2) self.stc2.GetEventHandler().ProcessEvent(clone) event.Skip() def OnScroll3(self, event): clone = event.Clone() clone.SetId(wx.NewId()) clone.SetEventObject(self.stc3) self.stc3.GetEventHandler().ProcessEvent(clone) event.Skip() class StcFrame(wx.Frame): """Main application window""" def __init__(self, parent, *args, **kwargs): super(StcFrame, self).__init__(parent, *args, **kwargs) self.panel = TestPanel(self) b = wx.Button(self, 40, "Fortran", (300,200), style=wx.NO_BORDER) b.SetToolTipString("This button has a style flag of wx.NO_BORDER.\nOn some platforms that will give it a flattened look.") self.Bind(wx.EVT_BUTTON, self.OnClick, b) def OnClick(self, event): t=wx.StaticText(self, label='F') e= STC(self) e.Text = """""" e.SetUseHorizontalScrollBar(False) e.SetUseVerticalScrollBar(False) self.SetSizer(self.panel.Autosizer) self.panel.Layout() if __name__ == '__main__': app = StyledTextApp(False) app.MainLoop()