class TextViewer(EditorPanel): ID = ID_TEXTVIEWER 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_Editor(self, prnt): self.Editor = CustomStyledTextCtrl(id=ID_TEXTVIEWERTEXTCTRL, parent=prnt, name="TextViewer", size=wx.Size(0, 0), style=0) self.Editor.ParentWindow = self self.Editor.CmdKeyAssign(ord('+'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMIN) self.Editor.CmdKeyAssign(ord('-'), wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMOUT) self.Editor.SetViewWhiteSpace(False) self.Editor.SetLexer(wx.stc.STC_LEX_CONTAINER) # Global default styles for all languages self.Editor.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces) self.Editor.StyleClearAll() # Reset all to be like the default self.Editor.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,size:%(size)d" % faces) self.Editor.SetSelBackground(1, "#E0E0E0") # Highlighting styles self.Editor.StyleSetSpec(STC_PLC_WORD, "fore:#00007F,bold,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_VARIABLE, "fore:#7F0000,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_PARAMETER, "fore:#7F007F,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_FUNCTION, "fore:#7F7F00,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_COMMENT, "fore:#7F7F7F,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_NUMBER, "fore:#007F7F,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_STRING, "fore:#007F00,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_JUMP, "fore:#FF7FFF,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_ERROR, "fore:#FF0000,back:#FFFF00,size:%(size)d" % faces) self.Editor.StyleSetSpec(STC_PLC_SEARCH_RESULT, "fore:#FFFFFF,back:#FFA500,size:%(size)d" % faces) # Indicators styles self.Editor.IndicatorSetStyle(0, wx.stc.STC_INDIC_SQUIGGLE) if self.ParentWindow is not None and self.Controler is not None: self.Editor.IndicatorSetForeground(0, wx.RED) else: self.Editor.IndicatorSetForeground(0, wx.WHITE) # Line numbers in the margin self.Editor.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER) self.Editor.SetMarginWidth(1, 50) # Folding self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS, "white", "#808080") self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS, "white", "#808080") self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_VLINE, "white", "#808080") self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_LCORNER, "white", "#808080") self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080") self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080") self.Editor.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER, "white", "#808080") # Indentation size self.Editor.SetTabWidth(2) self.Editor.SetUseTabs(0) self.Editor.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT | wx.stc.STC_MOD_BEFOREDELETE | wx.stc.STC_PERFORMED_USER) self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded, id=ID_TEXTVIEWERTEXTCTRL) self.Editor.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick) self.Editor.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnUpdateUI) self.Editor.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) if self.Controler is not None: self.Editor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_TEXTVIEWERTEXTCTRL) self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_TEXTVIEWERTEXTCTRL) def __init__(self, parent, tagname, window, controler, debug=False, instancepath=""): if tagname != "" and controler is not None: self.VARIABLE_PANEL_TYPE = controler.GetPouType(tagname.split("::")[1]) EditorPanel.__init__(self, parent, tagname, window, controler, debug) self.Keywords = [] self.Variables = {} self.Functions = {} self.TypeNames = [] self.Jumps = [] self.EnumeratedValues = [] self.DisableEvents = True self.TextSyntax = None self.CurrentAction = None self.InstancePath = instancepath self.ContextStack = [] self.CallStack = [] self.ResetSearchResults() self.RefreshHighlightsTimer = wx.Timer(self, -1) self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer) def __del__(self): self.RefreshHighlightsTimer.Stop() def GetTitle(self): if self.Debug or self.TagName == "": if len(self.InstancePath) > 15: return "..." + self.InstancePath[-12:] return self.InstancePath return EditorPanel.GetTitle(self) def GetInstancePath(self): return self.InstancePath def IsViewing(self, tagname): if self.Debug or self.TagName == "": return self.InstancePath == tagname else: return self.TagName == tagname def GetText(self): return self.Editor.GetText() def SetText(self, text): self.Editor.SetText(text) def SelectAll(self): self.Editor.SelectAll() def Colourise(self, start, end): self.Editor.Colourise(start, end) def StartStyling(self, pos, mask): self.Editor.StartStyling(pos, mask) def SetStyling(self, length, style): self.Editor.SetStyling(length, style) def GetCurrentPos(self): return self.Editor.GetCurrentPos() def ResetSearchResults(self): self.Highlights = [] self.SearchParams = None self.SearchResults = None self.CurrentFindHighlight = None def OnModification(self, event): if not self.DisableEvents: mod_type = event.GetModificationType() if mod_type & wx.stc.STC_MOD_BEFOREINSERT: if self.CurrentAction is 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()) wx.CallAfter(self.RefreshModel) elif mod_type & wx.stc.STC_MOD_BEFOREDELETE: if self.CurrentAction is 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()) wx.CallAfter(self.RefreshModel) event.Skip() def OnDoDrop(self, event): try: values = eval(event.GetDragText()) except Exception: values = event.GetDragText() if isinstance(values, tuple): message = None if values[1] in ["program", "debug"]: event.SetDragText("") elif values[1] in ["functionBlock", "function"]: blocktype = values[0] blockname = values[2] if len(values) > 3: blockinputs = values[3] else: blockinputs = None if values[1] != "function": if blockname == "": dialog = wx.TextEntryDialog(self.ParentWindow, _("Block name"), _("Please enter a block name"), "", wx.OK | wx.CANCEL | wx.CENTRE) if dialog.ShowModal() == wx.ID_OK: blockname = dialog.GetValue() else: event.SetDragText("") return dialog.Destroy() if blockname.upper() in [name.upper() for name in self.Controler.GetProjectPouNames(self.Debug)]: message = _("\"%s\" pou already exists!") % blockname elif blockname.upper() in [name.upper() for name in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]: message = _("\"%s\" element for this pou already exists!") % blockname else: self.Controler.AddEditedElementPouVar(self.TagName, values[0], blockname) self.RefreshVariablePanel() self.RefreshVariableTree() blockinfo = self.Controler.GetBlockType(blocktype, blockinputs, self.Debug) hint = ',\n '.join( [" " + fctdecl[0]+" := (*"+fctdecl[1]+"*)" for fctdecl in blockinfo["inputs"]] + [" " + fctdecl[0]+" => (*"+fctdecl[1]+"*)" for fctdecl in blockinfo["outputs"]]) if values[1] == "function": event.SetDragText(blocktype+"(\n "+hint+")") else: event.SetDragText(blockname+"(\n "+hint+")") elif values[1] == "location": pou_name, pou_type = self.Controler.GetEditedElementType(self.TagName, self.Debug) if len(values) > 2 and pou_type == "program": var_name = values[3] dlg = wx.TextEntryDialog( self.ParentWindow, _("Confirm or change variable name"), _('Variable Drop'), var_name) dlg.SetValue(var_name) var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None dlg.Destroy() if var_name is None: return elif var_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames(self.Debug)]: message = _("\"%s\" pou already exists!") % var_name elif var_name.upper() in [name.upper() for name in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]: message = _("\"%s\" element for this pou already exists!") % var_name else: location = values[0] if not location.startswith("%"): dialog = wx.SingleChoiceDialog( self.ParentWindow, _("Select a variable class:"), _("Variable class"), [_("Input"), _("Output"), _("Memory")], wx.DEFAULT_DIALOG_STYLE | wx.OK | wx.CANCEL) if dialog.ShowModal() == wx.ID_OK: selected = dialog.GetSelection() else: selected = None dialog.Destroy() if selected is None: event.SetDragText("") return if selected == 0: location = "%I" + location elif selected == 1: location = "%Q" + location else: location = "%M" + location if values[2] is not None: var_type = values[2] else: var_type = LOCATIONDATATYPES.get(location[2], ["BOOL"])[0] self.Controler.AddEditedElementPouVar( self.TagName, var_type, var_name, location=location, description=values[4]) self.RefreshVariablePanel() self.RefreshVariableTree() event.SetDragText(var_name) else: event.SetDragText("") elif values[1] == "NamedConstant": pou_name, pou_type = self.Controler.GetEditedElementType(self.TagName, self.Debug) if pou_type == "program": initval = values[0] var_name = values[3] dlg = wx.TextEntryDialog( self.ParentWindow, _("Confirm or change variable name"), _('Variable Drop'), var_name) dlg.SetValue(var_name) var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None dlg.Destroy() if var_name is None: return elif var_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames(self.Debug)]: message = _("\"%s\" pou already exists!") % var_name else: var_type = values[2] if not var_name.upper() in [name.upper() for name in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]: self.Controler.AddEditedElementPouVar(self.TagName, var_type, var_name, description=values[4], initval=initval) self.RefreshVariablePanel() self.RefreshVariableTree() event.SetDragText(var_name) elif values[1] == "Global": var_name = values[0] dlg = wx.TextEntryDialog( self.ParentWindow, _("Confirm or change variable name"), _('Variable Drop'), var_name) dlg.SetValue(var_name) var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None dlg.Destroy() if var_name is None: return elif var_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames(self.Debug)]: message = _("\"%s\" pou already exists!") % var_name else: if not var_name.upper() in [name.upper() for name in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]: self.Controler.AddEditedElementPouExternalVar(self.TagName, values[2], var_name) self.RefreshVariablePanel() self.RefreshVariableTree() event.SetDragText(var_name) elif values[1] == "Constant": event.SetDragText(values[0]) elif values[3] == self.TagName: self.ResetBuffer() event.SetDragText(values[0]) wx.CallAfter(self.RefreshModel) else: message = _("Variable don't belong to this POU!") if message is not None: dialog = wx.MessageDialog(self, message, _("Error"), wx.OK | wx.ICON_ERROR) dialog.ShowModal() dialog.Destroy() event.SetDragText("") event.Skip() def SetTextSyntax(self, syntax): self.TextSyntax = syntax if syntax in ["ST", "ALL"]: self.Editor.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL) self.Editor.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS) self.Editor.SetMarginSensitive(2, 1) self.Editor.SetMarginWidth(2, 12) if syntax == "ST": self.BlockStartKeywords = ST_BLOCK_START_KEYWORDS self.BlockEndKeywords = ST_BLOCK_START_KEYWORDS else: self.BlockStartKeywords = IEC_BLOCK_START_KEYWORDS self.BlockEndKeywords = IEC_BLOCK_START_KEYWORDS else: self.BlockStartKeywords = [] self.BlockEndKeywords = [] def SetKeywords(self, keywords): self.Keywords = [keyword.upper() for keyword in keywords] self.Colourise(0, -1) def RefreshJumpList(self): if self.TextSyntax == "IL": self.Jumps = [jump.upper() for jump in LABEL_MODEL.findall(self.GetText())] self.Colourise(0, -1) # Buffer the last model state def RefreshBuffer(self): self.Controler.BufferProject() if self.ParentWindow: self.ParentWindow.RefreshTitle() self.ParentWindow.RefreshFileMenu() self.ParentWindow.RefreshEditMenu() def StartBuffering(self): self.Controler.StartBuffering() if self.ParentWindow: self.ParentWindow.RefreshTitle() self.ParentWindow.RefreshFileMenu() self.ParentWindow.RefreshEditMenu() def ResetBuffer(self): if self.CurrentAction is not None: self.Controler.EndBuffering() self.CurrentAction = None def GetBufferState(self): if not self.Debug and self.TextSyntax != "ALL": return self.Controler.GetBufferState() return False, False def Undo(self): if not self.Debug and self.TextSyntax != "ALL": self.Controler.LoadPrevious() self.ParentWindow.CloseTabsWithoutModel() def Redo(self): if not self.Debug and self.TextSyntax != "ALL": self.Controler.LoadNext() self.ParentWindow.CloseTabsWithoutModel() def HasNoModel(self): if not self.Debug and self.TextSyntax != "ALL": return self.Controler.GetEditedElement(self.TagName) is None return False def RefreshView(self, variablepanel=True): EditorPanel.RefreshView(self, variablepanel) if self.Controler is not None: self.ResetBuffer() self.DisableEvents = True old_cursor_pos = self.GetCurrentPos() line = self.Editor.GetFirstVisibleLine() column = self.Editor.GetXOffset() old_text = self.GetText() new_text = self.Controler.GetEditedElementText(self.TagName, self.Debug) if old_text != new_text: self.SetText(new_text) new_cursor_pos = GetCursorPos(old_text, new_text) self.Editor.LineScroll(column, line) if new_cursor_pos is not None: self.Editor.GotoPos(new_cursor_pos) else: self.Editor.GotoPos(old_cursor_pos) self.RefreshJumpList() self.Editor.EmptyUndoBuffer() self.DisableEvents = False self.RefreshVariableTree() self.TypeNames = [typename.upper() for typename in self.Controler.GetDataTypes(self.TagName, True, self.Debug)] self.EnumeratedValues = [value.upper() for value in self.Controler.GetEnumeratedDataValues()] self.Functions = {} for category in self.Controler.GetBlockTypes(self.TagName, self.Debug): for blocktype in category["list"]: blockname = blocktype["name"].upper() if blocktype["type"] == "function" and blockname not in self.Keywords and blockname not in self.Variables.keys(): interface = dict([(name, {}) for name, type, modifier in blocktype["inputs"] + blocktype["outputs"] if name != '']) for param in ["EN", "ENO"]: if param not in interface: interface[param] = {} if blockname in self.Functions: self.Functions[blockname]["interface"].update(interface) self.Functions[blockname]["extensible"] |= blocktype["extensible"] else: self.Functions[blockname] = {"interface": interface, "extensible": blocktype["extensible"]} self.Colourise(0, -1) def RefreshVariableTree(self): words = self.TagName.split("::") self.Variables = self.GenerateVariableTree( [(variable.Name, variable.Type, variable.Tree) for variable in self.Controler.GetEditedElementInterfaceVars( self.TagName, True, self.Debug)]) if self.Controler.GetEditedElementType(self.TagName, self.Debug)[1] == "function" or words[0] == "T" and self.TextSyntax == "IL": return_type, (var_tree, var_dimension) = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, True, self.Debug) if return_type is not None: self.Variables[words[-1].upper()] = self.GenerateVariableTree(var_tree) else: self.Variables[words[-1].upper()] = {} def GenerateVariableTree(self, list): tree = {} for var_name, var_type, (var_tree, var_dimension) in list: tree[var_name.upper()] = self.GenerateVariableTree(var_tree) return tree def IsValidVariable(self, name, context): return context is not None and context.get(name, None) is not None def IsCallParameter(self, name, call): if call is not None: return (call["interface"].get(name.upper(), None) is not None or call["extensible"] and EXTENSIBLE_PARAMETER.match(name.upper()) is not None) return False def RefreshLineFolding(self, line_number): if self.TextSyntax in ["ST", "ALL"]: level = wx.stc.STC_FOLDLEVELBASE + self.Editor.GetLineIndentation(line_number) line = self.Editor.GetLine(line_number).strip() if line == "": if line_number > 0: if LineStartswith(self.Editor.GetLine(line_number - 1).strip(), self.BlockEndKeywords): level = self.Editor.GetFoldLevel(self.Editor.GetFoldParent(line_number - 1)) & wx.stc.STC_FOLDLEVELNUMBERMASK else: level = self.Editor.GetFoldLevel(line_number - 1) & wx.stc.STC_FOLDLEVELNUMBERMASK if level != wx.stc.STC_FOLDLEVELBASE: level |= wx.stc.STC_FOLDLEVELWHITEFLAG elif LineStartswith(line, self.BlockStartKeywords): level |= wx.stc.STC_FOLDLEVELHEADERFLAG elif LineStartswith(line, self.BlockEndKeywords): if LineStartswith(self.Editor.GetLine(line_number - 1).strip(), self.BlockEndKeywords): level = self.Editor.GetFoldLevel(self.Editor.GetFoldParent(line_number - 1)) & wx.stc.STC_FOLDLEVELNUMBERMASK else: level = self.Editor.GetFoldLevel(line_number - 1) & wx.stc.STC_FOLDLEVELNUMBERMASK self.Editor.SetFoldLevel(line_number, level) def OnStyleNeeded(self, event): self.TextChanged = True line_number = self.Editor.LineFromPosition(self.Editor.GetEndStyled()) if line_number == 0: start_pos = last_styled_pos = 0 else: start_pos = last_styled_pos = self.Editor.GetLineEndPosition(line_number - 1) + 1 self.RefreshLineFolding(line_number) end_pos = event.GetPosition() self.StartStyling(start_pos, 0xff) current_context = self.Variables current_call = None current_pos = last_styled_pos state = SPACE line = "" word = "" while current_pos < end_pos: char = chr(self.Editor.GetCharAt(current_pos)).upper() line += char if char == NEWLINE: self.ContextStack = [] current_context = self.Variables if state == COMMENT: self.SetStyling(current_pos - last_styled_pos, STC_PLC_COMMENT) elif state == NUMBER: self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) elif state == WORD: if word in self.Keywords or word in self.TypeNames: self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD) elif self.IsValidVariable(word, current_context): self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE) elif self.IsCallParameter(word, current_call): self.SetStyling(current_pos - last_styled_pos, STC_PLC_PARAMETER) elif word in self.Functions: self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION) elif self.TextSyntax == "IL" and word in self.Jumps: self.SetStyling(current_pos - last_styled_pos, STC_PLC_JUMP) elif word in self.EnumeratedValues: self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) else: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos): self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK) self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK) self.StartStyling(current_pos, 0xff) else: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) last_styled_pos = current_pos if (state != DPRAGMA) and (state != COMMENT): state = SPACE line = "" line_number += 1 self.RefreshLineFolding(line_number) elif line.endswith("(*") and state != COMMENT: self.SetStyling(current_pos - last_styled_pos - 1, STC_PLC_EMPTY) last_styled_pos = current_pos if state == WORD: current_context = self.Variables state = COMMENT elif line.endswith("{") and state not in [PRAGMA, DPRAGMA]: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) last_styled_pos = current_pos if state == WORD: current_context = self.Variables state = PRAGMA elif line.endswith("{{") and state == PRAGMA: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) last_styled_pos = current_pos state = DPRAGMA elif state == COMMENT: if line.endswith("*)"): self.SetStyling(current_pos - last_styled_pos + 2, STC_PLC_COMMENT) last_styled_pos = current_pos + 1 state = SPACE if len(self.CallStack) > 0: current_call = self.CallStack.pop() else: current_call = None elif state == PRAGMA: if line.endswith("}"): self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) last_styled_pos = current_pos state = SPACE elif state == DPRAGMA: if line.endswith("}}"): self.SetStyling(current_pos - last_styled_pos + 1, STC_PLC_EMPTY) last_styled_pos = current_pos + 1 state = SPACE elif (line.endswith("'") or line.endswith('"')) and state not in [STRING, WSTRING]: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) last_styled_pos = current_pos if state == WORD: current_context = self.Variables if line.endswith("'"): state = STRING else: state = WSTRING elif state == STRING: if line.endswith("'") and not line.endswith("$'"): self.SetStyling(current_pos - last_styled_pos + 1, STC_PLC_STRING) last_styled_pos = current_pos + 1 state = SPACE elif state == WSTRING: if line.endswith('"') and not line.endswith('$"'): self.SetStyling(current_pos - last_styled_pos + 1, STC_PLC_STRING) last_styled_pos = current_pos + 1 state = SPACE elif char in LETTERS: if state == NUMBER: word = "#" state = WORD elif state == SPACE: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) word = char last_styled_pos = current_pos state = WORD else: word += char elif char in NUMBERS or char == '.' and state != WORD: if state == SPACE: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) last_styled_pos = current_pos state = NUMBER elif state == WORD and char != '.': word += char elif char == '(' and state == SPACE: self.CallStack.append(current_call) current_call = None else: if state == WORD: if word in self.Keywords or word in self.TypeNames: self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD) elif self.IsValidVariable(word, current_context): self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE) elif self.IsCallParameter(word, current_call): self.SetStyling(current_pos - last_styled_pos, STC_PLC_PARAMETER) elif word in self.Functions: self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION) elif self.TextSyntax == "IL" and word in self.Jumps: self.SetStyling(current_pos - last_styled_pos, STC_PLC_JUMP) elif word in self.EnumeratedValues: self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) else: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos): self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK) self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK) self.StartStyling(current_pos, 0xff) if char == '.': if word != "]": if current_context is not None: current_context = current_context.get(word, None) else: current_context = None elif char == '(': self.CallStack.append(current_call) current_call = self.Functions.get(word, None) if current_call is None and self.IsValidVariable(word, current_context): current_call = {"interface": current_context.get(word, {}), "extensible": False} current_context = self.Variables else: if char == '[' and current_context is not None: self.ContextStack.append(current_context.get(word, None)) current_context = self.Variables word = "" last_styled_pos = current_pos state = SPACE elif state == NUMBER: self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) last_styled_pos = current_pos state = SPACE if char == ']': if len(self.ContextStack) > 0: current_context = self.ContextStack.pop() else: current_context = self.Variables word = char state = WORD elif char == ')': current_context = self.Variables if len(self.CallStack) > 0: current_call = self.CallStack.pop() else: current_call = None word = char state = WORD current_pos += 1 if state == COMMENT: self.SetStyling(current_pos - last_styled_pos + 2, STC_PLC_COMMENT) elif state == NUMBER: self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) elif state == WORD: if word in self.Keywords or word in self.TypeNames: self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD) elif self.IsValidVariable(word, current_context): self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE) elif self.IsCallParameter(word, current_call): self.SetStyling(current_pos - last_styled_pos, STC_PLC_PARAMETER) elif self.TextSyntax == "IL" and word in self.Functions: self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION) elif word in self.Jumps: self.SetStyling(current_pos - last_styled_pos, STC_PLC_JUMP) elif word in self.EnumeratedValues: self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) else: self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY) else: self.SetStyling(current_pos - start_pos, STC_PLC_EMPTY) self.ShowHighlights(start_pos, end_pos) event.Skip() def OnMarginClick(self, event): if event.GetMargin() == 2: line = self.Editor.LineFromPosition(event.GetPosition()) if self.Editor.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG: self.Editor.ToggleFold(line) event.Skip() def OnUpdateUI(self, event): selected = self.Editor.GetSelectedText() if self.ParentWindow and selected != "": self.ParentWindow.SetCopyBuffer(selected, True) event.Skip() def Cut(self): self.ResetBuffer() self.DisableEvents = True self.Editor.CmdKeyExecute(wx.stc.STC_CMD_CUT) self.DisableEvents = False self.RefreshModel() self.RefreshBuffer() def Copy(self): self.Editor.CmdKeyExecute(wx.stc.STC_CMD_COPY) if self.ParentWindow: self.ParentWindow.RefreshEditMenu() def Paste(self): self.ResetBuffer() self.DisableEvents = True self.Editor.CmdKeyExecute(wx.stc.STC_CMD_PASTE) self.DisableEvents = False self.RefreshModel() self.RefreshBuffer() def Search(self, criteria): return self.Controler.SearchInPou(self.TagName, criteria, self.Debug) def Find(self, direction, search_params): if self.SearchParams != search_params: self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT) self.SearchParams = search_params self.SearchResults = [ (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT) for infos, start, end, text in self.Search(search_params)] self.CurrentFindHighlight = None if len(self.SearchResults) > 0: if self.CurrentFindHighlight is not None: old_idx = self.SearchResults.index(self.CurrentFindHighlight) if self.SearchParams["wrap"]: idx = (old_idx + direction) % len(self.SearchResults) else: idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1)) if idx != old_idx: self.RemoveHighlight(*self.CurrentFindHighlight) self.CurrentFindHighlight = self.SearchResults[idx] self.AddHighlight(*self.CurrentFindHighlight) else: self.CurrentFindHighlight = self.SearchResults[0] self.AddHighlight(*self.CurrentFindHighlight) else: if self.CurrentFindHighlight is not None: self.RemoveHighlight(*self.CurrentFindHighlight) self.CurrentFindHighlight = None def RefreshModel(self): self.RefreshJumpList() self.Controler.SetEditedElementText(self.TagName, self.GetText()) self.ResetSearchResults() def OnKeyDown(self, event): key = event.GetKeyCode() if self.Controler is not None: if self.Editor.CallTipActive(): self.Editor.CallTipCancel() key_handled = False line = self.Editor.GetCurrentLine() if line == 0: start_pos = 0 else: start_pos = self.Editor.GetLineEndPosition(line - 1) + 1 end_pos = self.GetCurrentPos() lineText = self.Editor.GetTextRange(start_pos, end_pos).replace("\t", " ") # Code completion if key == wx.WXK_SPACE and event.ControlDown(): words = lineText.split(" ") words = [word for i, word in enumerate(words) if word != '' or i == len(words) - 1] kw = [] if self.TextSyntax == "IL": if len(words) == 1: kw = self.Keywords elif len(words) == 2: if words[0].upper() in ["CAL", "CALC", "CALNC"]: kw = self.Functions elif words[0].upper() in ["JMP", "JMPC", "JMPNC"]: kw = self.Jumps else: kw = self.Variables.keys() else: kw = self.Keywords + self.Variables.keys() + self.Functions.keys() if len(kw) > 0: if len(words[-1]) > 0: kw = [keyword for keyword in kw if keyword.startswith(words[-1])] kw.sort() self.Editor.AutoCompSetIgnoreCase(True) self.Editor.AutoCompShow(len(words[-1]), " ".join(kw)) key_handled = True elif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER: if self.TextSyntax in ["ST", "ALL"]: indent = self.Editor.GetLineIndentation(line) if LineStartswith(lineText.strip(), self.BlockStartKeywords): indent = (indent / 2 + 1) * 2 self.Editor.AddText("\n" + " " * indent) key_handled = True elif key == wx.WXK_BACK: if self.TextSyntax in ["ST", "ALL"]: if not self.Editor.GetSelectedText(): indent = self.Editor.GetColumn(self.Editor.GetCurrentPos()) if lineText.strip() == "" and len(lineText) > 0 and indent > 0: self.Editor.DelLineLeft() self.Editor.AddText(" " * ((max(0, indent - 1) / 2) * 2)) key_handled = True if not key_handled: event.Skip() else: event.Skip() def OnKillFocus(self, event): self.Editor.AutoCompCancel() event.Skip() # ------------------------------------------------------------------------------- # Highlights showing functions # ------------------------------------------------------------------------------- def OnRefreshHighlightsTimer(self, event): self.RefreshView() event.Skip() def ClearHighlights(self, highlight_type=None): EditorPanel.ClearHighlights(self, highlight_type) if highlight_type is None: self.Highlights = [] else: highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None) if highlight_type is not None: self.Highlights = [(infos, start, end, highlight) for (infos, start, end, highlight) in self.Highlights if highlight != highlight_type] self.RefreshView() def AddHighlight(self, infos, start, end, highlight_type): EditorPanel.AddHighlight(self, infos, start, end, highlight_type) highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None) if infos[0] == "body" and highlight_type is not None: self.Highlights.append((infos[1], start, end, highlight_type)) self.Editor.GotoPos(self.Editor.PositionFromLine(start[0]) + start[1]) self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True) def RemoveHighlight(self, infos, start, end, highlight_type): EditorPanel.RemoveHighlight(self, infos, start, end, highlight_type) highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None) if infos[0] == "body" and highlight_type is not None and \ (infos[1], start, end, highlight_type) in self.Highlights: self.Highlights.remove((infos[1], start, end, highlight_type)) self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True) def ShowHighlights(self, start_pos, end_pos): for indent, start, end, highlight_type in self.Highlights: if start[0] == 0: highlight_start_pos = start[1] - indent else: highlight_start_pos = self.Editor.GetLineEndPosition(start[0] - 1) + start[1] - indent + 1 if end[0] == 0: highlight_end_pos = end[1] - indent + 1 else: highlight_end_pos = self.Editor.GetLineEndPosition(end[0] - 1) + end[1] - indent + 2 if highlight_start_pos < end_pos and highlight_end_pos > start_pos: self.StartStyling(highlight_start_pos, 0xff) self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type) self.StartStyling(highlight_start_pos, 0x00) self.SetStyling(len(self.Editor.GetText()) - highlight_end_pos, wx.stc.STC_STYLE_DEFAULT)
class Beremiz(IDEFrame): def _init_utils(self): self.ConfNodeMenu = wx.Menu(title='') self.RecentProjectsMenu = wx.Menu(title='') IDEFrame._init_utils(self) def _init_coll_FileMenu_Items(self, parent): AppendMenu(parent, help='', id=wx.ID_NEW, kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N') AppendMenu(parent, help='', id=wx.ID_OPEN, kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O') parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu) parent.AppendSeparator() AppendMenu(parent, help='', id=wx.ID_SAVE, kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S') AppendMenu(parent, help='', id=wx.ID_SAVEAS, kind=wx.ITEM_NORMAL, text=_(u'Save as') + '\tCTRL+SHIFT+S') AppendMenu(parent, help='', id=wx.ID_CLOSE, kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W') AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL, kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W') parent.AppendSeparator() AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP, kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P') AppendMenu(parent, help='', id=wx.ID_PREVIEW, kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P') AppendMenu(parent, help='', id=wx.ID_PRINT, kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P') parent.AppendSeparator() AppendMenu(parent, help='', id=wx.ID_EXIT, kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q') self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW) self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN) self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE) self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS) self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE) self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL) self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP) self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW) self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT) self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT) self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None), (wx.ID_OPEN, "open", _(u'Open'), None), (wx.ID_SAVE, "save", _(u'Save'), None), (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None), (wx.ID_PRINT, "print", _(u'Print'), None)]) def _RecursiveAddMenuItems(self, menu, items): for name, text, helpstr, children in items: if len(children) > 0: new_menu = wx.Menu(title='') menu.AppendMenu(wx.ID_ANY, text, new_menu) self._RecursiveAddMenuItems(new_menu, children) else: item = menu.Append(wx.ID_ANY, text, helpstr) self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name), item) def _init_coll_AddMenu_Items(self, parent): IDEFrame._init_coll_AddMenu_Items(self, parent, False) self._RecursiveAddMenuItems(parent, GetAddMenuItems()) def _init_coll_HelpMenu_Items(self, parent): def handler(event): return wx.MessageBox(version.GetCommunityHelpMsg(), _(u'Community support'), wx.OK | wx.ICON_INFORMATION) item = parent.Append(wx.ID_ANY, _(u'Community support'), '') self.Bind(wx.EVT_MENU, handler, item) parent.Append(help='', id=wx.ID_ABOUT, kind=wx.ITEM_NORMAL, text=_(u'About')) self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT) def _init_coll_ConnectionStatusBar_Fields(self, parent): parent.SetFieldsCount(3) parent.SetStatusText(number=0, text='') parent.SetStatusText(number=1, text='') parent.SetStatusText(number=2, text='') parent.SetStatusWidths([-1, 300, 200]) def _init_ctrls(self, prnt): IDEFrame._init_ctrls(self, prnt) self.EditMenuSize = self.EditMenu.GetMenuItemCount() inspectorID = wx.NewId() self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=inspectorID) accels = [ wx.AcceleratorEntry(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord('I'), inspectorID) ] for method, shortcut in [("Stop", wx.WXK_F4), ("Run", wx.WXK_F5), ("Transfer", wx.WXK_F6), ("Connect", wx.WXK_F7), ("Build", wx.WXK_F11)]: def OnMethodGen(obj, meth): def OnMethod(evt): if obj.CTR is not None: obj.CTR.CallMethod('_' + meth) wx.CallAfter(self.RefreshStatusToolBar) return OnMethod newid = wx.NewId() self.Bind(wx.EVT_MENU, OnMethodGen(self, method), id=newid) accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, shortcut, newid)] self.SetAcceleratorTable(wx.AcceleratorTable(accels)) self.LogConsole = CustomStyledTextCtrl(name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0), size=wx.Size(0, 0)) self.LogConsole.Bind(wx.EVT_SET_FOCUS, self.OnLogConsoleFocusChanged) self.LogConsole.Bind(wx.EVT_KILL_FOCUS, self.OnLogConsoleFocusChanged) self.LogConsole.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnLogConsoleUpdateUI) self.LogConsole.SetReadOnly(True) self.LogConsole.SetWrapMode(wx.stc.STC_WRAP_CHAR) # Define Log Console styles self.LogConsole.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces) self.LogConsole.StyleClearAll() self.LogConsole.StyleSetSpec( 1, "face:%(mono)s,fore:#FF0000,size:%(size)d" % faces) self.LogConsole.StyleSetSpec( 2, "face:%(mono)s,fore:#FF0000,back:#FFFF00,size:%(size)d" % faces) # Define Log Console markers self.LogConsole.SetMarginSensitive(1, True) self.LogConsole.SetMarginType(1, wx.stc.STC_MARGIN_SYMBOL) self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED") self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT) self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick) self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified) self.MainTabs["LogConsole"] = (self.LogConsole, _("Console")) self.BottomNoteBook.AddPage(*self.MainTabs["LogConsole"]) # self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogConsole), wx.RIGHT) self.LogViewer = LogViewer(self.BottomNoteBook, self) self.MainTabs["LogViewer"] = (self.LogViewer, _("PLC Log")) self.BottomNoteBook.AddPage(*self.MainTabs["LogViewer"]) # self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogViewer), wx.RIGHT) StatusToolBar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER) StatusToolBar.SetToolBitmapSize(wx.Size(25, 25)) StatusToolBar.Realize() self.Panes["StatusToolBar"] = StatusToolBar self.AUIManager.AddPane( StatusToolBar, wx.aui.AuiPaneInfo().Name("StatusToolBar").Caption( _("Status ToolBar")).ToolbarPane().Top().Position( 1).LeftDockable(False).RightDockable(False)) self.AUIManager.Update() self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.ST_SIZEGRIP) self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar) self.ProgressStatusBar = wx.Gauge(self.ConnectionStatusBar, -1, range=100) self.ConnectionStatusBar.AddWidget(self.ProgressStatusBar, esb.ESB_EXACT_FIT, esb.ESB_EXACT_FIT, 2) self.ProgressStatusBar.Hide() self.SetStatusBar(self.ConnectionStatusBar) def __init_execute_path(self): if os.name == 'nt': # on windows, desktop shortcut launches Beremiz.py # with working dir set to mingw/bin. # then we prefix CWD to PATH in order to ensure that # commands invoked by build process by default are # found here. os.environ["PATH"] = os.getcwd() + ';' + os.environ["PATH"] def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True): # Add beremiz's icon in top left corner of the frame self.icon = wx.Icon(Bpath("images", "brz.ico"), wx.BITMAP_TYPE_ICO) self.__init_execute_path() IDEFrame.__init__(self, parent, debug) self.Log = LogPseudoFile(self.LogConsole, self.SelectTab) self.local_runtime = None self.runtime_port = None self.local_runtime_tmpdir = None self.LastPanelSelected = None # Define Tree item icon list self.LocationImageList = wx.ImageList(16, 16) self.LocationImageDict = {} # Icons for location items for imgname, itemtype in [("CONFIGURATION", LOCATION_CONFNODE), ("RESOURCE", LOCATION_MODULE), ("PROGRAM", LOCATION_GROUP), ("VAR_INPUT", LOCATION_VAR_INPUT), ("VAR_OUTPUT", LOCATION_VAR_OUTPUT), ("VAR_LOCAL", LOCATION_VAR_MEMORY)]: self.LocationImageDict[itemtype] = self.LocationImageList.Add( GetBitmap(imgname)) # Icons for other items for imgname, itemtype in [("Extension", ITEM_CONFNODE)]: self.TreeImageDict[itemtype] = self.TreeImageList.Add( GetBitmap(imgname)) if projectOpen is not None: projectOpen = DecodeFileSystemPath(projectOpen, False) if projectOpen is not None and os.path.isdir(projectOpen): self.CTR = ProjectController(self, self.Log) self.Controler = self.CTR result, _err = self.CTR.LoadProject(projectOpen, buildpath) if not result: self.LibraryPanel.SetController(self.Controler) self.ProjectTree.Enable(True) self.PouInstanceVariablesPanel.SetController(self.Controler) self.RefreshConfigRecentProjects(os.path.abspath(projectOpen)) self.RefreshAfterLoad() else: self.ResetView() self.ShowErrorMessage(result) else: self.CTR = ctr self.Controler = ctr if ctr is not None: ctr.SetAppFrame(self, self.Log) self.LibraryPanel.SetController(self.Controler) self.ProjectTree.Enable(True) self.PouInstanceVariablesPanel.SetController(self.Controler) self.RefreshAfterLoad() if self.EnableDebug: self.DebugVariablePanel.SetDataProducer(self.CTR) self.Bind(wx.EVT_CLOSE, self.OnCloseFrame) self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU) self.RefreshAll() self.LogConsole.SetFocus() def RefreshTitle(self): name = _("Beremiz") if self.CTR is not None: projectname = self.CTR.GetProjectName() if self.CTR.ProjectTestModified(): projectname = "~%s~" % projectname self.SetTitle("%s - %s" % (name, projectname)) else: self.SetTitle(name) def StartLocalRuntime(self, taskbaricon=True): if (self.local_runtime is None) or (self.local_runtime.exitcode is not None): # create temporary directory for runtime working directory self.local_runtime_tmpdir = tempfile.mkdtemp() # choose an arbitrary random port for runtime self.runtime_port = int(random.random() * 1000) + 61131 # launch local runtime self.local_runtime = ProcessLogger( self.Log, "\"%s\" \"%s\" -p %s -i localhost %s %s" % (sys.executable, Bpath("Beremiz_service.py"), self.runtime_port, { False: "-x 0", True: "-x 1" }[taskbaricon], self.local_runtime_tmpdir), no_gui=False, timeout=500, keyword=self.local_runtime_tmpdir, cwd=self.local_runtime_tmpdir) self.local_runtime.spin() return self.runtime_port def KillLocalRuntime(self): if self.local_runtime is not None: # shutdown local runtime self.local_runtime.kill(gently=False) # clear temp dir shutil.rmtree(self.local_runtime_tmpdir) self.local_runtime = None def OnOpenWidgetInspector(self, evt): # Activate the widget inspection tool from wx.lib.inspection import InspectionTool if not InspectionTool().initialized: InspectionTool().Init() # Find a widget to be selected in the tree. Use either the # one under the cursor, if any, or this frame. wnd = wx.FindWindowAtPointer() if not wnd: wnd = self InspectionTool().Show(wnd, True) def OnLogConsoleFocusChanged(self, event): self.RefreshEditMenu() event.Skip() def OnLogConsoleUpdateUI(self, event): self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True) event.Skip() def OnLogConsoleMarginClick(self, event): line_idx = self.LogConsole.LineFromPosition(event.GetPosition()) wx.CallAfter(self.SearchLineForError, self.LogConsole.GetLine(line_idx)) event.Skip() def OnLogConsoleModified(self, event): line_idx = self.LogConsole.LineFromPosition(event.GetPosition()) line = self.LogConsole.GetLine(line_idx) if line: result = MATIEC_ERROR_MODEL.match(line) if result is not None: self.LogConsole.MarkerAdd(line_idx, 0) event.Skip() def SearchLineForError(self, line): if self.CTR is not None: result = MATIEC_ERROR_MODEL.match(line) if result is not None: first_line, first_column, last_line, last_column, _error = result.groups( ) self.CTR.ShowError(self.Log, (int(first_line), int(first_column)), (int(last_line), int(last_column))) def CheckSaveBeforeClosing(self, title=_("Close Project")): """Function displaying an Error dialog in PLCOpenEditor. :returns: False if closing cancelled. """ if self.CTR.ProjectTestModified(): dialog = wx.MessageDialog( self, _("There are changes, do you want to save?"), title, wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION) answer = dialog.ShowModal() dialog.Destroy() if answer == wx.ID_YES: self.CTR.SaveProject() elif answer == wx.ID_CANCEL: return False for idx in xrange(self.TabsOpened.GetPageCount()): window = self.TabsOpened.GetPage(idx) if not window.CheckSaveBeforeClosing(): return False return True def GetTabInfos(self, tab): if isinstance(tab, EditorPanel) and \ not isinstance(tab, (Viewer, TextViewer, ResourceEditor, ConfigurationEditor, DataTypeEditor)): return ("confnode", tab.Controler.CTNFullName(), tab.GetTagName()) elif (isinstance(tab, TextViewer) and (tab.Controler is None or isinstance(tab.Controler, MiniTextControler))): return ("confnode", None, tab.GetInstancePath()) else: return IDEFrame.GetTabInfos(self, tab) def LoadTab(self, notebook, page_infos): if page_infos[0] == "confnode": if page_infos[1] is None: confnode = self.CTR else: confnode = self.CTR.GetChildByName(page_infos[1]) return notebook.GetPageIndex(confnode._OpenView(*page_infos[2:])) else: return IDEFrame.LoadTab(self, notebook, page_infos) # Strange hack required by WAMP connector, using twisted. # Twisted reactor needs to be stopped only before quit, # since it cannot be restarted ToDoBeforeQuit = [] def AddToDoBeforeQuit(self, Thing): self.ToDoBeforeQuit.append(Thing) def TryCloseFrame(self): if self.CTR is None or self.CheckSaveBeforeClosing( _("Close Application")): if self.CTR is not None: self.CTR.KillDebugThread() self.KillLocalRuntime() self.SaveLastState() for Thing in self.ToDoBeforeQuit: Thing() self.ToDoBeforeQuit = [] return True return False def OnCloseFrame(self, event): if self.TryCloseFrame(): self.LogConsole.Disconnect(-1, -1, wx.wxEVT_KILL_FOCUS) event.Skip() else: # prevent event to continue, i.e. cancel closing event.Veto() def RefreshFileMenu(self): self.RefreshRecentProjectsMenu() MenuToolBar = self.Panes["MenuToolBar"] if self.CTR is not None: selected = self.TabsOpened.GetSelection() if selected >= 0: window = self.TabsOpened.GetPage(selected) viewer_is_modified = window.IsModified() is_viewer = isinstance(window, Viewer) else: viewer_is_modified = is_viewer = False if self.TabsOpened.GetPageCount() > 0: self.FileMenu.Enable(wx.ID_CLOSE, True) if is_viewer: self.FileMenu.Enable(wx.ID_PREVIEW, True) self.FileMenu.Enable(wx.ID_PRINT, True) MenuToolBar.EnableTool(wx.ID_PRINT, True) else: self.FileMenu.Enable(wx.ID_PREVIEW, False) self.FileMenu.Enable(wx.ID_PRINT, False) MenuToolBar.EnableTool(wx.ID_PRINT, False) else: self.FileMenu.Enable(wx.ID_CLOSE, False) self.FileMenu.Enable(wx.ID_PREVIEW, False) self.FileMenu.Enable(wx.ID_PRINT, False) MenuToolBar.EnableTool(wx.ID_PRINT, False) self.FileMenu.Enable(wx.ID_PAGE_SETUP, True) project_modified = self.CTR.ProjectTestModified( ) or viewer_is_modified self.FileMenu.Enable(wx.ID_SAVE, project_modified) MenuToolBar.EnableTool(wx.ID_SAVE, project_modified) self.FileMenu.Enable(wx.ID_SAVEAS, True) MenuToolBar.EnableTool(wx.ID_SAVEAS, True) self.FileMenu.Enable(wx.ID_CLOSE_ALL, True) else: self.FileMenu.Enable(wx.ID_CLOSE, False) self.FileMenu.Enable(wx.ID_PAGE_SETUP, False) self.FileMenu.Enable(wx.ID_PREVIEW, False) self.FileMenu.Enable(wx.ID_PRINT, False) MenuToolBar.EnableTool(wx.ID_PRINT, False) self.FileMenu.Enable(wx.ID_SAVE, False) MenuToolBar.EnableTool(wx.ID_SAVE, False) self.FileMenu.Enable(wx.ID_SAVEAS, False) MenuToolBar.EnableTool(wx.ID_SAVEAS, False) self.FileMenu.Enable(wx.ID_CLOSE_ALL, False) def RefreshRecentProjectsMenu(self): try: recent_projects = map(DecodeFileSystemPath, self.GetConfigEntry("RecentProjects", [])) except Exception: recent_projects = [] while self.RecentProjectsMenu.GetMenuItemCount() > 0: item = self.RecentProjectsMenu.FindItemByPosition(0) self.RecentProjectsMenu.RemoveItem(item) self.FileMenu.Enable(ID_FILEMENURECENTPROJECTS, len(recent_projects) > 0) for idx, projectpath in enumerate(recent_projects): text = u'&%d: %s' % (idx + 1, projectpath) item = self.RecentProjectsMenu.Append(wx.ID_ANY, text, '') self.Bind(wx.EVT_MENU, self.GenerateOpenRecentProjectFunction(projectpath), item) def GenerateOpenRecentProjectFunction(self, projectpath): def OpenRecentProject(event): if self.CTR is not None and not self.CheckSaveBeforeClosing(): return self.OpenProject(projectpath) return OpenRecentProject def GenerateMenuRecursive(self, items, menu): for kind, infos in items: if isinstance(kind, list): text, id = infos submenu = wx.Menu('') self.GenerateMenuRecursive(kind, submenu) menu.AppendMenu(id, text, submenu) elif kind == wx.ITEM_SEPARATOR: menu.AppendSeparator() else: text, id, _help, callback = infos AppendMenu(menu, help='', id=id, kind=kind, text=text) if callback is not None: self.Bind(wx.EVT_MENU, callback, id=id) def RefreshEditorToolBar(self): IDEFrame.RefreshEditorToolBar(self) self.AUIManager.GetPane("EditorToolBar").Position(2) self.AUIManager.GetPane("StatusToolBar").Position(1) self.AUIManager.Update() def RefreshStatusToolBar(self): StatusToolBar = self.Panes["StatusToolBar"] StatusToolBar.ClearTools() StatusToolBar.SetMinSize(StatusToolBar.GetToolBitmapSize()) if self.CTR is not None: for confnode_method in self.CTR.StatusMethods: if "method" in confnode_method and confnode_method.get( "shown", True): tool = StatusToolBar.AddSimpleTool( wx.ID_ANY, GetBitmap(confnode_method.get("bitmap", "Unknown")), confnode_method["tooltip"]) self.Bind( wx.EVT_MENU, self.GetMenuCallBackFunction( confnode_method["method"]), tool) StatusToolBar.Realize() self.AUIManager.GetPane("StatusToolBar").BestSize( StatusToolBar.GetBestSize()).Show() else: self.AUIManager.GetPane("StatusToolBar").Hide() self.AUIManager.GetPane("EditorToolBar").Position(2) self.AUIManager.GetPane("StatusToolBar").Position(1) self.AUIManager.Update() def RefreshEditMenu(self): IDEFrame.RefreshEditMenu(self) if self.FindFocus() == self.LogConsole: self.EditMenu.Enable(wx.ID_COPY, True) self.Panes["MenuToolBar"].EnableTool(wx.ID_COPY, True) if self.CTR is not None: selected = self.TabsOpened.GetSelection() if selected >= 0: panel = self.TabsOpened.GetPage(selected) else: panel = None if panel != self.LastPanelSelected: for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()): item = self.EditMenu.FindItemByPosition(self.EditMenuSize) if item is not None: if item.IsSeparator(): self.EditMenu.RemoveItem(item) else: self.EditMenu.Delete(item.GetId()) self.LastPanelSelected = panel if panel is not None: items = panel.GetConfNodeMenuItems() else: items = [] if len(items) > 0: self.EditMenu.AppendSeparator() self.GenerateMenuRecursive(items, self.EditMenu) if panel is not None: panel.RefreshConfNodeMenu(self.EditMenu) else: for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()): item = self.EditMenu.FindItemByPosition(i) if item is not None: if item.IsSeparator(): self.EditMenu.RemoveItem(item) else: self.EditMenu.Delete(item.GetId()) self.LastPanelSelected = None self.MenuBar.UpdateMenus() def RefreshAll(self): self.RefreshStatusToolBar() def GetMenuCallBackFunction(self, method): """ Generate the callbackfunc for a given CTR method""" def OnMenu(event): # Disable button to prevent re-entrant call event.GetEventObject().Disable() # Call getattr(self.CTR, method)() # Re-enable button event.GetEventObject().Enable() return OnMenu def GetConfigEntry(self, entry_name, default): return cPickle.loads( str(self.Config.Read(entry_name, cPickle.dumps(default)))) def ResetConnectionStatusBar(self): for field in xrange(self.ConnectionStatusBar.GetFieldsCount()): self.ConnectionStatusBar.SetStatusText('', field) def ResetView(self): IDEFrame.ResetView(self) if self.CTR is not None: self.CTR.CloseProject() self.CTR = None self.Log.flush() if self.EnableDebug: self.DebugVariablePanel.SetDataProducer(None) self.ResetConnectionStatusBar() def RefreshConfigRecentProjects(self, projectpath, err=False): try: recent_projects = map(DecodeFileSystemPath, self.GetConfigEntry("RecentProjects", [])) except Exception: recent_projects = [] if projectpath in recent_projects: recent_projects.remove(projectpath) if not err: recent_projects.insert(0, projectpath) self.Config.Write( "RecentProjects", cPickle.dumps( map(EncodeFileSystemPath, recent_projects[:MAX_RECENT_PROJECTS]))) self.Config.Flush() def ResetPerspective(self): IDEFrame.ResetPerspective(self) self.RefreshStatusToolBar() def OnNewProjectMenu(self, event): if self.CTR is not None and not self.CheckSaveBeforeClosing(): return try: defaultpath = DecodeFileSystemPath( self.Config.Read("lastopenedfolder")) except Exception: defaultpath = os.path.expanduser("~") dialog = wx.DirDialog(self, _("Choose an empty directory for new project"), defaultpath) if dialog.ShowModal() == wx.ID_OK: projectpath = dialog.GetPath() self.Config.Write( "lastopenedfolder", EncodeFileSystemPath(os.path.dirname(projectpath))) self.Config.Flush() self.ResetView() ctr = ProjectController(self, self.Log) result = ctr.NewProject(projectpath) if not result: self.CTR = ctr self.Controler = self.CTR self.LibraryPanel.SetController(self.Controler) self.ProjectTree.Enable(True) self.PouInstanceVariablesPanel.SetController(self.Controler) self.RefreshConfigRecentProjects(projectpath) if self.EnableDebug: self.DebugVariablePanel.SetDataProducer(self.CTR) self.RefreshAfterLoad() IDEFrame.OnAddNewProject(self, event) else: self.ResetView() self.ShowErrorMessage(result) self.RefreshAll() self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU) dialog.Destroy() def OnOpenProjectMenu(self, event): if self.CTR is not None and not self.CheckSaveBeforeClosing(): return try: defaultpath = DecodeFileSystemPath( self.Config.Read("lastopenedfolder")) except Exception: defaultpath = os.path.expanduser("~") dialog = wx.DirDialog(self, _("Choose a project"), defaultpath, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) if dialog.ShowModal() == wx.ID_OK: self.OpenProject(dialog.GetPath()) dialog.Destroy() def OpenProject(self, projectpath): if os.path.isdir(projectpath): self.Config.Write( "lastopenedfolder", EncodeFileSystemPath(os.path.dirname(projectpath))) self.Config.Flush() self.ResetView() self.CTR = ProjectController(self, self.Log) self.Controler = self.CTR result, err = self.CTR.LoadProject(projectpath) if not result: self.LibraryPanel.SetController(self.Controler) self.ProjectTree.Enable(True) self.PouInstanceVariablesPanel.SetController(self.Controler) if self.EnableDebug: self.DebugVariablePanel.SetDataProducer(self.CTR) self.RefreshAfterLoad() else: self.ResetView() self.ShowErrorMessage(result) self.RefreshAll() self.SearchResultPanel.ResetSearchResults() else: self.ShowErrorMessage( _("\"%s\" folder is not a valid Beremiz project\n") % projectpath) err = True self.RefreshConfigRecentProjects(projectpath, err) self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU) def OnCloseProjectMenu(self, event): if self.CTR is not None and not self.CheckSaveBeforeClosing(): return self.ResetView() self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU) self.RefreshAll() def RefreshAfterLoad(self): self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) def RefreshAfterSave(self): self.RefreshAll() self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES) def OnSaveProjectMenu(self, event): selected = self.TabsOpened.GetSelection() if selected != -1: window = self.TabsOpened.GetPage(selected) window.Save() if self.CTR is not None: self.CTR.SaveProject() self.RefreshAfterSave() def OnSaveProjectAsMenu(self, event): selected = self.TabsOpened.GetSelection() if selected != -1: window = self.TabsOpened.GetPage(selected) window.SaveAs() if self.CTR is not None: self.CTR.SaveProjectAs() self.RefreshAfterSave() self.RefreshConfigRecentProjects(self.CTR.ProjectPath) def OnQuitMenu(self, event): self.Close() def OnAboutMenu(self, event): info = version.GetAboutDialogInfo() ShowAboutDialog(self, info) def OnProjectTreeItemBeginEdit(self, event): selected = event.GetItem() if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFNODE: event.Veto() else: IDEFrame.OnProjectTreeItemBeginEdit(self, event) def OnProjectTreeRightUp(self, event): item = event.GetItem() item_infos = self.ProjectTree.GetPyData(item) if item_infos["type"] == ITEM_CONFNODE: confnode_menu = wx.Menu(title='') confnode = item_infos["confnode"] if confnode is not None: menu_items = confnode.GetContextualMenuItems() if menu_items is not None: for text, helpstr, callback in menu_items: item = confnode_menu.Append(wx.ID_ANY, text, helpstr) self.Bind(wx.EVT_MENU, callback, item) else: for name, XSDClass, helpstr in confnode.CTNChildrenTypes: if not hasattr(XSDClass, 'CTNMaxCount') or not confnode.Children.get(name) \ or len(confnode.Children[name]) < XSDClass.CTNMaxCount: item = confnode_menu.Append( wx.ID_ANY, _("Add") + " " + name, helpstr) self.Bind( wx.EVT_MENU, self.GetAddConfNodeFunction(name, confnode), item) item = confnode_menu.Append(wx.ID_ANY, _("Delete")) self.Bind(wx.EVT_MENU, self.GetDeleteMenuFunction(confnode), item) self.PopupMenu(confnode_menu) confnode_menu.Destroy() event.Skip() elif item_infos["type"] == ITEM_RESOURCE: # prevent last resource to be delted parent = self.ProjectTree.GetItemParent(item) parent_name = self.ProjectTree.GetItemText(parent) if parent_name == _("Resources"): IDEFrame.OnProjectTreeRightUp(self, event) else: IDEFrame.OnProjectTreeRightUp(self, event) def OnProjectTreeItemActivated(self, event): selected = event.GetItem() item_infos = self.ProjectTree.GetPyData(selected) if item_infos["type"] == ITEM_CONFNODE: item_infos["confnode"]._OpenView() event.Skip() elif item_infos["type"] == ITEM_PROJECT: self.CTR._OpenView() else: IDEFrame.OnProjectTreeItemActivated(self, event) def ProjectTreeItemSelect(self, select_item): if select_item is not None and select_item.IsOk(): item_infos = self.ProjectTree.GetPyData(select_item) if item_infos["type"] == ITEM_CONFNODE: item_infos["confnode"]._OpenView(onlyopened=True) elif item_infos["type"] == ITEM_PROJECT: self.CTR._OpenView(onlyopened=True) else: IDEFrame.ProjectTreeItemSelect(self, select_item) def GetProjectElementWindow(self, element, tagname): is_a_CTN_tagname = len(tagname.split("::")) == 1 if is_a_CTN_tagname: confnode = self.CTR.GetChildByName(tagname) return confnode.GetView() else: return IDEFrame.GetProjectElementWindow(self, element, tagname) def SelectProjectTreeItem(self, tagname): if self.ProjectTree is not None: root = self.ProjectTree.GetRootItem() if root.IsOk(): words = tagname.split("::") if len(words) == 1: if tagname == "Project": self.SelectedItem = root self.ProjectTree.SelectItem(root) self.ResetSelectedItem() else: return self.RecursiveProjectTreeItemSelection( root, [(word, ITEM_CONFNODE) for word in tagname.split(".")]) elif words[0] == "R": return self.RecursiveProjectTreeItemSelection( root, [(words[2], ITEM_RESOURCE)]) elif not os.path.exists(words[0]): IDEFrame.SelectProjectTreeItem(self, tagname) def GetAddConfNodeFunction(self, name, confnode=None): def AddConfNodeMenuFunction(event): wx.CallAfter(self.AddConfNode, name, confnode) return AddConfNodeMenuFunction def GetDeleteMenuFunction(self, confnode): def DeleteMenuFunction(event): wx.CallAfter(self.DeleteConfNode, confnode) return DeleteMenuFunction def AddConfNode(self, ConfNodeType, confnode=None): if self.CTR.CheckProjectPathPerm(): ConfNodeName = "%s_0" % ConfNodeType if confnode is not None: confnode.CTNAddChild(ConfNodeName, ConfNodeType) else: self.CTR.CTNAddChild(ConfNodeName, ConfNodeType) self._Refresh(TITLE, FILEMENU, PROJECTTREE) def DeleteConfNode(self, confnode): if self.CTR.CheckProjectPathPerm(): dialog = wx.MessageDialog( self, _("Really delete node '%s'?") % confnode.CTNName(), _("Remove %s node") % confnode.CTNType, wx.YES_NO | wx.NO_DEFAULT) if dialog.ShowModal() == wx.ID_YES: confnode.CTNRemove() del confnode self._Refresh(TITLE, FILEMENU, PROJECTTREE) dialog.Destroy() # ------------------------------------------------------------------------------- # Highlights showing functions # ------------------------------------------------------------------------------- def ShowHighlight(self, infos, start, end, highlight_type): config_name = self.Controler.GetProjectMainConfigurationName() if config_name is not None and infos[0] == ComputeConfigurationName( config_name): self.CTR._OpenView() selected = self.TabsOpened.GetSelection() if selected != -1: viewer = self.TabsOpened.GetPage(selected) viewer.AddHighlight(infos[1:], start, end, highlight_type) else: IDEFrame.ShowHighlight(self, infos, start, end, highlight_type)