Merged
authorLaurent Bessard
Wed, 24 Apr 2013 17:37:46 +0200
changeset 1062 fd7c9a7cf882
parent 1053 b0ac30ba7eaf (current diff)
parent 1061 02f371f3e063 (diff)
child 1063 9b5995303db1
Merged
ProjectController.py
wxglade_hmi/wxglade_hmi.py
--- a/Beremiz.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/Beremiz.py	Wed Apr 24 17:37:46 2013 +0200
@@ -980,7 +980,6 @@
             self.CTR.SaveProjectAs()
             self.RefreshAll()
             self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
-        event.Skip()
     
     def OnQuitMenu(self, event):
         self.Close()
--- a/ConfigTreeNode.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/ConfigTreeNode.py	Wed Apr 24 17:37:46 2013 +0200
@@ -73,10 +73,12 @@
     def ConfNodePath(self):
         return os.path.join(self.CTNParent.ConfNodePath(), self.CTNType)
 
-    def CTNPath(self,CTNName=None):
+    def CTNPath(self,CTNName=None,project_path=None):
         if not CTNName:
             CTNName = self.CTNName()
-        return os.path.join(self.CTNParent.CTNPath(),
+        if not project_path:
+            project_path = self.CTNParent.CTNPath()
+        return os.path.join(project_path,
                             CTNName + NameTypeSeparator + self.CTNType)
     
     def CTNName(self):
@@ -113,7 +115,7 @@
     def RemoteExec(self, script, **kwargs):
         return self.CTNParent.RemoteExec(script, **kwargs)
     
-    def OnCTNSave(self):
+    def OnCTNSave(self, from_project_path=None):
         #Default, do nothing and return success
         return True
 
@@ -155,7 +157,7 @@
     def CTNMakeDir(self):
         os.mkdir(self.CTNPath())
 
-    def CTNRequestSave(self):
+    def CTNRequestSave(self, from_project_path=None):
         if self.GetCTRoot().CheckProjectPathPerm(False):
             # If confnode do not have corresponding directory
             ctnpath = self.CTNPath()
@@ -178,7 +180,7 @@
                 XMLFile.close()
             
             # Call the confnode specific OnCTNSave method
-            result = self.OnCTNSave()
+            result = self.OnCTNSave(from_project_path)
             if not result:
                 return _("Error while saving \"%s\"\n")%self.CTNPath()
     
@@ -186,7 +188,8 @@
             self.ChangesToSave = False
             # go through all children and do the same
             for CTNChild in self.IterChildren():
-                result = CTNChild.CTNRequestSave()
+                result = CTNChild.CTNRequestSave(
+                    CTNChild.CTNPath(project_path=from_project_path))
                 if result:
                     return result
         return None
--- a/ProjectController.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/ProjectController.py	Wed Apr 24 17:37:46 2013 +0200
@@ -234,7 +234,9 @@
                 return True
         return False
     
-    def _getProjectFilesPath(self):
+    def _getProjectFilesPath(self, project_path=None):
+        if project_path is not None:
+            return os.path.join(project_path, "project_files")
         projectfiles_path = os.path.join(self.GetProjectPath(), "project_files")
         if not os.path.exists(projectfiles_path):
             os.mkdir(projectfiles_path)
@@ -348,14 +350,19 @@
         self.ClearChildren()
         self.ResetAppFrame(None)
         
-    def SaveProject(self):
+    def SaveProject(self, from_project_path=None):
         if self.CheckProjectPathPerm(False):
+            if from_project_path is not None:
+                old_projectfiles_path = self._getProjectFilesPath(from_project_path)
+                if os.path.isdir(old_projectfiles_path):
+                    shutil.copytree(old_projectfiles_path, 
+                                    self._getProjectFilesPath(self.ProjectPath))
             self.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml'))
-            result = self.CTNRequestSave()
+            result = self.CTNRequestSave(from_project_path)
             if result:
                 self.logger.write_error(result)
     
-    def SaveProjectAs(self, dosave=True):
+    def SaveProjectAs(self):
         # Ask user to choose a path with write permissions
         if wx.Platform == '__WXMSW__':
             path = os.getenv("USERPROFILE")
@@ -367,9 +374,8 @@
         if answer == wx.ID_OK:
             newprojectpath = dirdialog.GetPath()
             if os.path.isdir(newprojectpath):
-                self.ProjectPath = newprojectpath
-                if dosave:
-                    self.SaveProject()
+                self.ProjectPath, old_project_path = newprojectpath, self.ProjectPath
+                self.SaveProject(old_project_path)
                 self._setBuildPath(self.BuildPath)
                 return True
         return False
--- a/c_ext/CFileEditor.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/c_ext/CFileEditor.py	Wed Apr 24 17:37:46 2013 +0200
@@ -8,24 +8,7 @@
 from controls import CustomGrid, CustomTable
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT
 from util.BitmapLibrary import GetBitmap
-
-if wx.Platform == '__WXMSW__':
-    faces = { 'times': 'Times New Roman',
-              'mono' : 'Courier New',
-              'helv' : 'Arial',
-              'other': 'Comic Sans MS',
-              'size' : 10,
-              'size2': 8,
-             }
-else:
-    faces = { 'times': 'Times',
-              'mono' : 'Courier',
-              'helv' : 'Helvetica',
-              'other': 'new century schoolbook',
-              'size' : 12,
-              'size2': 10,
-             }
-
+from editors.TextViewer import GetCursorPos, faces
 
 def AppendMenu(parent, help, id, kind, text):
     if wx.VERSION >= (2, 6, 0):
@@ -47,27 +30,6 @@
     "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", 
     "void", "volatile", "wchar_t", "while"]
 
-def GetCursorPos(old, new):
-    old_length = len(old)
-    new_length = len(new)
-    common_length = min(old_length, new_length)
-    i = 0
-    for i in xrange(common_length):
-        if old[i] != new[i]:
-            break
-    if old_length < new_length:
-        if common_length > 0 and old[i] != new[i]:
-            return i + new_length - old_length
-        else:
-            return i + new_length - old_length + 1
-    elif old_length > new_length or i < min(old_length, new_length) - 1:
-        if common_length > 0 and old[i] != new[i]:
-            return i
-        else:
-            return i + 1
-    else:
-        return None
-
 class CppEditor(stc.StyledTextCtrl):
 
     fold_symbols = 3
@@ -165,15 +127,15 @@
         self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,  "fore:#FFFFFF,back:#0000FF,bold")
         self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,    "fore:#000000,back:#FF0000,bold")
 
-        self.StyleSetSpec(stc.STC_C_COMMENT, 'fore:#408060')
-        self.StyleSetSpec(stc.STC_C_COMMENTLINE, 'fore:#408060')
-        self.StyleSetSpec(stc.STC_C_COMMENTDOC, 'fore:#408060')
-        self.StyleSetSpec(stc.STC_C_NUMBER, 'fore:#0076AE')
-        self.StyleSetSpec(stc.STC_C_WORD, 'bold,fore:#800056')
-        self.StyleSetSpec(stc.STC_C_STRING, 'fore:#2a00ff')
-        self.StyleSetSpec(stc.STC_C_PREPROCESSOR, 'bold,fore:#800056')
-        self.StyleSetSpec(stc.STC_C_OPERATOR, 'bold')
-        self.StyleSetSpec(stc.STC_C_STRINGEOL, 'back:#FFD5FF')
+        self.StyleSetSpec(stc.STC_C_COMMENT, 'fore:#408060,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_COMMENTLINE, 'fore:#408060,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_COMMENTDOC, 'fore:#408060,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_NUMBER, 'fore:#0076AE,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_WORD, 'bold,fore:#800056,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_STRING, 'fore:#2a00ff,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_PREPROCESSOR, 'bold,fore:#800056,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_OPERATOR, 'bold,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_C_STRINGEOL, 'back:#FFD5FF,size:%(size)d' % faces)
         
         # register some images for use in the AutoComplete box.
         #self.RegisterImage(1, images.getSmilesBitmap())
@@ -254,16 +216,19 @@
         self.ResetBuffer()
         self.DisableEvents = True
         old_cursor_pos = self.GetCurrentPos()
+        line = self.GetFirstVisibleLine()
+        column = self.GetXOffset()
         old_text = self.GetText()
         new_text = self.Controler.GetPartText(self.Name)
         self.SetText(new_text)
-        new_cursor_pos = GetCursorPos(old_text, new_text)
-        if new_cursor_pos != None:
-            self.GotoPos(new_cursor_pos)
-        else:
-            self.GotoPos(old_cursor_pos)
-        self.ScrollToColumn(0)
-        self.EmptyUndoBuffer()
+        if old_text != new_text:
+            new_cursor_pos = GetCursorPos(old_text, new_text)
+            self.LineScroll(column, line)
+            if new_cursor_pos != None:
+                self.GotoPos(new_cursor_pos)
+            else:
+                self.GotoPos(old_cursor_pos)
+            self.EmptyUndoBuffer()
         self.DisableEvents = False
         
         self.Colourise(0, -1)
--- a/c_ext/c_ext.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/c_ext/c_ext.py	Wed Apr 24 17:37:46 2013 +0200
@@ -144,7 +144,7 @@
     def CTNTestModified(self):
         return self.ChangesToSave or not self.CFileIsSaved()    
 
-    def OnCTNSave(self):
+    def OnCTNSave(self, from_project_path=None):
         filepath = self.CFileName()
         
         text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
--- a/canfestival/canfestival.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/canfestival/canfestival.py	Wed Apr 24 17:37:46 2013 +0200
@@ -1,4 +1,4 @@
-import os, sys
+import os, sys, shutil
 
 base_folder = os.path.split(sys.path[0])[0]
 CanFestivalPath = os.path.join(base_folder, "CanFestival-3")
@@ -154,7 +154,7 @@
     def CTNTestModified(self):
         return self.ChangesToSave or self.OneFileHasChanged()
         
-    def OnCTNSave(self):
+    def OnCTNSave(self, from_project_path=None):
         return self.SaveCurrentInFile(self.GetSlaveODPath())
 
     def SetParamsAttribute(self, path, value):
@@ -378,8 +378,10 @@
     def CTNTestModified(self):
         return self.ChangesToSave or self.HasChanged()
         
-    def OnCTNSave(self):
+    def OnCTNSave(self, from_project_path=None):
         self.SetRoot(self.CTNPath())
+        shutil.copytree(self.GetEDSFolder(from_project_path), 
+                        self.GetEDSFolder())
         return self.SaveProject() is None
 
     def CTNGenerate_C(self, buildpath, locations):
--- a/dialogs/FindInPouDialog.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/dialogs/FindInPouDialog.py	Wed Apr 24 17:37:46 2013 +0200
@@ -109,7 +109,8 @@
         self.ParentWindow = parent
         
         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
-    
+        
+        self.FindPattern.SetFocus()
         self.RefreshButtonsState()
     
     def RefreshButtonsState(self):
--- a/editors/ConfTreeNodeEditor.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/editors/ConfTreeNodeEditor.py	Wed Apr 24 17:37:46 2013 +0200
@@ -147,57 +147,22 @@
     
     def _init_Editor(self, parent):
         tabs_num = len(self.CONFNODEEDITOR_TABS)
-        if self.SHOW_PARAMS:
+        if self.SHOW_PARAMS and len(self.Controler.GetParamsAttributes()) > 0:
             tabs_num += 1
             
-        if tabs_num > 1:
+        if tabs_num > 1 or self.SHOW_BASE_PARAMS:
             self.Editor = wx.Panel(parent, 
                 style=wx.SUNKEN_BORDER|wx.SP_3D)
-            
-            main_sizer = wx.BoxSizer(wx.VERTICAL)
-            
-            self.ConfNodeNoteBook = wx.Notebook(self.Editor)
-            parent = self.ConfNodeNoteBook
-            main_sizer.AddWindow(self.ConfNodeNoteBook, 1, flag=wx.GROW)
-            
-            self.Editor.SetSizer(main_sizer)
-        else:
-            self.ConfNodeNoteBook = None
-            self.Editor = None
-        
-        for title, create_func_name in self.CONFNODEEDITOR_TABS:
-            editor = getattr(self, create_func_name)(parent)
-            if self.ConfNodeNoteBook is not None:
-                self.ConfNodeNoteBook.AddPage(editor, title)
-            else:
-                self.Editor = editor
-        
-        if self.SHOW_PARAMS:
-            
-            panel_style = wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL
-            if self.ConfNodeNoteBook is None:
-                panel_style |= wx.SUNKEN_BORDER
-            self.ParamsEditor = wx.ScrolledWindow(parent, 
-                  style=panel_style)
-            self.ParamsEditor.SetBackgroundColour(WINDOW_COLOUR)
-            self.ParamsEditor.Bind(wx.EVT_SIZE, self.OnWindowResize)
-            self.ParamsEditor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
-            
-            # Variable allowing disabling of ParamsEditor scroll when Popup shown 
-            self.ScrollingEnabled = True
+            self.Editor.SetBackgroundColour(WINDOW_COLOUR)
+            
+            self.MainSizer = wx.BoxSizer(wx.VERTICAL)
             
             if self.SHOW_BASE_PARAMS:
-                self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-                self.ParamsEditorSizer.AddGrowableCol(0)
-                self.ParamsEditorSizer.AddGrowableRow(1)
-                
-                self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
-                
                 baseparamseditor_sizer = wx.BoxSizer(wx.HORIZONTAL)
-                self.ParamsEditorSizer.AddSizer(baseparamseditor_sizer, border=5, 
-                      flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.TOP)
-                
-                self.FullIECChannel = wx.StaticText(self.ParamsEditor, -1)
+                self.MainSizer.AddSizer(baseparamseditor_sizer, border=5, 
+                      flag=wx.GROW|wx.ALL)
+                
+                self.FullIECChannel = wx.StaticText(self.Editor, -1)
                 self.FullIECChannel.SetFont(
                     wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL, 
                             wx.BOLD, faceName = faces["helv"]))
@@ -208,19 +173,19 @@
                 baseparamseditor_sizer.AddSizer(updownsizer, border=5, 
                       flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
                 
-                self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.ParamsEditor, 
+                self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(self.Editor, 
                       bitmap=GetBitmap('IECCDown'), size=wx.Size(16, 16), style=wx.NO_BORDER)
                 self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1), 
                       self.IECCUpButton)
                 updownsizer.AddWindow(self.IECCUpButton, flag=wx.ALIGN_LEFT)
                 
-                self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.ParamsEditor, 
+                self.IECCDownButton = wx.lib.buttons.GenBitmapButton(self.Editor, 
                       bitmap=GetBitmap('IECCUp'), size=wx.Size(16, 16), style=wx.NO_BORDER)
                 self.IECCDownButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(-1), 
                       self.IECCDownButton)
                 updownsizer.AddWindow(self.IECCDownButton, flag=wx.ALIGN_LEFT)
                 
-                self.ConfNodeName = wx.TextCtrl(self.ParamsEditor, 
+                self.ConfNodeName = wx.TextCtrl(self.Editor, 
                       size=wx.Size(150, 25), style=wx.NO_BORDER)
                 self.ConfNodeName.SetFont(
                     wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL, 
@@ -234,10 +199,46 @@
                 buttons_sizer = self.GenerateMethodButtonSizer()
                 baseparamseditor_sizer.AddSizer(buttons_sizer, flag=wx.ALIGN_CENTER)
             
+            if tabs_num > 1:
+                self.ConfNodeNoteBook = wx.Notebook(self.Editor)
+                parent = self.ConfNodeNoteBook
+                self.MainSizer.AddWindow(self.ConfNodeNoteBook, 1, flag=wx.GROW)
             else:
-                self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=5)
-                self.ParamsEditorSizer.AddGrowableCol(0)
-                self.ParamsEditorSizer.AddGrowableRow(0)
+                parent = self.Editor
+                self.ConfNodeNoteBook = None
+            
+            self.Editor.SetSizer(self.MainSizer)
+        else:
+            self.ConfNodeNoteBook = None
+            self.Editor = None
+        
+        for title, create_func_name in self.CONFNODEEDITOR_TABS:
+            editor = getattr(self, create_func_name)(parent)
+            if self.ConfNodeNoteBook is not None:
+                self.ConfNodeNoteBook.AddPage(editor, title)
+            elif self.SHOW_BASE_PARAMS:
+                self.MainSizer.AddWindow(editor, 1, flag=wx.GROW)
+            else:
+                self.Editor = editor
+        
+        if self.SHOW_PARAMS and len(self.Controler.GetParamsAttributes()) > 0:
+            
+            panel_style = wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL
+            if self.ConfNodeNoteBook is None and parent != self.Editor:
+                panel_style |= wx.SUNKEN_BORDER
+            self.ParamsEditor = wx.ScrolledWindow(parent, 
+                  style=panel_style)
+            self.ParamsEditor.SetBackgroundColour(WINDOW_COLOUR)
+            self.ParamsEditor.Bind(wx.EVT_SIZE, self.OnWindowResize)
+            self.ParamsEditor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
+            
+            # Variable allowing disabling of ParamsEditor scroll when Popup shown 
+            self.ScrollingEnabled = True
+            
+            self.ParamsEditorSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=1, vgap=5)
+            self.ParamsEditorSizer.AddGrowableCol(0)
+            self.ParamsEditorSizer.AddGrowableRow(0)
+            self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
             
             self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
             self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5, 
@@ -247,6 +248,8 @@
         
             if self.ConfNodeNoteBook is not None:
                 self.ConfNodeNoteBook.AddPage(self.ParamsEditor, _("Config"))
+            elif self.SHOW_BASE_PARAMS:
+                self.MainSizer.AddWindow(self.ParamsEditor, 1, flag=wx.GROW)
             else:
                 self.Editor = self.ParamsEditor
         else:
@@ -287,10 +290,10 @@
     
     def RefreshView(self):
         EditorPanel.RefreshView(self)
+        if self.SHOW_BASE_PARAMS:
+            self.ConfNodeName.ChangeValue(self.Controler.MandatoryParams[1].getName())
+            self.RefreshIECChannelControlsState()
         if self.ParamsEditor is not None:
-            if self.SHOW_BASE_PARAMS:
-                self.ConfNodeName.ChangeValue(self.Controler.MandatoryParams[1].getName())
-                self.RefreshIECChannelControlsState()
             self.RefreshConfNodeParamsSizer()
             self.RefreshScrollbars()
     
@@ -300,6 +303,7 @@
     def RefreshIECChannelControlsState(self):
         self.FullIECChannel.SetLabel(self.Controler.GetFullIEC_Channel())
         self.IECCDownButton.Enable(self.Controler.BaseParams.getIEC_Channel() > 0)
+        self.MainSizer.Layout()
     
     def RefreshConfNodeParamsSizer(self):
         self.Freeze()
@@ -320,7 +324,7 @@
         
         for confnode_method in self.Controler.ConfNodeMethods:
             if "method" in confnode_method and confnode_method.get("shown",True):
-                button = GenBitmapTextButton(self.ParamsEditor,
+                button = GenBitmapTextButton(self.Editor,
                     bitmap=GetBitmap(confnode_method.get("bitmap", "Unknown")), 
                     label=confnode_method["name"], style=wx.NO_BORDER)
                 button.SetFont(normal_bt_font)
--- a/editors/IECCodeViewer.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/editors/IECCodeViewer.py	Wed Apr 24 17:37:46 2013 +0200
@@ -1,9 +1,13 @@
 
 from editors.TextViewer import TextViewer
+from plcopen.plcopen import TestTextElement
 
 class IECCodeViewer(TextViewer):
     
     def __del__(self):
         TextViewer.__del__(self)
         if getattr(self, "_OnClose"):
-            self._OnClose(self)
\ No newline at end of file
+            self._OnClose(self)
+            
+    def Search(self, criteria):
+        return [((self.TagName, "body", 0),) + result for result in TestTextElement(self.Editor.GetText(), criteria)]
\ No newline at end of file
--- a/editors/ProjectNodeEditor.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/editors/ProjectNodeEditor.py	Wed Apr 24 17:37:46 2013 +0200
@@ -33,12 +33,10 @@
         
         ConfTreeNodeEditor.__init__(self, parent, controler, window, tagname)
         
-        if self.SHOW_BASE_PARAMS:
-            buttons_sizer = self.GenerateMethodButtonSizer()
-            self.ParamsEditorSizer.InsertSizer(0, buttons_sizer, 0, border=5, 
-                    flag=wx.LEFT|wx.RIGHT|wx.TOP)
-            self.ParamsEditorSizer.Layout()
-            
+        buttons_sizer = self.GenerateMethodButtonSizer()
+        self.MainSizer.InsertSizer(0, buttons_sizer, 0, border=5, flag=wx.ALL)
+        self.MainSizer.Layout()
+        
         self.VariableEditor = self.VariableEditorPanel
 
     def GetTagName(self):
--- a/editors/TextViewer.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/editors/TextViewer.py	Wed Apr 24 17:37:46 2013 +0200
@@ -80,6 +80,8 @@
 }
 
 def GetCursorPos(old, new):
+    if old == "":
+        return 0
     old_length = len(old)
     new_length = len(new)
     common_length = min(old_length, new_length)
@@ -445,17 +447,20 @@
             self.ResetBuffer()
             self.DisableEvents = True
             old_cursor_pos = self.GetCurrentPos()
+            line = self.Editor.GetFirstVisibleLine()
+            column = self.Editor.GetXOffset()
             old_text = self.GetText()
             new_text = self.Controler.GetEditedElementText(self.TagName, self.Debug)
-            self.SetText(new_text)
-            new_cursor_pos = GetCursorPos(old_text, new_text)
-            if new_cursor_pos != None:
-                self.Editor.GotoPos(new_cursor_pos)
-            else:
-                self.Editor.GotoPos(old_cursor_pos)
-            self.Editor.ScrollToColumn(0)
-            self.RefreshJumpList()
-            self.Editor.EmptyUndoBuffer()
+            if old_text != new_text:
+                self.SetText(new_text)
+                new_cursor_pos = GetCursorPos(old_text, new_text)
+                self.Editor.LineScroll(column, line)
+                if new_cursor_pos != None:
+                    self.Editor.GotoPos(new_cursor_pos)
+                else:
+                    self.Editor.GotoPos(old_cursor_pos)
+                self.RefreshJumpList()
+                self.Editor.EmptyUndoBuffer()
             self.DisableEvents = False
             
             self.RefreshVariableTree()
@@ -764,6 +769,9 @@
         self.RefreshModel()
         self.RefreshBuffer()
     
+    def Search(self, criteria):
+        return self.Controler.SearchInPou(self.TagName, criteria, self.Debug)
+    
     def Find(self, direction, search_params):
         if self.SearchParams != search_params:
             self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
@@ -779,7 +787,8 @@
             self.SearchResults = [
                 (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)
                 for infos, start, end, text in 
-                self.Controler.SearchInPou(self.TagName, criteria, self.Debug)]
+                self.Search(criteria)]
+            self.CurrentFindHighlight = None
         
         if len(self.SearchResults) > 0:
             if self.CurrentFindHighlight is not None:
@@ -801,6 +810,8 @@
                 self.RemoveHighlight(*self.CurrentFindHighlight)
             self.CurrentFindHighlight = None
     
+        print self.CurrentFindHighlight
+    
     def RefreshModel(self):
         self.RefreshJumpList()
         self.Controler.SetEditedElementText(self.TagName, self.GetText())
--- a/editors/Viewer.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/editors/Viewer.py	Wed Apr 24 17:37:46 2013 +0200
@@ -3167,6 +3167,7 @@
                         blocks.append((block, (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)))
             blocks.sort(sort_blocks)
             self.SearchResults.extend([infos for block, infos in blocks])
+            self.CurrentFindHighlight = None
         
         if len(self.SearchResults) > 0:
             if self.CurrentFindHighlight is not None:
--- a/graphics/FBD_Objects.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/graphics/FBD_Objects.py	Wed Apr 24 17:37:46 2013 +0200
@@ -263,43 +263,49 @@
             self.Pen = MiterPen(self.Colour)
             
             # Extract the inputs properties and create or modify the corresponding connector
-            idx = 0
-            for idx, (input_name, input_type, input_modifier) in enumerate(inputs):
-                if idx < len(self.Inputs):
-                    connector = self.Inputs[idx]
-                    connector.SetName(input_name)
-                    connector.SetType(input_type)
-                else:
-                    connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
-                    self.Inputs.append(connector)
+            input_connectors = []
+            for input_name, input_type, input_modifier in inputs:
+                connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
                 if input_modifier == "negated":
                     connector.SetNegated(True)
                 elif input_modifier != "none":
                     connector.SetEdge(input_modifier)
-            for i in xrange(idx + 1, len(self.Inputs)):
-                self.Inputs[i].UnConnect(delete = True)
-            self.Inputs = self.Inputs[:idx + 1]
+                for input in self.Inputs:
+                    if input.GetName() == input_name:
+                        wires = input.GetWires()[:]
+                        input.UnConnect()
+                        for wire in wires:
+                            connector.Connect(wire)
+                        break
+                input_connectors.append(connector)
+            for input in self.Inputs:
+                input.UnConnect(delete = True)
+            self.Inputs = input_connectors
             
             # Extract the outputs properties and create or modify the corresponding connector
-            idx = 0
-            for idx, (output_name, output_type, output_modifier) in enumerate(outputs):
-                if idx < len(self.Outputs):
-                    connector = self.Outputs[idx]
-                    connector.SetName(output_name)
-                    connector.SetType(output_type)
-                else:
-                    connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
-                    self.Outputs.append(connector)
+            output_connectors = []
+            for output_name, output_type, output_modifier in outputs:
+                connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
                 if output_modifier == "negated":
                     connector.SetNegated(True)
                 elif output_modifier != "none":
                     connector.SetEdge(output_modifier)
-            for i in xrange(idx + 1, len(self.Outputs)):
-                self.Outputs[i].UnConnect(delete = True)
-            self.Outputs = self.Outputs[:idx + 1]
+                for output in self.Outputs:
+                    if output.GetName() == output_name:
+                        wires = output.GetWires()[:]
+                        output.UnConnect()
+                        for wire in wires:
+                            connector.Connect(wire)
+                        break
+                output_connectors.append(connector)
+            for output in self.Outputs:
+                output.UnConnect(delete = True)
+            self.Outputs = output_connectors
                 
             self.RefreshMinSize()
             self.RefreshConnectors()
+            for output in self.Outputs:
+                output.RefreshWires()
             self.RefreshBoundingBox()
     
     # Returns the block type
--- a/graphics/GraphicCommons.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/graphics/GraphicCommons.py	Wed Apr 24 17:37:46 2013 +0200
@@ -1695,6 +1695,10 @@
     def InsertConnect(self, idx, wire, refresh = True):
         if wire not in self.Wires:
             self.Wires.insert(idx, wire)
+            if wire[1] == 0:
+                wire[0].ConnectStartPoint(None, self)
+            else:
+                wire[0].ConnectEndPoint(None, self)
             if refresh:
                 self.ParentBlock.RefreshModel(False)
     
--- a/py_ext/PythonEditor.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/py_ext/PythonEditor.py	Wed Apr 24 17:37:46 2013 +0200
@@ -1,59 +1,25 @@
+import re
+import keyword
+
 import wx
 import wx.grid
-import wx.stc  as  stc
-import keyword
-
+import wx.stc as stc
+
+from plcopen.plcopen import TestTextElement
+from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
-
-if wx.Platform == '__WXMSW__':
-    faces = { 'times': 'Times New Roman',
-              'mono' : 'Courier New',
-              'helv' : 'Arial',
-              'other': 'Comic Sans MS',
-              'size' : 10,
-              'size2': 8,
-             }
-elif wx.Platform == '__WXMAC__':
-    faces = { 'times': 'Times New Roman',
-              'mono' : 'Monaco',
-              'helv' : 'Arial',
-              'other': 'Comic Sans MS',
-              'size' : 12,
-              'size2': 10,
-             }
-else:
-    faces = { 'times': 'Times',
-              'mono' : 'Courier',
-              'helv' : 'Helvetica',
-              'other': 'new century schoolbook',
-              'size' : 12,
-              'size2': 10,
-             }
+from editors.TextViewer import GetCursorPos, faces
+
+[STC_PYTHON_ERROR, STC_PYTHON_SEARCH_RESULT] = range(15, 17)
+
+HIGHLIGHT_TYPES = {
+    ERROR_HIGHLIGHT: STC_PYTHON_ERROR,
+    SEARCH_RESULT_HIGHLIGHT: STC_PYTHON_SEARCH_RESULT,
+}
 
 [ID_PYTHONEDITOR,
 ] = [wx.NewId() for _init_ctrls in range(1)]
 
-def GetCursorPos(old, new):
-    old_length = len(old)
-    new_length = len(new)
-    common_length = min(old_length, new_length)
-    i = 0
-    for i in xrange(common_length):
-        if old[i] != new[i]:
-            break
-    if old_length < new_length:
-        if common_length > 0 and old[i] != new[i]:
-            return i + new_length - old_length
-        else:
-            return i + new_length - old_length + 1
-    elif old_length > new_length or i < min(old_length, new_length) - 1:
-        if common_length > 0 and old[i] != new[i]:
-            return i
-        else:
-            return i + 1
-    else:
-        return None
-
 class PythonEditor(ConfTreeNodeEditor):
 
     fold_symbols = 3
@@ -136,7 +102,7 @@
         self.PythonCodeEditor.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
         self.PythonCodeEditor.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
         self.PythonCodeEditor.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
-
+        
         # Global default style
         if wx.Platform == '__WXMSW__':
             self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Courier New')
@@ -155,39 +121,43 @@
         # The rest remains unchanged.
 
         # Line numbers in margin
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2')    
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2,size:%(size)d' % faces)    
         # Highlighted brace
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00,size:%(size)d' % faces)
         # Unmatched brace
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000,size:%(size)d' % faces)
         # Indentation guide
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD")
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_INDENTGUIDE, 'fore:#CDCDCD,size:%(size)d' % faces)
 
         # Python styles
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_DEFAULT, 'fore:#000000,size:%(size)d' % faces)
         # Comments
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_COMMENTLINE,  'fore:#008000,back:#F0FFF0')
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_COMMENTLINE,  'fore:#008000,back:#F0FFF0,size:%(size)d' % faces)
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0,size:%(size)d' % faces)
         # Numbers
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_NUMBER, 'fore:#008080')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_NUMBER, 'fore:#008080,size:%(size)d' % faces)
         # Strings and characters
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_STRING, 'fore:#800080')
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_CHARACTER, 'fore:#800080')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_STRING, 'fore:#800080,size:%(size)d' % faces)
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_CHARACTER, 'fore:#800080,size:%(size)d' % faces)
         # Keywords
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_WORD, 'fore:#000080,bold,size:%(size)d' % faces)
         # Triple quotes
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA')
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA,size:%(size)d' % faces)
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA,size:%(size)d' % faces)
         # Class names
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_CLASSNAME, 'fore:#0000FF,bold')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_CLASSNAME, 'fore:#0000FF,bold,size:%(size)d' % faces)
         # Function names
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_DEFNAME, 'fore:#008080,bold')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_DEFNAME, 'fore:#008080,bold,size:%(size)d' % faces)
         # Operators
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_OPERATOR, 'fore:#800000,bold')
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_OPERATOR, 'fore:#800000,bold,size:%(size)d' % faces)
         # Identifiers. I leave this as not bold because everything seems
         # to be an identifier if it doesn't match the above criterae
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, 'fore:#000000')
-
+        self.PythonCodeEditor.StyleSetSpec(stc.STC_P_IDENTIFIER, 'fore:#000000,size:%(size)d' % faces)
+        
+        # Highlighting styles
+        self.PythonCodeEditor.StyleSetSpec(STC_PYTHON_ERROR, 'fore:#FF0000,back:#FFFF00,size:%(size)d' % faces)
+        self.PythonCodeEditor.StyleSetSpec(STC_PYTHON_SEARCH_RESULT, 'fore:#FFFFFF,back:#FFA500,size:%(size)d' % faces)
+        
         # Caret color
         self.PythonCodeEditor.SetCaretForeground("BLUE")
         # Selection background
@@ -219,13 +189,13 @@
         # EOL: Since we are loading/saving ourselves, and the
         # strings will always have \n's in them, set the STC to
         # edit them that way.            
-        self.PythonCodeEditor.SetEOLMode(wx.stc.STC_EOL_LF)
+        self.PythonCodeEditor.SetEOLMode(stc.STC_EOL_LF)
         self.PythonCodeEditor.SetViewEOL(False)
         
         # No right-edge mode indicator
         self.PythonCodeEditor.SetEdgeMode(stc.STC_EDGE_NONE)
         
-        self.PythonCodeEditor.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE)
+        self.PythonCodeEditor.SetModEventMask(stc.STC_MOD_BEFOREINSERT|stc.STC_MOD_BEFOREDELETE)
 
         self.PythonCodeEditor.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_PYTHONEDITOR)
         self.PythonCodeEditor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
@@ -239,6 +209,14 @@
         self.DisableEvents = False
         self.CurrentAction = None
     
+        self.Highlights = []
+        self.SearchParams = None
+        self.SearchResults = None
+        self.CurrentFindHighlight = None
+        
+        self.RefreshHighlightsTimer = wx.Timer(self, -1)
+        self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
+    
     def GetBufferState(self):
         return self.Controler.GetBufferState()
         
@@ -305,19 +283,24 @@
         self.ResetBuffer()
         self.DisableEvents = True
         old_cursor_pos = self.PythonCodeEditor.GetCurrentPos()
+        line = self.PythonCodeEditor.GetFirstVisibleLine()
+        column = self.PythonCodeEditor.GetXOffset()
         old_text = self.PythonCodeEditor.GetText()
         new_text = self.Controler.GetPythonCode()
-        self.PythonCodeEditor.SetText(new_text)
-        new_cursor_pos = GetCursorPos(old_text, new_text)
-        if new_cursor_pos != None:
-            self.PythonCodeEditor.GotoPos(new_cursor_pos)
-        else:
-            self.PythonCodeEditor.GotoPos(old_cursor_pos)
-        self.PythonCodeEditor.ScrollToColumn(0)
-        self.PythonCodeEditor.EmptyUndoBuffer()
+        if old_text != new_text:
+            self.PythonCodeEditor.SetText(new_text)
+            new_cursor_pos = GetCursorPos(old_text, new_text)
+            self.PythonCodeEditor.LineScroll(column, line)
+            if new_cursor_pos != None:
+                self.PythonCodeEditor.GotoPos(new_cursor_pos)
+            else:
+                self.PythonCodeEditor.GotoPos(old_cursor_pos)
+            self.PythonCodeEditor.EmptyUndoBuffer()
         self.DisableEvents = False
         
         self.PythonCodeEditor.Colourise(0, -1)
+        
+        self.ShowHighlights()
 
     def RefreshModel(self):
         self.Controler.SetPythonCode(self.PythonCodeEditor.GetText())
@@ -338,7 +321,7 @@
                 self.PythonCodeEditor.AutoCompShow(0, " ".join([word + "?1" for word in keyword.kwlist]))
         else:
             event.Skip()
-
+    
     def OnKillFocus(self, event):
         self.PythonCodeEditor.AutoCompCancel()
         event.Skip()
@@ -483,3 +466,89 @@
         self.DisableEvents = False
         self.RefreshModel()
         self.RefreshBuffer()
+
+    def Find(self, direction, search_params):
+        if self.SearchParams != search_params:
+            self.ClearHighlights(SEARCH_RESULT_HIGHLIGHT)
+            
+            self.SearchParams = search_params
+            criteria = {
+                "raw_pattern": search_params["find_pattern"], 
+                "pattern": re.compile(search_params["find_pattern"]),
+                "case_sensitive": search_params["case_sensitive"],
+                "regular_expression": search_params["regular_expression"],
+                "filter": "all"}
+            
+            self.SearchResults = [
+                (start, end, SEARCH_RESULT_HIGHLIGHT)
+                for start, end, text in 
+                TestTextElement(self.PythonCodeEditor.GetText(), criteria)]
+            self.CurrentFindHighlight = None
+        
+        if len(self.SearchResults) > 0:
+            if self.CurrentFindHighlight is not None:
+                old_idx = self.SearchResults.index(self.CurrentFindHighlight)
+                if self.SearchParams["wrap"]:
+                    idx = (old_idx + direction) % len(self.SearchResults)
+                else:
+                    idx = max(0, min(old_idx + direction, len(self.SearchResults) - 1))
+                if idx != old_idx:
+                    self.RemoveHighlight(*self.CurrentFindHighlight)
+                    self.CurrentFindHighlight = self.SearchResults[idx]
+                    self.AddHighlight(*self.CurrentFindHighlight)
+            else:
+                self.CurrentFindHighlight = self.SearchResults[0]
+                self.AddHighlight(*self.CurrentFindHighlight)
+            
+        else:
+            if self.CurrentFindHighlight is not None:
+                self.RemoveHighlight(*self.CurrentFindHighlight)
+            self.CurrentFindHighlight = None
+
+#-------------------------------------------------------------------------------
+#                        Highlights showing functions
+#-------------------------------------------------------------------------------
+
+    def OnRefreshHighlightsTimer(self, event):
+        self.RefreshView()
+        event.Skip()
+
+    def ClearHighlights(self, highlight_type=None):
+        if highlight_type is None:
+            self.Highlights = []
+        else:
+            highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+            if highlight_type is not None:
+                self.Highlights = [(start, end, highlight) for (start, end, highlight) in self.Highlights if highlight != highlight_type]
+        self.RefreshView()
+
+    def AddHighlight(self, start, end, highlight_type):
+        highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+        if highlight_type is not None:
+            self.Highlights.append((start, end, highlight_type))
+            self.PythonCodeEditor.GotoPos(self.PythonCodeEditor.PositionFromLine(start[0]) + start[1])
+            self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+            self.RefreshView()
+
+    def RemoveHighlight(self, start, end, highlight_type):
+        highlight_type = HIGHLIGHT_TYPES.get(highlight_type, None)
+        if (highlight_type is not None and 
+            (start, end, highlight_type) in self.Highlights):
+            self.Highlights.remove((start, end, highlight_type))
+            self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
+    
+    def ShowHighlights(self):
+        for start, end, highlight_type in self.Highlights:
+            if start[0] == 0:
+                highlight_start_pos = start[1]
+            else:
+                highlight_start_pos = self.PythonCodeEditor.GetLineEndPosition(start[0] - 1) + start[1] + 1
+            if end[0] == 0:
+                highlight_end_pos = end[1] - indent + 1
+            else:
+                highlight_end_pos = self.PythonCodeEditor.GetLineEndPosition(end[0] - 1) + end[1] + 2
+            self.PythonCodeEditor.StartStyling(highlight_start_pos, 0xff)
+            self.PythonCodeEditor.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
+            self.PythonCodeEditor.StartStyling(highlight_start_pos, 0x00)
+            self.PythonCodeEditor.SetStyling(len(self.PythonCodeEditor.GetText()) - highlight_end_pos, stc.STC_STYLE_DEFAULT)
+                
--- a/py_ext/PythonFileCTNMixin.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/py_ext/PythonFileCTNMixin.py	Wed Apr 24 17:37:46 2013 +0200
@@ -48,7 +48,7 @@
     def CTNTestModified(self):
         return self.ChangesToSave or not self.PythonIsSaved()
     
-    def OnCTNSave(self):
+    def OnCTNSave(self, from_project_path=None):
         filepath = self.PythonFileName()
         
         text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
--- a/svgui/svgui.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/svgui/svgui.py	Wed Apr 24 17:37:46 2013 +0200
@@ -5,12 +5,13 @@
 
 from POULibrary import POULibrary
 from docutil import open_svg
+from py_ext import PythonFileCTNMixin
 
 class SVGUILibrary(POULibrary):
     def GetLibraryPath(self):
         return os.path.join(os.path.split(__file__)[0], "pous.xml") 
 
-class SVGUI:
+class SVGUI(PythonFileCTNMixin):
 
     ConfNodeMethods = [
         {"bitmap" : "ImportSVG",
@@ -26,13 +27,21 @@
     def ConfNodePath(self):
         return os.path.join(os.path.dirname(__file__))
 
-    def _getSVGpath(self):
-        # define name for IEC raw code file
-        return os.path.join(self.CTNPath(), "gui.svg")
+    def _getSVGpath(self, project_path=None):
+        if project_path is None:
+            project_path = self.CTNPath()
+        # define name for SVG file containing gui layout
+        return os.path.join(project_path, "gui.svg")
 
     def _getSVGUIserverpath(self):
         return os.path.join(os.path.dirname(__file__), "svgui_server.py")
 
+    def OnCTNSave(self, from_project_path=None):
+        if from_project_path is not None:
+            shutil.copyfile(self._getSVGpath(from_project_path),
+                            self._getSVGpath())
+        return PythonFileCTNMixin.OnCTNSave(self, from_project_path)
+
     def CTNGenerate_C(self, buildpath, locations):
         """
         Return C code generated by iec2c compiler 
--- a/wxglade_hmi/wxglade_hmi.py	Wed Apr 24 18:34:00 2013 +0900
+++ b/wxglade_hmi/wxglade_hmi.py	Wed Apr 24 17:37:46 2013 +0200
@@ -1,5 +1,5 @@
 import wx
-import os, sys
+import os, sys, shutil
 from xml.dom import minidom
 
 from py_ext import PythonFileCTNMixin
@@ -16,9 +16,11 @@
     def ConfNodePath(self):
         return os.path.join(os.path.dirname(__file__))
 
-    def _getWXGLADEpath(self):
-        # define name for IEC raw code file
-        return os.path.join(self.CTNPath(), "hmi.wxg")
+    def _getWXGLADEpath(self, project_path=None):
+        if project_path is None:
+            project_path = self.CTNPath()
+        # define name for wxGlade gui file
+        return os.path.join(project_path, "hmi.wxg")
 
     def launch_wxglade(self, options, wait=False):
         from wxglade import __file__ as fileName
@@ -29,6 +31,11 @@
         mode = {False:os.P_NOWAIT, True:os.P_WAIT}[wait]
         os.spawnv(mode, sys.executable, ["\"%s\""%sys.executable] + [glade] + options)
 
+    def OnCTNSave(self, from_project_path=None):
+        if from_project_path is not None:
+            shutil.copyfile(self._getWXGLADEpath(from_project_path),
+                            self._getWXGLADEpath())
+        return PythonFileCTNMixin.OnCTNSave(self, from_project_path)
 
     def CTNGenerate_C(self, buildpath, locations):
         """
@@ -128,3 +135,4 @@
             if wx.Platform == '__WXMSW__':
                 wxg_filename = "\"%s\""%wxg_filename
             self.launch_wxglade([wxg_filename])
+