Adding support for defining a description for a user POU type, and displaying this information in library tree.
Adding support for displaying a tooltip containing the informations of a function block when passing mouse over it in a graphic editor.
--- a/PLCControler.py Thu Jan 12 17:04:22 2012 +0100
+++ b/PLCControler.py Tue Jan 17 16:44:00 2012 +0100
@@ -809,6 +809,27 @@
resource.setname(new_name)
self.BufferProject()
+ # Return the description of the pou given by its name
+ def GetPouDescription(self, name, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its type
+ pou = project.getpou(name)
+ if pou is not None:
+ return pou.getdescription()
+ return ""
+
+ # Return the description of the pou given by its name
+ def SetPouDescription(self, name, description, debug = False):
+ project = self.GetProject(debug)
+ if project is not None:
+ # Found the pou correponding to name and return its type
+ pou = project.getpou(name)
+ if pou is not None:
+ pou.setdescription(description)
+ project.RefreshCustomBlockTypes()
+ self.BufferProject()
+
# Return the type of the pou given by its name
def GetPouType(self, name, debug = False):
project = self.GetProject(debug)
--- a/PLCOpenEditor.py Thu Jan 12 17:04:22 2012 +0100
+++ b/PLCOpenEditor.py Tue Jan 17 16:44:00 2012 +0100
@@ -1939,6 +1939,12 @@
def RefreshLibraryTree(self):
if self.Controler is not None:
to_delete = []
+ selected_name = None
+ selected = self.LibraryTree.GetSelection()
+ if selected.IsOk():
+ selected_pydata = self.LibraryTree.GetPyData(selected)
+ if selected_pydata is not None and selected_pydata["type"] != CATEGORY:
+ selected_name = self.LibraryTree.GetItemText(selected)
blocktypes = self.Controler.GetBlockTypes()
root = self.LibraryTree.GetRootItem()
if not root.IsOk():
@@ -1968,6 +1974,10 @@
else:
self.LibraryTree.SetItemText(blocktype_item, blocktype["name"])
self.LibraryTree.SetPyData(blocktype_item, {"type" : BLOCK, "block_type" : blocktype["type"], "inputs" : tuple([type for name, type, modifier in blocktype["inputs"]])})
+ if selected_name == blocktype["name"]:
+ self.LibraryTree.SelectItem(blocktype_item)
+ comment = blocktype["comment"]
+ self.LibraryComment.SetValue(_(comment) + blocktype.get("usage", ""))
blocktype_item, category_cookie = self.LibraryTree.GetNextChild(category_item, category_cookie)
while blocktype_item.IsOk():
to_delete.append(blocktype_item)
--- a/Viewer.py Thu Jan 12 17:04:22 2012 +0100
+++ b/Viewer.py Tue Jan 17 16:44:00 2012 +0100
@@ -1666,22 +1666,20 @@
self.RefreshVisibleElements()
else:
if not event.Dragging():
- if self.Debug:
+ highlighted = self.FindElement(event)
+ if self.HighlightedElement is not None and self.HighlightedElement != highlighted:
+ self.HighlightedElement.ClearToolTip()
+ self.HighlightedElement.SetHighlighted(False)
+ self.HighlightedElement = None
+ if highlighted is not None:
tooltip_pos = self.Editor.ClientToScreen(event.GetPosition())
tooltip_pos.x += 10
tooltip_pos.y += 10
- highlighted = self.FindElement(event)
- if self.HighlightedElement is not None and self.HighlightedElement != highlighted:
- if self.Debug and isinstance(self.HighlightedElement, Wire):
- self.HighlightedElement.ClearToolTip()
- self.HighlightedElement.SetHighlighted(False)
- self.HighlightedElement = None
- if highlighted is not None and self.HighlightedElement != highlighted:
- if self.Debug and isinstance(highlighted, Wire):
+ if self.HighlightedElement != highlighted:
highlighted.CreateToolTip(tooltip_pos)
- highlighted.SetHighlighted(True)
- elif self.Debug and highlighted is not None and isinstance(highlighted, Wire):
- highlighted.MoveToolTip(tooltip_pos)
+ highlighted.SetHighlighted(True)
+ else:
+ highlighted.MoveToolTip(tooltip_pos)
self.HighlightedElement = highlighted
if self.rubberBand.IsShown():
self.rubberBand.OnMotion(event, dc, self.Scaling)
@@ -1722,8 +1720,7 @@
if self.SelectedElement is not None and self.SelectedElement.GetDragging():
event.Skip()
elif self.HighlightedElement is not None:
- if isinstance(self.HighlightedElement, Wire):
- self.HighlightedElement.ClearToolTip()
+ self.HighlightedElement.ClearToolTip()
self.HighlightedElement.SetHighlighted(False)
self.HighlightedElement = None
event.Skip()
--- a/controls/VariablePanel.py Thu Jan 12 17:04:22 2012 +0100
+++ b/controls/VariablePanel.py Tue Jan 17 16:44:00 2012 +0100
@@ -29,7 +29,7 @@
from types import TupleType, StringType, UnicodeType
from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS
-from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD
+from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT
from dialogs.ArrayTypeDialog import ArrayTypeDialog
from CustomGrid import CustomGrid
from CustomTable import CustomTable
@@ -86,7 +86,7 @@
"External": lambda x: {"Constant": "Constant"}.get(x, "")
}
-LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9](?:\.[0-9])*)))?)$")
+LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$")
class VariableTable(CustomTable):
@@ -198,7 +198,10 @@
grid.SetCellEditor(row, col, editor)
grid.SetCellRenderer(row, col, renderer)
- highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
+ if colname == "Location" and LOCATION_MODEL.match(self.GetValueByName(row, "Location")) is None:
+ highlight_colours = ERROR_HIGHLIGHT
+ else:
+ highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
grid.SetCellBackgroundColour(row, col, highlight_colours[0])
grid.SetCellTextColour(row, col, highlight_colours[1])
self.ResizeRow(grid, row)
@@ -306,12 +309,13 @@
message.Destroy()
[ID_VARIABLEEDITORPANEL, ID_VARIABLEEDITORPANELVARIABLESGRID,
- ID_VARIABLEEDITORCONTROLPANEL, ID_VARIABLEEDITORPANELRETURNTYPE,
+ ID_VARIABLEEDITORCONTROLPANEL, ID_VARIABLEEDITORPANELRETURNTYPELABEL,
+ ID_VARIABLEEDITORPANELRETURNTYPE, ID_VARIABLEEDITORPANELDESCRIPTIONLABEL,
+ ID_VARIABLEEDITORPANELDESCRIPTION, ID_VARIABLEEDITORPANELCLASSFILTERLABEL,
ID_VARIABLEEDITORPANELCLASSFILTER, ID_VARIABLEEDITORPANELADDBUTTON,
ID_VARIABLEEDITORPANELDELETEBUTTON, ID_VARIABLEEDITORPANELUPBUTTON,
- ID_VARIABLEEDITORPANELDOWNBUTTON, ID_VARIABLEEDITORPANELSTATICTEXT1,
- ID_VARIABLEEDITORPANELSTATICTEXT2, ID_VARIABLEEDITORPANELSTATICTEXT3,
-] = [wx.NewId() for _init_ctrls in range(12)]
+ ID_VARIABLEEDITORPANELDOWNBUTTON,
+] = [wx.NewId() for _init_ctrls in range(13)]
class VariablePanel(wx.Panel):
@@ -331,9 +335,11 @@
parent.AddGrowableRow(1)
def _init_coll_ControlPanelSizer_Items(self, parent):
- parent.AddWindow(self.staticText1, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ parent.AddWindow(self.ReturnTypeLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
parent.AddWindow(self.ReturnType, 0, border=0, flag=0)
- parent.AddWindow(self.staticText2, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ parent.AddWindow(self.DescriptionLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ parent.AddWindow(self.Description, 0, border=0, flag=0)
+ parent.AddWindow(self.ClassFilterLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
parent.AddWindow(self.ClassFilter, 0, border=0, flag=0)
parent.AddWindow(self.AddButton, 0, border=0, flag=0)
parent.AddWindow(self.DeleteButton, 0, border=0, flag=0)
@@ -346,7 +352,7 @@
def _init_sizers(self):
self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
- self.ControlPanelSizer = wx.FlexGridSizer(cols=8, hgap=5, rows=1, vgap=5)
+ self.ControlPanelSizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5)
self._init_coll_MainSizer_Items(self.MainSizer)
self._init_coll_MainSizer_Growables(self.MainSizer)
@@ -379,8 +385,8 @@
size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
self.ControlPanel.SetScrollRate(10, 0)
- self.staticText1 = wx.StaticText(id=ID_VARIABLEEDITORPANELSTATICTEXT1,
- label=_('Return Type:'), name='staticText1', parent=self.ControlPanel,
+ self.ReturnTypeLabel = wx.StaticText(id=ID_VARIABLEEDITORPANELRETURNTYPELABEL,
+ label=_('Return Type:'), name='ReturnTypeLabel', parent=self.ControlPanel,
pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
self.ReturnType = wx.ComboBox(id=ID_VARIABLEEDITORPANELRETURNTYPE,
@@ -388,8 +394,18 @@
size=wx.Size(145, 28), style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, id=ID_VARIABLEEDITORPANELRETURNTYPE)
- self.staticText2 = wx.StaticText(id=ID_VARIABLEEDITORPANELSTATICTEXT2,
- label=_('Class Filter:'), name='staticText2', parent=self.ControlPanel,
+ self.DescriptionLabel = wx.StaticText(id=ID_VARIABLEEDITORPANELDESCRIPTIONLABEL,
+ label=_('Description:'), name='DescriptionLabel', parent=self.ControlPanel,
+ pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
+
+ self.Description = wx.TextCtrl(id=ID_VARIABLEEDITORPANELDESCRIPTION,
+ name='Description', parent=self.ControlPanel, pos=wx.Point(0, 0),
+ size=wx.Size(250, 28), style=wx.TE_PROCESS_ENTER)
+ self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, id=ID_VARIABLEEDITORPANELDESCRIPTION)
+ self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged)
+
+ self.ClassFilterLabel = wx.StaticText(id=ID_VARIABLEEDITORPANELCLASSFILTERLABEL,
+ label=_('Class Filter:'), name='ClassFilterLabel', parent=self.ControlPanel,
pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
self.ClassFilter = wx.ComboBox(id=ID_VARIABLEEDITORPANELCLASSFILTER,
@@ -577,15 +593,15 @@
def RefreshView(self):
self.PouNames = self.Controler.GetProjectPouNames(self.Debug)
+ returnType = None
+ description = None
words = self.TagName.split("::")
if self.ElementType == "config":
self.PouIsUsed = False
- returnType = None
self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
elif self.ElementType == "resource":
self.PouIsUsed = False
- returnType = None
self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
else:
if self.ElementType == "function":
@@ -593,21 +609,30 @@
for base_type in self.Controler.GetDataTypes(self.TagName, True, debug=self.Debug):
self.ReturnType.Append(base_type)
returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
- else:
- returnType = None
+ description = self.Controler.GetPouDescription(words[1])
self.PouIsUsed = self.Controler.PouIsUsed(words[1])
self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
if returnType is not None:
self.ReturnType.SetStringSelection(returnType)
- self.ReturnType.Enable(self.Debug)
- self.staticText1.Show()
+ self.ReturnType.Enable(not self.Debug)
+ self.ReturnTypeLabel.Show()
self.ReturnType.Show()
else:
self.ReturnType.Enable(False)
- self.staticText1.Hide()
+ self.ReturnTypeLabel.Hide()
self.ReturnType.Hide()
+ if description is not None:
+ self.Description.SetValue(description)
+ self.Description.Enable(not self.Debug)
+ self.DescriptionLabel.Show()
+ self.Description.Show()
+ else:
+ self.Description.Enable(False)
+ self.DescriptionLabel.Hide()
+ self.Description.Hide()
+
self.RefreshValues()
self.VariablesGrid.RefreshButtons()
@@ -619,6 +644,15 @@
self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)
event.Skip()
+ def OnDescriptionChanged(self, event):
+ words = self.TagName.split("::")
+ old_description = self.Controler.GetPouDescription(words[1])
+ new_description = self.Description.GetValue()
+ if new_description != old_description:
+ self.Controler.SetPouDescription(words[1], new_description)
+ self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)
+ event.Skip()
+
def OnClassFilter(self, event):
self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]]
self.RefreshTypeList()
@@ -660,12 +694,12 @@
self.ParentWindow.RefreshView(variablepanel = False)
self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)
event.Skip()
- elif colname == "Location" and LOCATION_MODEL.match(value) is None:
- message = _("\"%s\" is not a valid location!") % value
else:
self.SaveValues()
if colname == "Class":
self.ParentWindow.RefreshView(variablepanel = False)
+ elif colname == "Location":
+ wx.CallAfter(self.ParentWindow.RefreshView)
if message is not None:
dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
--- a/graphics/FBD_Objects.py Thu Jan 12 17:04:22 2012 +0100
+++ b/graphics/FBD_Objects.py Tue Jan 17 16:44:00 2012 +0100
@@ -41,6 +41,7 @@
def __init__(self, parent, type, name, id = None, extension = 0, inputs = None, connectors = {}, executionControl = False, executionOrder = 0):
Graphic_Element.__init__(self, parent)
self.Type = None
+ self.Description = None
self.Extension = None
self.ExecutionControl = False
self.Id = id
@@ -233,10 +234,13 @@
for i in xrange(self.Extension - len(blocktype["inputs"])):
start += 1
inputs.append(("IN%d"%start, inputs[-1][1], inputs[-1][2]))
+ comment = blocktype["comment"]
+ self.Description = _(comment) + blocktype.get("usage", "")
else:
self.Colour = wx.RED
inputs = connectors.get("inputs", [])
outputs = connectors.get("outputs", [])
+ self.Description = None
if self.ExecutionControl:
inputs.insert(0, ("EN","BOOL","none"))
outputs.insert(0, ("ENO","BOOL","none"))
@@ -368,6 +372,9 @@
for output in self.Outputs:
output.RefreshWires()
+ def GetToolTipValue(self):
+ return self.Description
+
# Adds an highlight to the block
def AddHighlight(self, infos, start, end ,highlight_type):
if infos[0] in ["type", "name"] and start[0] == 0 and end[0] == 0:
--- a/graphics/GraphicCommons.py Thu Jan 12 17:04:22 2012 +0100
+++ b/graphics/GraphicCommons.py Tue Jan 17 16:44:00 2012 +0100
@@ -101,6 +101,9 @@
# Define highlight refresh inhibition period in second
REFRESH_HIGHLIGHT_PERIOD = 0.1
+# Define tooltip wait for displaying period in second
+TOOLTIP_WAIT_PERIOD = 0.5
+
HANDLE_CURSORS = {
(1, 1) : 2,
(3, 3) : 2,
@@ -602,6 +605,13 @@
self.Size = wx.Size(0, 0)
self.BoundingBox = wx.Rect(0, 0, 0, 0)
self.Visible = False
+ self.ToolTip = None
+ self.ToolTipPos = None
+ self.ToolTipTimer = wx.Timer(self.Parent, -1)
+ self.Parent.Bind(wx.EVT_TIMER, self.OnToolTipTimer, self.ToolTipTimer)
+
+ def __del__(self):
+ self
def GetDefinition(self):
return [self.Id], []
@@ -967,6 +977,36 @@
return movex, movey
return 0, 0
+ def OnToolTipTimer(self, event):
+ value = self.GetToolTipValue()
+ if value is not None and self.ToolTipPos is not None:
+ self.ToolTip = ToolTip(self.Parent, value)
+ self.ToolTip.MoveToolTip(self.ToolTipPos)
+ self.ToolTip.Show()
+
+ def GetToolTipValue(self):
+ return None
+
+ def CreateToolTip(self, pos):
+ value = self.GetToolTipValue()
+ if value is not None:
+ self.ToolTipPos = pos
+ self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
+
+ def MoveToolTip(self, pos):
+ if self.ToolTip is not None:
+ self.ToolTip.MoveToolTip(pos)
+ elif self.ToolTipPos is not None:
+ self.ToolTipPos = pos
+ self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
+
+ def ClearToolTip(self):
+ self.ToolTipTimer.Stop()
+ self.ToolTipPos = None
+ if self.ToolTip is not None:
+ self.ToolTip.Destroy()
+ self.ToolTip = None
+
# Override this method for defining the method to call for adding an highlight to this element
def AddHighlight(self, infos, start, end, highlight_type):
pass
@@ -1792,7 +1832,6 @@
self.OverStart = False
self.OverEnd = False
self.ComputingType = False
- self.ToolTip = None
self.Font = parent.GetMiniFont()
def GetDefinition(self):
@@ -1806,24 +1845,13 @@
self.StartConnected = None
self.EndConnected = None
- def CreateToolTip(self, pos):
+ def GetToolTipValue(self):
if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
if isinstance(self.Value, StringType) and self.Value.find("#") == -1:
- computed_value = "\"%s\""%self.Value
+ return "\"%s\""%self.Value
else:
- computed_value = str(self.Value)
- self.ToolTip = ToolTip(self.Parent, computed_value)
- self.ToolTip.MoveToolTip(pos)
- self.ToolTip.Show()
-
- def MoveToolTip(self, pos):
- if self.ToolTip is not None:
- self.ToolTip.MoveToolTip(pos)
-
- def ClearToolTip(self):
- if self.ToolTip is not None:
- self.ToolTip.Destroy()
- self.ToolTip = None
+ return str(self.Value)
+ return None
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
--- a/plcopen/plcopen.py Thu Jan 12 17:04:22 2012 +0100
+++ b/plcopen/plcopen.py Tue Jan 17 16:44:00 2012 +0100
@@ -433,8 +433,13 @@
def AddCustomBlockType(self, pou):
pou_name = pou.getname()
pou_type = pou.getpouType()
+ pou_description = pou.getdescription()
+ if pou_description != "":
+ pou_comment = "%s\n%s" % (pou_name, pou_description)
+ else:
+ pou_comment = pou_name
block_infos = {"name" : pou_name, "type" : pou_type, "extensible" : False,
- "inputs" : [], "outputs" : [], "comment" : "",
+ "inputs" : [], "outputs" : [], "comment" : pou_comment,
"generate" : generate_block, "initialise" : initialise_block}
if pou.getinterface():
return_type = pou.interface.getreturnType()
@@ -477,10 +482,8 @@
block_infos["outputs"].append((var.getname(), var_type["name"].upper(), "none"))
else:
block_infos["outputs"].append((var.getname(), var_type["name"], "none"))
- if pou.getbodyType() in ["FBD","LD","SFC"]:
- for instance in pou.getinstances():
- if isinstance(instance, PLCOpenClasses.get("commonObjects_comment", None)):
- block_infos["comment"] = instance.getcontentText()
+ block_infos["usage"] = "\n (%s) => (%s)" % (", ".join(["%s:%s" % (input[1], input[0]) for input in block_infos["inputs"]]),
+ ", ".join(["%s:%s" % (output[1], output[0]) for output in block_infos["outputs"]]))
self.CustomBlockTypes.append(block_infos)
setattr(cls, "AddCustomBlockType", AddCustomBlockType)
@@ -1188,6 +1191,21 @@
cls = PLCOpenClasses.get("pous_pou", None)
if cls:
+ def setdescription(self, description):
+ doc = self.getdocumentation()
+ if doc is None:
+ doc = PLCOpenClasses["formattedText"]()
+ self.setdocumentation(doc)
+ doc.settext(description)
+ setattr(cls, "setdescription", setdescription)
+
+ def getdescription(self):
+ doc = self.getdocumentation()
+ if doc is not None:
+ return doc.gettext()
+ return ""
+ setattr(cls, "getdescription", getdescription)
+
def setbodyType(self, type):
if len(self.body) > 0:
if type == "IL":