py_ext/PythonEditor.py
changeset 1062 fd7c9a7cf882
parent 1060 ac9896336b90
child 1091 5f612651d227
equal deleted inserted replaced
1053:b0ac30ba7eaf 1062:fd7c9a7cf882
       
     1 import re
       
     2 import keyword
       
     3 
     1 import wx
     4 import wx
     2 import wx.grid
     5 import wx.grid
     3 import wx.stc  as  stc
     6 import wx.stc as stc
     4 import keyword
     7 
     5 
     8 from plcopen.plcopen import TestTextElement
       
     9 from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
     6 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
    10 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
     7 
    11 from editors.TextViewer import GetCursorPos, faces
     8 if wx.Platform == '__WXMSW__':
    12 
     9     faces = { 'times': 'Times New Roman',
    13 [STC_PYTHON_ERROR, STC_PYTHON_SEARCH_RESULT] = range(15, 17)
    10               'mono' : 'Courier New',
    14 
    11               'helv' : 'Arial',
    15 HIGHLIGHT_TYPES = {
    12               'other': 'Comic Sans MS',
    16     ERROR_HIGHLIGHT: STC_PYTHON_ERROR,
    13               'size' : 10,
    17     SEARCH_RESULT_HIGHLIGHT: STC_PYTHON_SEARCH_RESULT,
    14               'size2': 8,
    18 }
    15              }
       
    16 elif wx.Platform == '__WXMAC__':
       
    17     faces = { 'times': 'Times New Roman',
       
    18               'mono' : 'Monaco',
       
    19               'helv' : 'Arial',
       
    20               'other': 'Comic Sans MS',
       
    21               'size' : 12,
       
    22               'size2': 10,
       
    23              }
       
    24 else:
       
    25     faces = { 'times': 'Times',
       
    26               'mono' : 'Courier',
       
    27               'helv' : 'Helvetica',
       
    28               'other': 'new century schoolbook',
       
    29               'size' : 12,
       
    30               'size2': 10,
       
    31              }
       
    32 
    19 
    33 [ID_PYTHONEDITOR,
    20 [ID_PYTHONEDITOR,
    34 ] = [wx.NewId() for _init_ctrls in range(1)]
    21 ] = [wx.NewId() for _init_ctrls in range(1)]
    35 
       
    36 def GetCursorPos(old, new):
       
    37     old_length = len(old)
       
    38     new_length = len(new)
       
    39     common_length = min(old_length, new_length)
       
    40     i = 0
       
    41     for i in xrange(common_length):
       
    42         if old[i] != new[i]:
       
    43             break
       
    44     if old_length < new_length:
       
    45         if common_length > 0 and old[i] != new[i]:
       
    46             return i + new_length - old_length
       
    47         else:
       
    48             return i + new_length - old_length + 1
       
    49     elif old_length > new_length or i < min(old_length, new_length) - 1:
       
    50         if common_length > 0 and old[i] != new[i]:
       
    51             return i
       
    52         else:
       
    53             return i + 1
       
    54     else:
       
    55         return None
       
    56 
    22 
    57 class PythonEditor(ConfTreeNodeEditor):
    23 class PythonEditor(ConfTreeNodeEditor):
    58 
    24 
    59     fold_symbols = 3
    25     fold_symbols = 3
    60     CONFNODEEDITOR_TABS = [
    26     CONFNODEEDITOR_TABS = [
   134 
   100 
   135 
   101 
   136         self.PythonCodeEditor.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
   102         self.PythonCodeEditor.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
   137         self.PythonCodeEditor.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
   103         self.PythonCodeEditor.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
   138         self.PythonCodeEditor.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
   104         self.PythonCodeEditor.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
   139 
   105         
   140         # Global default style
   106         # Global default style
   141         if wx.Platform == '__WXMSW__':
   107         if wx.Platform == '__WXMSW__':
   142             self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Courier New')
   108             self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Courier New')
   143         elif wx.Platform == '__WXMAC__':
   109         elif wx.Platform == '__WXMAC__':
   144             # TODO: if this looks fine on Linux too, remove the Mac-specific case 
   110             # TODO: if this looks fine on Linux too, remove the Mac-specific case 
   153 
   119 
   154         # Following style specs only indicate differences from default.
   120         # Following style specs only indicate differences from default.
   155         # The rest remains unchanged.
   121         # The rest remains unchanged.
   156 
   122 
   157         # Line numbers in margin
   123         # Line numbers in margin
   158         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2')    
   124         self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2,size:%(size)d' % faces)    
   159         # Highlighted brace
   125         # Highlighted brace
   160         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00')
   126         self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00,size:%(size)d' % faces)
   161         # Unmatched brace
   127         # Unmatched brace
   162         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000')
   128         self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000,size:%(size)d' % faces)
   163         # Indentation guide
   129         # Indentation guide
   164         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD")
   130         self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_INDENTGUIDE, 'fore:#CDCDCD,size:%(size)d' % faces)
   165 
   131 
   166         # Python styles
   132         # Python styles
   167         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000')
   133         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_DEFAULT, 'fore:#000000,size:%(size)d' % faces)
   168         # Comments
   134         # Comments
   169         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_COMMENTLINE,  'fore:#008000,back:#F0FFF0')
   135         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_COMMENTLINE,  'fore:#008000,back:#F0FFF0,size:%(size)d' % faces)
   170         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0')
   136         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0,size:%(size)d' % faces)
   171         # Numbers
   137         # Numbers
   172         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_NUMBER, 'fore:#008080')
   138         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_NUMBER, 'fore:#008080,size:%(size)d' % faces)
   173         # Strings and characters
   139         # Strings and characters
   174         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_STRING, 'fore:#800080')
   140         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_STRING, 'fore:#800080,size:%(size)d' % faces)
   175         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_CHARACTER, 'fore:#800080')
   141         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_CHARACTER, 'fore:#800080,size:%(size)d' % faces)
   176         # Keywords
   142         # Keywords
   177         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold')
   143         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_WORD, 'fore:#000080,bold,size:%(size)d' % faces)
   178         # Triple quotes
   144         # Triple quotes
   179         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA')
   145         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA,size:%(size)d' % faces)
   180         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA')
   146         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA,size:%(size)d' % faces)
   181         # Class names
   147         # Class names
   182         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_CLASSNAME, 'fore:#0000FF,bold')
   148         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_CLASSNAME, 'fore:#0000FF,bold,size:%(size)d' % faces)
   183         # Function names
   149         # Function names
   184         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_DEFNAME, 'fore:#008080,bold')
   150         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_DEFNAME, 'fore:#008080,bold,size:%(size)d' % faces)
   185         # Operators
   151         # Operators
   186         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_OPERATOR, 'fore:#800000,bold')
   152         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_OPERATOR, 'fore:#800000,bold,size:%(size)d' % faces)
   187         # Identifiers. I leave this as not bold because everything seems
   153         # Identifiers. I leave this as not bold because everything seems
   188         # to be an identifier if it doesn't match the above criterae
   154         # to be an identifier if it doesn't match the above criterae
   189         self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, 'fore:#000000')
   155         self.PythonCodeEditor.StyleSetSpec(stc.STC_P_IDENTIFIER, 'fore:#000000,size:%(size)d' % faces)
   190 
   156         
       
   157         # Highlighting styles
       
   158         self.PythonCodeEditor.StyleSetSpec(STC_PYTHON_ERROR, 'fore:#FF0000,back:#FFFF00,size:%(size)d' % faces)
       
   159         self.PythonCodeEditor.StyleSetSpec(STC_PYTHON_SEARCH_RESULT, 'fore:#FFFFFF,back:#FFA500,size:%(size)d' % faces)
       
   160         
   191         # Caret color
   161         # Caret color
   192         self.PythonCodeEditor.SetCaretForeground("BLUE")
   162         self.PythonCodeEditor.SetCaretForeground("BLUE")
   193         # Selection background
   163         # Selection background
   194         self.PythonCodeEditor.SetSelBackground(1, '#66CCFF')
   164         self.PythonCodeEditor.SetSelBackground(1, '#66CCFF')
   195 
   165 
   217         self.PythonCodeEditor.SetViewWhiteSpace(False)   # Don't view white space
   187         self.PythonCodeEditor.SetViewWhiteSpace(False)   # Don't view white space
   218 
   188 
   219         # EOL: Since we are loading/saving ourselves, and the
   189         # EOL: Since we are loading/saving ourselves, and the
   220         # strings will always have \n's in them, set the STC to
   190         # strings will always have \n's in them, set the STC to
   221         # edit them that way.            
   191         # edit them that way.            
   222         self.PythonCodeEditor.SetEOLMode(wx.stc.STC_EOL_LF)
   192         self.PythonCodeEditor.SetEOLMode(stc.STC_EOL_LF)
   223         self.PythonCodeEditor.SetViewEOL(False)
   193         self.PythonCodeEditor.SetViewEOL(False)
   224         
   194         
   225         # No right-edge mode indicator
   195         # No right-edge mode indicator
   226         self.PythonCodeEditor.SetEdgeMode(stc.STC_EDGE_NONE)
   196         self.PythonCodeEditor.SetEdgeMode(stc.STC_EDGE_NONE)
   227         
   197         
   228         self.PythonCodeEditor.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE)
   198         self.PythonCodeEditor.SetModEventMask(stc.STC_MOD_BEFOREINSERT|stc.STC_MOD_BEFOREDELETE)
   229 
   199 
   230         self.PythonCodeEditor.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_PYTHONEDITOR)
   200         self.PythonCodeEditor.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_PYTHONEDITOR)
   231         self.PythonCodeEditor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
   201         self.PythonCodeEditor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
   232         self.PythonCodeEditor.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_PYTHONEDITOR)
   202         self.PythonCodeEditor.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_PYTHONEDITOR)
   233         
   203         
   236     def __init__(self, parent, controler, window):
   206     def __init__(self, parent, controler, window):
   237         ConfTreeNodeEditor.__init__(self, parent, controler, window)
   207         ConfTreeNodeEditor.__init__(self, parent, controler, window)
   238         
   208         
   239         self.DisableEvents = False
   209         self.DisableEvents = False
   240         self.CurrentAction = None
   210         self.CurrentAction = None
       
   211     
       
   212         self.Highlights = []
       
   213         self.SearchParams = None
       
   214         self.SearchResults = None
       
   215         self.CurrentFindHighlight = None
       
   216         
       
   217         self.RefreshHighlightsTimer = wx.Timer(self, -1)
       
   218         self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
   241     
   219     
   242     def GetBufferState(self):
   220     def GetBufferState(self):
   243         return self.Controler.GetBufferState()
   221         return self.Controler.GetBufferState()
   244         
   222         
   245     def Undo(self):
   223     def Undo(self):
   303         ConfTreeNodeEditor.RefreshView(self)
   281         ConfTreeNodeEditor.RefreshView(self)
   304         
   282         
   305         self.ResetBuffer()
   283         self.ResetBuffer()
   306         self.DisableEvents = True
   284         self.DisableEvents = True
   307         old_cursor_pos = self.PythonCodeEditor.GetCurrentPos()
   285         old_cursor_pos = self.PythonCodeEditor.GetCurrentPos()
       
   286         line = self.PythonCodeEditor.GetFirstVisibleLine()
       
   287         column = self.PythonCodeEditor.GetXOffset()
   308         old_text = self.PythonCodeEditor.GetText()
   288         old_text = self.PythonCodeEditor.GetText()
   309         new_text = self.Controler.GetPythonCode()
   289         new_text = self.Controler.GetPythonCode()
   310         self.PythonCodeEditor.SetText(new_text)
   290         if old_text != new_text:
   311         new_cursor_pos = GetCursorPos(old_text, new_text)
   291             self.PythonCodeEditor.SetText(new_text)
   312         if new_cursor_pos != None:
   292             new_cursor_pos = GetCursorPos(old_text, new_text)
   313             self.PythonCodeEditor.GotoPos(new_cursor_pos)
   293             self.PythonCodeEditor.LineScroll(column, line)
   314         else:
   294             if new_cursor_pos != None:
   315             self.PythonCodeEditor.GotoPos(old_cursor_pos)
   295                 self.PythonCodeEditor.GotoPos(new_cursor_pos)
   316         self.PythonCodeEditor.ScrollToColumn(0)
   296             else:
   317         self.PythonCodeEditor.EmptyUndoBuffer()
   297                 self.PythonCodeEditor.GotoPos(old_cursor_pos)
       
   298             self.PythonCodeEditor.EmptyUndoBuffer()
   318         self.DisableEvents = False
   299         self.DisableEvents = False
   319         
   300         
   320         self.PythonCodeEditor.Colourise(0, -1)
   301         self.PythonCodeEditor.Colourise(0, -1)
       
   302         
       
   303         self.ShowHighlights()
   321 
   304 
   322     def RefreshModel(self):
   305     def RefreshModel(self):
   323         self.Controler.SetPythonCode(self.PythonCodeEditor.GetText())
   306         self.Controler.SetPythonCode(self.PythonCodeEditor.GetText())
   324 
   307 
   325     def OnKeyPressed(self, event):
   308     def OnKeyPressed(self, event):
   336 
   319 
   337                 # Images are specified with a appended "?type"
   320                 # Images are specified with a appended "?type"
   338                 self.PythonCodeEditor.AutoCompShow(0, " ".join([word + "?1" for word in keyword.kwlist]))
   321                 self.PythonCodeEditor.AutoCompShow(0, " ".join([word + "?1" for word in keyword.kwlist]))
   339         else:
   322         else:
   340             event.Skip()
   323             event.Skip()
   341 
   324     
   342     def OnKillFocus(self, event):
   325     def OnKillFocus(self, event):
   343         self.PythonCodeEditor.AutoCompCancel()
   326         self.PythonCodeEditor.AutoCompCancel()
   344         event.Skip()
   327         event.Skip()
   345 
   328 
   346     def OnUpdateUI(self, evt):
   329     def OnUpdateUI(self, evt):
   481         self.DisableEvents = True
   464         self.DisableEvents = True
   482         self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
   465         self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
   483         self.DisableEvents = False
   466         self.DisableEvents = False
   484         self.RefreshModel()
   467         self.RefreshModel()
   485         self.RefreshBuffer()
   468         self.RefreshBuffer()
       
   469 
       
   470     def Find(self, direction, search_params):
       
   471         if self.SearchParams != search_params:
       
   472             self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
       
   473             
       
   474             self.SearchParams = search_params
       
   475             criteria = {
       
   476                 "raw_pattern": search_params["find_pattern"], 
       
   477                 "pattern": re.compile(search_params["find_pattern"]),
       
   478                 "case_sensitive": search_params["case_sensitive"],
       
   479                 "regular_expression": search_params["regular_expression"],
       
   480                 "filter": "all"}
       
   481             
       
   482             self.SearchResults = [
       
   483                 (start, end, SEARCH_RESULT_HIGHLIGHT)
       
   484                 for start, end, text in 
       
   485                 TestTextElement(self.PythonCodeEditor.GetText(), criteria)]
       
   486             self.CurrentFindHighlight = None
       
   487         
       
   488         if len(self.SearchResults) > 0:
       
   489             if self.CurrentFindHighlight is not None:
       
   490                 old_idx = self.SearchResults.index(self.CurrentFindHighlight)
       
   491                 if self.SearchParams["wrap"]:
       
   492                     idx = (old_idx + direction) % len(self.SearchResults)
       
   493                 else:
       
   494                     idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1))
       
   495                 if idx != old_idx:
       
   496                     self.RemoveHighlight(*self.CurrentFindHighlight)
       
   497                     self.CurrentFindHighlight = self.SearchResults[idx]
       
   498                     self.AddHighlight(*self.CurrentFindHighlight)
       
   499             else:
       
   500                 self.CurrentFindHighlight = self.SearchResults[0]
       
   501                 self.AddHighlight(*self.CurrentFindHighlight)
       
   502             
       
   503         else:
       
   504             if self.CurrentFindHighlight is not None:
       
   505                 self.RemoveHighlight(*self.CurrentFindHighlight)
       
   506             self.CurrentFindHighlight = None
       
   507 
       
   508 #-------------------------------------------------------------------------------
       
   509 #                        Highlights showing functions
       
   510 #-------------------------------------------------------------------------------
       
   511 
       
   512     def OnRefreshHighlightsTimer(self, event):
       
   513         self.RefreshView()
       
   514         event.Skip()
       
   515 
       
   516     def ClearHighlights(self, highlight_type=None):
       
   517         if highlight_type is None:
       
   518             self.Highlights = []
       
   519         else:
       
   520             highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
       
   521             if highlight_type is not None:
       
   522                 self.Highlights = [(start, end, highlight) for (start, end, highlight) in self.Highlights if highlight != highlight_type]
       
   523         self.RefreshView()
       
   524 
       
   525     def AddHighlight(self, start, end, highlight_type):
       
   526         highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
       
   527         if highlight_type is not None:
       
   528             self.Highlights.append((start, end, highlight_type))
       
   529             self.PythonCodeEditor.GotoPos(self.PythonCodeEditor.PositionFromLine(start[0]) + start[1])
       
   530             self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
       
   531             self.RefreshView()
       
   532 
       
   533     def RemoveHighlight(self, start, end, highlight_type):
       
   534         highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
       
   535         if (highlight_type is not None and 
       
   536             (start, end, highlight_type) in self.Highlights):
       
   537             self.Highlights.remove((start, end, highlight_type))
       
   538             self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
       
   539     
       
   540     def ShowHighlights(self):
       
   541         for start, end, highlight_type in self.Highlights:
       
   542             if start[0] == 0:
       
   543                 highlight_start_pos = start[1]
       
   544             else:
       
   545                 highlight_start_pos = self.PythonCodeEditor.GetLineEndPosition(start[0] - 1) + start[1] + 1
       
   546             if end[0] == 0:
       
   547                 highlight_end_pos = end[1] - indent + 1
       
   548             else:
       
   549                 highlight_end_pos = self.PythonCodeEditor.GetLineEndPosition(end[0] - 1) + end[1] + 2
       
   550             self.PythonCodeEditor.StartStyling(highlight_start_pos, 0xff)
       
   551             self.PythonCodeEditor.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
       
   552             self.PythonCodeEditor.StartStyling(highlight_start_pos, 0x00)
       
   553             self.PythonCodeEditor.SetStyling(len(self.PythonCodeEditor.GetText()) - highlight_end_pos, stc.STC_STYLE_DEFAULT)
       
   554