TextViewer.py
changeset 27 dae55dd9ee14
child 47 2b2f8d88e6d3
equal deleted inserted replaced
26:36d378bd852e 27:dae55dd9ee14
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C): Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #Lesser General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from wxPython.wx import *
       
    26 from wxPython.stc import * 
       
    27 import wx
       
    28 
       
    29 import re
       
    30 
       
    31 #-------------------------------------------------------------------------------
       
    32 #                         Textual programs Viewer class
       
    33 #-------------------------------------------------------------------------------
       
    34 
       
    35 
       
    36 NEWLINE = "\n"
       
    37 NUMBERS = [str(i) for i in xrange(10)]
       
    38 LETTERS = ['_']
       
    39 for i in xrange(26):
       
    40     LETTERS.append(chr(ord('a') + i))
       
    41     LETTERS.append(chr(ord('A') + i))
       
    42 
       
    43 [wxSTC_PLC_WORD, wxSTC_PLC_COMMENT, wxSTC_PLC_NUMBER, wxSTC_PLC_VARIABLE, 
       
    44  wxSTC_PLC_FUNCTION, wxSTC_PLC_JUMP] = range(6)
       
    45 [SPACE, WORD, NUMBER, COMMENT] = range(4)
       
    46 
       
    47 [wxID_TEXTVIEWER,
       
    48 ] = [wx.NewId() for _init_ctrls in range(1)]
       
    49 
       
    50 if wx.Platform == '__WXMSW__':
       
    51     faces = { 'times': 'Times New Roman',
       
    52               'mono' : 'Courier New',
       
    53               'helv' : 'Arial',
       
    54               'other': 'Comic Sans MS',
       
    55               'size' : 10,
       
    56              }
       
    57 else:
       
    58     faces = { 'times': 'Times',
       
    59               'mono' : 'Courier',
       
    60               'helv' : 'Helvetica',
       
    61               'other': 'new century schoolbook',
       
    62               'size' : 12,
       
    63              }
       
    64 re_texts = {}
       
    65 re_texts["letter"] = "[A-Za-z]"
       
    66 re_texts["digit"] = "[0-9]"
       
    67 re_texts["identifier"] = "((?:%(letter)s|(?:_(?:%(letter)s|%(digit)s)))(?:_?(?:%(letter)s|%(digit)s))*)"%re_texts
       
    68 IDENTIFIER_MODEL = re.compile(re_texts["identifier"])
       
    69 LABEL_MODEL = re.compile("[ \t\n]%(identifier)s:[ \t\n]"%re_texts)
       
    70 
       
    71 class TextViewer(wxStyledTextCtrl):
       
    72     
       
    73     def __init__(self, parent, window, controler):
       
    74         wxStyledTextCtrl.__init__(self, parent, wxID_TEXTVIEWER, style=0)
       
    75         
       
    76         self.CmdKeyAssign(ord('+'), wxSTC_SCMOD_CTRL, wxSTC_CMD_ZOOMIN)
       
    77         self.CmdKeyAssign(ord('-'), wxSTC_SCMOD_CTRL, wxSTC_CMD_ZOOMOUT)
       
    78         
       
    79         self.SetViewWhiteSpace(False)
       
    80         
       
    81         self.SetLexer(wxSTC_LEX_CONTAINER)
       
    82         
       
    83         # Global default styles for all languages
       
    84         self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
       
    85         self.StyleClearAll()  # Reset all to be like the default
       
    86         
       
    87         self.StyleSetSpec(wxSTC_STYLE_LINENUMBER,  "back:#C0C0C0,size:%(size)d" % faces)
       
    88         self.SetSelBackground(1, "#E0E0E0")
       
    89         
       
    90         # Highlighting styles
       
    91         self.StyleSetSpec(wxSTC_PLC_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
       
    92         self.StyleSetSpec(wxSTC_PLC_VARIABLE, "fore:#7F0000,size:%(size)d" % faces)
       
    93         self.StyleSetSpec(wxSTC_PLC_FUNCTION, "fore:#7F7F00,size:%(size)d" % faces)
       
    94         self.StyleSetSpec(wxSTC_PLC_COMMENT, "fore:#7F7F7F,size:%(size)d" % faces)
       
    95         self.StyleSetSpec(wxSTC_PLC_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
       
    96         self.StyleSetSpec(wxSTC_PLC_JUMP, "fore:#007F00,size:%(size)d" % faces)
       
    97         
       
    98         # Indicators styles
       
    99         self.IndicatorSetStyle(0, wxSTC_INDIC_SQUIGGLE)
       
   100         self.IndicatorSetForeground(0, wxRED)
       
   101         
       
   102         # Line numbers in the margin
       
   103         self.SetMarginType(1, wxSTC_MARGIN_NUMBER)
       
   104         self.SetMarginWidth(1, 50)
       
   105         
       
   106         # Indentation size
       
   107         self.SetTabWidth(2)
       
   108         self.SetUseTabs(0)
       
   109         
       
   110         self.Keywords = []
       
   111         self.Variables = []
       
   112         self.Functions = []
       
   113         self.Jumps = []
       
   114         self.TextChanged = False
       
   115         self.TextSyntax = "ST"
       
   116         
       
   117         self.Controler = controler
       
   118 
       
   119         EVT_KEY_DOWN(self, self.OnKeyDown)
       
   120         EVT_STC_STYLENEEDED(self, wxID_TEXTVIEWER, self.OnStyleNeeded)
       
   121         EVT_KILL_FOCUS(self, self.OnKillFocus)
       
   122     
       
   123     def SetTextSyntax(self, syntax):
       
   124         self.TextSyntax = syntax
       
   125     
       
   126     def SetKeywords(self, keywords):
       
   127         self.Keywords = [keyword.upper() for keyword in keywords]
       
   128         self.Colourise(0, -1)
       
   129     
       
   130     def SetVariables(self, variables):
       
   131         self.Variables = [variable.upper() for variable in variables]
       
   132         self.Colourise(0, -1)
       
   133     
       
   134     def SetFunctions(self, blocktypes):
       
   135         self.Functions = []
       
   136         for category in blocktypes:
       
   137             for blocktype in category["list"]:
       
   138                 if blocktype["name"] not in self.Keywords and blocktype["name"] not in self.Variables:
       
   139                     self.Functions.append(blocktype["name"].upper())
       
   140         self.Colourise(0, -1)
       
   141     
       
   142     def RefreshJumpList(self):
       
   143         self.Jumps = [jump.upper() for jump in LABEL_MODEL.findall(self.GetText())]
       
   144         self.Colourise(0, -1)
       
   145     
       
   146     def RefreshView(self):
       
   147         self.SetText(self.Controler.GetCurrentElementEditingText())
       
   148         self.RefreshJumpList()
       
   149     
       
   150     def OnStyleNeeded(self, event):
       
   151         self.TextChanged = True
       
   152         line = self.LineFromPosition(self.GetEndStyled())
       
   153         if line == 0:
       
   154             start_pos = 0
       
   155         else:
       
   156             start_pos = self.GetLineEndPosition(line - 1) + 1
       
   157         end_pos = event.GetPosition()
       
   158         self.StartStyling(start_pos, 0xff)
       
   159         
       
   160         i = start_pos
       
   161         state = SPACE
       
   162         line = ""
       
   163         word = ""
       
   164         while i < end_pos:
       
   165             char = chr(self.GetCharAt(i)).upper()
       
   166             line += char
       
   167             if char == NEWLINE:
       
   168                 if state == COMMENT:
       
   169                     self.SetStyling(i - start_pos + 1, wxSTC_PLC_COMMENT)
       
   170                 elif state == NUMBER:
       
   171                     self.SetStyling(i - start_pos, wxSTC_PLC_NUMBER)
       
   172                 elif state == WORD:
       
   173                     if word in self.Keywords:
       
   174                         self.SetStyling(i - start_pos, wxSTC_PLC_WORD)
       
   175                     elif word in self.Variables:
       
   176                         self.SetStyling(i - start_pos, wxSTC_PLC_VARIABLE)
       
   177                     elif word in self.Functions:
       
   178                         self.SetStyling(i - start_pos, wxSTC_PLC_FUNCTION)
       
   179                     elif word in self.Jumps:
       
   180                         self.SetStyling(i - start_pos, wxSTC_PLC_JUMP)
       
   181                     else:
       
   182                         self.SetStyling(i - start_pos, 31)
       
   183                         if self.GetCurrentPos() < start_pos or self.GetCurrentPos() > i:
       
   184                             self.StartStyling(start_pos, wxSTC_INDICS_MASK)
       
   185                             self.SetStyling(i - start_pos, wxSTC_INDIC0_MASK)
       
   186                             self.StartStyling(i, 0xff)    
       
   187                 else:
       
   188                     self.SetStyling(i - start_pos, 31)
       
   189                 start_pos = i
       
   190                 state = SPACE
       
   191                 line = ""
       
   192             elif line.endswith("(*") and state != COMMENT:
       
   193                 self.SetStyling(i - start_pos - 1, 31)
       
   194                 start_pos = i
       
   195                 state = COMMENT
       
   196             elif state == COMMENT:
       
   197                 if line.endswith("*)"):
       
   198                     self.SetStyling(i - start_pos + 2, wxSTC_PLC_COMMENT)
       
   199                     start_pos = i + 1
       
   200                     state = SPACE
       
   201             elif char in LETTERS:
       
   202                 if state == NUMBER:
       
   203                     word = "#"
       
   204                     state = WORD
       
   205                 elif state == SPACE:
       
   206                     self.SetStyling(i - start_pos, 31)
       
   207                     word = char
       
   208                     start_pos = i
       
   209                     state = WORD
       
   210                 else:
       
   211                     word += char
       
   212             elif char in NUMBERS or char == '.' and state != WORD:
       
   213                 if state == SPACE:
       
   214                     self.SetStyling(i - start_pos, 31)
       
   215                     start_pos = i
       
   216                     state = NUMBER
       
   217                 if state == WORD and char != '.':
       
   218                     word += char
       
   219             else:
       
   220                 if state == WORD:
       
   221                     if word in self.Keywords:
       
   222                         self.SetStyling(i - start_pos, wxSTC_PLC_WORD)
       
   223                     elif word in self.Variables:
       
   224                         self.SetStyling(i - start_pos, wxSTC_PLC_VARIABLE)
       
   225                     elif word in self.Functions:
       
   226                         self.SetStyling(i - start_pos, wxSTC_PLC_FUNCTION)
       
   227                     elif word in self.Jumps:
       
   228                         self.SetStyling(i - start_pos, wxSTC_PLC_JUMP)
       
   229                     else:
       
   230                         self.SetStyling(i - start_pos, 31)
       
   231                         if self.GetCurrentPos() < start_pos or self.GetCurrentPos() > i:
       
   232                             self.StartStyling(start_pos, wxSTC_INDICS_MASK)
       
   233                             self.SetStyling(i - start_pos, wxSTC_INDIC0_MASK)
       
   234                             self.StartStyling(i, 0xff)
       
   235                     word = ""
       
   236                     start_pos = i
       
   237                     state = SPACE
       
   238                 elif state == NUMBER:
       
   239                     self.SetStyling(i - start_pos, wxSTC_PLC_NUMBER)
       
   240                     start_pos = i
       
   241                     state = SPACE
       
   242             i += 1
       
   243         if state == COMMENT:
       
   244             self.SetStyling(i - start_pos + 2, wxSTC_PLC_COMMENT)
       
   245         elif state == NUMBER:
       
   246             self.SetStyling(i - start_pos, wxSTC_PLC_NUMBER)
       
   247         elif state == WORD:
       
   248             if word in self.Keywords:
       
   249                 self.SetStyling(i - start_pos, wxSTC_PLC_WORD)
       
   250             elif word in self.Variables:
       
   251                 self.SetStyling(i - start_pos, wxSTC_PLC_VARIABLE)
       
   252             elif word in self.Functions:
       
   253                 self.SetStyling(i - start_pos, wxSTC_PLC_FUNCTION)
       
   254             elif word in self.Jumps:
       
   255                 self.SetStyling(i - start_pos, wxSTC_PLC_JUMP)
       
   256             else:
       
   257                 self.SetStyling(i - start_pos, 31)
       
   258         else:
       
   259             self.SetStyling(i - start_pos, 31)
       
   260         event.Skip()
       
   261     
       
   262     def Cut(self):
       
   263         self.CmdKeyExecute(wxSTC_CMD_CUT)
       
   264         
       
   265     def Copy(self):
       
   266         self.CmdKeyExecute(wxSTC_CMD_COPY)
       
   267     
       
   268     def Paste(self):
       
   269         self.CmdKeyExecute(wxSTC_CMD_PASTE)
       
   270     
       
   271     def RefreshModel(self):
       
   272         if self.TextChanged:
       
   273             self.RefreshJumpList()
       
   274             self.Controler.SetCurrentElementEditingText(self.GetText())
       
   275     
       
   276     def OnKeyDown(self, event):
       
   277         if self.CallTipActive():
       
   278             self.CallTipCancel()
       
   279         key = event.KeyCode()
       
   280 
       
   281         # Code completion
       
   282         if key == WXK_SPACE and event.ControlDown():
       
   283             
       
   284             line = self.GetCurrentLine()
       
   285             if line == 0:
       
   286                 start_pos = 0
       
   287             else:
       
   288                 start_pos = self.GetLineEndPosition(line - 1) + 1
       
   289             end_pos = self.GetCurrentPos()
       
   290 
       
   291             lineText = self.GetTextRange(start_pos, end_pos).replace("\t", " ")
       
   292             words = lineText.split(" ")
       
   293             words = [word for i, word in enumerate(words) if word != '' or i == len(words) - 1]
       
   294                         
       
   295             kw = []
       
   296             
       
   297             if self.TextSyntax == "IL":
       
   298                 if len(words) == 1:
       
   299                     kw = self.Keywords
       
   300                 elif len(words) == 2:
       
   301                     if words[0].upper() in ["CAL", "CALC", "CALNC"]:
       
   302                         kw = self.Functions
       
   303                     elif words[0].upper() in ["JMP", "JMPC", "JMPNC"]:
       
   304                         kw = self.Jumps
       
   305                     else:
       
   306                         kw = self.Variables
       
   307             else:
       
   308                 kw = self.Keywords + self.Variables + self.Functions
       
   309             if len(kw) > 0:
       
   310                 kw.sort()
       
   311                 self.AutoCompSetIgnoreCase(True)
       
   312                 self.AutoCompShow(len(words[-1]), " ".join(kw))
       
   313         else:
       
   314             self.TextChanged = False
       
   315             wxCallAfter(self.RefreshModel)
       
   316             event.Skip()
       
   317 
       
   318     def OnKillFocus(self, event):
       
   319         self.AutoCompCancel()
       
   320         event.Skip()
       
   321