# HG changeset patch # User laurent # Date 1248873430 -7200 # Node ID cd90e4c1026125c5804dd79369c2c3a500c655cf # Parent a7f58414dea0f1b583ba005f4d88149044110f74 Move python evaluator to create a python plugin containing any related python module diff -r a7f58414dea0 -r cd90e4c10261 PythonSTC.py --- a/PythonSTC.py Wed Jul 29 10:49:31 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,596 +0,0 @@ -import keyword -import os -import wx -import wx.stc as stc - -#---------------------------------------------------------------------- - -""" -This Python editor class comes from the wxPython demo, a bit tweaked -""" - -#---------------------------------------------------------------------- - - -if wx.Platform == '__WXMSW__': - faces = { 'times': 'Times New Roman', - 'mono' : 'Courier New', - 'helv' : 'Arial', - 'other': 'Comic Sans MS', - 'size' : 10, - 'size2': 8, - } -elif wx.Platform == '__WXMAC__': - faces = { 'times': 'Times New Roman', - 'mono' : 'Monaco', - 'helv' : 'Arial', - 'other': 'Comic Sans MS', - 'size' : 12, - 'size2': 10, - } -else: - faces = { 'times': 'Times', - 'mono' : 'Courier', - 'helv' : 'Helvetica', - 'other': 'new century schoolbook', - 'size' : 12, - 'size2': 10, - } - - -#---------------------------------------------------------------------- - -class PythonSTC(stc.StyledTextCtrl): - - fold_symbols = 2 - - def __init__(self, parent, ID, - pos=wx.DefaultPosition, size=wx.DefaultSize, - style=0): - stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style) - - self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) - self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) - - self.SetLexer(stc.STC_LEX_PYTHON) - self.SetKeyWords(0, " ".join(keyword.kwlist)) - - self.SetProperty("fold", "1") - self.SetProperty("tab.timmy.whinge.level", "1") - self.SetMargins(0,0) - - self.SetViewWhiteSpace(False) - #self.SetBufferedDraw(False) - #self.SetViewEOL(True) - #self.SetEOLMode(stc.STC_EOL_CRLF) - #self.SetUseAntiAliasing(True) - - self.SetEdgeMode(stc.STC_EDGE_BACKGROUND) - self.SetEdgeColumn(78) - - # Setup a margin to hold fold markers - #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? - self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) - self.SetMarginMask(2, stc.STC_MASK_FOLDERS) - self.SetMarginSensitive(2, True) - self.SetMarginWidth(2, 12) - - if self.fold_symbols == 0: - # Arrow pointing right for contracted folders, arrow pointing down for expanded - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") - - elif self.fold_symbols == 1: - # Plus for contracted folders, minus for expanded - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") - - elif self.fold_symbols == 2: - # Like a flattened tree control using circular headers and curved joins - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040") - - elif self.fold_symbols == 3: - # Like a flattened tree control using square headers - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080") - - - self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI) - self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick) - self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed) - - # Make some styles, The lexer defines what each style is used for, we - # just have to define what each style looks like. This set is adapted from - # Scintilla sample property files. - - # Global default styles for all languages - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces) - self.StyleClearAll() # Reset all to be like the default - - # Global default styles for all languages - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces) - self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces) - self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces) - self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold") - self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold") - - # Python styles - # Default - self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces) - # Comments - self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces) - # Number - self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces) - # String - self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces) - # Single quoted string - self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces) - # Keyword - self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces) - # Triple quotes - self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces) - # Triple double quotes - self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces) - # Class name definition - self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces) - # Function or method name definition - self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces) - # Operators - self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces) - # Identifiers - self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces) - # Comment-blocks - self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces) - # End of line where string is not closed - self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces) - - self.SetCaretForeground("BLUE") - - def OnKeyPressed(self, event): - if self.CallTipActive(): - self.CallTipCancel() - - event.Skip() -## For later use -# key = event.GetKeyCode() -# if key == 32 and event.ControlDown(): -# pos = self.GetCurrentPos() -# -# # Tips -# if event.ShiftDown(): -# self.CallTipSetBackground("yellow") -# self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n' -# 'show some suff, maybe parameters..\n\n' -# 'fubar(param1, param2)') -# # Code completion -# else: -# #lst = [] -# #for x in range(50000): -# # lst.append('%05d' % x) -# #st = " ".join(lst) -# #print len(st) -# #self.AutoCompShow(0, st) -# -# kw = keyword.kwlist[:] -# kw.append("zzzzzz?2") -# kw.append("aaaaa?2") -# kw.append("__init__?3") -# kw.append("zzaaaaa?2") -# kw.append("zzbaaaa?2") -# kw.append("this_is_a_longer_value") -# #kw.append("this_is_a_much_much_much_much_much_much_much_longer_value") -# -# kw.sort() # Python sorts are case sensitive -# self.AutoCompSetIgnoreCase(False) # so this needs to match -# -# # Images are specified with a appended "?type" -# for i in range(len(kw)): -# if kw[i] in keyword.kwlist: -# kw[i] = kw[i] + "?1" -# -# self.AutoCompShow(0, " ".join(kw)) -# else: -# event.Skip() - - - def OnUpdateUI(self, evt): - # check for matching braces - braceAtCaret = -1 - braceOpposite = -1 - charBefore = None - caretPos = self.GetCurrentPos() - - if caretPos > 0: - charBefore = self.GetCharAt(caretPos - 1) - styleBefore = self.GetStyleAt(caretPos - 1) - - # check before - if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR: - braceAtCaret = caretPos - 1 - - # check after - if braceAtCaret < 0: - charAfter = self.GetCharAt(caretPos) - styleAfter = self.GetStyleAt(caretPos) - - if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR: - braceAtCaret = caretPos - - if braceAtCaret >= 0: - braceOpposite = self.BraceMatch(braceAtCaret) - - if braceAtCaret != -1 and braceOpposite == -1: - self.BraceBadLight(braceAtCaret) - else: - self.BraceHighlight(braceAtCaret, braceOpposite) - #pt = self.PointFromPosition(braceOpposite) - #self.Refresh(True, wxRect(pt.x, pt.y, 5,5)) - #print pt - #self.Refresh(False) - - - def OnMarginClick(self, evt): - # fold and unfold as needed - if evt.GetMargin() == 2: - if evt.GetShift() and evt.GetControl(): - self.FoldAll() - else: - lineClicked = self.LineFromPosition(evt.GetPosition()) - - if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG: - if evt.GetShift(): - self.SetFoldExpanded(lineClicked, True) - self.Expand(lineClicked, True, True, 1) - elif evt.GetControl(): - if self.GetFoldExpanded(lineClicked): - self.SetFoldExpanded(lineClicked, False) - self.Expand(lineClicked, False, True, 0) - else: - self.SetFoldExpanded(lineClicked, True) - self.Expand(lineClicked, True, True, 100) - else: - self.ToggleFold(lineClicked) - - - def FoldAll(self): - lineCount = self.GetLineCount() - expanding = True - - # find out if we are folding or unfolding - for lineNum in range(lineCount): - if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG: - expanding = not self.GetFoldExpanded(lineNum) - break - - lineNum = 0 - - while lineNum < lineCount: - level = self.GetFoldLevel(lineNum) - if level & stc.STC_FOLDLEVELHEADERFLAG and \ - (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE: - - if expanding: - self.SetFoldExpanded(lineNum, True) - lineNum = self.Expand(lineNum, True) - lineNum = lineNum - 1 - else: - lastChild = self.GetLastChild(lineNum, -1) - self.SetFoldExpanded(lineNum, False) - - if lastChild > lineNum: - self.HideLines(lineNum+1, lastChild) - - lineNum = lineNum + 1 - - - - def Expand(self, line, doExpand, force=False, visLevels=0, level=-1): - lastChild = self.GetLastChild(line, level) - line = line + 1 - - while line <= lastChild: - if force: - if visLevels > 0: - self.ShowLines(line, line) - else: - self.HideLines(line, line) - else: - if doExpand: - self.ShowLines(line, line) - - if level == -1: - level = self.GetFoldLevel(line) - - if level & stc.STC_FOLDLEVELHEADERFLAG: - if force: - if visLevels > 1: - self.SetFoldExpanded(line, True) - else: - self.SetFoldExpanded(line, False) - - line = self.Expand(line, doExpand, force, visLevels-1) - - else: - if doExpand and self.GetFoldExpanded(line): - line = self.Expand(line, True, force, visLevels-1) - else: - line = self.Expand(line, False, force, visLevels-1) - else: - line = line + 1 - - return line - - -#---------------------------------------------------------------------- -class PythonCodeEditor(PythonSTC): - def __init__(self, parent): - PythonSTC.__init__(self, parent, -1, style=wx.BORDER_NONE) - self.SetUpEditor() - - # Some methods to make it compatible with how the wxTextCtrl is used - def SetValue(self, value): - if wx.USE_UNICODE: - value = value.decode('utf-8') - self.SetText(value) - self.EmptyUndoBuffer() - self.SetSavePoint() - - def IsModified(self): - return self.GetModify() - - def Clear(self): - self.ClearAll() - - def SetInsertionPoint(self, pos): - self.SetCurrentPos(pos) - self.SetAnchor(pos) - - def ShowPosition(self, pos): - line = self.LineFromPosition(pos) - #self.EnsureVisible(line) - self.GotoLine(line) - - def GetLastPosition(self): - return self.GetLength() - - def GetPositionFromLine(self, line): - return self.PositionFromLine(line) - - def GetRange(self, start, end): - return self.GetTextRange(start, end) - - def GetSelection(self): - return self.GetAnchor(), self.GetCurrentPos() - - def SetSelection(self, start, end): - self.SetSelectionStart(start) - self.SetSelectionEnd(end) - - def SelectLine(self, line): - start = self.PositionFromLine(line) - end = self.GetLineEndPosition(line) - self.SetSelection(start, end) - - def SetUpEditor(self): - """ - This method carries out the work of setting up the demo editor. - It's seperate so as not to clutter up the init code. - """ - import keyword - - self.SetLexer(stc.STC_LEX_PYTHON) - self.SetKeyWords(0, " ".join(keyword.kwlist)) - - # Enable folding - self.SetProperty("fold", "1" ) - - # Highlight tab/space mixing (shouldn't be any) - self.SetProperty("tab.timmy.whinge.level", "1") - - # Set left and right margins - self.SetMargins(2,2) - - # Set up the numbers in the margin for margin #1 - self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER) - # Reasonable value for, say, 4-5 digits using a mono font (40 pix) - self.SetMarginWidth(1, 40) - - # Indentation and tab stuff - self.SetIndent(4) # Proscribed indent size for wx - self.SetIndentationGuides(True) # Show indent guides - self.SetBackSpaceUnIndents(True)# Backspace unindents rather than delete 1 space - self.SetTabIndents(True) # Tab key indents - self.SetTabWidth(4) # Proscribed tab size for wx - self.SetUseTabs(False) # Use spaces rather than tabs, or - # TabTimmy will complain! - # White space - self.SetViewWhiteSpace(False) # Don't view white space - - # EOL: Since we are loading/saving ourselves, and the - # strings will always have \n's in them, set the STC to - # edit them that way. - self.SetEOLMode(wx.stc.STC_EOL_LF) - self.SetViewEOL(False) - - # No right-edge mode indicator - self.SetEdgeMode(stc.STC_EDGE_NONE) - - # Setup a margin to hold fold markers - self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) - self.SetMarginMask(2, stc.STC_MASK_FOLDERS) - self.SetMarginSensitive(2, True) - self.SetMarginWidth(2, 12) - - # and now set up the fold markers - self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "black") - self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "black") - - # Global default style - if wx.Platform == '__WXMSW__': - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, - 'fore:#000000,back:#FFFFFF,face:Courier New') - elif wx.Platform == '__WXMAC__': - # TODO: if this looks fine on Linux too, remove the Mac-specific case - # and use this whenever OS != MSW. - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, - 'fore:#000000,back:#FFFFFF,face:Monaco') - else: - defsize = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT).GetPointSize() - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, - 'fore:#000000,back:#FFFFFF,face:Courier,size:%d'%defsize) - - # Clear styles and revert to default. - self.StyleClearAll() - - # Following style specs only indicate differences from default. - # The rest remains unchanged. - - # Line numbers in margin - self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2') - # Highlighted brace - self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00') - # Unmatched brace - self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000') - # Indentation guide - self.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD") - - # Python styles - self.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000') - # Comments - self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, 'fore:#008000,back:#F0FFF0') - self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0') - # Numbers - self.StyleSetSpec(wx.stc.STC_P_NUMBER, 'fore:#008080') - # Strings and characters - self.StyleSetSpec(wx.stc.STC_P_STRING, 'fore:#800080') - self.StyleSetSpec(wx.stc.STC_P_CHARACTER, 'fore:#800080') - # Keywords - self.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold') - # Triple quotes - self.StyleSetSpec(wx.stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA') - self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA') - # Class names - self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, 'fore:#0000FF,bold') - # Function names - self.StyleSetSpec(wx.stc.STC_P_DEFNAME, 'fore:#008080,bold') - # Operators - self.StyleSetSpec(wx.stc.STC_P_OPERATOR, 'fore:#800000,bold') - # Identifiers. I leave this as not bold because everything seems - # to be an identifier if it doesn't match the above criterae - self.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, 'fore:#000000') - - # Caret color - self.SetCaretForeground("BLUE") - # Selection background - self.SetSelBackground(1, '#66CCFF') - - self.SetSelBackground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)) - self.SetSelForeground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)) - - def RegisterModifiedEvent(self, eventHandler): - self.Bind(wx.stc.EVT_STC_CHANGE, eventHandler) - - -class PythonCodePanel(wx.Panel): - """Panel for the 'Demo Code' tab""" - def __init__(self, parent, mainFrame): - wx.Panel.__init__(self, parent, size=(1,1)) - self.mainFrame = mainFrame - self.editor = PythonCodeEditor(self) - self.editor.RegisterModifiedEvent(self.OnCodeModified) - - self.btnSave = wx.Button(self, -1, "Save") - self.btnRestore = wx.Button(self, -1, "Restore") - self.btnSave.Enable(False) - self.btnSave.Bind(wx.EVT_BUTTON, self.OnSave) - self.btnRestore.Bind(wx.EVT_BUTTON, self.OnRestore) - - self.controlBox = wx.BoxSizer(wx.HORIZONTAL) - self.controlBox.Add(self.btnSave, 0, wx.RIGHT, 5) - self.controlBox.Add(self.btnRestore, 0) - - self.box = wx.BoxSizer(wx.VERTICAL) - self.box.Add(self.controlBox, 0, wx.EXPAND) - self.box.Add(wx.StaticLine(self), 0, wx.EXPAND) - self.box.Add(self.editor, 1, wx.EXPAND) - - self.box.Fit(self) - self.SetSizer(self.box) - - self.sourceFile = None - - self.Bind(wx.EVT_MENU, self.OnSave, id=wx.ID_SAVE) - accel = wx.AcceleratorTable([wx.AcceleratorEntry(wx.ACCEL_CTRL, 83, wx.ID_SAVE)]) - self.SetAcceleratorTable(accel) - - # Loads from a file object - def LoadSourceFile(self, filename): - self.sourceFile = filename - if os.path.exists(filename): - self.LoadSource(file(filename).read()) - - def LoadSource(self, source): - self.editor.Clear() - self.editor.SetValue(source) - self.JumpToLine(0) - self.btnSave.Enable(False) - - def JumpToLine(self, line, highlight=False): - self.editor.GotoLine(line) - self.editor.SetFocus() - if highlight: - self.editor.SelectLine(line) - - def OnCodeModified(self, event): - self.btnSave.Enable(self.editor.IsModified()) - # TODO : add callback - - def OnSave(self, event): - overwriteMsg = "You are about to overwrite that file\n" + \ - "Do you want to continue?" - dlg = wx.MessageDialog(self, overwriteMsg, "wxPython Demo", - wx.YES_NO | wx.NO_DEFAULT| wx.ICON_EXCLAMATION) - result = dlg.ShowModal() - if result == wx.ID_NO: - return - dlg.Destroy() - - source = self.editor.GetText().encode("utf-8") - - f = file(self.sourceFile, "w") - f.write(source) - f.close() - - # TODO - #self.mainFrame.SetTreeModified(True) - - - def OnRestore(self, event): - self.LoadSourceFile(self.sourceFile) - self.btnSave.Enable(self.editor.IsModified()) diff -r a7f58414dea0 -r cd90e4c10261 plugger.py --- a/plugger.py Wed Jul 29 10:49:31 2009 +0200 +++ b/plugger.py Wed Jul 29 15:17:10 2009 +0200 @@ -910,14 +910,6 @@ # define name for IEC raw code file return os.path.join(self.PlugPath(), "raw_plc.st") - def _getPYTHONcodepath(self): - # define name for IEC raw code file - return os.path.join(self.PlugPath(), "runtime.py") - - def _getWXGLADEpath(self): - # define name for IEC raw code file - return os.path.join(self.PlugPath(), "hmi.wxg") - def GetLocations(self): locations = [] filepath = os.path.join(self._getBuildPath(),"LOCATED_VARIABLES.h") @@ -1073,15 +1065,6 @@ else: return None - def launch_wxglade(self, options, wait=False): - from wxglade import __file__ as fileName - path = os.path.dirname(fileName) - glade = os.path.join(path, 'wxglade.py') - if wx.Platform == '__WXMSW__': - glade = "\"%s\""%glade - mode = {False:os.P_NOWAIT, True:os.P_WAIT}[wait] - os.spawnv(mode, sys.executable, ["\"%s\""%sys.executable] + [glade] + options) - ####################################################################### # # C CODE GENERATION METHODS @@ -1096,27 +1079,10 @@ @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND """ - res = ([(C_file_name, self.plcCFLAGS) + return ([(C_file_name, self.plcCFLAGS) for C_file_name in self.PLCGeneratedCFiles ], "", # no ldflags False) # do not expose retreive/publish calls - - pyfile=self._getPYTHONcodepath() - if os.path.exists(pyfile): - res += (("runtime.py", file(pyfile,"rb")),) - wxgfile=self._getWXGLADEpath() - if os.path.exists(wxgfile): - hmipyfile=os.path.join(self._getBuildPath(),"hmi.py") - if wx.Platform == '__WXMSW__': - wxgfile = "\"%s\""%wxgfile - _hmipyfile = "\"%s\""%hmipyfile - else: - _hmipyfile = hmipyfile - self.launch_wxglade(['-o', _hmipyfile, '-g', 'python', wxgfile], wait=True) - res += (("hmi.py", file(hmipyfile,"rb")),) - - return res - def ResetIECProgramsAndVariables(self): """ @@ -1211,23 +1177,6 @@ return debug_code - def Generate_plc_python(self): - """ - Generate trace/debug code out of PLC variable list - """ - self.GetIECProgramsAndVariables() - - python_eval_fb_list = [] - for v in self._VariablesList : - if v["vartype"] == "FB" and v["type"] in ["PYTHON_EVAL","PYTHON_POLL"]: - python_eval_fb_list.append(v) - python_eval_fb_count = max(1, len(python_eval_fb_list)) - - # prepare debug code - python_code = targets.code("plc_python") % { - "python_eval_fb_count": python_eval_fb_count} - return python_code - def Generate_plc_common_main(self): """ Use plugins layout given in LocationCFilesAndCFLAGS to @@ -1334,8 +1283,6 @@ for generator, filename, name in [ # debugger code (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"), - # IEC<->python gateway code - (self.Generate_plc_python, "plc_python.c", "IEC-Python gateway"), # init/cleanup/retrieve/publish, run and align code (self.Generate_plc_common_main,"plc_common_main.c","Common runtime")]: try: @@ -1407,32 +1354,6 @@ new_dialog.Show() - def _editPYTHONcode(self): - from PythonSTC import PythonCodePanel - new_dialog = wx.Frame(self.AppFrame) - - PYTHON_viewer = PythonCodePanel(new_dialog, self.AppFrame) - #ST_viewer.Enable(False) - pyfile=self._getPYTHONcodepath() - PYTHON_viewer.LoadSourceFile(pyfile) - - new_dialog.Show() - - def _editWXGLADE(self): - wxg_filename = self._getWXGLADEpath() - if not os.path.exists(wxg_filename): - open(wxg_filename,"w").write(""" - - - - frame_1 - - -""") - if wx.Platform == '__WXMSW__': - wxg_filename = "\"%s\""%wxg_filename - self.launch_wxglade([wxg_filename]) - def _EditPLC(self): if self.PLCEditor is None: self.RefreshPluginsBlockLists() @@ -1873,12 +1794,4 @@ "name" : _("Raw IEC code"), "tooltip" : _("Edit raw IEC code added to code generated by PLCGenerator"), "method" : "_editIECrawcode"}, - {"bitmap" : opjimg("editPYTHONcode"), - "name" : "Python code", - "tooltip" : "Write Python runtime code, for use with python_eval FBs", - "method" : "_editPYTHONcode"}, - {"bitmap" : opjimg("editWXGLADE"), - "name" : "WXGLADE GUI", - "tooltip" : "Edit a WxWidgets GUI with WXGlade", - "method" : "_editWXGLADE"}, ] diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/PythonEditor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/PythonEditor.py Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,594 @@ +import wx, wx.grid +import wx.stc as stc +import keyword + +if wx.Platform == '__WXMSW__': + faces = { 'times': 'Times New Roman', + 'mono' : 'Courier New', + 'helv' : 'Arial', + 'other': 'Comic Sans MS', + 'size' : 10, + 'size2': 8, + } +elif wx.Platform == '__WXMAC__': + faces = { 'times': 'Times New Roman', + 'mono' : 'Monaco', + 'helv' : 'Arial', + 'other': 'Comic Sans MS', + 'size' : 12, + 'size2': 10, + } +else: + faces = { 'times': 'Times', + 'mono' : 'Courier', + 'helv' : 'Helvetica', + 'other': 'new century schoolbook', + 'size' : 12, + 'size2': 10, + } + +def AppendMenu(parent, help, id, kind, text): + if wx.VERSION >= (2, 6, 0): + parent.Append(help=help, id=id, kind=kind, text=text) + else: + parent.Append(helpString=help, id=id, kind=kind, item=text) + + +[ID_PYTHONEDITOR, +] = [wx.NewId() for _init_ctrls in range(1)] + +def GetCursorPos(old, new): + old_length = len(old) + new_length = len(new) + common_length = min(old_length, new_length) + i = 0 + for i in xrange(common_length): + if old[i] != new[i]: + break + if old_length < new_length: + if common_length > 0 and old[i] != new[i]: + return i + new_length - old_length + else: + return i + new_length - old_length + 1 + elif old_length > new_length or i < min(old_length, new_length) - 1: + if common_length > 0 and old[i] != new[i]: + return i + else: + return i + 1 + else: + return None + +class PythonEditor(stc.StyledTextCtrl): + + fold_symbols = 3 + + def __init__(self, parent, window, controler): + stc.StyledTextCtrl.__init__(self, parent, ID_PYTHONEDITOR, wx.DefaultPosition, + wx.DefaultSize, 0) + + self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) + self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) + + self.SetLexer(stc.STC_LEX_PYTHON) + self.SetKeyWords(0, " ".join(keyword.kwlist)) + + self.SetProperty("fold", "1") + self.SetProperty("tab.timmy.whinge.level", "1") + self.SetMargins(0,0) + + self.SetViewWhiteSpace(False) + #self.SetBufferedDraw(False) + #self.SetViewEOL(True) + #self.SetEOLMode(stc.STC_EOL_CRLF) + #self.SetUseAntiAliasing(True) + + self.SetEdgeMode(stc.STC_EDGE_BACKGROUND) + self.SetEdgeColumn(78) + + # Set up the numbers in the margin for margin #1 + self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER) + # Reasonable value for, say, 4-5 digits using a mono font (40 pix) + self.SetMarginWidth(1, 40) + + # Setup a margin to hold fold markers + self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) + self.SetMarginMask(2, stc.STC_MASK_FOLDERS) + self.SetMarginSensitive(2, True) + self.SetMarginWidth(2, 12) + + if self.fold_symbols == 0: + # Arrow pointing right for contracted folders, arrow pointing down for expanded + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") + + elif self.fold_symbols == 1: + # Plus for contracted folders, minus for expanded + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black") + + elif self.fold_symbols == 2: + # Like a flattened tree control using circular headers and curved joins + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040") + + elif self.fold_symbols == 3: + # Like a flattened tree control using square headers + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") + self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080") + + + self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI) + self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick) + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed) + + # Global default style + if wx.Platform == '__WXMSW__': + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, + 'fore:#000000,back:#FFFFFF,face:Courier New') + elif wx.Platform == '__WXMAC__': + # TODO: if this looks fine on Linux too, remove the Mac-specific case + # and use this whenever OS != MSW. + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, + 'fore:#000000,back:#FFFFFF,face:Monaco') + else: + defsize = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT).GetPointSize() + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, + 'fore:#000000,back:#FFFFFF,face:Courier,size:%d'%defsize) + + # Clear styles and revert to default. + self.StyleClearAll() + + # Following style specs only indicate differences from default. + # The rest remains unchanged. + + # Line numbers in margin + self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2') + # Highlighted brace + self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00') + # Unmatched brace + self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000') + # Indentation guide + self.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD") + + # Python styles + self.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000') + # Comments + self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, 'fore:#008000,back:#F0FFF0') + self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0') + # Numbers + self.StyleSetSpec(wx.stc.STC_P_NUMBER, 'fore:#008080') + # Strings and characters + self.StyleSetSpec(wx.stc.STC_P_STRING, 'fore:#800080') + self.StyleSetSpec(wx.stc.STC_P_CHARACTER, 'fore:#800080') + # Keywords + self.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold') + # Triple quotes + self.StyleSetSpec(wx.stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA') + self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA') + # Class names + self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, 'fore:#0000FF,bold') + # Function names + self.StyleSetSpec(wx.stc.STC_P_DEFNAME, 'fore:#008080,bold') + # Operators + self.StyleSetSpec(wx.stc.STC_P_OPERATOR, 'fore:#800000,bold') + # Identifiers. I leave this as not bold because everything seems + # to be an identifier if it doesn't match the above criterae + self.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, 'fore:#000000') + + # Caret color + self.SetCaretForeground("BLUE") + # Selection background + self.SetSelBackground(1, '#66CCFF') + + self.SetSelBackground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)) + self.SetSelForeground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)) + + # register some images for use in the AutoComplete box. + #self.RegisterImage(1, images.getSmilesBitmap()) + self.RegisterImage(1, + wx.ArtProvider.GetBitmap(wx.ART_DELETE, size=(16,16))) + self.RegisterImage(2, + wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16))) + self.RegisterImage(3, + wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16))) + + # Indentation and tab stuff + self.SetIndent(4) # Proscribed indent size for wx + self.SetIndentationGuides(True) # Show indent guides + self.SetBackSpaceUnIndents(True)# Backspace unindents rather than delete 1 space + self.SetTabIndents(True) # Tab key indents + self.SetTabWidth(4) # Proscribed tab size for wx + self.SetUseTabs(False) # Use spaces rather than tabs, or + # TabTimmy will complain! + # White space + self.SetViewWhiteSpace(False) # Don't view white space + + # EOL: Since we are loading/saving ourselves, and the + # strings will always have \n's in them, set the STC to + # edit them that way. + self.SetEOLMode(wx.stc.STC_EOL_LF) + self.SetViewEOL(False) + + # No right-edge mode indicator + self.SetEdgeMode(stc.STC_EDGE_NONE) + + self.Controler = controler + self.ParentWindow = window + + self.DisableEvents = True + self.CurrentAction = None + + self.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE) + + self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_PYTHONEDITOR) + self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) + self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_PYTHONEDITOR) + + def OnModification(self, event): + if not self.DisableEvents: + mod_type = event.GetModificationType() + if not (mod_type&wx.stc.STC_PERFORMED_UNDO or mod_type&wx.stc.STC_PERFORMED_REDO): + if mod_type&wx.stc.STC_MOD_BEFOREINSERT: + if self.CurrentAction == None: + self.StartBuffering() + elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1: + self.Controler.EndBuffering() + self.StartBuffering() + self.CurrentAction = ("Add", event.GetPosition()) + elif mod_type&wx.stc.STC_MOD_BEFOREDELETE: + if self.CurrentAction == None: + self.StartBuffering() + elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1: + self.Controler.EndBuffering() + self.StartBuffering() + self.CurrentAction = ("Delete", event.GetPosition()) + event.Skip() + + def OnDoDrop(self, event): + self.ResetBuffer() + wx.CallAfter(self.RefreshModel) + event.Skip() + + def IsViewing(self, name): + return self.Name == name + + # Buffer the last model state + def RefreshBuffer(self): + self.Controler.BufferPython() + if self.ParentWindow: + self.ParentWindow.RefreshTitle() + self.ParentWindow.RefreshEditMenu() + + def StartBuffering(self): + self.Controler.StartBuffering() + if self.ParentWindow: + self.ParentWindow.RefreshTitle() + self.ParentWindow.RefreshEditMenu() + + def ResetBuffer(self): + if self.CurrentAction != None: + self.Controler.EndBuffering() + self.CurrentAction = None + + def RefreshView(self): + self.ResetBuffer() + self.DisableEvents = True + old_cursor_pos = self.GetCurrentPos() + old_text = self.GetText() + new_text = self.Controler.GetPythonCode() + self.SetText(new_text) + new_cursor_pos = GetCursorPos(old_text, new_text) + if new_cursor_pos != None: + self.GotoPos(new_cursor_pos) + else: + self.GotoPos(old_cursor_pos) + self.ScrollToColumn(0) + self.EmptyUndoBuffer() + self.DisableEvents = False + + self.Colourise(0, -1) + + def RefreshModel(self): + self.Controler.SetPythonCode(self.GetText()) + + def OnKeyPressed(self, event): + if self.CallTipActive(): + self.CallTipCancel() + key = event.GetKeyCode() + + if key == 32 and event.ControlDown(): + pos = self.GetCurrentPos() + + # Tips + if event.ShiftDown(): + pass +## self.CallTipSetBackground("yellow") +## self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n' +## 'show some suff, maybe parameters..\n\n' +## 'fubar(param1, param2)') + # Code completion + else: + self.AutoCompSetIgnoreCase(False) # so this needs to match + + # Images are specified with a appended "?type" + self.AutoCompShow(0, " ".join([word + "?1" for word in keyword.kwlist])) + else: + wx.CallAfter(self.RefreshModel) + event.Skip() + + def OnKillFocus(self, event): + self.AutoCompCancel() + event.Skip() + + def OnUpdateUI(self, evt): + # check for matching braces + braceAtCaret = -1 + braceOpposite = -1 + charBefore = None + caretPos = self.GetCurrentPos() + + if caretPos > 0: + charBefore = self.GetCharAt(caretPos - 1) + styleBefore = self.GetStyleAt(caretPos - 1) + + # check before + if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR: + braceAtCaret = caretPos - 1 + + # check after + if braceAtCaret < 0: + charAfter = self.GetCharAt(caretPos) + styleAfter = self.GetStyleAt(caretPos) + + if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR: + braceAtCaret = caretPos + + if braceAtCaret >= 0: + braceOpposite = self.BraceMatch(braceAtCaret) + + if braceAtCaret != -1 and braceOpposite == -1: + self.BraceBadLight(braceAtCaret) + else: + self.BraceHighlight(braceAtCaret, braceOpposite) + #pt = self.PointFromPosition(braceOpposite) + #self.Refresh(True, wxRect(pt.x, pt.y, 5,5)) + #print pt + #self.Refresh(False) + + + def OnMarginClick(self, evt): + # fold and unfold as needed + if evt.GetMargin() == 2: + if evt.GetShift() and evt.GetControl(): + self.FoldAll() + else: + lineClicked = self.LineFromPosition(evt.GetPosition()) + + if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG: + if evt.GetShift(): + self.SetFoldExpanded(lineClicked, True) + self.Expand(lineClicked, True, True, 1) + elif evt.GetControl(): + if self.GetFoldExpanded(lineClicked): + self.SetFoldExpanded(lineClicked, False) + self.Expand(lineClicked, False, True, 0) + else: + self.SetFoldExpanded(lineClicked, True) + self.Expand(lineClicked, True, True, 100) + else: + self.ToggleFold(lineClicked) + + + def FoldAll(self): + lineCount = self.GetLineCount() + expanding = True + + # find out if we are folding or unfolding + for lineNum in range(lineCount): + if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG: + expanding = not self.GetFoldExpanded(lineNum) + break + + lineNum = 0 + + while lineNum < lineCount: + level = self.GetFoldLevel(lineNum) + if level & stc.STC_FOLDLEVELHEADERFLAG and \ + (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE: + + if expanding: + self.SetFoldExpanded(lineNum, True) + lineNum = self.Expand(lineNum, True) + lineNum = lineNum - 1 + else: + lastChild = self.GetLastChild(lineNum, -1) + self.SetFoldExpanded(lineNum, False) + + if lastChild > lineNum: + self.HideLines(lineNum+1, lastChild) + + lineNum = lineNum + 1 + + + + def Expand(self, line, doExpand, force=False, visLevels=0, level=-1): + lastChild = self.GetLastChild(line, level) + line = line + 1 + + while line <= lastChild: + if force: + if visLevels > 0: + self.ShowLines(line, line) + else: + self.HideLines(line, line) + else: + if doExpand: + self.ShowLines(line, line) + + if level == -1: + level = self.GetFoldLevel(line) + + if level & stc.STC_FOLDLEVELHEADERFLAG: + if force: + if visLevels > 1: + self.SetFoldExpanded(line, True) + else: + self.SetFoldExpanded(line, False) + + line = self.Expand(line, doExpand, force, visLevels-1) + + else: + if doExpand and self.GetFoldExpanded(line): + line = self.Expand(line, True, force, visLevels-1) + else: + line = self.Expand(line, False, force, visLevels-1) + else: + line = line + 1 + + return line + + +#------------------------------------------------------------------------------- +# PythonEditor Main Frame Class +#------------------------------------------------------------------------------- + + +[ID_PYTHONEDITORFRAME, +] = [wx.NewId() for _init_ctrls in range(1)] + +class PythonEditorFrame(wx.Frame): + + if wx.VERSION < (2, 6, 0): + def Bind(self, event, function, id = None): + if id is not None: + event(self, id, function) + else: + event(self, function) + + def _init_coll_EditMenu_Items(self, parent): + AppendMenu(parent, help='', id=wx.ID_REFRESH, + kind=wx.ITEM_NORMAL, text=u'Refresh\tCTRL+R') + AppendMenu(parent, help='', id=wx.ID_UNDO, + kind=wx.ITEM_NORMAL, text=u'Undo\tCTRL+Z') + AppendMenu(parent, help='', id=wx.ID_REDO, + kind=wx.ITEM_NORMAL, text=u'Redo\tCTRL+Y') + self.Bind(wx.EVT_MENU, self.OnRefreshMenu, id=wx.ID_REFRESH) + self.Bind(wx.EVT_MENU, self.OnUndoMenu, id=wx.ID_UNDO) + self.Bind(wx.EVT_MENU, self.OnRedoMenu, id=wx.ID_REDO) + + def _init_coll_MenuBar_Menus(self, parent): + parent.Append(menu=self.EditMenu, title=u'&Edit') + + def _init_utils(self): + self.MenuBar = wx.MenuBar() + + self.EditMenu = wx.Menu(title='') + + self._init_coll_MenuBar_Menus(self.MenuBar) + self._init_coll_EditMenu_Items(self.EditMenu) + + def _init_ctrls(self, prnt): + wx.Frame.__init__(self, id=ID_PYTHONEDITORFRAME, name=u'PythonEditor', + parent=prnt, pos=wx.DefaultPosition, size=wx.Size(800, 650), + style=wx.DEFAULT_FRAME_STYLE, title=u'PythonEditor') + self._init_utils() + self.SetClientSize(wx.Size(1000, 600)) + self.SetMenuBar(self.MenuBar) + self.Bind(wx.EVT_CLOSE, self.OnCloseFrame) + + self.Bind(wx.EVT_MENU, self.OnSaveMenu, id=wx.ID_SAVE) + accel = wx.AcceleratorTable([wx.AcceleratorEntry(wx.ACCEL_CTRL, 83, wx.ID_SAVE)]) + self.SetAcceleratorTable(accel) + + if wx.VERSION >= (2, 8, 0): + self.AUIManager = wx.aui.AuiManager(self) + self.AUIManager.SetDockSizeConstraint(0.5, 0.5) + + self.PythonEdited = PythonEditor(self, self, self.Controler) + if wx.VERSION < (2, 8, 0): + self.MainSizer = wx.BoxSizer(wx.VERTICAL) + self.MainSizer.AddWindow(self.PythonEdited, 0, border=0, flag=wx.GROW) + self.SetSizer(self.MainSizer) + else: + self.AUIManager.AddPane(self.PythonEdited, wx.aui.AuiPaneInfo().CentrePane()) + + self.StatusBar = wx.StatusBar( name='HelpBar', + parent=self, style=wx.ST_SIZEGRIP) + self.SetStatusBar(self.StatusBar) + + if wx.VERSION >= (2, 8, 0): + self.AUIManager.Update() + + def __init__(self, parent, controler): + self.Controler = controler + + self._init_ctrls(parent) + + self.PythonEdited.RefreshView() + self.RefreshTitle() + self.RefreshEditMenu() + + def OnCloseFrame(self, event): + if wx.VERSION >= (2, 8, 0): + self.AUIManager.UnInit() + if getattr(self, "_onclose", None) is not None: + self._onclose() + event.Skip() + + def OnSaveMenu(self, event): + if getattr(self, "_onsave", None) != None: + self._onsave() + self.RefreshTitle() + self.RefreshEditMenu() + event.Skip() + + def RefreshTitle(self): + self.SetTitle("PythonEditor - %s"%self.Controler.GetFilename()) + +#------------------------------------------------------------------------------- +# Edit Project Menu Functions +#------------------------------------------------------------------------------- + + def RefreshEditMenu(self): + undo, redo = self.Controler.GetBufferState() + self.EditMenu.Enable(wx.ID_UNDO, undo) + self.EditMenu.Enable(wx.ID_REDO, redo) + + def OnRefreshMenu(self, event): + self.PythonEdited.RefreshView() + event.Skip() + + def OnUndoMenu(self, event): + self.Controler.LoadPrevious() + self.PythonEdited.RefreshView() + self.RefreshTitle() + self.RefreshEditMenu() + event.Skip() + + def OnRedoMenu(self, event): + self.Controler.LoadNext() + self.PythonEdited.RefreshView() + self.RefreshTitle() + self.RefreshEditMenu() + event.Skip() + diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/README Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,1 @@ +Asynchronous \ No newline at end of file diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/__init__.py Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,1 @@ +from python import * diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/modules/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/modules/__init__.py Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,13 @@ +from os import listdir, path + +_base_path = path.split(__file__)[0] + +__all__ = [name for name in listdir(_base_path) if path.isdir(path.join(_base_path, name)) and name.upper() != "CVS" or name.endswith(".py") and not name.startswith("__")] + +helps = [] +for name in __all__: + helpfilename = path.join(_base_path, name, "README") + if path.isfile(helpfilename): + helps.append(open(helpfilename).readline().strip()) + else: + helps.append(name) diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/plc_python.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/plc_python.c Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,214 @@ +/* + * Python Asynchronous execution code + * + * PLC put python commands in a fifo, respecting execution order + * with the help of C pragmas inserted in python_eval FB code + * + * Buffer content is read asynchronously, (from non real time part), + * commands are executed and result stored for later use by PLC. + * + * In this implementation, fifo is a list of pointer to python_eval + * function blocks structures. Some local variables have been added in + * python_eval interface. We use those local variables as buffer and state + * flags. + * + * */ + +#include "iec_types_all.h" +#include "POUS.h" +#include + +/* The fifo (fixed size, as number of FB is fixed) */ +static PYTHON_EVAL* EvalFBs[%(python_eval_fb_count)d]; +/* Producer and consumer cursors */ +static int Current_PLC_EvalFB; +static int Current_Python_EvalFB; + +/* A global IEC-Python gateway state, for use inside python_eval FBs*/ +static int PythonState; +#define PYTHON_LOCKED_BY_PYTHON 0 +#define PYTHON_LOCKED_BY_PLC 1 +#define PYTHON_MUSTWAKEUP 2 +#define PYTHON_FINISHED 4 + +/* Each python_eval FunctionBlock have it own state */ +#define PYTHON_FB_FREE 0 +#define PYTHON_FB_REQUESTED 1 +#define PYTHON_FB_PROCESSING 2 +#define PYTHON_FB_ANSWERED 3 + +int WaitPythonCommands(void); +void UnBlockPythonCommands(void); +int TryLockPython(void); +void UnLockPython(void); +void LockPython(void); + +int __init_%(location)s() +{ + int i; + /* Initialize cursors */ + Current_Python_EvalFB = 0; + Current_PLC_EvalFB = 0; + PythonState = PYTHON_LOCKED_BY_PYTHON; + for(i = 0; i < %(python_eval_fb_count)d; i++) + EvalFBs[i] = NULL; + return 0; +} + +void __cleanup_%(location)s() +{ + PythonState = PYTHON_FINISHED; + UnBlockPythonCommands(); +} + +void __retrieve_%(location)s() +{ + /* Check Python thread is not being + * modifying internal python_eval data */ + PythonState = TryLockPython() ? + PYTHON_LOCKED_BY_PLC : + PYTHON_LOCKED_BY_PYTHON; + /* If python thread _is_ in, then PythonState remains PYTHON_LOCKED_BY_PYTHON + * and python_eval will no do anything */ +} + +void __publish_%(location)s() +{ + if(PythonState & PYTHON_LOCKED_BY_PLC){ + /* If runnig PLC did push something in the fifo*/ + if(PythonState & PYTHON_MUSTWAKEUP){ + /* WakeUp python thread */ + UnBlockPythonCommands(); + } + UnLockPython(); + } +} +/** + * Called by the PLC, each time a python_eval + * FB instance is executed + */ +void __PythonEvalFB(int poll, PYTHON_EVAL* data__) +{ + /* detect rising edge on TRIG to trigger evaluation */ + if(((data__->TRIG && !data__->TRIGM1) || + /* polling is equivalent to trig on value rather than on rising edge*/ + (poll && data__->TRIG )) && + /* trig only if not already trigged */ + data__->TRIGGED == 0){ + /* mark as trigged */ + data__->TRIGGED = 1; + /* make a safe copy of the code */ + data__->PREBUFFER = data__->CODE; + } + /* retain value for next rising edge detection */ + data__->TRIGM1 = data__->TRIG; + + /* python thread is not in ? */ + if( PythonState & PYTHON_LOCKED_BY_PLC){ + /* if some answer are waiting, publish*/ + if(data__->STATE == PYTHON_FB_ANSWERED){ + /* Copy buffer content into result*/ + data__->RESULT = data__->BUFFER; + /* signal result presece to PLC*/ + data__->ACK = 1; + /* Mark as free */ + data__->STATE = PYTHON_FB_FREE; + /* mark as not trigged */ + if(!poll) + data__->TRIGGED = 0; + /*printf("__PythonEvalFB pop %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/ + }else if(poll){ + /* when in polling, no answer == ack down */ + data__->ACK = 0; + } + /* got the order to act ?*/ + if(data__->TRIGGED == 1 && + /* and not already being processed */ + data__->STATE == PYTHON_FB_FREE) + { + /* Enter the block in the fifo + /* Don't have to check if fifo cell is free + * as fifo size == FB count, and a FB cannot + * be requested twice */ + EvalFBs[Current_PLC_EvalFB] = data__; + /* copy into BUFFER local*/ + data__->BUFFER = data__->PREBUFFER; + /* Set ACK pin to low so that we can set a rising edge on result */ + if(!poll){ + /* when not polling, a new answer imply reseting ack*/ + data__->ACK = 0; + }else{ + /* when in polling, acting reset trigger */ + data__->TRIGGED = 0; + } + /* Mark FB busy */ + data__->STATE = PYTHON_FB_REQUESTED; + /* Have to wakeup python thread in case he was asleep */ + PythonState |= PYTHON_MUSTWAKEUP; + /*printf("__PythonEvalFB push %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/ + /* Get a new line */ + Current_PLC_EvalFB = (Current_PLC_EvalFB + 1) %% %(python_eval_fb_count)d; + } + } +} + +char* PythonIterator(char* result) +{ + char* next_command; + PYTHON_EVAL* data__; + //printf("PythonIterator result %%s\n", result); + /* take python mutex to prevent changing PLC data while PLC running */ + LockPython(); + /* Get current FB */ + data__ = EvalFBs[Current_Python_EvalFB]; + if(data__ && /* may be null at first run */ + data__->STATE == PYTHON_FB_PROCESSING){ /* some answer awaited*/ + /* If result not None */ + if(result){ + /* Get results len */ + data__->BUFFER.len = strlen(result); + /* prevent results overrun */ + if(data__->BUFFER.len > STR_MAX_LEN) + { + data__->BUFFER.len = STR_MAX_LEN; + /* TODO : signal error */ + } + /* Copy results to buffer */ + strncpy(data__->BUFFER.body, result, data__->BUFFER.len); + }else{ + data__->BUFFER.len = 0; + } + /* remove block from fifo*/ + EvalFBs[Current_Python_EvalFB] = NULL; + /* Mark block as answered */ + data__->STATE = PYTHON_FB_ANSWERED; + /* Get a new line */ + Current_Python_EvalFB = (Current_Python_EvalFB + 1) %% %(python_eval_fb_count)d; + //printf("PythonIterator ++ Current_Python_EvalFB %%d\n", Current_Python_EvalFB); + } + /* while next slot is empty */ + while(((data__ = EvalFBs[Current_Python_EvalFB]) == NULL) || + /* or doesn't contain command */ + data__->STATE != PYTHON_FB_REQUESTED) + { + UnLockPython(); + /* wait next FB to eval */ + //printf("PythonIterator wait\n"); + if(WaitPythonCommands()) return NULL; + /*emergency exit*/ + if(PythonState & PYTHON_FINISHED) return NULL; + LockPython(); + } + /* Mark block as processing */ + data__->STATE = PYTHON_FB_PROCESSING; + //printf("PythonIterator\n"); + /* make BUFFER a null terminated string */ + data__->BUFFER.body[data__->BUFFER.len] = 0; + /* next command is BUFFER */ + next_command = data__->BUFFER.body; + /* free python mutex */ + UnLockPython(); + /* return the next command to eval */ + return next_command; +} + diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/pous.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/pous.xml Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + N + + + + + + + TRIG + + + + + + + CODE + + + + + + + + + + + ACK + + + + + + + + + + + RESULT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + COUNTER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + USINT#1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + USINT#0 + + + + + + + + + + + COUNTER + + + + + + + + + + diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/python.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/python.py Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,280 @@ +import wx +import os +import modules +from plugger import PlugTemplate, opjimg +from PythonEditor import PythonEditorFrame + +from xml.dom import minidom +from xmlclass import * +import cPickle + +PythonClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "python_xsd.xsd")) + +#------------------------------------------------------------------------------- +# Undo Buffer for PythonCode +#------------------------------------------------------------------------------- + +# Length of the buffer +UNDO_BUFFER_LENGTH = 20 + +""" +Class implementing a buffer of changes made on the current editing model +""" +class UndoBuffer: + + # Constructor initialising buffer + def __init__(self, currentstate, issaved = False): + self.Buffer = [] + self.CurrentIndex = -1 + self.MinIndex = -1 + self.MaxIndex = -1 + # if current state is defined + if currentstate: + self.CurrentIndex = 0 + self.MinIndex = 0 + self.MaxIndex = 0 + # Initialising buffer with currentstate at the first place + for i in xrange(UNDO_BUFFER_LENGTH): + if i == 0: + self.Buffer.append(currentstate) + else: + self.Buffer.append(None) + # Initialising index of state saved + if issaved: + self.LastSave = 0 + else: + self.LastSave = -1 + + # Add a new state in buffer + def Buffering(self, currentstate): + self.CurrentIndex = (self.CurrentIndex + 1) % UNDO_BUFFER_LENGTH + self.Buffer[self.CurrentIndex] = currentstate + # Actualising buffer limits + self.MaxIndex = self.CurrentIndex + if self.MinIndex == self.CurrentIndex: + # If the removed state was the state saved, there is no state saved in the buffer + if self.LastSave == self.MinIndex: + self.LastSave = -1 + self.MinIndex = (self.MinIndex + 1) % UNDO_BUFFER_LENGTH + self.MinIndex = max(self.MinIndex, 0) + + # Return current state of buffer + def Current(self): + return self.Buffer[self.CurrentIndex] + + # Change current state to previous in buffer and return new current state + def Previous(self): + if self.CurrentIndex != self.MinIndex: + self.CurrentIndex = (self.CurrentIndex - 1) % UNDO_BUFFER_LENGTH + return self.Buffer[self.CurrentIndex] + return None + + # Change current state to next in buffer and return new current state + def Next(self): + if self.CurrentIndex != self.MaxIndex: + self.CurrentIndex = (self.CurrentIndex + 1) % UNDO_BUFFER_LENGTH + return self.Buffer[self.CurrentIndex] + return None + + # Return True if current state is the first in buffer + def IsFirst(self): + return self.CurrentIndex == self.MinIndex + + # Return True if current state is the last in buffer + def IsLast(self): + return self.CurrentIndex == self.MaxIndex + + # Note that current state is saved + def CurrentSaved(self): + self.LastSave = self.CurrentIndex + + # Return True if current state is saved + def IsCurrentSaved(self): + return self.LastSave == self.CurrentIndex + +class PythonCodeTemplate: + + def __init__(self): + + self.PluginMethods.insert(0, + {"bitmap" : opjimg("editPYTHONcode"), + "name" : _("Edit Python File"), + "tooltip" : _("Edit Python File"), + "method" : "_OpenView"}, + ) + + filepath = self.PythonFileName() + + self.Buffering = False + self.PythonCode = PythonClasses["Python"]() + self.PythonBuffer = UndoBuffer(self.Copy(self.PythonCode), False) + if os.path.isfile(filepath): + xmlfile = open(filepath, 'r') + tree = minidom.parse(xmlfile) + xmlfile.close() + + for child in tree.childNodes: + if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "Python": + self.PythonCode.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"]) + self.PythonBuffer = UndoBuffer(self.Copy(self.PythonCode), True) + else: + self.OnPlugSave() + + def PluginPath(self): + return os.path.join(self.PlugParent.PluginPath(), "modules", self.PlugType) + + def PythonFileName(self): + return os.path.join(self.PlugPath(), "python.xml") + + def GetFilename(self): + if self.PythonBuffer.IsCurrentSaved(): + return "python" + else: + return "~python~" + + def SetPythonCode(self, text): + self.PythonCode.settext(text) + + def GetPythonCode(self): + return self.PythonCode.gettext() + + _View = None + def _OpenView(self): + if not self._View: + def _onclose(): + self._View = None + def _onsave(): + self.GetPlugRoot().SaveProject() + self._View = PythonEditorFrame(self.GetPlugRoot().AppFrame, self) + self._View._onclose = _onclose + self._View._onsave = _onsave + self._View.Show() + + def OnPlugSave(self): + filepath = self.PythonFileName() + + text = "\n" + extras = {"xmlns":"http://www.w3.org/2001/XMLSchema", + "xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance", + "xsi:schemaLocation" : "python_xsd.xsd"} + text += self.PythonCode.generateXMLText("Python", 0, extras) + + xmlfile = open(filepath,"w") + xmlfile.write(text) + xmlfile.close() + + self.PythonBuffer.CurrentSaved() + return True + +#------------------------------------------------------------------------------- +# Current Buffering Management Functions +#------------------------------------------------------------------------------- + + """ + Return a copy of the project + """ + def Copy(self, model): + return cPickle.loads(cPickle.dumps(model)) + + def BufferPython(self): + self.PythonBuffer.Buffering(self.Copy(self.PythonCode)) + + def StartBuffering(self): + self.PythonBuffer.Buffering(self.PythonCode) + self.Buffering = True + + def EndBuffering(self): + if self.Buffering: + self.PythonCode = self.Copy(self.PythonCode) + self.Buffering = False + + def PythonCodeIsSaved(self): + if self.PythonBuffer: + return self.PythonBuffer.IsCurrentSaved() + else: + return True + + def LoadPrevious(self): + self.PythonCode = self.Copy(self.PythonBuffer.Previous()) + + def LoadNext(self): + self.PythonCode = self.Copy(self.PythonBuffer.Next()) + + def GetBufferState(self): + first = self.PythonBuffer.IsFirst() + last = self.PythonBuffer.IsLast() + return not first, not last + +def _GetClassFunction(name): + def GetRootClass(): + __import__("plugins.python.modules." + name) + return getattr(modules, name).RootClass + return GetRootClass + +class RootClass(PythonCodeTemplate): + + # For root object, available Childs Types are modules of the modules packages. + PlugChildsTypes = [(name, _GetClassFunction(name), help) for name, help in zip(modules.__all__,modules.helps)] + + def PluginPath(self): + return os.path.join(self.PlugParent.PluginPath(), self.PlugType) + + def PlugGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + current_location = self.GetCurrentLocation() + # define a unique name for the generated C file + location_str = "_".join(map(lambda x:str(x), current_location)) + + plugin_root = self.GetPlugRoot() + plugin_root.GetIECProgramsAndVariables() + + plc_python_filepath = os.path.join(os.path.split(__file__)[0], "plc_python.c") + plc_python_file = open(plc_python_filepath, 'r') + plc_python_code = plc_python_file.read() + plc_python_file.close() + python_eval_fb_list = [] + for v in plugin_root._VariablesList : + if v["vartype"] == "FB" and v["type"] in ["PYTHON_EVAL","PYTHON_POLL"]: + python_eval_fb_list.append(v) + python_eval_fb_count = max(1, len(python_eval_fb_list)) + + # prepare python code + plc_python_code = plc_python_code % { + "python_eval_fb_count": python_eval_fb_count, + "location": location_str} + + Gen_Pythonfile_path = os.path.join(buildpath, "python_%s.c"%location_str) + pythonfile = open(Gen_Pythonfile_path,'w') + pythonfile.write(plc_python_code) + pythonfile.close() + + runtimefile_path = os.path.join(buildpath, "runtime_%s.py"%location_str) + runtimefile = open(runtimefile_path, 'w') + runtimefile.write(self.GetPythonCode()) + runtimefile.write(""" +def _runtime_%(location)s_begin(): + print "runtime_begin" + +def _runtime_%(location)s_cleanup(): + print "runtime_cleanup" + +""" % {"location": location_str}) + runtimefile.close() + + if wx.Platform == '__WXMSW__': + matiec_flags = " -I../../matiec/lib" + else: + matiec_flags = " -I../matiec/lib" + + return [(Gen_Pythonfile_path, matiec_flags)], "", True, ("runtime_%s.py"%location_str, file(runtimefile_path,"rb")) diff -r a7f58414dea0 -r cd90e4c10261 plugins/python/python_xsd.xsd --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/python/python_xsd.xsd Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,18 @@ + + + + + + + Formatted text according to parts of XHTML 1.1 + + + + + + + diff -r a7f58414dea0 -r cd90e4c10261 pous.xml --- a/pous.xml Wed Jul 29 10:49:31 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,457 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - N - - - - - - - TRIG - - - - - - - CODE - - - - - - - - - - - ACK - - - - - - - - - - - RESULT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - COUNTER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USINT#1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USINT#0 - - - - - - - - - - - COUNTER - - - - - - - - - - diff -r a7f58414dea0 -r cd90e4c10261 runtime/PLCObject.py --- a/runtime/PLCObject.py Wed Jul 29 10:49:31 2009 +0200 +++ b/runtime/PLCObject.py Wed Jul 29 15:17:10 2009 +0200 @@ -53,6 +53,7 @@ self.PLCStatus = "Stopped" self.PLClibraryHandle = None self.PLClibraryLock = Lock() + self.DummyIteratorLock = None # Creates fake C funcs proxies self._FreePLC() self.daemon = daemon @@ -93,10 +94,30 @@ self._startPLC.restype = ctypes.c_int self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)] - def StopPLCLock(): - self.PLClibraryLock.acquire() - self.PLClibraryHandle.stopPLC() - self.PLClibraryLock.release() + self.DummyIteratorLock = Lock() + self.DummyIteratorLock.acquire() + + self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None) + if self._PythonIterator is not None: + self._PythonIterator.restype = ctypes.c_char_p + self._PythonIterator.argtypes = [ctypes.c_char_p] + + def StopPLCLock(): + self.PLClibraryLock.acquire() + self.PLClibraryHandle.stopPLC() + self.PLClibraryLock.release() + + else: + def DummyIterator(res): + self.DummyIteratorLock.acquire() + return None + self._PythonIterator = DummyIterator + + def StopPLCLock(): + self.PLClibraryLock.acquire() + self.PLClibraryHandle.stopPLC() + self.DummyIteratorLock.release() + self.PLClibraryLock.release() self._stopPLC = StopPLCLock self._stopPLC.restype = None @@ -123,10 +144,6 @@ self._resumeDebug = self.PLClibraryHandle.resumeDebug self._resumeDebug.restype = None - - self._PythonIterator = self.PLClibraryHandle.PythonIterator - self._PythonIterator.restype = ctypes.c_char_p - self._PythonIterator.argtypes = [ctypes.c_char_p] return True except: @@ -188,59 +205,78 @@ def PrepareRuntimePy(self): self.python_threads_vars = globals().copy() self.python_threads_vars["WorkingDir"] = self.workingdir - pyfile = os.path.join(self.workingdir, "runtime.py") - hmifile = os.path.join(self.workingdir, "hmi.py") - if os.path.exists(hmifile): - try: - execfile(hmifile, self.python_threads_vars) - if os.path.exists(pyfile): - try: - # TODO handle exceptions in runtime.py - # pyfile may redefine _runtime_cleanup - # or even call _PythonThreadProc itself. - execfile(pyfile, self.python_threads_vars) - except: - PLCprint(traceback.format_exc()) - if self.python_threads_vars.has_key('wx'): - wx = self.python_threads_vars['wx'] - # try to instanciate the first frame found. - for name, obj in self.python_threads_vars.iteritems(): - # obj is a class - if type(obj)==type(type) and issubclass(obj,wx.Frame): - def create_frame(): - self.hmi_frame = obj(None) - self.python_threads_vars[name] = self.hmi_frame - # keep track of class... never know - self.python_threads_vars['Class_'+name] = obj - self.hmi_frame.Bind(wx.EVT_CLOSE, OnCloseFrame) - self.hmi_frame.Show() - - def OnCloseFrame(evt): - wx.MessageBox(_("Please stop PLC to close")) - create_frame() - break - except: - PLCprint(traceback.format_exc()) - elif os.path.exists(pyfile): - try: - # TODO handle exceptions in runtime.py - # pyfile may redefine _runtime_cleanup - # or even call _PythonThreadProc itself. - execfile(pyfile, self.python_threads_vars) - except: - PLCprint(traceback.format_exc()) - runtime_begin = self.python_threads_vars.get("_runtime_begin",None) - if runtime_begin is not None: + self.python_threads_vars["_runtime_begin"] = [] + self.python_threads_vars["_runtime_cleanup"] = [] +# pyfile = os.path.join(self.workingdir, "runtime.py") +# hmifile = os.path.join(self.workingdir, "hmi.py") +# if os.path.exists(hmifile): +# try: +# execfile(hmifile, self.python_threads_vars) +# if os.path.exists(pyfile): +# try: +# # TODO handle exceptions in runtime.py +# # pyfile may redefine _runtime_cleanup +# # or even call _PythonThreadProc itself. +# execfile(pyfile, self.python_threads_vars) +# except: +# PLCprint(traceback.format_exc()) +# if self.python_threads_vars.has_key('wx'): +# wx = self.python_threads_vars['wx'] +# # try to instanciate the first frame found. +# for name, obj in self.python_threads_vars.iteritems(): +# # obj is a class +# if type(obj)==type(type) and issubclass(obj,wx.Frame): +# def create_frame(): +# self.hmi_frame = obj(None) +# self.python_threads_vars[name] = self.hmi_frame +# # keep track of class... never know +# self.python_threads_vars['Class_'+name] = obj +# self.hmi_frame.Bind(wx.EVT_CLOSE, OnCloseFrame) +# self.hmi_frame.Show() +# +# def OnCloseFrame(evt): +# wx.MessageBox(_("Please stop PLC to close")) +# create_frame() +# break +# except: +# PLCprint(traceback.format_exc()) +# elif os.path.exists(pyfile): +# try: +# # TODO handle exceptions in runtime.py +# # pyfile may redefine _runtime_cleanup +# # or even call _PythonThreadProc itself. +# execfile(pyfile, self.python_threads_vars) +# except: +# PLCprint(traceback.format_exc()) + for filename in os.listdir(self.workingdir): + name, ext = os.path.splitext(filename) + if name.startswith("runtime") and ext == ".py": + try: + # TODO handle exceptions in runtime.py + # pyfile may redefine _runtime_cleanup + # or even call _PythonThreadProc itself. + execfile(os.path.join(self.workingdir, filename), self.python_threads_vars) + except: + PLCprint(traceback.format_exc()) + runtime_begin = self.python_threads_vars.get("_%s_begin" % name, None) + if runtime_begin is not None: + self.python_threads_vars["_runtime_begin"].append(runtime_begin) + runtime_cleanup = self.python_threads_vars.get("_%s_cleanup" % name, None) + if runtime_cleanup is not None: + self.python_threads_vars["_runtime_cleanup"].append(runtime_cleanup) + + for runtime_begin in self.python_threads_vars.get("_runtime_begin", []): runtime_begin() def FinishRuntimePy(self): - runtime_cleanup = None - if self.python_threads_vars is not None: - runtime_cleanup = self.python_threads_vars.get("_runtime_cleanup",None) - if runtime_cleanup is not None: - runtime_cleanup() - if self.hmi_frame is not None: - self.hmi_frame.Destroy() + for runtime_cleanup in self.python_threads_vars.get("_runtime_cleanup", []): + runtime_cleanup() +# if self.python_threads_vars is not None: +# runtime_cleanup = self.python_threads_vars.get("_runtime_cleanup",None) +# if runtime_cleanup is not None: +# runtime_cleanup() +# if self.hmi_frame is not None: +# self.hmi_frame.Destroy() self.python_threads_vars = None def PythonThreadProc(self, debug): diff -r a7f58414dea0 -r cd90e4c10261 targets/plc_common_main.c --- a/targets/plc_common_main.c Wed Jul 29 10:49:31 2009 +0200 +++ b/targets/plc_common_main.c Wed Jul 29 15:17:10 2009 +0200 @@ -18,11 +18,6 @@ /*void __retrieve_debug(void);*/ void __publish_debug(void); -void __init_python(void); -void __cleanup_python(void); -void __retrieve_python(void); -void __publish_python(void); - /* * Variables used by generated C softPLC and plugins **/ @@ -47,14 +42,10 @@ %(retrieve_calls)s - __retrieve_python(); - /*__retrieve_debug();*/ config_run__(__tick); - __publish_python(); - __publish_debug(); %(publish_calls)s @@ -71,7 +62,6 @@ setlocale(LC_NUMERIC, "C"); config_init__(); __init_debug(); - __init_python(); %(init_calls)s return 0; } @@ -82,7 +72,6 @@ { %(cleanup_calls)s __cleanup_debug(); - __cleanup_python(); } diff -r a7f58414dea0 -r cd90e4c10261 targets/plc_python.c --- a/targets/plc_python.c Wed Jul 29 10:49:31 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,213 +0,0 @@ -/* - * Python Asynchronous execution code - * - * PLC put python commands in a fifo, respecting execution order - * with the help of C pragmas inserted in python_eval FB code - * - * Buffer content is read asynchronously, (from non real time part), - * commands are executed and result stored for later use by PLC. - * - * In this implementation, fifo is a list of pointer to python_eval - * function blocks structures. Some local variables have been added in - * python_eval interface. We use those local variables as buffer and state - * flags. - * - * */ - -#include "iec_types_all.h" -#include "POUS.h" -#include - -/* The fifo (fixed size, as number of FB is fixed) */ -static PYTHON_EVAL* EvalFBs[%(python_eval_fb_count)d]; -/* Producer and consumer cursors */ -static int Current_PLC_EvalFB; -static int Current_Python_EvalFB; - -/* A global IEC-Python gateway state, for use inside python_eval FBs*/ -static int PythonState; -#define PYTHON_LOCKED_BY_PYTHON 0 -#define PYTHON_LOCKED_BY_PLC 1 -#define PYTHON_MUSTWAKEUP 2 -#define PYTHON_FINISHED 4 - -/* Each python_eval FunctionBlock have it own state */ -#define PYTHON_FB_FREE 0 -#define PYTHON_FB_REQUESTED 1 -#define PYTHON_FB_PROCESSING 2 -#define PYTHON_FB_ANSWERED 3 - -int WaitPythonCommands(void); -void UnBlockPythonCommands(void); -int TryLockPython(void); -void UnLockPython(void); -void LockPython(void); - -void __init_python() -{ - int i; - /* Initialize cursors */ - Current_Python_EvalFB = 0; - Current_PLC_EvalFB = 0; - PythonState = PYTHON_LOCKED_BY_PYTHON; - for(i = 0; i < %(python_eval_fb_count)d; i++) - EvalFBs[i] = NULL; -} - -void __cleanup_python() -{ - PythonState = PYTHON_FINISHED; - UnBlockPythonCommands(); -} - -void __retrieve_python() -{ - /* Check Python thread is not being - * modifying internal python_eval data */ - PythonState = TryLockPython() ? - PYTHON_LOCKED_BY_PLC : - PYTHON_LOCKED_BY_PYTHON; - /* If python thread _is_ in, then PythonState remains PYTHON_LOCKED_BY_PYTHON - * and python_eval will no do anything */ -} - -void __publish_python() -{ - if(PythonState & PYTHON_LOCKED_BY_PLC){ - /* If runnig PLC did push something in the fifo*/ - if(PythonState & PYTHON_MUSTWAKEUP){ - /* WakeUp python thread */ - UnBlockPythonCommands(); - } - UnLockPython(); - } -} -/** - * Called by the PLC, each time a python_eval - * FB instance is executed - */ -void __PythonEvalFB(int poll, PYTHON_EVAL* data__) -{ - /* detect rising edge on TRIG to trigger evaluation */ - if(((data__->TRIG && !data__->TRIGM1) || - /* polling is equivalent to trig on value rather than on rising edge*/ - (poll && data__->TRIG )) && - /* trig only if not already trigged */ - data__->TRIGGED == 0){ - /* mark as trigged */ - data__->TRIGGED = 1; - /* make a safe copy of the code */ - data__->PREBUFFER = data__->CODE; - } - /* retain value for next rising edge detection */ - data__->TRIGM1 = data__->TRIG; - - /* python thread is not in ? */ - if( PythonState & PYTHON_LOCKED_BY_PLC){ - /* if some answer are waiting, publish*/ - if(data__->STATE == PYTHON_FB_ANSWERED){ - /* Copy buffer content into result*/ - data__->RESULT = data__->BUFFER; - /* signal result presece to PLC*/ - data__->ACK = 1; - /* Mark as free */ - data__->STATE = PYTHON_FB_FREE; - /* mark as not trigged */ - if(!poll) - data__->TRIGGED = 0; - /*printf("__PythonEvalFB pop %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/ - }else if(poll){ - /* when in polling, no answer == ack down */ - data__->ACK = 0; - } - /* got the order to act ?*/ - if(data__->TRIGGED == 1 && - /* and not already being processed */ - data__->STATE == PYTHON_FB_FREE) - { - /* Enter the block in the fifo - /* Don't have to check if fifo cell is free - * as fifo size == FB count, and a FB cannot - * be requested twice */ - EvalFBs[Current_PLC_EvalFB] = data__; - /* copy into BUFFER local*/ - data__->BUFFER = data__->PREBUFFER; - /* Set ACK pin to low so that we can set a rising edge on result */ - if(!poll){ - /* when not polling, a new answer imply reseting ack*/ - data__->ACK = 0; - }else{ - /* when in polling, acting reset trigger */ - data__->TRIGGED = 0; - } - /* Mark FB busy */ - data__->STATE = PYTHON_FB_REQUESTED; - /* Have to wakeup python thread in case he was asleep */ - PythonState |= PYTHON_MUSTWAKEUP; - /*printf("__PythonEvalFB push %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/ - /* Get a new line */ - Current_PLC_EvalFB = (Current_PLC_EvalFB + 1) %% %(python_eval_fb_count)d; - } - } -} - -char* PythonIterator(char* result) -{ - char* next_command; - PYTHON_EVAL* data__; - //printf("PythonIterator result %%s\n", result); - /* take python mutex to prevent changing PLC data while PLC running */ - LockPython(); - /* Get current FB */ - data__ = EvalFBs[Current_Python_EvalFB]; - if(data__ && /* may be null at first run */ - data__->STATE == PYTHON_FB_PROCESSING){ /* some answer awaited*/ - /* If result not None */ - if(result){ - /* Get results len */ - data__->BUFFER.len = strlen(result); - /* prevent results overrun */ - if(data__->BUFFER.len > STR_MAX_LEN) - { - data__->BUFFER.len = STR_MAX_LEN; - /* TODO : signal error */ - } - /* Copy results to buffer */ - strncpy(data__->BUFFER.body, result, data__->BUFFER.len); - }else{ - data__->BUFFER.len = 0; - } - /* remove block from fifo*/ - EvalFBs[Current_Python_EvalFB] = NULL; - /* Mark block as answered */ - data__->STATE = PYTHON_FB_ANSWERED; - /* Get a new line */ - Current_Python_EvalFB = (Current_Python_EvalFB + 1) %% %(python_eval_fb_count)d; - //printf("PythonIterator ++ Current_Python_EvalFB %%d\n", Current_Python_EvalFB); - } - /* while next slot is empty */ - while(((data__ = EvalFBs[Current_Python_EvalFB]) == NULL) || - /* or doesn't contain command */ - data__->STATE != PYTHON_FB_REQUESTED) - { - UnLockPython(); - /* wait next FB to eval */ - //printf("PythonIterator wait\n"); - if(WaitPythonCommands()) return NULL; - /*emergency exit*/ - if(PythonState & PYTHON_FINISHED) return NULL; - LockPython(); - } - /* Mark block as processing */ - data__->STATE = PYTHON_FB_PROCESSING; - //printf("PythonIterator\n"); - /* make BUFFER a null terminated string */ - data__->BUFFER.body[data__->BUFFER.len] = 0; - /* next command is BUFFER */ - next_command = data__->BUFFER.body; - /* free python mutex */ - UnLockPython(); - /* return the next command to eval */ - return next_command; -} - diff -r a7f58414dea0 -r cd90e4c10261 tests/python/plc.xml --- a/tests/python/plc.xml Wed Jul 29 10:49:31 2009 +0200 +++ b/tests/python/plc.xml Wed Jul 29 15:17:10 2009 +0200 @@ -8,7 +8,7 @@ productVersion="0.0" creationDateTime="2008-12-14T16:21:19"/> + modificationDateTime="2009-07-29T14:39:02"> @@ -466,8 +466,8 @@ - - + + diff -r a7f58414dea0 -r cd90e4c10261 tests/python/python@python/baseplugin.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/python/python@python/baseplugin.xml Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,2 @@ + + diff -r a7f58414dea0 -r cd90e4c10261 tests/python/python@python/python.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/python/python@python/python.xml Wed Jul 29 15:17:10 2009 +0200 @@ -0,0 +1,8 @@ + + + + diff -r a7f58414dea0 -r cd90e4c10261 tests/python/runtime.py --- a/tests/python/runtime.py Wed Jul 29 10:49:31 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -import time,sys -def myprintfunc(arg): - print arg - sys.stdout.flush() - return arg