Adding support for Undo/Redo and Unsaved File On Close detection
authorlbessard
Thu, 26 Jul 2007 17:23:21 +0200
changeset 56 7187e1c00975
parent 55 e24d2f917c7e
child 57 9bf197698af0
Adding support for Undo/Redo and Unsaved File On Close detection
Dialogs.py
LDViewer.py
PLCControler.py
PLCOpenEditor.py
RessourceEditor.py
SFCViewer.py
TextViewer.py
Viewer.py
examples/example.xml
graphics/GraphicCommons.py
--- a/Dialogs.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/Dialogs.py	Thu Jul 26 17:23:21 2007 +0200
@@ -384,21 +384,23 @@
         self.RefreshNameList()
 
     def SetValues(self, values):
-        for name, value in values.items():
-            if name == "type":
-                if value == INPUT:
-                    self.Class.SetStringSelection("Input")
-                if value == OUTPUT:
-                    self.Class.SetStringSelection("Output")
-                if value == INOUT:
-                    self.Class.SetStringSelection("InOut")
-            elif name == "name" and value != "":
-                if self.Name.FindString(value) != wxNOT_FOUND:
-                    self.Name.SetStringSelection(value)
-                    self.Expression.Enable(False)
-                else:
-                    self.Expression.SetValue(value)
-                    self.Name.Enable(False)
+        value_type = values.get("type", None)
+        value_name = values.get("name", None)
+        if value_type:
+            if value_type == INPUT:
+                self.Class.SetStringSelection("Input")
+            if value_type == OUTPUT:
+                self.Class.SetStringSelection("Output")
+            if value_type == INOUT:
+                self.Class.SetStringSelection("InOut")
+            self.RefreshNameList()
+        if value_name:
+            if self.Name.FindString(value_name) != wxNOT_FOUND:
+                self.Name.SetStringSelection(value_name)
+                self.Expression.Enable(False)
+            else:
+                self.Expression.SetValue(value_name)
+                self.Name.Enable(False)
         self.RefreshPreview()
         
     def GetValues(self):
--- a/LDViewer.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/LDViewer.py	Thu Jul 26 17:23:21 2007 +0200
@@ -411,7 +411,29 @@
                         self.DeleteWire(self.SelectedElement)
                     else:
                         self.SelectedElement.Delete()
-            self.Refresh()
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
+            elif keycode == WXK_LEFT:
+                if event.ControlDown() and event.ShiftDown():
+                    self.Scroll(0, ypos)
+                elif event.ControlDown():
+                    self.Scroll(max(0, xpos - 1), ypos)
+            elif keycode == WXK_RIGHT:
+                if event.ControlDown() and event.ShiftDown():
+                    self.Scroll(xmax, ypos)
+                elif event.ControlDown():
+                    self.Scroll(min(xpos + 1, xmax), ypos)
+            elif keycode == WXK_UP:
+                if event.ControlDown() and event.ShiftDown():
+                    self.Scroll(xpos, 0)
+                elif event.ControlDown():
+                    self.Scroll(xpos, max(0, ypos - 1))
+            elif keycode == WXK_DOWN:
+                if event.ControlDown() and event.ShiftDown():
+                    self.Scroll(xpos, ymax)
+                elif event.ControlDown():
+                    self.Scroll(xpos, min(ypos + 1, ymax))
         event.Skip()
 
 #-------------------------------------------------------------------------------
@@ -493,6 +515,8 @@
             rung.SelectElement(wire)
             self.RefreshPosition(coil)
             self.Rungs.append(rung)
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
             self.Refresh()
 
     def AddLadderContact(self):
@@ -584,6 +608,8 @@
                 rung.RefreshBoundingBox()
                 new_bbox = rung.GetBoundingBox()
                 self.RefreshRungs(new_bbox.height - old_bbox.height, rungindex + 1)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
         else:
             message = wxMessageDialog(self, "You must select the wire where a contact should be added!", "Error", wxOK|wxICON_ERROR)
@@ -803,6 +829,8 @@
                 rung.RefreshBoundingBox()
                 new_bbox = rung.GetBoundingBox()
                 self.RefreshRungs(new_bbox.height - old_bbox.height, rungindex + 1)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
             else:
                 message = wxMessageDialog(self, "The group of block must be coherent!", "Error", wxOK|wxICON_ERROR)
@@ -1158,6 +1186,8 @@
                 contact.SetName(values["name"])
                 contact.SetType(values["type"])
                 contact.RefreshModel(False)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
             dialog.Destroy()
 
@@ -1182,6 +1212,8 @@
                 coil.SetName(values["name"])
                 coil.SetType(values["type"])
                 coil.RefreshModel(False)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
             dialog.Destroy()
 
--- a/PLCControler.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/PLCControler.py	Thu Jul 26 17:23:21 2007 +0200
@@ -162,6 +162,7 @@
         self.VerifyXML = False
         self.Project = None
         self.ProjectBuffer = None
+        self.Buffering = False
         self.FilePath = ""
         self.FileName = ""
         self.ProgramFilePath = ""
@@ -187,16 +188,10 @@
         # Create the project
         self.Project = plcopen.project()
         self.Project.setName(name)
+        self.SetFilePath("")
         # Initialize the project buffer
-        #self.ProjectBuffer = UndoBuffer(self.Copy(self.Project))
-    
-    # Change project name
-    def SetProjectName(self, name):
-        self.Project.setName(name)
-    
-    # Return project name
-    def GetProjectName(self):
-        return self.Project.getName()
+        self.ProjectBuffer = UndoBuffer(self.Copy(self.Project), False)
+        self.Buffering = False
     
     # Return project pou names
     def GetProjectPouNames(self):
@@ -219,6 +214,10 @@
                         variables.append(action.getName())
         return variables
     
+    # Return if project is saved
+    def ProjectIsSaved(self):
+        return self.ProjectBuffer.IsCurrentSaved()
+    
     # Return file path if project is an open file
     def GetFilePath(self):
         return self.FilePath
@@ -244,12 +243,19 @@
             self.FileName = os.path.splitext(os.path.basename(filepath))[0]
     
     # Change project properties
-    def SetProjectProperties(self, values):
-        self.Project.setFileHeader(values)
-    
+    def SetProjectProperties(self, name = None, properties = None):
+        if name != None:
+            self.Project.setName(name)
+        if properties != None:
+            self.Project.setFileHeader(properties)
+        if name != None or properties != None:
+            self.BufferProject()
+            
     # Return project properties
     def GetProjectProperties(self):
-        return self.Project.getFileHeader()
+        properties = self.Project.getFileHeader()
+        properties["projectName"] = self.Project.getName()
+        return properties
     
     # Return project informations
     def GetProjectInfos(self):
@@ -376,15 +382,15 @@
 
     def GenerateProgram(self, filepath):
         if self.Project:
-            #try:
-            program = GenerateCurrentProgram(self.Project)
-            programfile = open(filepath, "w")
-            programfile.write(program)
-            programfile.close()
-            self.ProgramFilePath = filepath
-            return True
-            #except:
-            #    pass
+            try:
+                program = GenerateCurrentProgram(self.Project)
+                programfile = open(filepath, "w")
+                programfile.write(program)
+                programfile.close()
+                self.ProgramFilePath = filepath
+                return True
+            except:
+                pass
         return False
 
 #-------------------------------------------------------------------------------
@@ -397,6 +403,7 @@
         self.Project.appendPou(name, pou_type, body_type)
         self.RefreshPouUsingTree()
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Remove a pou from project
     def ProjectRemovePou(self, name):
@@ -418,40 +425,47 @@
         self.Project.removePou(name)
         self.RefreshPouUsingTree()
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Add a configuration to Project
     def ProjectAddConfiguration(self, name):
         self.Project.addConfiguration(name)
         self.RefreshPouUsingTree()
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Remove a configuration from project
     def ProjectRemoveConfiguration(self, name):
         self.Project.removeConfiguration(name)
         self.RefreshPouUsingTree()
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Add a resource to a configuration of the Project
     def ProjectAddConfigurationResource(self, config, name):
         self.Project.addConfigurationResource(config, name)
         self.RefreshPouUsingTree()
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Remove a resource from a configuration of the project
     def ProjectRemoveConfigurationResource(self, config, name):
         self.Project.removeConfigurationResource(config, name)
         self.RefreshPouUsingTree()
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Add a Transition to a Project Pou
     def ProjectAddPouTransition(self, pou_name, transition_name, transition_type):
         pou = self.Project.getPou(pou_name)
         pou.addTransition(transition_name, transition_type)
+        self.BufferProject()
     
     # Add a Transition to a Project Pou
     def ProjectAddPouAction(self, pou_name, action_name, action_type):
         pou = self.Project.getPou(pou_name)
         pou.addAction(action_name, action_type)
+        self.BufferProject()
         
     # Change the name of a pou
     def ChangePouName(self, old_name, new_name):
@@ -464,6 +478,7 @@
             self.ElementsOpened[idx] = new_name
         self.RefreshPouUsingTree()
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Change the name of a pou transition
     def ChangePouTransitionName(self, pou_name, old_name, new_name):
@@ -477,6 +492,7 @@
         if old_computedname in self.ElementsOpened:
             idx = self.ElementsOpened.index(old_computedname)
             self.ElementsOpened[idx] = new_computedname
+        self.BufferProject()
     
     # Change the name of a pou action
     def ChangePouActionName(self, pou_name, old_name, new_name):
@@ -490,7 +506,8 @@
         if old_computedname in self.ElementsOpened:
             idx = self.ElementsOpened.index(old_computedname)
             self.ElementsOpened[idx] = new_computedname
-
+        self.BufferProject()
+    
     # Change the name of a pou action
     def ChangePouVariableName(self, pou_name, old_name, new_name):
         # Found the pou action corresponding to old name and change its name to new name
@@ -500,6 +517,7 @@
                 if var.getName() == old_name:
                     var.setName(new_name)
         self.RefreshBlockTypes()
+        self.BufferProject()
         
     # Change the name of a configuration
     def ChangeConfigurationName(self, old_name, new_name):
@@ -509,7 +527,8 @@
         # If configuration is currently opened, change its name in the list of opened elements
         for idx, element in enumerate(self.ElementsOpened):
             self.ElementsOpened[idx] = element.replace(old_name, new_name)
-
+        self.BufferProject()
+    
     # Change the name of a configuration resource
     def ChangeConfigurationResourceName(self, config_name, old_name, new_name):
         # Found the resource corresponding to old name and change its name to new name
@@ -521,6 +540,7 @@
         if old_computedname in self.ElementsOpened:
             idx = self.ElementsOpened.index(old_computedname)
             self.ElementsOpened[idx] = new_computedname
+        self.BufferProject()
     
     # Return the type of the pou given by its name
     def GetPouType(self, name):
@@ -579,11 +599,13 @@
     def ProjectRemovePouTransition(self, pou_name, transition_name):
         pou = self.Project.getPou(pou_name)
         pou.removeTransition(transition_name)
+        self.BufferProject()
     
     # Add a Transition to a Project Pou
     def ProjectRemovePouAction(self, pou_name, action_name):
         pou = self.Project.getPou(pou_name)
         pou.removeAction(action_name)
+        self.BufferProject()
     
     # Extract varlists from a list of vars
     def ExtractVarLists(self, vars):
@@ -627,7 +649,8 @@
             for vartype, varlist in self.ExtractVarLists(vars):
                 configuration.globalVars.append(varlist)
         self.RefreshBlockTypes()
-
+        self.BufferProject()
+    
     # Return the configuration globalvars
     def GetConfigurationGlobalVars(self, name):
         vars = []
@@ -669,7 +692,8 @@
             for vartype, varlist in self.ExtractVarLists(vars):
                 resource.globalVars.append(varlist)
         self.RefreshBlockTypes()
-
+        self.BufferProject()
+    
     # Return the resource globalvars
     def GetConfigurationResourceGlobalVars(self, config_name, name):
         vars = []
@@ -745,7 +769,8 @@
         # Set Pou interface
         pou.setVars(self.ExtractVarLists(vars))
         self.RefreshBlockTypes()
-
+        self.BufferProject()
+    
     # Replace the return type of the pou given by its name (only for functions)
     def SetPouInterfaceReturnType(self, name, type):
         pou = self.Project.getPou(name)
@@ -759,6 +784,7 @@
         # Change return type
         return_type.setValue(type)
         self.RefreshBlockTypes()
+        self.BufferProject()
     
     # Return the return type of the pou given by its name
     def GetPouInterfaceReturnTypeByName(self, name):
@@ -1951,8 +1977,9 @@
         
         self.Project = plcopen.project()
         self.Project.loadXMLTree(tree.childNodes[0])
-        self.UndoBuffer = UndoBuffer(self.Copy(self.Project), True)
         self.SetFilePath(filepath)
+        self.ProjectBuffer = UndoBuffer(self.Copy(self.Project), True)
+        self.Buffering = False
         self.ElementsOpened = []
         self.CurrentElementEditing = None
         self.RefreshPouUsingTree()
@@ -1982,7 +2009,7 @@
                 xmlfile = open(self.FilePath,"w")
             xmlfile.write(text)
             xmlfile.close()
-            #self.ProjectBuffer.CurrentSaved()
+            self.ProjectBuffer.CurrentSaved()
             if filepath:
                 self.SetFilePath(filepath)
             return True
@@ -1998,18 +2025,25 @@
         return cPickle.loads(cPickle.dumps(model))
 
     def BufferProject(self):
-        self.ProjectBuffer.Buffering(self.Copy(self))
+        self.ProjectBuffer.Buffering(self.Copy(self.Project))
+
+    def StartBuffering(self):
+        self.ProjectBuffer.Buffering(self.Project)
+        self.Buffering = True
+        
+    def EndBuffering(self):
+        if self.Buffering:
+            self.Project = self.Copy(self.Project)
+            self.Buffering = False
 
     def ProjectIsSaved(self):
         return self.ProjectBuffer.IsCurrentSaved()
 
     def LoadPrevious(self):
         self.Project = self.Copy(self.ProjectBuffer.Previous())
-        self.RefreshElementsOpened()
     
     def LoadNext(self):
         self.Project = self.Copy(self.ProjectBuffer.Next())
-        self.RefreshElementsOpened()
     
     def GetBufferState(self):
         first = self.ProjectBuffer.IsFirst()
--- a/PLCOpenEditor.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/PLCOpenEditor.py	Thu Jul 26 17:23:21 2007 +0200
@@ -190,6 +190,10 @@
               kind=wx.ITEM_NORMAL, text=u'Remove Configuration')
         self.Bind(wx.EVT_MENU, self.OnRefreshMenu,
               id=wxID_PLCOPENEDITOREDITMENUITEMS0)
+        self.Bind(wx.EVT_MENU, self.OnUndoMenu,
+              id=wxID_PLCOPENEDITOREDITMENUITEMS1)
+        self.Bind(wx.EVT_MENU, self.OnRedoMenu,
+              id=wxID_PLCOPENEDITOREDITMENUITEMS2)
         self.Bind(wx.EVT_MENU, self.OnCutMenu,
               id=wxID_PLCOPENEDITOREDITMENUITEMS4)
         self.Bind(wx.EVT_MENU, self.OnCopyMenu,
@@ -373,6 +377,7 @@
         self._init_utils()
         self.SetClientSize(wx.Size(1000, 600))
         self.SetMenuBar(self.menuBar1)
+        self.Bind(wx.EVT_CLOSE, self.OnCloseFrame, id=wxID_PLCOPENEDITOR)
         
         self.splitterWindow1 = wx.SplitterWindow(id=wxID_PLCOPENEDITORSPLITTERWINDOW1,
               name='splitterWindow1', parent=self, point=wx.Point(0, 0),
@@ -443,11 +448,18 @@
         
         self.RefreshFileMenu()
         self.RefreshEditMenu()
+        self.RefreshTitle()
         self.RefreshToolBar()
 
     def GetDrawingMode(self):
         return self.DrawingMode
 
+    def RefreshTitle(self):
+        if self.Controler.HasOpenedProject() > 0:
+            self.SetTitle("PLCOpenEditor - %s"%self.Controler.GetFilename())
+        else:
+            self.SetTitle("PLCOpenEditor")
+
     def RefreshFileMenu(self):
         if self.FileMenu:
             if self.Controler.HasOpenedProject():
@@ -470,8 +482,13 @@
 
     def RefreshEditMenu(self):
         if self.EditMenu:
-            self.EditMenu.FindItemByPosition(1).Enable(False)
-            self.EditMenu.FindItemByPosition(2).Enable(False)
+            if self.Controler.HasOpenedProject():
+                undo, redo = self.Controler.GetBufferState()
+                self.EditMenu.Enable(wxID_PLCOPENEDITOREDITMENUITEMS1, undo)
+                self.EditMenu.Enable(wxID_PLCOPENEDITOREDITMENUITEMS2, redo)
+            else:
+                self.EditMenu.Enable(wxID_PLCOPENEDITOREDITMENUITEMS1, False)
+                self.EditMenu.Enable(wxID_PLCOPENEDITOREDITMENUITEMS2, False)
             if self.Controler.HasOpenedProject():
                 if self.TabsOpened.GetPageCount() > 0:
                     self.EditMenu.FindItemByPosition(0).Enable(True)
@@ -495,18 +512,29 @@
 
     def ShowProperties(self):
         old_values = self.Controler.GetProjectProperties()
-        old_values["projectName"] = self.Controler.GetProjectName()
         dialog = ProjectDialog(self)
         dialog.SetValues(old_values)
         if dialog.ShowModal() == wxID_OK:
             new_values = dialog.GetValues()
             projectname = new_values.pop("projectName")
             new_values["creationDateTime"] = old_values["creationDateTime"]
-            self.Controler.SetProjectName(projectname)
-            self.Controler.SetProjectProperties(new_values)
+            self.Controler.SetProjectProperties(projectname, new_values)
             self.RefreshProjectTree()
         dialog.Destroy()
 
+    def OnCloseFrame(self, event):
+        if not self.Controler.ProjectIsSaved():
+            dialog = wxMessageDialog(self, "There are changes, do you want to save?",  "Close Application", wxYES_NO|wxCANCEL|wxICON_QUESTION)
+            answer = dialog.ShowModal()
+            dialog.Destroy()
+            if answer == wxID_YES:
+                self.SaveProject()
+                event.Skip()
+            elif answer == wxID_NO:
+                event.Skip()
+        else:
+            event.Skip()
+
     def OnNewProjectMenu(self, event):
         dialog = ProjectDialog(self)
         if dialog.ShowModal() == wxID_OK:
@@ -514,7 +542,8 @@
             projectname = values.pop("projectName")
             values["creationDateTime"] = datetime(*localtime()[:6])
             self.Controler.CreateNewProject(projectname)
-            self.Controler.SetProjectProperties(values)
+            self.Controler.SetProjectProperties(projectname, values)
+            self.RefreshTitle()
             self.RefreshFileMenu()
             self.RefreshEditMenu()
             self.RefreshProjectTree()
@@ -533,6 +562,7 @@
                 self.Controler.OpenXMLFile(filepath)
                 self.TabsOpened.DeleteAllPages()
                 self.RefreshProjectTree()
+            self.RefreshTitle()
             self.RefreshFileMenu()
             self.RefreshEditMenu()
             self.RefreshToolBar()
@@ -546,6 +576,7 @@
             self.TabsOpened.DeletePage(selected)
             if self.TabsOpened.GetPageCount() > 0:
                 self.TabsOpened.SetSelection(min(selected, self.TabsOpened.GetPageCount() - 1))
+            self.RefreshTitle()
             self.RefreshFileMenu()
             self.RefreshEditMenu()
             self.RefreshToolBar()
@@ -555,6 +586,7 @@
         self.Controler.Reset()
         self.TabsOpened.DeleteAllPages()
         self.ProjectTree.DeleteAllItems()
+        self.RefreshTitle()
         self.RefreshFileMenu()
         self.RefreshEditMenu()
         self.RefreshToolBar()
@@ -589,13 +621,15 @@
         result = self.Controler.SaveXMLFile()
         if not result:
             self.SaveProjectAs()
+        else:
+            self.RefreshTitle()
     
     def SaveProjectAs(self):
         filepath = self.Controler.GetFilePath()
         if filepath != "":
             directory, filename = os.path.split(filepath)
         else:
-            directory, filename = os.getcwd(), "%s.xml"%self.Controler.GetProjectName()
+            directory, filename = os.getcwd(), "%(projectName)s.xml"%self.Controler.GetProjectProperties()
         dialog = wxFileDialog(self, "Choose a file", directory, filename,  "PLCOpen files (*.xml)|*.xml|All files|*.*", wxSAVE|wxOVERWRITE_PROMPT)
         if dialog.ShowModal() == wxID_OK:
             filepath = dialog.GetPath()
@@ -609,6 +643,7 @@
                 message = wxMessageDialog(self, "%s is not a valid folder!"%os.path.dirname(filepath), "Error", wxOK|wxICON_ERROR)
                 message.ShowModal()
                 message.Destroy()
+            self.RefreshTitle()
         dialog.Destroy()
 
     def OnPropertiesMenu(self, event):
@@ -768,6 +803,9 @@
         event.Skip()
         
     def OnPouSelectedChanged(self, event):
+        old_selected = self.TabsOpened.GetSelection()
+        if old_selected >= 0:
+            self.TabsOpened.GetPage(old_selected).ResetBuffer()
         selected = event.GetSelection()
         if selected >= 0:
             self.Controler.RefreshCurrentElementEditing(selected)
@@ -809,7 +847,7 @@
                 item = event.GetItem()
                 itemtype = self.ProjectTree.GetPyData(item)
                 if itemtype == ITEM_PROJECT:
-                    self.Controler.SetProjectName(new_name)
+                    self.Controler.SetProjectProperties(name = new_name)
                 elif itemtype == ITEM_POU:
                     if new_name.upper() in self.Controler.GetProjectPouNames():
                         message = "\"%s\" pou already exists!"%new_name
@@ -1088,6 +1126,9 @@
                     else:
                         idx = self.Controler.ChangePouActionEditing(grandparent_name, name)
             if idx != None:
+                old_selected = self.TabsOpened.GetSelection()
+                if old_selected >= 0:
+                    self.TabsOpened.GetPage(old_selected).ResetBuffer()
                 self.TabsOpened.SetSelection(idx)
                 window = self.TabsOpened.GetPage(idx)
                 window.RefreshView()
@@ -1197,9 +1238,30 @@
     def OnRefreshMenu(self, event):
         selected = self.TabsOpened.GetSelection()
         if selected != -1:
+            self.TabsOpened.GetPage(selected).RefreshView()
             self.TabsOpened.GetPage(selected).Refresh()
         event.Skip()
-    
+
+    def OnUndoMenu(self, event):
+        self.Controler.LoadPrevious()
+        selected = self.TabsOpened.GetSelection()
+        if selected != -1:
+            self.TabsOpened.GetPage(selected).RefreshView()
+            self.TabsOpened.GetPage(selected).Refresh()
+        self.RefreshTitle()
+        self.RefreshEditMenu()
+        event.Skip()
+    
+    def OnRedoMenu(self, event):
+        self.Controler.LoadNext()
+        selected = self.TabsOpened.GetSelection()
+        if selected != -1:
+            self.TabsOpened.GetPage(selected).RefreshView()
+            self.TabsOpened.GetPage(selected).Refresh()
+        self.RefreshTitle()
+        self.RefreshEditMenu()
+        event.Skip()
+
     def OnCutMenu(self, event):
         selected = self.TabsOpened.GetSelection()
         if selected != -1:
@@ -1244,9 +1306,12 @@
             selected = dialog.GetStringSelection()
             if not self.Controler.PouIsUsed(selected):
                 self.Controler.ProjectRemovePou(selected)
+                deleted = None
                 for i in xrange(self.TabsOpened.GetPageCount()):
                     if self.TabsOpened.GetPageText(i) == selected:
-                        self.TabsOpened.DeletePage(i)
+                        deleted = i
+                if deleted != None:
+                    self.TabsOpened.DeletePage(i)
                 self.RefreshProjectTree()
                 self.RefreshToolBar()
             else:
@@ -2173,6 +2238,8 @@
             self.Viewer = wx.Panel(id=wxID_POUEDITORPANELVIEWER,
               name='ConfigPanel', parent=self, pos=wx.Point(0, 0),
               size=wx.Size(-1, -1), style=wx.TAB_TRAVERSAL)
+            self.Viewer.ResetBuffer = lambda: None
+            self.Viewer.RefreshView = lambda: None
         elif element_type == "resource":
             self.Viewer = ResourceEditor(self, self.Parent, self.Controler)
         elif element_type == "FBD":
@@ -2327,6 +2394,9 @@
             if language not in ["IL", "ST"]:
                 self.Viewer.SetMode(mode)
 
+    def ResetBuffer(self):
+        self.Viewer.ResetBuffer()
+
     def RefreshView(self):
         self.PouNames = self.Controler.GetProjectPouNames()
         
@@ -2360,9 +2430,8 @@
             self.ReturnType.SetStringSelection(returnType)
         self.RefreshValues()
         self.RefreshButtons()
-        if self.ElementType != "config":
-            self.Viewer.RefreshView()
-                
+        self.Viewer.RefreshView()
+        
     def OnClassFilter(self, event):
         self.Filter = self.FilterChoiceTransfer[self.ClassFilter.GetStringSelection()]
         self.RefreshTypeList()
@@ -2381,15 +2450,16 @@
             self.ClassList = [self.Filter]
 
     def RefreshButtons(self):
-        table_length = len(self.Table.data)
-        row_class = None
-        if table_length and self.PouIsUsed:
-            row = self.VariablesGrid.GetGridCursorRow()
-            row_class = self.Table.GetValueByName(row, "Class")
-        self.AddButton.Enable(not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"])
-        self.DeleteButton.Enable(table_length > 0 and row_class not in ["Input", "Output", "InOut"])
-        self.UpButton.Enable(table_length > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])
-        self.DownButton.Enable(table_length > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])
+        if self.Table:
+            table_length = len(self.Table.data)
+            row_class = None
+            if table_length and self.PouIsUsed:
+                row = self.VariablesGrid.GetGridCursorRow()
+                row_class = self.Table.GetValueByName(row, "Class")
+            self.AddButton.Enable(not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"])
+            self.DeleteButton.Enable(table_length > 0 and row_class not in ["Input", "Output", "InOut"])
+            self.UpButton.Enable(table_length > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])
+            self.DownButton.Enable(table_length > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])
 
     def OnAddButton(self, event):
         new_row = self.DefaultValue.copy()
--- a/RessourceEditor.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/RessourceEditor.py	Thu Jul 26 17:23:21 2007 +0200
@@ -419,6 +419,16 @@
         
     def RefreshModel(self):
         self.Controler.SetCurrentResourceEditingInfos(self.TasksTable.GetData(), self.InstancesTable.GetData())
+        self.RefreshBuffer()
+        
+    def ResetBuffer(self):
+        pass
+
+    # Buffer the last model state
+    def RefreshBuffer(self):
+        self.Controler.BufferProject()
+        self.Parent.RefreshTitle()
+        self.Parent.RefreshEditMenu()
 
     def RefreshView(self):
         tasks, instances = self.Controler.GetCurrentResourceEditingInfos()
@@ -433,7 +443,6 @@
     def OnAddTaskButton(self, event):
         self.TasksTable.AppendRow(self.TasksDefaultValue.copy())
         self.RefreshModel()
-        self.RefreshView()
         event.Skip()
 
     def OnDeleteTaskButton(self, event):
--- a/SFCViewer.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/SFCViewer.py	Thu Jul 26 17:23:21 2007 +0200
@@ -313,6 +313,9 @@
         if keycode == WXK_DELETE and self.SelectedElement:
             self.SelectedElement.Delete()
             self.SelectedElement = None
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
+            self.Refresh()
         elif keycode == WXK_LEFT:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(0, ypos)
@@ -320,6 +323,9 @@
                 self.Scroll(max(0, xpos - 1), ypos)
             elif self.SelectedElement:
                 self.SelectedElement.Move(-scaling[0], 0)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
         elif keycode == WXK_RIGHT:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(xmax, ypos)
@@ -327,6 +333,9 @@
                 self.Scroll(min(xpos + 1, xmax), ypos)
             elif self.SelectedElement:
                 self.SelectedElement.Move(scaling[0], 0)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
         elif keycode == WXK_UP:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(xpos, 0)
@@ -334,6 +343,9 @@
                 self.Scroll(xpos, max(0, ypos - 1))
             elif self.SelectedElement:
                 self.SelectedElement.Move(0, -scaling[1])
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
         elif keycode == WXK_DOWN:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(xpos, ymax)
@@ -341,7 +353,9 @@
                 self.Scroll(xpos, min(ypos + 1, ymax))
             elif self.SelectedElement:
                 self.SelectedElement.Move(0, scaling[1])
-        self.Refresh()
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
         
 #-------------------------------------------------------------------------------
 #                          Adding element functions
@@ -364,6 +378,8 @@
             self.Controler.AddCurrentElementEditingStep(id)
             self.RefreshStepModel(step)
             self.Parent.RefreshProjectTree()
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
 
@@ -417,6 +433,8 @@
                     self.SelectedElement = step
                     self.SelectedElement.SetSelected(True)
                 self.Parent.RefreshProjectTree()
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
             dialog.Destroy()
     
@@ -444,6 +462,8 @@
                     self.AddBlock(actionblock)
                     self.Controler.AddCurrentElementEditingActionBlock(id)
                     self.RefreshActionBlockModel(actionblock)
+                    self.RefreshBuffer()
+                    self.RefreshScrollBars()
                     self.Refresh()
                 dialog.Destroy()
     
@@ -638,6 +658,8 @@
                             transition_connectors = transition.GetConnectors()
                             previous = transition_connectors["output"]
                         self.CreateStep("Step", previous)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
             dialog.Destroy()
     
@@ -654,6 +676,8 @@
                 else:
                     previous = divergence_connectors["outputs"][-1]
                 step = self.CreateStep("Step", previous)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
     
     def AddJump(self):
@@ -681,23 +705,10 @@
                 transition.RefreshOutputPosition()
                 wire.SetPoints([wxPoint(pos.x, pos.y + SFC_WIRE_MIN_SIZE), wxPoint(pos.x, pos.y)])
                 self.RefreshJumpModel(jump)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
                 self.Refresh()
             dialog.Destroy()
-    
-    def AddComment(self, bbox):
-        dialog = wxTextEntryDialog(self.Parent, "Add a new comment", "Please enter comment text", "", wxOK|wxCANCEL|wxTE_MULTILINE)
-        if dialog.ShowModal() == wxID_OK:
-            value = dialog.GetValue()
-            id = self.GetNewId()
-            comment = Comment(self, value, id)
-            comment.SetPosition(bbox.x, bbox.y)
-            min_width, min_height = comment.GetMinSize()
-            comment.SetSize(max(min_width,bbox.width),max(min_height,bbox.height))
-            self.AddComment(comment)
-            self.Controler.AddCurrentElementEditingComment(id)
-            self.RefreshCommentModel(comment)
-            self.Refresh()
-        dialog.Destroy()
 
     def EditStepContent(self, step):
         dialog = StepNameDialog(self.Parent, "Edit step name", "Please enter step name", step.GetName(), wxOK|wxCANCEL)
@@ -711,6 +722,8 @@
             size = step.GetSize()
             step.UpdateSize(max(min_size[0], size[0]), max(min_size[1], size[1]))
             step.RefreshModel()
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
 
@@ -723,6 +736,8 @@
             transition.SetType(values["type"])
             transition.SetCondition(values["value"])
             transition.RefreshModel()
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
 
@@ -737,6 +752,8 @@
             value = dialog.GetStringSelection()
             jump.SetTarget(value)
             jump.RefreshModel()
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
 
@@ -750,6 +767,8 @@
             actions = dialog.GetValues()
             actionblock.SetActions(actions)
             actionblock.RefreshModel()
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
         
--- a/TextViewer.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/TextViewer.py	Thu Jul 26 17:23:21 2007 +0200
@@ -25,6 +25,7 @@
 from wxPython.wx import *
 from wxPython.stc import * 
 import wx
+from types import *
 
 import re
 
@@ -68,6 +69,27 @@
 IDENTIFIER_MODEL = re.compile(re_texts["identifier"])
 LABEL_MODEL = re.compile("[ \t\n]%(identifier)s:[ \t\n]"%re_texts)
 
+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 TextViewer(wxStyledTextCtrl):
     
     def __init__(self, parent, window, controler):
@@ -112,14 +134,40 @@
         self.Functions = []
         self.Jumps = []
         self.TextChanged = False
+        self.DisableEvents = True
         self.TextSyntax = "ST"
-        
+        self.CurrentAction = None
+        
+        self.Parent = window
         self.Controler = controler
 
+        self.SetModEventMask(wxSTC_MOD_BEFOREINSERT|wxSTC_MOD_BEFOREDELETE)
+
         EVT_KEY_DOWN(self, self.OnKeyDown)
         EVT_STC_STYLENEEDED(self, wxID_TEXTVIEWER, self.OnStyleNeeded)
         EVT_STC_DO_DROP(self, wxID_TEXTVIEWER, self.OnDoDrop)
         EVT_KILL_FOCUS(self, self.OnKillFocus)
+        EVT_STC_MODIFIED(self, wxID_TEXTVIEWER, self.OnModification)
+    
+    def OnModification(self, event):
+        if not self.DisableEvents:
+            mod_type = event.GetModificationType()
+            if not (mod_type&wxSTC_PERFORMED_UNDO or mod_type&wxSTC_PERFORMED_REDO):
+                if mod_type&wxSTC_MOD_BEFOREINSERT:
+                    if self.CurrentAction == None:
+                        self.StartBuffering()
+                    elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1:
+                        self.Controler.EndBuffering()
+                        self.StartBuffering()
+                    self.CurrentAction = ("Add", event.GetPosition())
+                elif mod_type&wxSTC_MOD_BEFOREDELETE:
+                    if self.CurrentAction == None:
+                        self.StartBuffering()
+                    elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1:
+                        self.Controler.EndBuffering()
+                        self.StartBuffering()
+                    self.CurrentAction = ("Delete", event.GetPosition())
+        event.Skip()
     
     def OnDoDrop(self, event):
         try:
@@ -156,10 +204,39 @@
         self.Jumps = [jump.upper() for jump in LABEL_MODEL.findall(self.GetText())]
         self.Colourise(0, -1)
     
+    # Buffer the last model state
+    def RefreshBuffer(self):
+        self.Controler.BufferProject()
+        self.Parent.RefreshTitle()
+        self.Parent.RefreshEditMenu()
+    
+    def StartBuffering(self):
+        self.Controler.StartBuffering()
+        self.Parent.RefreshTitle()
+        self.Parent.RefreshEditMenu()
+    
+    def ResetBuffer(self):
+        if self.CurrentAction != None:
+            self.Controler.EndBuffering()
+            self.CurrentAction = None
+    
     def RefreshView(self):
-        self.SetText(self.Controler.GetCurrentElementEditingText())
+        self.ResetBuffer()
+        self.DisableEvents = True
+        old_cursor_pos = self.GetCurrentPos()
+        old_text = self.GetText()
+        new_text = self.Controler.GetCurrentElementEditingText()
+        self.SetText(new_text)
+        new_cursor_pos = GetCursorPos(old_text, new_text)
+        if new_cursor_pos != None:
+            self.SetSelection(new_cursor_pos, new_cursor_pos)
+            self.EnsureCaretVisible()
+        else:
+            self.SetSelection(old_cursor_pos, old_cursor_pos)
         self.RefreshJumpList()
-    
+        self.EmptyUndoBuffer()
+        self.DisableEvents = False
+        
     def OnStyleNeeded(self, event):
         self.TextChanged = True
         line = self.LineFromPosition(self.GetEndStyled())
@@ -273,13 +350,17 @@
         event.Skip()
     
     def Cut(self):
+        self.ResetBuffer()
         self.CmdKeyExecute(wxSTC_CMD_CUT)
-        
+        self.RefreshBuffer()
+    
     def Copy(self):
         self.CmdKeyExecute(wxSTC_CMD_COPY)
     
     def Paste(self):
+        self.ResetBuffer()
         self.CmdKeyExecute(wxSTC_CMD_PASTE)
+        self.RefreshBuffer()
     
     def RefreshModel(self):
         if self.TextChanged:
--- a/Viewer.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/Viewer.py	Thu Jul 26 17:23:21 2007 +0200
@@ -266,15 +266,24 @@
             self.SelectedElement.SetSelected(False)
             self.SelectedElement = None
             self.Refresh()
-
+    
     # Return current drawing mode
     def GetDrawingMode(self):
         return self.Parent.GetDrawingMode()
+    
+    # Buffer the last model state
+    def RefreshBuffer(self):
+        self.Controler.BufferProject()
+        self.Parent.RefreshTitle()
+        self.Parent.RefreshEditMenu()
 
 #-------------------------------------------------------------------------------
 #                          Refresh functions
 #-------------------------------------------------------------------------------
 
+    def ResetBuffer(self):
+        pass
+
     # Refresh Viewer elements
     def RefreshView(self):
         self.current_id = 0
@@ -849,6 +858,7 @@
                 self.SelectedElement.GeneratePoints()
                 self.SelectedElement.RefreshModel()
                 self.SelectedElement.SetSelected(True)
+                self.RefreshBuffer()
             else:
                 self.SelectedElement.Delete()
                 self.SelectedElement = None
@@ -927,6 +937,9 @@
             self.SelectedElement.Clean()
             self.SelectedElement.Delete()
             self.SelectedElement = None
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
+            self.Refresh()
         elif keycode == WXK_LEFT:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(0, ypos)
@@ -934,6 +947,9 @@
                 self.Scroll(max(0, xpos - 1), ypos)
             elif self.SelectedElement:
                 self.SelectedElement.Move(-scaling[0], 0)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
         elif keycode == WXK_RIGHT:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(xmax, ypos)
@@ -941,6 +957,9 @@
                 self.Scroll(min(xpos + 1, xmax), ypos)
             elif self.SelectedElement:
                 self.SelectedElement.Move(scaling[0], 0)
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
         elif keycode == WXK_UP:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(xpos, 0)
@@ -948,6 +967,9 @@
                 self.Scroll(xpos, max(0, ypos - 1))
             elif self.SelectedElement:
                 self.SelectedElement.Move(0, -scaling[1])
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
         elif keycode == WXK_DOWN:
             if event.ControlDown() and event.ShiftDown():
                 self.Scroll(xpos, ymax)
@@ -955,7 +977,9 @@
                 self.Scroll(xpos, min(ypos + 1, ymax))
             elif self.SelectedElement:
                 self.SelectedElement.Move(0, scaling[1])
-        self.Refresh()
+                self.RefreshBuffer()
+                self.RefreshScrollBars()
+                self.Refresh()
 
 #-------------------------------------------------------------------------------
 #                          Model adding functions
@@ -977,6 +1001,7 @@
             self.AddBlock(block)
             self.Controler.AddCurrentElementEditingBlock(id)
             self.RefreshBlockModel(block)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1002,6 +1027,7 @@
             self.AddBlock(variable)
             self.Controler.AddCurrentElementEditingVariable(id, values["type"])
             self.RefreshVariableModel(variable)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1018,6 +1044,7 @@
             self.AddBlock(connection)
             self.Controler.AddCurrentElementEditingConnection(id, values["type"])
             self.RefreshConnectionModel(connection)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1034,6 +1061,7 @@
             self.AddComment(comment)
             self.Controler.AddCurrentElementEditingComment(id)
             self.RefreshCommentModel(comment)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1058,6 +1086,7 @@
             self.AddBlock(contact)
             self.Controler.AddCurrentElementEditingContact(id)
             self.RefreshContactModel(contact)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1085,6 +1114,7 @@
             self.AddBlock(coil)
             self.Controler.AddCurrentElementEditingCoil(id)
             self.RefreshCoilModel(contact)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1101,6 +1131,7 @@
             self.AddBlock(powerrail)
             self.Controler.AddCurrentElementEditingPowerRail(id, values["type"])
             self.RefreshPowerRailModel(powerrail)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1118,6 +1149,7 @@
             self.AddBlock(transition)
             self.Controler.AddCurrentElementEditingTransition(id)
             self.RefreshTransitionModel(transition)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1135,6 +1167,7 @@
             self.AddBlock(divergence)
             self.Controler.AddCurrentElementEditingDivergence(id, values["type"])
             self.RefreshDivergenceModel(divergence)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1158,6 +1191,7 @@
             block.SetSize(values["width"], values["height"])
             block.SetType(values["type"], values["extension"])
             self.RefreshBlockModel(block)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1187,6 +1221,7 @@
                 self.Controler.RemoveCurrentElementEditingInstance(id)
                 self.Controler.AddCurrentElementEditingVariable(id, values["type"])
             self.RefreshVariableModel(variable)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1207,6 +1242,7 @@
                 self.Controler.RemoveCurrentElementEditingInstance(id)
                 self.Controler.AddCurrentElementEditingConnection(id, values["type"])
             self.RefreshConnectionModel(connection)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1229,6 +1265,7 @@
             contact.SetType(values["type"])
             contact.SetSize(values["width"], values["height"])
             self.RefreshContactModel(contact)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1254,6 +1291,7 @@
             coil.SetType(values["type"])
             coil.SetSize(values["width"], values["height"])
             self.RefreshContactModel(coil)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
@@ -1271,6 +1309,7 @@
                 self.Controler.RemoveCurrentElementEditingInstance(id)
                 self.Controler.AddCurrentElementEditingPowerRail(id, values["type"])
             self.RefreshPowerRailModel(powerrail)
+            self.RefreshBuffer()
             self.RefreshScrollBars()
             self.Refresh()
         dialog.Destroy()
--- a/examples/example.xml	Wed Jul 25 10:06:29 2007 +0200
+++ b/examples/example.xml	Thu Jul 26 17:23:21 2007 +0200
@@ -862,6 +862,7 @@
   Control_State := TRUE;
 ELSE
   Control_State := FALSE;
+
   PumpSpeed := 10.0;
 END_IF;
           </ST>
@@ -873,11 +874,11 @@
     <configurations>
       <configuration name="ConfigTest">
         <resource name="ResourceTest">
-          <task interval="01:00:00.100000" name="Toto" priority="6">
+          <task interval="00:00:00.010000" name="Toto" priority="6">
             <pouInstance type="SFCTest" name="Program1"/>
           </task>
           <globalVars>
-            <variable name="Titi" address="M30">
+            <variable name="Titi" address="%MW30">
               <type>
                 <INT/>
               </type>
@@ -885,19 +886,18 @@
           </globalVars>
         </resource>
         <globalVars>
-          <variable name="Toto" address="M10">
+          <variable name="Toto" address="%MW10">
             <type>
               <INT/>
             </type>
           </variable>
-          <variable name="Tutu" address="M20">
+          <variable name="Tutu" address="%MX20">
             <type>
               <BOOL/>
             </type>
           </variable>
         </globalVars>
       </configuration>
-      <configuration name="ConfigTest2"/>
     </configurations>
   </instances>
 </project>
--- a/graphics/GraphicCommons.py	Wed Jul 25 10:06:29 2007 +0200
+++ b/graphics/GraphicCommons.py	Thu Jul 26 17:23:21 2007 +0200
@@ -401,6 +401,7 @@
             movey = pos.y - self.oldPos.y
             self.ProcessDragging(movex, movey)
             self.RefreshModel()
+            self.Parent.RefreshBuffer()
         self.SetSelected(True)
         self.oldPos = None
 
@@ -1601,7 +1602,6 @@
                 if self.EndConnected:
                     self.UnConnectEndPoint()
                 self.MoveEndPoint(new_pos)
-            self.RefreshModel()
         # A segment has been handled, move a segment
         elif handle_type == HANDLE_SEGMENT:
             self.MoveSegment(handle[0], movex, movey)