Fixing bugs introduced with the new Beremiz extension paradigm
authorLaurent Bessard
Fri, 08 Jun 2012 12:40:31 +0200
changeset 2 0c697b8fc443
parent 1 4f6d393cb36e
child 3 169d552a345c
child 4 e8a0af6b89d9
Fixing bugs introduced with the new Beremiz extension paradigm
LPCManager.py
--- a/LPCManager.py	Sun May 20 14:57:52 2012 +0200
+++ b/LPCManager.py	Fri Jun 08 12:40:31 2012 +0200
@@ -59,28 +59,32 @@
     __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation
 
 _base_folder = os.path.split(sys.path[0])[0]
-sys.path.append(os.path.join(base_folder, "beremiz"))
-
-_base_path = path.split(__file__)[0]
+sys.path.append(os.path.join(_base_folder, "beremiz"))
+
+_base_path = os.path.split(__file__)[0]
+
+import connectors
+from LPCconnector import LPC_connector_factory
+connectors.connectors["LPC"]=lambda:LPC_connector_factory
+
+import targets
+from LPCtarget import LPC_target 
+targets.targets["LPC"] = {"xsd": os.path.join(_base_path, "LPCtarget", "XSD"),
+                          "class": lambda:LPC_target,
+                          "code": os.path.join(_base_path,"LPCtarget","plc_LPC_main.c")} 
+targets.toolchains["makefile"] = os.path.join(_base_path, "LPCtarget", "XSD_toolchain_makefile")
+
+# helper func to get path to images
+from util import misc
+misc.opjimg = lambda imgname: os.path.join(_base_folder, "beremiz", "images", imgname+".png")
 
 from Beremiz import *
 from ProjectController import ProjectController
 from ConfigTreeNode import ConfigTreeNode
-
-import connectors
-from LPCconnector import LPC_connector_factory
-connectors.connectors["LPC"]=lambda:LPC_connector_factory
-
-import targets
-from LPCtarget import LPC_target 
-targets.targets["LPC"]={"xsd": path.join(_base_path, "LPCtarget", "XSD"),
-                        "class": LPC_target,
-                        "code": path.join(_base_path,"LPCtarget","plc_LPC_main.c")} 
-targets.toolchains["makefile"]= path.join(_base_path, "LPCtarget", "XSD_toolchain_makefile"),
-
-from util import opjimg
+from ProjectNodeEditor import ProjectNodeEditor
+
 from plcopen.structures import LOCATIONDATATYPES
-from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP,\
+from PLCControler import PLCControler, LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP,\
                          LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
 from PLCOpenEditor import IDEFrame, ProjectDialog
 
@@ -88,10 +92,112 @@
 try:
     from canfestival import RootClass as CanOpenRootClass
     from canfestival.canfestival import _SlaveCTN, _NodeListCTN, NodeManager
+    from canfestival.NetworkEditor import NetworkEditor
+    from canfestival.SlaveEditor import SlaveEditor
     havecanfestival = True
 except:
     havecanfestival = False
     
+SCROLLBAR_UNIT = 10
+WINDOW_COLOUR = wx.Colour(240,240,240)
+TITLE_COLOUR = wx.Colour(200,200,220)
+CHANGED_TITLE_COLOUR = wx.Colour(220,200,220)
+CHANGED_WINDOW_COLOUR = wx.Colour(255,240,240)
+
+if wx.Platform == '__WXMSW__':
+    faces = { 'times': 'Times New Roman',
+              'mono' : 'Courier New',
+              'helv' : 'Arial',
+              'other': 'Comic Sans MS',
+              'size' : 16,
+             }
+else:
+    faces = { 'times': 'Times',
+              'mono' : 'Courier',
+              'helv' : 'Helvetica',
+              'other': 'new century schoolbook',
+              'size' : 18,
+             }
+
+# Some helpers to tweak GenBitmapTextButtons
+# TODO: declare customized classes instead.
+gen_mini_GetBackgroundBrush = lambda obj:lambda dc: wx.Brush(obj.GetParent().GetBackgroundColour(), wx.SOLID)
+gen_textbutton_GetLabelSize = lambda obj:lambda:(wx.lib.buttons.GenButton._GetLabelSize(obj)[:-1] + (False,))
+
+def make_genbitmaptogglebutton_flat(button):
+    button.GetBackgroundBrush = gen_mini_GetBackgroundBrush(button)
+    button.labelDelta = 0
+    button.SetBezelWidth(0)
+    button.SetUseFocusIndicator(False)
+
+# Patch wx.lib.imageutils so that gray is supported on alpha images
+import wx.lib.imageutils
+from wx.lib.imageutils import grayOut as old_grayOut
+def grayOut(anImage):
+    if anImage.HasAlpha():
+        AlphaData = anImage.GetAlphaData()
+    else :
+        AlphaData = None
+
+    old_grayOut(anImage)
+
+    if AlphaData is not None:
+        anImage.SetAlphaData(AlphaData)
+
+wx.lib.imageutils.grayOut = grayOut
+
+class GenBitmapTextButton(wx.lib.buttons.GenBitmapTextButton):
+    def _GetLabelSize(self):
+        """ used internally """
+        w, h = self.GetTextExtent(self.GetLabel())
+        if not self.bmpLabel:
+            return w, h, False       # if there isn't a bitmap use the size of the text
+
+        w_bmp = self.bmpLabel.GetWidth()+2
+        h_bmp = self.bmpLabel.GetHeight()+2
+        height = h + h_bmp
+        if w_bmp > w:
+            width = w_bmp
+        else:
+            width = w
+        return width, height, False
+
+    def DrawLabel(self, dc, width, height, dw=0, dy=0):
+        bmp = self.bmpLabel
+        if bmp != None:     # if the bitmap is used
+            if self.bmpDisabled and not self.IsEnabled():
+                bmp = self.bmpDisabled
+            if self.bmpFocus and self.hasFocus:
+                bmp = self.bmpFocus
+            if self.bmpSelected and not self.up:
+                bmp = self.bmpSelected
+            bw,bh = bmp.GetWidth(), bmp.GetHeight()
+            if not self.up:
+                dw = dy = self.labelDelta
+            hasMask = bmp.GetMask() != None
+        else:
+            bw = bh = 0     # no bitmap -> size is zero
+
+        dc.SetFont(self.GetFont())
+        if self.IsEnabled():
+            dc.SetTextForeground(self.GetForegroundColour())
+        else:
+            dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
+
+        label = self.GetLabel()
+        tw, th = dc.GetTextExtent(label)        # size of text
+        if not self.up:
+            dw = dy = self.labelDelta
+
+        pos_x = (width-bw)/2+dw      # adjust for bitmap and text to centre
+        pos_y = (height-bh-th)/2+dy
+        if bmp !=None:
+            dc.DrawBitmap(bmp, pos_x, pos_y, hasMask) # draw bitmap if available
+            pos_x = (width-tw)/2+dw      # adjust for bitmap and text to centre
+            pos_y += bh + 2
+
+        dc.DrawText(label, pos_x, pos_y)      # draw the text
+
 
 #-------------------------------------------------------------------------------
 #                          CANFESTIVAL CONFNODE HACK
@@ -397,6 +503,9 @@
         "Master_NodeId": 1,
     }
     
+    class LPCSlaveEditor(SlaveEditor):
+        SHOW_PARAMS = False
+    
     class LPCCanOpenSlave(_SlaveCTN):
         XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
         <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -418,6 +527,8 @@
         </xsd:schema>
         """ % DEFAULT_SETTINGS
         
+        EditorType = LPCSlaveEditor
+        
         def __init__(self):
             # TODO change netname when name change
             NodeManager.__init__(self)
@@ -437,6 +548,9 @@
         
         def GetCanDevice(self):
             return str(self.BaseParams.getIEC_Channel())
+    
+    class LPCNetworkEditor(NetworkEditor):
+        SHOW_PARAMS = False
         
     class LPCCanOpenMaster(_NodeListCTN):
         XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
@@ -450,7 +564,9 @@
           </xsd:element>
         </xsd:schema>
         """ % DEFAULT_SETTINGS
-    
+        
+        EditorType = LPCNetworkEditor
+        
         def GetCanDevice(self):
             return str(self.BaseParams.getIEC_Channel())
     
@@ -468,10 +584,12 @@
             if self.GetChildByName("Master") is None:
                 master = self.CTNAddChild("Master", "CanOpenNode", 0)
                 master.BaseParams.setEnabled(False)
+                master.CTNRequestSave()
             
             if self.GetChildByName("Slave") is None:
                 slave = self.CTNAddChild("Slave", "CanOpenSlave", 1)
                 slave.BaseParams.setEnabled(False)
+                slave.CTNRequestSave()
     
 
 #-------------------------------------------------------------------------------
@@ -498,34 +616,42 @@
 
 [SIMULATION_MODE, TRANSFER_MODE] = range(2)
 
+class LPCProjectNodeEditor(ProjectNodeEditor):
+    SHOW_PARAMS = False
+    ENABLE_REQUIRED = False
+
 class LPCProjectController(ProjectController):
 
-    ConfNodeMethods = [
-        {"bitmap" : opjimg("Debug"),
+    StatusMethods = [
+        {"bitmap" : "Debug",
          "name" : _("Simulate"),
          "tooltip" : _("Simulate PLC"),
          "method" : "_Simulate"},
-        {"bitmap" : opjimg("Run"),
+        {"bitmap" : "Run",
          "name" : _("Run"),
          "shown" : False,
          "tooltip" : _("Start PLC"),
          "method" : "_Run"},
-        {"bitmap" : opjimg("Stop"),
+        {"bitmap" : "Stop",
          "name" : _("Stop"),
          "shown" : False,
          "tooltip" : _("Stop Running PLC"),
          "method" : "_Stop"},
-        {"bitmap" : opjimg("Build"),
+        {"bitmap" : "Build",
          "name" : _("Build"),
          "tooltip" : _("Build project into build folder"),
          "method" : "_Build"},
-        {"bitmap" : opjimg("Transfer"),
+        {"bitmap" : "Transfer",
          "name" : _("Transfer"),
          "shown" : False,
          "tooltip" : _("Transfer PLC"),
          "method" : "_Transfer"},
     ]
-
+    
+    ConfNodeMethods = []
+    
+    EditorType = LPCProjectNodeEditor
+    
     def __init__(self, frame, logger, buildpath):
         self.OrigBuildPath = buildpath
         
@@ -547,6 +673,19 @@
         
         self.AbortTransferTimer = None
     
+    def GetProjectInfos(self):
+        infos = PLCControler.GetProjectInfos(self)
+        configurations = infos["values"].pop(-1)
+        resources = None
+        for config_infos in configurations["values"]:
+            if resources is None:
+                resources = config_infos["values"][0]
+            else:
+                resources["values"].extend(config_infos["values"][0]["values"])
+        if resources is not None:
+            infos["values"].append(resources)
+        return infos
+    
     def ConfNodeLibraryFilePath(self):
         if self.OrigBuildPath is not None:
             return os.path.join(self.OrigBuildPath, "pous.xml")
@@ -717,6 +856,7 @@
             canopen = self.CTNAddChild("CanOpen", "CanOpen", 0)
             canopen.BaseParams.setEnabled(False)
             canopen.LoadChildren()
+            canopen.CTNRequestSave()
         
         if self.CTNTestModified():
             self.SaveProject()
@@ -1084,9 +1224,6 @@
         AppendMenu(parent, help='', id=wx.ID_PRINT,
               kind=wx.ITEM_NORMAL, text=_(u'Print'))
         parent.AppendSeparator()
-        AppendMenu(parent, help='', id=wx.ID_PROPERTIES,
-              kind=wx.ITEM_NORMAL, text=_(u'Properties'))
-        parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_EXIT,
               kind=wx.ITEM_NORMAL, text=_(u'Quit\tCTRL+Q'))
         
@@ -1095,18 +1232,20 @@
         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.OnPropertiesMenu, id=wx.ID_PROPERTIES)
         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
     
         self.AddToMenuToolBar([(wx.ID_SAVE, "save.png", _(u'Save'), None),
                                (wx.ID_PRINT, "print.png", _(u'Print'), None)])
     
+    def _init_coll_AddMenu_Items(self, parent):
+        IDEFrame._init_coll_AddMenu_Items(self, parent, False)
+        new_id = wx.NewId()
+        AppendMenu(parent, help='', id=new_id,
+                  kind=wx.ITEM_NORMAL, text=_(u'&Resource'))
+        self.Bind(wx.EVT_MENU, self.AddResourceMenu, id=new_id)
+    
     def _init_ctrls(self, prnt):
-        IDEFrame._init_ctrls(self, prnt)
-        
-        self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=ID_BEREMIZINSPECTOR)
-        accel = wx.AcceleratorTable([wx.AcceleratorEntry(wx.ACCEL_CTRL|wx.ACCEL_ALT, ord('I'), ID_BEREMIZINSPECTOR)])
-        self.SetAcceleratorTable(accel)
+        Beremiz._init_ctrls(self, prnt)
         
         self.PLCConfig = wx.ScrolledWindow(id=ID_BEREMIZPLCCONFIG,
               name='PLCConfig', parent=self.LeftNoteBook, pos=wx.Point(0, 0),
@@ -1114,16 +1253,29 @@
         self.PLCConfig.SetBackgroundColour(wx.WHITE)
         self.PLCConfig.Bind(wx.EVT_LEFT_DOWN, self.OnPanelLeftDown)
         self.PLCConfig.Bind(wx.EVT_SIZE, self.OnMoveWindow)
+        self.MainTabs["PLCConfig"] = (self.PLCConfig, _("Topology"))
         self.LeftNoteBook.InsertPage(0, self.PLCConfig, _("Topology"), True)
         
-        self.LogConsole = wx.TextCtrl(id=ID_BEREMIZLOGCONSOLE, value='',
-                  name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0),
-                  size=wx.Size(0, 0), style=wx.TE_MULTILINE|wx.TE_RICH2)
-        self.LogConsole.Bind(wx.EVT_LEFT_DCLICK, self.OnLogConsoleDClick)
-        self.BottomNoteBook.AddPage(self.LogConsole, _("Log Console"))
-        
-        self._init_beremiz_sizers()
-
+        self.PLCConfigMainSizer = wx.FlexGridSizer(cols=1, hgap=2, rows=2, vgap=2)
+        self.PLCParamsSizer = wx.BoxSizer(wx.VERTICAL)
+        self.ConfNodeTreeSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=0, vgap=2)
+        self.ConfNodeTreeSizer.AddGrowableCol(0)
+        self.ConfNodeTreeSizer.AddGrowableCol(1)
+        
+        self.PLCConfigMainSizer.AddSizer(self.PLCParamsSizer, 0, border=10, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+        self.PLCConfigMainSizer.AddSizer(self.ConfNodeTreeSizer, 0, border=10, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT)
+        self.PLCConfigMainSizer.AddGrowableCol(0)
+        self.PLCConfigMainSizer.AddGrowableRow(1)
+        
+        self.PLCConfig.SetSizer(self.PLCConfigMainSizer)
+        
+        self.AUIManager.Update()
+
+    def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True):
+        self.ConfNodeInfos = {}
+        
+        Beremiz.__init__(self, parent, projectOpen, buildpath, ctr, debug)
+        
     def OnCloseFrame(self, event):
         global frame
         
@@ -1144,18 +1296,16 @@
             
         event.Veto()
 
-    def ShowProperties(self):
-        old_values = self.Controler.GetProjectProperties()
-        dialog = ProjectDialog(self ,False)
-        dialog.SetValues(old_values)
-        if dialog.ShowModal() == wx.ID_OK:
-            new_values = dialog.GetValues()
-            new_values["creationDateTime"] = old_values["creationDateTime"]
-            if new_values != old_values:
-                self.Controler.SetProjectProperties(None, new_values)
-                self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, 
-                              PROJECTTREE, POUINSTANCEVARIABLESPANEL, SCALING)
-        dialog.Destroy()
+    def OnMoveWindow(self, event):
+        self.GetBestSize()
+        self.RefreshScrollBars()
+        event.Skip()
+
+    def OnPanelLeftDown(self, event):
+        focused = self.FindFocus()
+        if isinstance(focused, TextCtrlAutoComplete):
+            focused.DismissListBox()
+        event.Skip()
 
     def RefreshFileMenu(self):
         MenuToolBar = self.Panes["MenuToolBar"]
@@ -1184,7 +1334,6 @@
             project_modified = self.CTR.ProjectTestModified()
             self.FileMenu.Enable(wx.ID_SAVE, project_modified)
             MenuToolBar.EnableTool(wx.ID_SAVE, project_modified)
-            self.FileMenu.Enable(wx.ID_PROPERTIES, True)
         else:
             self.FileMenu.Enable(wx.ID_CLOSE, False)
             self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
@@ -1193,8 +1342,19 @@
             MenuToolBar.EnableTool(wx.ID_PRINT, False)
             self.FileMenu.Enable(wx.ID_SAVE, False)
             MenuToolBar.EnableTool(wx.ID_SAVE, False)
-            self.FileMenu.Enable(wx.ID_PROPERTIES, False)
-        
+            
+    def RefreshScrollBars(self):
+        xstart, ystart = self.PLCConfig.GetViewStart()
+        window_size = self.PLCConfig.GetClientSize()
+        sizer = self.PLCConfig.GetSizer()
+        if sizer:
+            maxx, maxy = sizer.GetMinSize()
+            posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
+            posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
+            self.PLCConfig.Scroll(posx, posy)
+            self.PLCConfig.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
+                maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
+
     def RefreshPLCParams(self):
         self.Freeze()
         self.ClearSizer(self.PLCParamsSizer)
@@ -1234,6 +1394,69 @@
         self.RefreshScrollBars()
         self.Thaw()
 
+    def GenerateMethodButtonSizer(self, confnode, parent, horizontal = True):
+        normal_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = faces["helv"])
+        mouseover_bt_font=wx.Font(faces["size"] / 3, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline=True, faceName = faces["helv"])
+        if horizontal:
+            msizer = wx.FlexGridSizer(cols=len(confnode.ConfNodeMethods))
+        else:
+            msizer = wx.FlexGridSizer(cols=1)
+        for confnode_method in confnode.ConfNodeMethods:
+            if "method" in confnode_method and confnode_method.get("shown",True):
+                id = wx.NewId()
+                label = confnode_method["name"]
+                button = GenBitmapTextButton(id=id, parent=parent,
+                    bitmap=wx.Bitmap(Bpath("images", "%s.png"%confnode_method.get("bitmap", "Unknown"))), label=label, 
+                    name=label, pos=wx.DefaultPosition, style=wx.NO_BORDER)
+                button.SetFont(normal_bt_font)
+                button.SetToolTipString(confnode_method["tooltip"])
+                button.Bind(wx.EVT_BUTTON, self.GetButtonCallBackFunction(confnode, confnode_method["method"]), id=id)
+                # a fancy underline on mouseover
+                def setFontStyle(b, s):
+                    def fn(event):
+                        b.SetFont(s)
+                        b.Refresh()
+                        event.Skip()
+                    return fn
+                button.Bind(wx.EVT_ENTER_WINDOW, setFontStyle(button, mouseover_bt_font))
+                button.Bind(wx.EVT_LEAVE_WINDOW, setFontStyle(button, normal_bt_font))
+                #hack to force size to mini
+                if not confnode_method.get("enabled",True):
+                    button.Disable()
+                msizer.AddWindow(button, 0, border=0, flag=wx.ALIGN_CENTER)
+        return msizer
+
+    def GenerateEnableButton(self, parent, sizer, confnode):
+        enabled = confnode.CTNEnabled()
+        if enabled is not None:
+            enablebutton_id = wx.NewId()
+            enablebutton = wx.lib.buttons.GenBitmapToggleButton(id=enablebutton_id, bitmap=wx.Bitmap(Bpath( 'images', 'Disabled.png')),
+                  name='EnableButton', parent=parent, size=wx.Size(16, 16), pos=wx.Point(0, 0), style=0)#wx.NO_BORDER)
+            enablebutton.SetToolTipString(_("Enable/Disable this confnode"))
+            make_genbitmaptogglebutton_flat(enablebutton)
+            enablebutton.SetBitmapSelected(wx.Bitmap(Bpath( 'images', 'Enabled.png')))
+            enablebutton.SetToggle(enabled)
+            def toggleenablebutton(event):
+                res = self.SetConfNodeParamsAttribute(confnode, "BaseParams.Enabled", enablebutton.GetToggle())
+                enablebutton.SetToggle(res)
+                event.Skip()
+            enablebutton.Bind(wx.EVT_BUTTON, toggleenablebutton, id=enablebutton_id)
+            sizer.AddWindow(enablebutton, 0, border=0, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+        else:
+            sizer.AddSpacer(wx.Size(16, 16))
+
+    def RefreshConfNodeTree(self):
+        self.Freeze()
+        self.ClearSizer(self.ConfNodeTreeSizer)
+        if self.CTR is not None:
+            for child in self.CTR.IECSortedChildren():
+                self.GenerateTreeBranch(child)
+                if not self.ConfNodeInfos[child]["expanded"]:
+                    self.CollapseConfNode(child)
+        self.PLCConfigMainSizer.Layout()
+        self.RefreshScrollBars()
+        self.Thaw()
+
     def GenerateTreeBranch(self, confnode):
         leftwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1))
         if confnode.CTNTestModified():
@@ -1327,7 +1550,10 @@
         st.SetLabel(confnode.MandatoryParams[1].getName())
         leftwindowsizer.AddWindow(st, 0, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
         
-        rightwindow = self.GenerateParamsPanel(confnode, bkgdclr)
+        rightwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1))
+        rightwindow.SetBackgroundColour(bkgdclr) 
+        rightwindowsizer = self.GenerateMethodButtonSizer(confnode, rightwindow, not self.ConfNodeInfos[confnode]["right_visible"])
+        rightwindow.SetSizer(rightwindowsizer)
         self.ConfNodeTreeSizer.AddWindow(rightwindow, 0, border=0, flag=wx.GROW)
 
         self.ConfNodeInfos[confnode]["left"] = leftwindow
@@ -1361,6 +1587,175 @@
             if locations_infos["root"]["expanded"]:
                 self.ExpandLocation(locations_infos, "root")
 
+    def ExpandConfNode(self, confnode, force = False):
+        for child in self.ConfNodeInfos[confnode]["children"]:
+            self.ConfNodeInfos[child]["left"].Show()
+            self.ConfNodeInfos[child]["right"].Show()
+            if force or self.ConfNodeInfos[child]["expanded"]:
+                self.ExpandConfNode(child, force)
+                if force:
+                    self.ConfNodeInfos[child]["expanded"] = True
+        locations_infos = self.ConfNodeInfos[confnode].get("locations_infos", None)
+        if locations_infos is not None:
+            if force or locations_infos["root"]["expanded"]:
+                self.ExpandLocation(locations_infos, "root", force)
+                if force:
+                    locations_infos["root"]["expanded"] = True
+    
+    def CollapseConfNode(self, confnode, force = False):
+        for child in self.ConfNodeInfos[confnode]["children"]:
+            self.ConfNodeInfos[child]["left"].Hide()
+            self.ConfNodeInfos[child]["right"].Hide()
+            self.CollapseConfNode(child, force)
+            if force:
+                self.ConfNodeInfos[child]["expanded"] = False
+        locations_infos = self.ConfNodeInfos[confnode].get("locations_infos", None)
+        if locations_infos is not None:
+            self.CollapseLocation(locations_infos, "root", force)
+            if force:
+                locations_infos["root"]["expanded"] = False
+
+    def ExpandLocation(self, locations_infos, group, force = False, refresh_size=True):
+        locations_infos[group]["expanded"] = True
+        if group == "root":
+            if locations_infos[group]["left"] is not None:
+                locations_infos[group]["left"].Show()
+            if locations_infos[group]["right"] is not None:
+                locations_infos[group]["right"].Show()
+        elif locations_infos["root"]["left"] is not None:
+            locations_infos["root"]["left"].Expand(locations_infos[group]["item"])
+            if force:
+                for child in locations_infos[group]["children"]:
+                    self.ExpandLocation(locations_infos, child, force, False)
+        if locations_infos["root"]["left"] is not None and refresh_size:
+            self.RefreshTreeCtrlSize(locations_infos["root"]["left"])
+        
+    def CollapseLocation(self, locations_infos, group, force = False, refresh_size=True):
+        locations_infos[group]["expanded"] = False
+        if group == "root":
+            if locations_infos[group]["left"] is not None:
+                locations_infos[group]["left"].Hide()
+            if locations_infos[group]["right"] is not None:
+                locations_infos[group]["right"].Hide()
+        elif locations_infos["root"]["left"] is not None:
+            locations_infos["root"]["left"].Collapse(locations_infos[group]["item"])
+            if force:
+                for child in locations_infos[group]["children"]:
+                    self.CollapseLocation(locations_infos, child, force, False)
+        if locations_infos["root"]["left"] is not None and refresh_size:
+            self.RefreshTreeCtrlSize(locations_infos["root"]["left"])
+
+    def GenerateLocationTreeBranch(self, treectrl, root, locations_infos, parent, location):
+        location_name = "%s.%s" % (parent, location["name"])
+        if not locations_infos.has_key(location_name):
+            locations_infos[location_name] = {"expanded" : False}
+        
+        if location["type"] in [LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY]:
+            label = "%(name)s (%(location)s)" % location
+        elif location["location"] != "":
+            label = "%(location)s: %(name)s" % location
+        else:
+            label = location["name"]
+        item = treectrl.AppendItem(root, label)
+        treectrl.SetPyData(item, location_name)
+        treectrl.SetItemImage(item, self.LocationImageDict[location["type"]])
+        
+        locations_infos[location_name]["item"] = item
+        locations_infos[location_name]["children"] = []
+        infos = location.copy()
+        infos.pop("children")
+        locations_infos[location_name]["infos"] = infos
+        for child in location["children"]:
+            child_name = "%s.%s" % (location_name, child["name"])
+            locations_infos[location_name]["children"].append(child_name)
+            self.GenerateLocationTreeBranch(treectrl, item, locations_infos, location_name, child)
+        if locations_infos[location_name]["expanded"]:
+            self.ExpandLocation(locations_infos, location_name)
+    
+    def GenerateLocationBeginDragFunction(self, locations_infos):
+        def OnLocationBeginDragFunction(event):
+            item = event.GetItem()
+            location_name = locations_infos["root"]["left"].GetPyData(item)
+            if location_name is not None:
+                infos = locations_infos[location_name]["infos"]
+                if infos["type"] in [LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY]:
+                    data = wx.TextDataObject(str((infos["location"], "location", infos["IEC_type"], infos["var_name"], infos["description"])))
+                    dragSource = wx.DropSource(self)
+                    dragSource.SetData(data)
+                    dragSource.DoDragDrop()
+        return OnLocationBeginDragFunction
+    
+    def RefreshTreeCtrlSize(self, treectrl):
+        rect = self.GetTreeCtrlItemRect(treectrl, treectrl.GetRootItem())
+        treectrl.SetMinSize(wx.Size(max(rect.width, rect.x + rect.width) + 20, max(rect.height, rect.y + rect.height)))
+        self.PLCConfigMainSizer.Layout()
+        self.PLCConfig.Refresh()
+        wx.CallAfter(self.RefreshScrollBars)
+
+    def GetTreeCtrlItemRect(self, treectrl, item):
+        item_rect = treectrl.GetBoundingRect(item, True)
+        if item_rect is not None:
+            minx, miny = item_rect.x, item_rect.y
+            maxx, maxy = item_rect.x + item_rect.width, item_rect.y + item_rect.height
+        else:
+            minx = miny = maxx = maxy = 0
+        
+        if treectrl.ItemHasChildren(item) and (item == treectrl.GetRootItem() or treectrl.IsExpanded(item)):
+            if wx.VERSION >= (2, 6, 0):
+                child, item_cookie = treectrl.GetFirstChild(item)
+            else:
+                child, item_cookie = treectrl.GetFirstChild(item, 0)
+            while child.IsOk():
+                child_rect = self.GetTreeCtrlItemRect(treectrl, child)
+                minx = min(minx, child_rect.x)
+                miny = min(miny, child_rect.y)
+                maxx = max(maxx, child_rect.x + child_rect.width)
+                maxy = max(maxy, child_rect.y + child_rect.height)
+                child, item_cookie = treectrl.GetNextChild(item, item_cookie)
+                
+        return wx.Rect(minx, miny, maxx - minx, maxy - miny)
+    
+    def GenerateLocationExpandCollapseFunction(self, locations_infos, expand):
+        def OnLocationExpandedFunction(event):
+            item = event.GetItem()
+            location_name = locations_infos["root"]["left"].GetPyData(item)
+            if location_name is not None:
+                locations_infos[location_name]["expanded"] = expand
+                self.RefreshTreeCtrlSize(locations_infos["root"]["left"])
+            event.Skip()
+        return OnLocationExpandedFunction
+    
+    def GetButtonCallBackFunction(self, confnode, method):
+        """ Generate the callbackfunc for a given confnode method"""
+        def OnButtonClick(event):
+            # Disable button to prevent re-entrant call 
+            event.GetEventObject().Disable()
+            # Call
+            getattr(confnode,method)()
+            # Re-enable button 
+            event.GetEventObject().Enable()
+            # Trigger refresh on Idle
+            wx.CallAfter(self.RefreshAll)
+            event.Skip()
+        return OnButtonClick
+    
+    def ClearSizer(self, sizer):
+        staticboxes = []
+        for item in sizer.GetChildren():
+            if item.IsSizer():
+                item_sizer = item.GetSizer()
+                self.ClearSizer(item_sizer)
+                if isinstance(item_sizer, wx.StaticBoxSizer):
+                    staticboxes.append(item_sizer.GetStaticBox())
+        sizer.Clear(True)
+        for staticbox in staticboxes:
+            staticbox.Destroy()
+                
+    def RefreshAll(self):
+        Beremiz.RefreshAll(self)
+        self.RefreshPLCParams()
+        self.RefreshConfNodeTree()
+
 class StdoutPseudoFile:
     
     def __init__(self, port):