Adding support for integrating CanFestival plugin panels in Beremiz main frame
authorsmarteh-dev
Thu, 09 Feb 2012 21:23:11 +0100
changeset 680 61746934df41
parent 679 d72f3a42f440
child 681 383864958dac
Adding support for integrating CanFestival plugin panels in Beremiz main frame
LPCBeremiz.py
plugins/canfestival/NetworkEditor.py
plugins/canfestival/SlaveEditor.py
plugins/canfestival/canfestival.py
--- a/LPCBeremiz.py	Tue Feb 07 19:14:10 2012 +0100
+++ b/LPCBeremiz.py	Thu Feb 09 21:23:11 2012 +0100
@@ -60,6 +60,8 @@
 
 from Beremiz import *
 from plugger import PluginsRoot, PlugTemplate, opjimg, connectors
+from plugins.canfestival import RootClass as CanOpenRootClass
+from plugins.canfestival.canfestival import _SlavePlug, _NodeListPlug, NodeManager
 from plcopen.structures import LOCATIONDATATYPES
 from PLCControler import LOCATION_PLUGIN, LOCATION_MODULE, LOCATION_GROUP,\
                          LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
@@ -188,6 +190,9 @@
             return self.VariableLocationTree
         raise KeyError, "Only 'children' key is available"
     
+    def PlugEnabled(self):
+        return None
+    
     def SetIcon(self, icon):
         self.Icon = icon
     
@@ -355,6 +360,93 @@
         return [(Gen_Module_path, matiec_flags)],"",True
 
 #-------------------------------------------------------------------------------
+#                          LPC CanFestival Plugin Class
+#-------------------------------------------------------------------------------
+
+DEFAULT_SETTINGS = {
+    "CAN_Baudrate": "125K",
+    "Slave_NodeId": 2,
+    "Master_NodeId": 1,
+}
+
+class LPCCanOpenSlave(_SlavePlug):
+    XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+      <xsd:element name="CanFestivalSlaveNode">
+        <xsd:complexType>
+          <xsd:attribute name="CAN_Baudrate" type="xsd:string" use="optional" default="%(CAN_Baudrate)s"/>
+          <xsd:attribute name="NodeId" type="xsd:string" use="optional" default="%(Slave_NodeId)d"/>
+          <xsd:attribute name="Sync_Align" type="xsd:integer" use="optional" default="0"/>
+          <xsd:attribute name="Sync_Align_Ratio" use="optional" default="50">
+            <xsd:simpleType>
+                <xsd:restriction base="xsd:integer">
+                    <xsd:minInclusive value="1"/>
+                    <xsd:maxInclusive value="99"/>
+                </xsd:restriction>
+            </xsd:simpleType>
+          </xsd:attribute>
+        </xsd:complexType>
+      </xsd:element>
+    </xsd:schema>
+    """ % DEFAULT_SETTINGS
+    
+    def __init__(self):
+        # TODO change netname when name change
+        NodeManager.__init__(self)
+        odfilepath = self.GetSlaveODPath()
+        if(os.path.isfile(odfilepath)):
+            self.OpenFileInCurrent(odfilepath)
+        else:
+            self.CreateNewNode("SlaveNode",  # Name - will be changed at build time
+                               0x00,         # NodeID - will be changed at build time
+                               "slave",      # Type
+                               "",           # description 
+                               "None",       # profile
+                               "", # prfile filepath
+                               "heartbeat",  # NMT
+                               [])           # options
+            self.OnPlugSave()
+    
+    def GetCanDevice(self):
+        return str(self.BaseParams.getIEC_Channel())
+    
+class LPCCanOpenMaster(_NodeListPlug):
+    XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+      <xsd:element name="CanFestivalNode">
+        <xsd:complexType>
+          <xsd:attribute name="CAN_Baudrate" type="xsd:string" use="optional" default="%(CAN_Baudrate)s"/>
+          <xsd:attribute name="NodeId" type="xsd:string" use="optional" default="%(Master_NodeId)d"/>
+          <xsd:attribute name="Sync_TPDOs" type="xsd:boolean" use="optional" default="true"/>
+        </xsd:complexType>
+      </xsd:element>
+    </xsd:schema>
+    """ % DEFAULT_SETTINGS
+
+    def GetCanDevice(self):
+        return str(self.BaseParams.getIEC_Channel())
+
+class LPCCanOpen(CanOpenRootClass):
+    XSD = None
+    PlugChildsTypes = [("CanOpenNode",LPCCanOpenMaster, "CanOpen Master"),
+                       ("CanOpenSlave",LPCCanOpenSlave, "CanOpen Slave")]
+    
+    def GetCanDriver(self):
+        return ""
+    
+    def LoadChilds(self):
+        PlugTemplate.LoadChilds(self)
+        
+        if self.GetChildByName("Master") is None:
+            master = self.PlugAddChild("Master", "CanOpenNode", 0)
+            master.BaseParams.setEnabled(False)
+        
+        if self.GetChildByName("Slave") is None:
+            slave = self.PlugAddChild("Slave", "CanOpenSlave", 1)
+            slave.BaseParams.setEnabled(False)
+    
+
+#-------------------------------------------------------------------------------
 #                              LPCPluginsRoot Class
 #-------------------------------------------------------------------------------
 
@@ -411,7 +503,7 @@
         
         PluginsRoot.__init__(self, frame, logger)
         
-        self.PlugChildsTypes += [("LPCBus", LPCBus, "LPC bus")]
+        self.PlugChildsTypes += [("LPCBus", LPCBus, "LPC bus"), ("CanOpen", LPCCanOpen, "CanOpen bus")]
         self.PlugType = "LPC"
         
         self.OnlineMode = "OFF"
@@ -590,6 +682,11 @@
             #Load and init all the childs
             self.LoadChilds()
         
+        if self.GetChildByName("CanOpen") is None:
+            canopen = self.PlugAddChild("CanOpen", "CanOpen", 0)
+            canopen.BaseParams.setEnabled(False)
+            canopen.LoadChilds()
+        
         if self.PlugTestModified():
             self.SaveProject()
         
@@ -1043,9 +1140,14 @@
         
         self.PluginTreeSizer.AddWindow(leftwindow, 0, border=0, flag=wx.GROW)
         
+        leftwindowvsizer = wx.BoxSizer(wx.VERTICAL)
+        leftwindow.SetSizer(leftwindowvsizer)
+        
         leftwindowsizer = wx.BoxSizer(wx.HORIZONTAL)
-        leftwindow.SetSizer(leftwindowsizer)
-                
+        leftwindowvsizer.AddSizer(leftwindowsizer, 0, border=0, flag=0)
+        
+        self.GenerateEnableButton(leftwindow, leftwindowsizer, plugin)
+        
         st = wx.StaticText(leftwindow, -1)
         st.SetFont(wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL, wx.BOLD, faceName = faces["helv"]))
         st.SetLabel(plugin.GetFullIEC_Channel())
@@ -1105,9 +1207,7 @@
         st.SetLabel(plugin.MandatoryParams[1].getName())
         leftwindowsizer.AddWindow(st, 0, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
         
-        rightwindow = wx.Panel(self.PLCConfig, -1, size=wx.Size(-1, -1))
-        rightwindow.SetBackgroundColour(bkgdclr)
-        
+        rightwindow = self.GenerateParamsPanel(plugin, bkgdclr)
         self.PluginTreeSizer.AddWindow(rightwindow, 0, border=0, flag=wx.GROW)
 
         self.PluginInfos[plugin]["left"] = leftwindow
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/canfestival/NetworkEditor.py	Thu Feb 09 21:23:11 2012 +0100
@@ -0,0 +1,122 @@
+import os, sys
+base_folder = os.path.split(sys.path[0])[0]
+CanFestivalPath = os.path.join(base_folder, "CanFestival-3")
+
+import wx
+
+from subindextable import EditingPanel
+from networkedit import NetworkEditorTemplate
+from controls import EditorPanel
+
+[ID_NETWORKEDITOR, 
+] = [wx.NewId() for _init_ctrls in range(1)]
+
+[ID_NETWORKEDITORPLUGINMENUADDSLAVE, ID_NETWORKEDITORPLUGINMENUREMOVESLAVE, 
+ ID_NETWORKEDITORPLUGINMENUMASTER, 
+] = [wx.NewId() for _init_coll_PluginMenu_Items in range(3)]
+
+[ID_NETWORKEDITORMASTERMENUNODEINFOS, ID_NETWORKEDITORMASTERMENUDS301PROFILE,
+ ID_NETWORKEDITORMASTERMENUDS302PROFILE, ID_NETWORKEDITORMASTERMENUDSOTHERPROFILE,
+ ID_NETWORKEDITORMASTERMENUADD, 
+] = [wx.NewId() for _init_coll_MasterMenu_Items in range(5)]
+
+[ID_NETWORKEDITORADDMENUSDOSERVER, ID_NETWORKEDITORADDMENUSDOCLIENT,
+ ID_NETWORKEDITORADDMENUPDOTRANSMIT, ID_NETWORKEDITORADDMENUPDORECEIVE,
+ ID_NETWORKEDITORADDMENUMAPVARIABLE, ID_NETWORKEDITORADDMENUUSERTYPE,
+] = [wx.NewId() for _init_coll_AddMenu_Items in range(6)]
+
+class NetworkEditor(EditorPanel, NetworkEditorTemplate):
+    
+    ID = ID_NETWORKEDITOR
+    
+    def _init_coll_MainSizer_Items(self, parent):
+        parent.AddWindow(self.NetworkNodes, 0, border=5, flag=wx.GROW|wx.ALL)
+
+    def _init_coll_MainSizer_Growables(self, parent):
+        parent.AddGrowableCol(0)
+        parent.AddGrowableRow(0)
+    
+    def _init_sizers(self):
+        self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=0)
+        
+        self._init_coll_MainSizer_Items(self.MainSizer)
+        self._init_coll_MainSizer_Growables(self.MainSizer)
+        
+        self.Editor.SetSizer(self.MainSizer)
+    
+    def _init_Editor(self, prnt):
+        self.Editor = wx.Panel(id=-1, parent=prnt, pos=wx.Point(0, 0), 
+                size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
+        
+        NetworkEditorTemplate._init_ctrls(self, self.Editor)
+        
+        self._init_sizers()
+        
+    def __init__(self, parent, controler, window):
+        EditorPanel.__init__(self, parent, "", window, controler)
+        NetworkEditorTemplate.__init__(self, controler, window, False)
+    
+        img = wx.Bitmap(os.path.join(CanFestivalPath, "objdictgen", "networkedit.png"), wx.BITMAP_TYPE_PNG).ConvertToImage()
+        self.SetIcon(wx.BitmapFromImage(img.Rescale(16, 16)))
+        
+        self.RefreshNetworkNodes()
+        self.RefreshBufferState()
+    
+    def __del__(self):
+        self.Controler.OnCloseEditor(self)
+    
+    def GetPluginMenuItems(self):
+        add_menu = [(wx.ITEM_NORMAL, (_('SDO Server'), ID_NETWORKEDITORADDMENUSDOSERVER, '', self.OnAddSDOServerMenu)),
+                    (wx.ITEM_NORMAL, (_('SDO Client'), ID_NETWORKEDITORADDMENUSDOCLIENT, '', self.OnAddSDOClientMenu)),
+                    (wx.ITEM_NORMAL, (_('PDO Transmit'), ID_NETWORKEDITORADDMENUPDOTRANSMIT, '', self.OnAddPDOTransmitMenu)),
+                    (wx.ITEM_NORMAL, (_('PDO Receive'), ID_NETWORKEDITORADDMENUPDORECEIVE, '', self.OnAddPDOReceiveMenu)),
+                    (wx.ITEM_NORMAL, (_('Map Variable'), ID_NETWORKEDITORADDMENUMAPVARIABLE, '', self.OnAddMapVariableMenu)),
+                    (wx.ITEM_NORMAL, (_('User Type'), ID_NETWORKEDITORADDMENUUSERTYPE, '', self.OnAddUserTypeMenu))]
+        
+        profile = self.Manager.GetCurrentProfileName()
+        if profile not in ("None", "DS-301"):
+            other_profile_text = _("%s Profile") % profile
+            add_menu.append((wx.ITEM_SEPARATOR, None))
+            for text, indexes in self.Manager.GetCurrentSpecificMenu():
+                add_menu.append((wx.ITEM_NORMAL, (text, wx.NewId(), '', self.GetProfileCallBack(text))))
+        else:
+            other_profile_text = _('Other Profile')
+        
+        master_menu = [(wx.ITEM_NORMAL, (_('Node infos'), ID_NETWORKEDITORMASTERMENUNODEINFOS, '', self.OnNodeInfosMenu)),
+                       (wx.ITEM_NORMAL, (_('DS-301 Profile'), ID_NETWORKEDITORMASTERMENUDS301PROFILE, '', self.OnCommunicationMenu)),
+                       (wx.ITEM_NORMAL, (_('DS-302 Profile'), ID_NETWORKEDITORMASTERMENUDS302PROFILE, '', self.OnOtherCommunicationMenu)),
+                       (wx.ITEM_NORMAL, (other_profile_text, ID_NETWORKEDITORMASTERMENUDSOTHERPROFILE, '', self.OnEditProfileMenu)),
+                       (wx.ITEM_SEPARATOR, None),
+                       (add_menu, (_('Add'), ID_NETWORKEDITORMASTERMENUADD))]
+        
+        return [(wx.ITEM_NORMAL, (_('Add slave'), ID_NETWORKEDITORPLUGINMENUADDSLAVE, '', self.OnAddSlaveMenu)),
+                (wx.ITEM_NORMAL, (_('Remove slave'), ID_NETWORKEDITORPLUGINMENUREMOVESLAVE, '', self.OnRemoveSlaveMenu)),
+                (wx.ITEM_SEPARATOR, None),
+                (master_menu, (_('Master'), ID_NETWORKEDITORPLUGINMENUMASTER))]
+    
+    def RefreshMainMenu(self):
+        pass
+    
+    def RefreshPluginMenu(self, plugin_menu):
+        plugin_menu.Enable(ID_NETWORKEDITORPLUGINMENUMASTER, self.NetworkNodes.GetSelection() == 0)
+    
+    def GetTitle(self):
+        fullname = self.Controler.PlugFullName()
+        if not self.Manager.CurrentIsSaved():
+            return "~%s~" % fullname
+        return fullname
+
+    def RefreshView(self):
+        self.RefreshCurrentIndexList()
+    
+    def RefreshBufferState(self):
+        NetworkEditorTemplate.RefreshBufferState(self)
+        self.ParentWindow.RefreshTitle()
+        self.ParentWindow.RefreshFileMenu()
+        self.ParentWindow.RefreshEditMenu()
+        self.ParentWindow.RefreshPageTitles()
+    
+    def OnNodeSelectedChanged(self, event):
+        NetworkEditorTemplate.OnNodeSelectedChanged(self, event)
+        wx.CallAfter(self.ParentWindow.RefreshPluginMenu)
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/canfestival/SlaveEditor.py	Thu Feb 09 21:23:11 2012 +0100
@@ -0,0 +1,83 @@
+import os, sys
+base_folder = os.path.split(sys.path[0])[0]
+CanFestivalPath = os.path.join(base_folder, "CanFestival-3")
+
+import wx
+
+from subindextable import EditingPanel
+from nodeeditor import NodeEditorTemplate
+from controls import EditorPanel
+
+[ID_SLAVEEDITORPLUGINMENUNODEINFOS, ID_SLAVEEDITORPLUGINMENUDS301PROFILE,
+ ID_SLAVEEDITORPLUGINMENUDS302PROFILE, ID_SLAVEEDITORPLUGINMENUDSOTHERPROFILE,
+ ID_SLAVEEDITORPLUGINMENUADD, 
+] = [wx.NewId() for _init_coll_PluginMenu_Items in range(5)]
+
+[ID_SLAVEEDITORADDMENUSDOSERVER, ID_SLAVEEDITORADDMENUSDOCLIENT,
+ ID_SLAVEEDITORADDMENUPDOTRANSMIT, ID_SLAVEEDITORADDMENUPDORECEIVE,
+ ID_SLAVEEDITORADDMENUMAPVARIABLE, ID_SLAVEEDITORADDMENUUSERTYPE,
+] = [wx.NewId() for _init_coll_AddMenu_Items in range(6)]
+
+class SlaveEditor(EditorPanel, NodeEditorTemplate):
+    
+    def _init_Editor(self, prnt):
+        self.Editor = EditingPanel(prnt, self, self.Controler, self.Editable)
+        
+    def __init__(self, parent, controler, window, editable=True):
+        self.Editable = editable
+        EditorPanel.__init__(self, parent, "", window, controler)
+        NodeEditorTemplate.__init__(self, controler, window, False)
+        
+        img = wx.Bitmap(os.path.join(CanFestivalPath, "objdictgen", "networkedit.png"), wx.BITMAP_TYPE_PNG).ConvertToImage()
+        self.SetIcon(wx.BitmapFromImage(img.Rescale(16, 16)))
+    
+    def __del__(self):
+        self.Controler.OnCloseEditor(self)
+    
+    def GetPluginMenuItems(self):
+        if self.Editable:
+            add_menu = [(wx.ITEM_NORMAL, (_('SDO Server'), ID_SLAVEEDITORADDMENUSDOSERVER, '', self.OnAddSDOServerMenu)),
+                        (wx.ITEM_NORMAL, (_('SDO Client'), ID_SLAVEEDITORADDMENUSDOCLIENT, '', self.OnAddSDOClientMenu)),
+                        (wx.ITEM_NORMAL, (_('PDO Transmit'), ID_SLAVEEDITORADDMENUPDOTRANSMIT, '', self.OnAddPDOTransmitMenu)),
+                        (wx.ITEM_NORMAL, (_('PDO Receive'), ID_SLAVEEDITORADDMENUPDORECEIVE, '', self.OnAddPDOReceiveMenu)),
+                        (wx.ITEM_NORMAL, (_('Map Variable'), ID_SLAVEEDITORADDMENUMAPVARIABLE, '', self.OnAddMapVariableMenu)),
+                        (wx.ITEM_NORMAL, (_('User Type'), ID_SLAVEEDITORADDMENUUSERTYPE, '', self.OnAddUserTypeMenu))]
+            
+            profile = self.Controler.GetCurrentProfileName()
+            if profile not in ("None", "DS-301"):
+                other_profile_text = _("%s Profile") % profile
+                add_menu.append((wx.ITEM_SEPARATOR, None))
+                for text, indexes in self.Manager.GetCurrentSpecificMenu():
+                    add_menu.append((wx.ITEM_NORMAL, (text, wx.NewId(), '', self.GetProfileCallBack(text))))
+            else:
+                other_profile_text = _('Other Profile')
+            
+            return [(wx.ITEM_NORMAL, (_('Node infos'), ID_SLAVEEDITORPLUGINMENUNODEINFOS, '', self.OnNodeInfosMenu)),
+                    (wx.ITEM_NORMAL, (_('DS-301 Profile'), ID_SLAVEEDITORPLUGINMENUDS301PROFILE, '', self.OnCommunicationMenu)),
+                    (wx.ITEM_NORMAL, (_('DS-302 Profile'), ID_SLAVEEDITORPLUGINMENUDS302PROFILE, '', self.OnOtherCommunicationMenu)),
+                    (wx.ITEM_NORMAL, (other_profile_text, ID_SLAVEEDITORPLUGINMENUDSOTHERPROFILE, '', self.OnEditProfileMenu)),
+                    (wx.ITEM_SEPARATOR, None),
+                    (add_menu, (_('Add'), ID_SLAVEEDITORPLUGINMENUADD))]
+        return []
+    
+    def RefreshPluginMenu(self, plugin_menu):
+        plugin_menu.Enable(ID_SLAVEEDITORPLUGINMENUDSOTHERPROFILE, False)
+    
+    def GetTitle(self):
+        fullname = self.Controler.PlugFullName()
+        if not self.Controler.CurrentIsSaved():
+            return "~%s~" % fullname
+        return fullname
+
+    def RefreshView(self):
+        self.Editor.RefreshIndexList()
+
+    def RefreshCurrentIndexList(self):
+        self.RefreshView()
+    
+    def RefreshBufferState(self):
+        self.ParentWindow.RefreshTitle()
+        self.ParentWindow.RefreshFileMenu()
+        self.ParentWindow.RefreshEditMenu()
+        self.ParentWindow.RefreshPageTitles()
+            
--- a/plugins/canfestival/canfestival.py	Tue Feb 07 19:14:10 2012 +0100
+++ b/plugins/canfestival/canfestival.py	Thu Feb 09 21:23:11 2012 +0100
@@ -13,6 +13,9 @@
 from commondialogs import CreateNodeDialog
 import wx
 
+from SlaveEditor import SlaveEditor
+from NetworkEditor import NetworkEditor
+
 from gnosis.xml.pickle import *
 from gnosis.xml.pickle.util import setParanoia
 setParanoia(0)
@@ -59,9 +62,8 @@
       </xsd:element>
     </xsd:schema>
     """ % DEFAULT_SETTINGS
-
-    def GetSlaveODPath(self):
-        return os.path.join(self.PlugPath(), 'slave.od')
+    
+    EditorType = SlaveEditor
 
     def __init__(self):
         # TODO change netname when name change
@@ -97,34 +99,18 @@
                                    "heartbeat",  # NMT
                                    [])           # options
             dialog.Destroy()
-    _View = None
+            self.OnPlugSave()
+
+    def GetSlaveODPath(self):
+        return os.path.join(self.PlugPath(), 'slave.od')
+
+    def GetCanDevice(self):
+        return self.CanFestivalSlaveNode.getCan_Device()
+
     def _OpenView(self):
-        if not self._View:
-            open_objdictedit = True
-            has_permissions = self.GetPlugRoot().CheckProjectPathPerm()
-            if not has_permissions:
-                dialog = wx.MessageDialog(self.GetPlugRoot().AppFrame,
-                                          _("You don't have write permissions.\nOpen ObjDictEdit anyway ?"),
-                                          _("Open ObjDictEdit"),
-                                          wx.YES_NO|wx.ICON_QUESTION)
-                open_objdictedit = dialog.ShowModal() == wx.ID_YES
-                dialog.Destroy()
-            if open_objdictedit:
-                def _onclose():
-                    self._View = None
-                if has_permissions:
-                    def _onsave():
-                        self.GetPlugRoot().SaveProject()
-                else:
-                    def _onsave():
-                        pass
-            
-                self._View = objdictedit(self.GetPlugRoot().AppFrame, self)
-                # TODO redefine BusId when IEC channel change
-                self._View.SetBusId(self.GetCurrentLocation())
-                self._View._onclose = _onclose
-                self._View._onsave = _onsave
-                self._View.Show()
+        PlugTemplate._OpenView(self)
+        if self._View is not None:
+            self._View.SetBusId(self.GetCurrentLocation())
 
     PluginMethods = [
         {"bitmap" : os.path.join("images", "NetworkEdit"),
@@ -143,6 +129,15 @@
     def OnPlugSave(self):
         return self.SaveCurrentInFile(self.GetSlaveODPath())
 
+    def SetParamsAttribute(self, path, value):
+        result = PlugTemplate.SetParamsAttribute(self, path, value)
+        
+        # Filter IEC_Channel and Name, that have specific behavior
+        if path == "BaseParams.IEC_Channel" and self._View is not None:
+            self._View.SetBusId(self.GetCurrentLocation())
+        
+        return result
+        
     def PlugGenerate_C(self, buildpath, locations):
         """
         Generate C code
@@ -173,10 +168,38 @@
             raise Exception, res
         return [(Gen_OD_path,local_canfestival_config.getCFLAGS(CanFestivalPath))],"",False
 
+    def LoadPrevious(self):
+        self.LoadCurrentPrevious()
+    
+    def LoadNext(self):
+        self.LoadCurrentNext()
+    
+    def GetBufferState(self):
+        return self.GetCurrentBufferState()
+
 #--------------------------------------------------
 #                    MASTER
 #--------------------------------------------------
 
+class MiniNodeManager(NodeManager):
+    
+    def __init__(self, parent, filepath, fullname):
+        NodeManager.__init__(self)
+        
+        self.OpenFileInCurrent(filepath)
+            
+        self.Parent = parent
+        self.Fullname = fullname
+        
+    def OnCloseEditor(self, view):
+        self.Parent.OnCloseEditor(view)
+    
+    def PlugFullName(self):
+        return self.Fullname
+    
+    def GetBufferState(self):
+        return self.GetCurrentBufferState()
+
 class _NodeListPlug(NodeList):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -190,56 +213,61 @@
       </xsd:element>
     </xsd:schema>
     """ % DEFAULT_SETTINGS
-
+    
+    EditorType = NetworkEditor
+    
     def __init__(self):
         manager = NodeManager()
-        # TODO change netname when name change
-        NodeList.__init__(self, manager, self.BaseParams.getName())
+        NodeList.__init__(self, manager)
         self.LoadProject(self.PlugPath())
-
-    _View = None
+        self.SetNetworkName(self.BaseParams.getName())
+    
+    def GetCanDevice(self):
+        return self.CanFestivalNode.getCan_Device()
+    
+    def SetParamsAttribute(self, path, value):
+        result = PlugTemplate.SetParamsAttribute(self, path, value)
+        
+        # Filter IEC_Channel and Name, that have specific behavior
+        if path == "BaseParams.IEC_Channel" and self._View is not None:
+            self._View.SetBusId(self.GetCurrentLocation())
+        elif path == "BaseParams.Name":
+            self.SetNetworkName(value)
+        
+        return result
+    
     def _OpenView(self):
-        if not self._View:
-            open_networkedit = True
-            has_permissions = self.GetPlugRoot().CheckProjectPathPerm()
-            if not has_permissions:
-                dialog = wx.MessageDialog(self.GetPlugRoot().AppFrame,
-                                          _("You don't have write permissions.\nOpen NetworkEdit anyway ?"),
-                                          _("Open NetworkEdit"),
-                                          wx.YES_NO|wx.ICON_QUESTION)
-                open_networkedit = dialog.ShowModal() == wx.ID_YES
-                dialog.Destroy()
-            if open_networkedit:
-                def _onclose():
-                    self._View = None
-                if has_permissions:
-                    def _onsave():
-                        self.GetPlugRoot().SaveProject()
-                else:
-                    def _onsave():
-                        pass
-                self._View = networkedit(self.GetPlugRoot().AppFrame, self)
-                # TODO redefine BusId when IEC channel change
-                self._View.SetBusId(self.GetCurrentLocation())
-                self._View._onclose = _onclose
-                self._View._onsave = _onsave
-                self._View.Show()
-
+        PlugTemplate._OpenView(self)
+        if self._View is not None:
+            self._View.SetBusId(self.GetCurrentLocation())
+    
+    _GeneratedView = None
     def _ShowMasterGenerated(self):
-        buildpath = self._getBuildPath()
-        # Eventually create build dir
-        if not os.path.exists(buildpath):
-            self.GetPlugRoot().logger.write_error(_("Error: No PLC built\n"))
-            return
-        
-        masterpath = os.path.join(buildpath, "MasterGenerated.od")
-        if not os.path.exists(masterpath):
-            self.GetPlugRoot().logger.write_error(_("Error: No Master generated\n"))
-            return
-        
-        new_dialog = objdictedit(None, filesOpen=[masterpath])
-        new_dialog.Show()
-
+        if self._GeneratedView is None:
+            buildpath = self._getBuildPath()
+            # Eventually create build dir
+            if not os.path.exists(buildpath):
+                self.GetPlugRoot().logger.write_error(_("Error: No PLC built\n"))
+                return
+            
+            masterpath = os.path.join(buildpath, "MasterGenerated.od")
+            if not os.path.exists(masterpath):
+                self.GetPlugRoot().logger.write_error(_("Error: No Master generated\n"))
+                return
+            
+            app_frame = self.GetPlugRoot().AppFrame
+            
+            manager = MiniNodeManager(self, masterpath, self.PlugFullName() + ".generated_master")
+            self._GeneratedView = SlaveEditor(app_frame.TabsOpened, manager, app_frame, False)
+            
+            app_frame.EditProjectElement(self._GeneratedView, "MasterGenerated")
+    
+    def _CloseGenerateView(self):
+        if self._GeneratedView is not None:
+            app_frame = self.GetPlugRoot().AppFrame
+            if app_frame is not None:
+                app_frame.DeletePage(self._GeneratedView)
+    
     PluginMethods = [
         {"bitmap" : os.path.join("images", "NetworkEdit"),
          "name" : _("Edit network"), 
@@ -250,10 +278,16 @@
          "tooltip" : _("Show Master generated by config_utils"),
          "method" : "_ShowMasterGenerated"}
     ]
+    
+    def OnCloseEditor(self, view):
+        PlugTemplate.OnCloseEditor(self, view)
+        if self._GeneratedView == view:
+            self._GeneratedView = None
 
     def OnPlugClose(self):
-        if self._View:
-            self._View.Close()
+        PlugTemplate.OnPlugClose(self)
+        self._CloseGenerateView()
+        return True
 
     def PlugTestModified(self):
         return self.ChangesToSave or self.HasChanged()
@@ -275,6 +309,7 @@
             }, ...]
         @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
         """
+        self._CloseGenerateView()
         current_location = self.GetCurrentLocation()
         # define a unique name for the generated C file
         prefix = "_".join(map(str, current_location))
@@ -295,6 +330,15 @@
         
         return [(Gen_OD_path,local_canfestival_config.getCFLAGS(CanFestivalPath))],"",False
     
+    def LoadPrevious(self):
+        self.Manager.LoadCurrentPrevious()
+    
+    def LoadNext(self):
+        self.Manager.LoadCurrentNext()
+    
+    def GetBufferState(self):
+        return self.Manager.GetCurrentBufferState()
+    
 class RootClass:
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -317,14 +361,16 @@
                     if child["name"] == "CAN_Driver":
                         DLL_LIST= getattr(local_canfestival_config,"DLL_LIST",None)
                         if DLL_LIST is not None:
-                            child["type"] = DLL_LIST
-                        return infos    
+                            child["type"] = DLL_LIST  
         return infos
-
+    
+    def GetCanDriver(self):
+        return self.CanFestivalInstance.getCAN_Driver()
+    
     def PlugGenerate_C(self, buildpath, locations):
         
         format_dict = {"locstr" : "_".join(map(str,self.GetCurrentLocation())),
-                       "candriver" : self.CanFestivalInstance.getCAN_Driver(),
+                       "candriver" : self.GetCanDriver(),
                        "nodes_includes" : "",
                        "board_decls" : "",
                        "nodes_init" : "",
@@ -409,7 +455,7 @@
             # Declare CAN channels according user filled config
             format_dict["board_decls"] += 'BOARD_DECL(%s, "%s", "%s")\n'%(
                    nodename,
-                   child_data.getCAN_Device(),
+                   child.GetCanDevice(),
                    child_data.getCAN_Baudrate())
             format_dict["nodes_open"] += 'NODE_OPEN(%s)\n    '%(nodename)
             format_dict["nodes_close"] += 'NODE_CLOSE(%s)\n    '%(nodename)