Adding support for defining a description for a user POU type, and displaying this information in library tree.
authorlaurent
Tue, 17 Jan 2012 16:44:00 +0100
changeset 625 b7062a7018ec
parent 624 efedc9d06a59
child 626 ac0a8f6462c3
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.
PLCControler.py
PLCOpenEditor.py
Viewer.py
controls/VariablePanel.py
graphics/FBD_Objects.py
graphics/GraphicCommons.py
plcopen/plcopen.py
--- 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":