RC4 1.1 Korean release
authorEdouard Tisserant
Wed, 31 Jul 2013 10:45:07 +0900
branch1.1 Korean release
changeset 1280 72a826dfcfbb
parent 977 c8e008b8cefe (current diff)
parent 1279 0eb9f8af479f (diff)
child 1384 02fe382c4511
RC4
c_ext/cext_xsd.xsd
controls/DebugVariablePanel.py
images/icons.svg
targets/plc_common_main.c
tests/python/python@py_ext/py_ext.xml
--- a/Beremiz.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/Beremiz.py	Wed Jul 31 10:45:07 2013 +0900
@@ -130,7 +130,7 @@
         AddBitmapFolder(os.path.join(extension_folder, "images"))
         execfile(extfilename, locals())
 
-import wx.lib.buttons, wx.lib.statbmp
+import wx.lib.buttons, wx.lib.statbmp, wx.stc
 import cPickle
 import types, time, re, platform, time, traceback, commands
 
@@ -146,6 +146,8 @@
 from editors.DataTypeEditor import DataTypeEditor
 from util.MiniTextControler import MiniTextControler
 from util.ProcessLogger import ProcessLogger
+from controls.LogViewer import LogViewer
+from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
 
 from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY, ITEM_PROJECT, ITEM_RESOURCE
 from ProjectController import ProjectController, MATIEC_ERROR_MODEL, ITEM_CONFNODE
@@ -176,7 +178,17 @@
         if self._bitmap:
             dc.DrawBitmap(self._bitmap, 0, 0, True)
 
-                        
+if wx.Platform == '__WXMSW__':
+    faces = {
+        'mono' : 'Courier New',
+        'size' : 8,
+    }
+else:
+    faces = {
+        'mono' : 'Courier',
+        'size' : 10,
+    }
+
 from threading import Lock,Timer,currentThread
 MainThread = currentThread().ident
 REFRESH_PERIOD = 0.1
@@ -184,10 +196,9 @@
 class LogPseudoFile:
     """ Base class for file like objects to facilitate StdOut for the Shell."""
     def __init__(self, output, risecall):
-        self.red_white = wx.TextAttr("RED", "WHITE")
-        self.red_yellow = wx.TextAttr("RED", "YELLOW")
-        self.black_white = wx.TextAttr("BLACK", "WHITE")
-        self.default_style = None
+        self.red_white = 1
+        self.red_yellow = 2
+        self.black_white = wx.stc.STC_STYLE_DEFAULT
         self.output = output
         self.risecall = risecall
         # to prevent rapid fire on rising log panel
@@ -242,14 +253,21 @@
             self.lock.acquire()
             for s, style in self.stack:
                 if style is None : style=self.black_white
-                if self.default_style != style: 
-                    self.output.SetDefaultStyle(style)
-                    self.default_style = style
+                if style != self.black_white:
+                    self.output.StartStyling(self.output.GetLength(), 0xff)
+                
+                # Temporary deactivate read only mode on StyledTextCtrl for
+                # adding text. It seems that text modifications, even 
+                # programmatically, are disabled in StyledTextCtrl when read
+                # only is active 
+                self.output.SetReadOnly(False)
                 self.output.AppendText(s)
-                self.output.ScrollLines(s.count('\n')+1)
+                self.output.SetReadOnly(True)
+                
+                if style != self.black_white:
+                    self.output.SetStyling(len(s), style)
             self.stack = []
             self.lock.release()
-            self.output.ShowPosition(self.output.GetLastPosition())
             self.output.Thaw()
             self.LastRefreshTime = gettime()
             try:
@@ -258,7 +276,7 @@
                 pass
             newtime = time.time()
             if newtime - self.rising_timer > 1:
-                self.risecall()
+                self.risecall(self.output)
             self.rising_timer = newtime
         
     def write_warning(self, s):
@@ -272,10 +290,15 @@
         wx.GetApp().Yield()
 
     def flush(self):
-        self.output.SetValue("")
+        # Temporary deactivate read only mode on StyledTextCtrl for clearing
+        # text. It seems that text modifications, even programmatically, are
+        # disabled in StyledTextCtrl when read only is active 
+        self.output.SetReadOnly(False)
+        self.output.SetText("")
+        self.output.SetReadOnly(True)
     
     def isatty(self):
-        return false
+        return False
 
 [ID_BEREMIZ, ID_BEREMIZMAINSPLITTER, 
  ID_BEREMIZPLCCONFIG, ID_BEREMIZLOGCONSOLE, 
@@ -287,7 +310,7 @@
 CONFNODEMENU_POSITION = 3
 
 class Beremiz(IDEFrame):
-	
+    
     def _init_utils(self):
         self.ConfNodeMenu = wx.Menu(title='')
         self.RecentProjectsMenu = wx.Menu(title='')
@@ -339,10 +362,13 @@
     
     def _init_coll_AddMenu_Items(self, parent):
         IDEFrame._init_coll_AddMenu_Items(self, parent, False)
-        new_id = wx.NewId()
-        AppendMenu(parent, help='', id=new_id,
-                  kind=wx.ITEM_NORMAL, text=_(u'&Resource'))
-        self.Bind(wx.EVT_MENU, self.AddResourceMenu, id=new_id)
+        
+        # Disable add resource until matiec is able to handle multiple ressource definition
+        #new_id = wx.NewId()
+        #AppendMenu(parent, help='', id=new_id,
+        #          kind=wx.ITEM_NORMAL, text=_(u'&Resource'))
+        #self.Bind(wx.EVT_MENU, self.AddResourceMenu, id=new_id)
+        
         for name, XSDClass, help in ProjectController.CTNChildrenTypes:
             new_id = wx.NewId()
             AppendMenu(parent, help='', id=new_id, 
@@ -354,6 +380,15 @@
               kind=wx.ITEM_NORMAL, text=_(u'About'))
         self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
     
+    def _init_coll_ConnectionStatusBar_Fields(self, parent):
+        parent.SetFieldsCount(3)
+
+        parent.SetStatusText(number=0, text='')
+        parent.SetStatusText(number=1, text='')
+        parent.SetStatusText(number=2, text='')
+
+        parent.SetStatusWidths([-1, 300, 200])
+    
     def _init_ctrls(self, prnt):
         IDEFrame._init_ctrls(self, prnt)
         
@@ -378,14 +413,40 @@
         
         self.SetAcceleratorTable(wx.AcceleratorTable(accels))
         
-        self.LogConsole = wx.TextCtrl(id=ID_BEREMIZLOGCONSOLE, value='',
+        self.LogConsole = CustomStyledTextCtrl(id=ID_BEREMIZLOGCONSOLE,
                   name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0),
-                  size=wx.Size(0, 0), style=wx.TE_MULTILINE|wx.TE_RICH2)
-        self.LogConsole.Bind(wx.EVT_LEFT_DCLICK, self.OnLogConsoleDClick)
-        self.MainTabs["LogConsole"] = (self.LogConsole, _("Log Console"))
+                  size=wx.Size(0, 0))
+        self.LogConsole.Bind(wx.EVT_SET_FOCUS, self.OnLogConsoleFocusChanged)
+        self.LogConsole.Bind(wx.EVT_KILL_FOCUS, self.OnLogConsoleFocusChanged)
+        self.LogConsole.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnLogConsoleUpdateUI)
+        self.LogConsole.SetReadOnly(True)
+        self.LogConsole.SetWrapMode(wx.stc.STC_WRAP_CHAR)
+        
+        # Define Log Console styles
+        self.LogConsole.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
+        self.LogConsole.StyleClearAll()
+        self.LogConsole.StyleSetSpec(1, "face:%(mono)s,fore:#FF0000,size:%(size)d" % faces)
+        self.LogConsole.StyleSetSpec(2, "face:%(mono)s,fore:#FF0000,back:#FFFF00,size:%(size)d" % faces)
+        
+        # Define Log Console markers
+        self.LogConsole.SetMarginSensitive(1, True)
+        self.LogConsole.SetMarginType(1, wx.stc.STC_MARGIN_SYMBOL)
+        self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED")
+        
+        self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT)
+        
+        self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick)
+        self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified)
+        
+        self.MainTabs["LogConsole"] = (self.LogConsole, _("Console"))
         self.BottomNoteBook.AddPage(*self.MainTabs["LogConsole"])
         #self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogConsole), wx.RIGHT)
-
+        
+        self.LogViewer = LogViewer(self.BottomNoteBook, self)
+        self.MainTabs["LogViewer"] = (self.LogViewer, _("PLC Log"))
+        self.BottomNoteBook.AddPage(*self.MainTabs["LogViewer"])
+        #self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogViewer), wx.RIGHT)
+        
         StatusToolBar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
                 wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
         StatusToolBar.SetToolBitmapSize(wx.Size(25, 25))
@@ -398,9 +459,13 @@
         
         self.AUIManager.Update()
         
+        self.ConnectionStatusBar = wx.StatusBar(self, style=wx.ST_SIZEGRIP)
+        self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar)
+        self.SetStatusBar(self.ConnectionStatusBar)
+        
     def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True):
         IDEFrame.__init__(self, parent, debug)
-        self.Log = LogPseudoFile(self.LogConsole,self.RiseLogConsole)
+        self.Log = LogPseudoFile(self.LogConsole,self.SelectTab)
         
         self.local_runtime = None
         self.runtime_port = None
@@ -433,15 +498,6 @@
         if projectOpen is not None:
             projectOpen = DecodeFileSystemPath(projectOpen, False)
         
-        if (self.EnableSaveProjectState() and ctr is None and 
-            projectOpen is None and self.Config.HasEntry("currenteditedproject")):
-            try:
-                projectOpen = DecodeFileSystemPath(self.Config.Read("currenteditedproject"))
-                if projectOpen == "":
-                    projectOpen = None
-            except:
-                projectOpen = None
-        
         if projectOpen is not None and os.path.isdir(projectOpen):
             self.CTR = ProjectController(self, self.Log)
             self.Controler = self.CTR
@@ -472,9 +528,6 @@
         self.RefreshAll()
         self.LogConsole.SetFocus()
 
-    def RiseLogConsole(self):
-        self.BottomNoteBook.SetSelection(self.BottomNoteBook.GetPageIndex(self.LogConsole))
-        
     def RefreshTitle(self):
         name = _("Beremiz")
         if self.CTR is not None:
@@ -527,21 +580,37 @@
             wnd = self
         InspectionTool().Show(wnd, True)
 
-    def OnLogConsoleDClick(self, event):
-        wx.CallAfter(self.SearchLineForError)
+    def OnLogConsoleFocusChanged(self, event):
+        self.RefreshEditMenu()
         event.Skip()
 
-    def SearchLineForError(self):
+    def OnLogConsoleUpdateUI(self, event):
+        self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True)
+        event.Skip()
+
+    def OnLogConsoleMarginClick(self, event):
+        line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
+        wx.CallAfter(self.SearchLineForError, self.LogConsole.GetLine(line_idx))
+        event.Skip()
+        
+    def OnLogConsoleModified(self, event):
+        line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
+        line = self.LogConsole.GetLine(line_idx)
+        if line:
+            result = MATIEC_ERROR_MODEL.match(line)
+            if result is not None:
+                self.LogConsole.MarkerAdd(line_idx, 0)
+        event.Skip()
+
+    def SearchLineForError(self, line):
         if self.CTR is not None:
-            text = self.LogConsole.GetRange(0, self.LogConsole.GetInsertionPoint())
-            line = self.LogConsole.GetLineText(len(text.splitlines()) - 1)
             result = MATIEC_ERROR_MODEL.match(line)
             if result is not None:
                 first_line, first_column, last_line, last_column, error = result.groups()
                 infos = self.CTR.ShowError(self.Log,
                                                   (int(first_line), int(first_column)), 
                                                   (int(last_line), int(last_column)))
-	
+    
     ## Function displaying an Error dialog in PLCOpenEditor.
     #  @return False if closing cancelled.
     def CheckSaveBeforeClosing(self, title=_("Close Project")):
@@ -590,6 +659,10 @@
             return IDEFrame.LoadTab(self, notebook, page_infos)
     
     def OnCloseFrame(self, event):
+        for evt_type in [wx.EVT_SET_FOCUS, 
+                         wx.EVT_KILL_FOCUS, 
+                         wx.stc.EVT_STC_UPDATEUI]:
+            self.LogConsole.Unbind(evt_type)
         if self.CTR is None or self.CheckSaveBeforeClosing(_("Close Application")):
             if self.CTR is not None:
                 self.CTR.KillDebugThread()
@@ -597,13 +670,6 @@
             
             self.SaveLastState()
             
-            if self.CTR is not None:
-                project_path = os.path.realpath(self.CTR.GetProjectPath())
-            else:
-                project_path = ""
-            self.Config.Write("currenteditedproject", EncodeFileSystemPath(project_path))    
-            self.Config.Flush()
-            
             event.Skip()
         else:
             event.Veto()
@@ -728,6 +794,9 @@
     
     def RefreshEditMenu(self):
         IDEFrame.RefreshEditMenu(self)
+        if self.FindFocus() == self.LogConsole:
+            self.EditMenu.Enable(wx.ID_COPY, True)
+            self.Panes["MenuToolBar"].EnableTool(wx.ID_COPY, True)
         
         if self.CTR is not None:
             selected = self.TabsOpened.GetSelection()
@@ -781,6 +850,10 @@
     def GetConfigEntry(self, entry_name, default):
         return cPickle.loads(str(self.Config.Read(entry_name, cPickle.dumps(default))))
     
+    def ResetConnectionStatusBar(self):
+        for field in xrange(self.ConnectionStatusBar.GetFieldsCount()):
+            self.ConnectionStatusBar.SetStatusText('', field)
+    
     def ResetView(self):
         IDEFrame.ResetView(self)
         self.ConfNodeInfos = {}
@@ -790,6 +863,7 @@
         self.Log.flush()
         if self.EnableDebug:
             self.DebugVariablePanel.SetDataProducer(None)
+            self.ResetConnectionStatusBar()
     
     def RefreshConfigRecentProjects(self, projectpath):
         try:
@@ -808,10 +882,6 @@
         IDEFrame.ResetPerspective(self)
         self.RefreshStatusToolBar()
     
-    def RestoreLastLayout(self):
-        IDEFrame.RestoreLastLayout(self)
-        self.RefreshStatusToolBar()
-    
     def OnNewProjectMenu(self, event):
         if self.CTR is not None and not self.CheckSaveBeforeClosing():
             return
@@ -878,8 +948,6 @@
                 self.RefreshConfigRecentProjects(projectpath)
                 if self.EnableDebug:
                     self.DebugVariablePanel.SetDataProducer(self.CTR)
-                if self.EnableSaveProjectState():
-                    self.LoadProjectLayout()
                 self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
             else:
                 self.ResetView()
@@ -893,7 +961,6 @@
         if self.CTR is not None and not self.CheckSaveBeforeClosing():
             return
         
-        self.SaveProjectLayout()
         self.ResetView()
         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
         self.RefreshAll()
@@ -917,7 +984,6 @@
             self.CTR.SaveProjectAs()
             self.RefreshAll()
             self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)
-        event.Skip()
     
     def OnQuitMenu(self, event):
         self.Close()
@@ -933,10 +999,7 @@
             IDEFrame.OnProjectTreeItemBeginEdit(self, event)
     
     def OnProjectTreeRightUp(self, event):
-        if wx.Platform == '__WXMSW__':
-            item = event.GetItem()
-        else:
-            item, flags = self.ProjectTree.HitTest(wx.Point(event.GetX(), event.GetY()))
+        item = event.GetItem()
         item_infos = self.ProjectTree.GetPyData(item)
         
         if item_infos["type"] == ITEM_CONFNODE:
@@ -964,6 +1027,11 @@
             confnode_menu.Destroy()
             
             event.Skip()
+        elif item_infos["type"] != ITEM_PROJECT:
+            parent = self.ProjectTree.GetItemParent(item)
+            parent_name = self.ProjectTree.GetItemText(parent)
+            if item_infos["type"] != ITEM_RESOURCE or parent_name == _("Resources"):
+                IDEFrame.OnProjectTreeRightUp(self, event)
         else:
             IDEFrame.OnProjectTreeRightUp(self, event)
     
@@ -980,15 +1048,15 @@
             IDEFrame.OnProjectTreeItemActivated(self, event)
     
     def ProjectTreeItemSelect(self, select_item):
-        name = self.ProjectTree.GetItemText(select_item)
-        item_infos = self.ProjectTree.GetPyData(select_item)
-        if item_infos["type"] == ITEM_CONFNODE:
-            item_infos["confnode"]._OpenView(onlyopened=True)
-        elif item_infos["type"] == ITEM_PROJECT:
-            self.CTR._OpenView(onlyopened=True)
-        else:
-            IDEFrame.ProjectTreeItemSelect(self, select_item)
-            
+        if select_item is not None and select_item.IsOk():
+            name = self.ProjectTree.GetItemText(select_item)
+            item_infos = self.ProjectTree.GetPyData(select_item)
+            if item_infos["type"] == ITEM_CONFNODE:
+                item_infos["confnode"]._OpenView(onlyopened=True)
+            elif item_infos["type"] == ITEM_PROJECT:
+                self.CTR._OpenView(onlyopened=True)
+            else:
+                IDEFrame.ProjectTreeItemSelect(self, select_item)
     
     def SelectProjectTreeItem(self, tagname):
         if self.ProjectTree is not None:
@@ -999,13 +1067,13 @@
                     if tagname == "Project":
                         self.SelectedItem = root
                         self.ProjectTree.SelectItem(root)
-                        wx.CallAfter(self.ResetSelectedItem)
+                        self.ResetSelectedItem()
                     else:
                         return self.RecursiveProjectTreeItemSelection(root, 
                               [(word, ITEM_CONFNODE) for word in tagname.split(".")])
                 elif words[0] == "R":
                     return self.RecursiveProjectTreeItemSelection(root, [(words[2], ITEM_RESOURCE)])
-                else:
+                elif not os.path.exists(words[0]):
                     IDEFrame.SelectProjectTreeItem(self, tagname)
             
     def GetAddConfNodeFunction(self, name, confnode=None):
@@ -1065,6 +1133,7 @@
 #-------------------------------------------------------------------------------
 #                               Exception Handler
 #-------------------------------------------------------------------------------
+import threading, traceback
 
 Max_Traceback_List_Size = 20
 
@@ -1159,11 +1228,26 @@
     #sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args)
     sys.excepthook = handle_exception
 
+    init_old = threading.Thread.__init__
+    def init(self, *args, **kwargs):
+        init_old(self, *args, **kwargs)
+        run_old = self.run
+        def run_with_except_hook(*args, **kw):
+            try:
+                run_old(*args, **kw)
+            except (KeyboardInterrupt, SystemExit):
+                raise
+            except:
+                sys.excepthook(*sys.exc_info())
+        self.run = run_with_except_hook
+    threading.Thread.__init__ = init
+
 if __name__ == '__main__':
     # Install a exception handle for bug reports
     AddExceptHook(os.getcwd(),updateinfo_url)
     
     frame = Beremiz(None, projectOpen, buildpath)
-    splash.Close()
+    if splash:
+        splash.Close()
     frame.Show()
     app.MainLoop()
--- a/Beremiz_service.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/Beremiz_service.py	Wed Jul 31 10:45:07 2013 +0900
@@ -23,7 +23,7 @@
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 import os, sys, getopt
-from threading import Thread,Timer
+from threading import Thread
 
 def usage():
     print """
@@ -114,6 +114,8 @@
         import gettext
         
         CWD = os.path.split(os.path.realpath(__file__))[0]
+        def Bpath(*args):
+            return os.path.join(CWD,*args)
         
         # Get folder containing translation files
         localedir = os.path.join(CWD,"locale")
@@ -138,109 +140,9 @@
         if __name__ == '__main__':
             __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation
         
-        try:
-            from wx.lib.embeddedimage import PyEmbeddedImage
-        except:
-            import cStringIO
-            import base64
-            
-            class PyEmbeddedImage:
-                def __init__(self, image_string):
-                    stream = cStringIO.StringIO(base64.b64decode(image_string))
-                    self.Image = wx.ImageFromStream(stream)
-                def GetImage(self):
-                    return self.Image
-        
-        defaulticon = PyEmbeddedImage(
-        "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABc5J"
-        "REFUSIl9lW1MW9cZx3/n2vf6BQO2MZiXGBISILCVUEUlitYpjaKpXZJ1XZZ2kzJVY9r6IeLD"
-        "pGTaNG3KtGmNNGlbpW3VFhRp0l6aZCllpVUqtVNJtBFKE5QXLxCjpCYEY7DBr9hcm3vPPgQY"
-        "IQmPdKR7/vd5/v/n5dxzhZSSNeYBOoGDQGcoFPINDAyUDQ0NOUdGRmyGYSiBQGCpoaGhuGnT"
-        "psShQ4f6WltbewEBVAK3gCBgrjJKKZFSKlLKeillt5Ty40gkMnnw4MFFQG60ysrKZHd3dyoe"
-        "j//bNM0Le/fuPd/e3r5lmRMpJWK5ghrgFeBIT09P4/Hjx73pdFo47HaaNlfRutnJru0OKsoE"
-        "E3GVqaSNa6EUw1dvIKWkoqKCrVu3FoeHh9WamppfRiKRn6wUYAUcwE7g2e7u7vrTp09XGIZB"
-        "W1Mdv3qtmoBPrG0hHVsMhKLj6nqOqOWn/Pjnv2dgYIC5uTl1uSM71/pbgUbg6bNnz/rPnDnj"
-        "dzoddO0P8Oo+jY2suDDD1Zv9DA1dfghXVbVBCFEqpcwAKEDTxMSE58SJE8+oqsq3nq/l1X0a"
-        "QihYtNLHLqRET03wuYp7fO9r26mpKlsVUBSl0W63V6/shZTyyIEDB344Njb21JYaG7/5bgkA"
-        "Dm8zTS/+7bHZLy0mSN+7yNztt8nPjYHFwfvXDf1P70zZ0ok0LS0tZy9fvvxNAGswGFQnJyef"
-        "KnM5+NHLzuUDsrFZ7R68zS/hrGon1PcNMPI0BIzs9tcCNvNfDqxW64uqqvqKxWJc6e3trVVV"
-        "leaAk6ryJ5N/9tH3GXv7Je7/5xermN3diMPXCkDfgrkg3UU0txWLxeLw+/1fB1BGR0frbTYb"
-        "TXWWDbNeysUoZKbIRIZBPviOzKU8ejLMHyPFcMprrweQ7iUAXC7XPiGEak2lUk02m42mWn1D"
-        "gfrnTiKNIrbyzSAUjEKWCx+/Mf+HyELBrLBvBhAIKDdgGsrLy+sAv1UIUa1pGv7yxQ0FbGX1"
-        "D+0LQmHW7fVavE5Mo/gAFCCcoOs6NpvNA7gVRVGCmqYRz1hXg7NFU39rjshawjcuvs4P+o/y"
-        "24uvE1+I4VCdfGfXUb76+VdWfQQCkbJSKBQoFApJTdMsCvApQDSlAjCTN7I/y5CNllpq1wqE"
-        "YmPciIzwwdi7BKevreK7Gp5dfbYoFoozJrquo+v6rMViWbQCV4QQzGTsQJY3kzIhvFpgfYte"
-        "7jhCMp9kk7uep+ueWcWj6f8Xqioq8ck0xcIS6XT6vpRy3gqMqKpqRBfKLLNF1ZRV6YBiPDrw"
-        "vduefwTL6hl6b74FgFVR0T4rJTU3jcvlymcymal8Ph+z9vf3p7u6uv5y/vz5bw994ld2fmUH"
-        "7nYFRVG4Gb3Guv8FpmmQzCcIJ+5w8c5HRFL3UYRC+ZKX633j6LpObW3tDcMwrsODq4Jbt27V"
-        "HT58+N7o6KgCYHfY2f2lXfi+6CJbnsAwjUeyXzFFKLgdHqb+mmL8xh22bduWmJycfHN2dvbX"
-        "uVwuoQC0tbXlKisrYytBi/lFZsKzOErtTyQWCOxWO36ljvl/FLk+dJOSkhJTUZR35+fn+3K5"
-        "XAIeXNcASz6fbxzwrxDYVQdqpARvs498IYchDUxpogiBVVFxqE7U/5Zx4c8fEo/FKS0tlR0d"
-        "HZ8ODg6+l06nr6zwrAp4PJ6Qpmlf2L9/fywYDFaOXB0RI1dHaGpuoq29Fa1Uxe62YeZMInei"
-        "jAY/IRqNAtDZ2blUV1fXPzg4+F5VVdU/H6p0eYjqsWPHvnz37t0XwuHw7d27d4eTyeTvLl26"
-        "FJiamnpim6qrq9mzZ094fHz875FI5J3p6ekr631WBARgaWlpCezYsePeuXPnzFAo5Dp58uS+"
-        "dDp91GKxNBYKBW82m3Vomqa7XK7pbDYbnJmZuR2LxYL5fP79WCyWeeys1h/D9e97enqsp06d"
-        "8mWzWU+xWPTkcjmXaZpxwzDCsVhsbqNggP8BMJOU3UUUf+0AAAAASUVORK5CYII=")
-        
-        #----------------------------------------------------------------------
-        starticon = PyEmbeddedImage(
-        "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABbpJ"
-        "REFUSIl9lltsFNcdxn9nZnbHs15fd23j9TXYC0UCKzEhMQ+oIS2g1kQ1pbFStX0opFWsovSh"
-        "rUqp2pS2ioTUolaKFOGHqGkiJcKRuDhOaZRiZCsCXyBgCBBfMfbu+oa9s17wzuzl9MH24mDD"
-        "XzoPc/6fft+c72jOGSGlZEVlAU8D9cB20zQ9HR0duRcvXszq7e01EomEUlFREa+srLR8Pl+g"
-        "sbHx3zk5ORcAFfACA8Bt4CFUSomUUkgpS6SUB6SUH5umOXLgwIEHqqrKJfGao7S0VB49ejRo"
-        "2/YnUsrT+/fvb66pqSldYiKlRCytoBB4Gfjx6dOnq5qamjwTExOKqqqU+QrYUJFN7QY32Qbc"
-        "vSeYCGtcux1i5M5dAPx+P1VVVQvnzp0ziouLfx8MBt9cXoAGZABbgZ1HjhwpO378eEEymaSi"
-        "tIBjPy9lU5nKoyWExF2yjy+mN3HsH+/Q3d3NwMCAsZTI9pVaDXgK2Hr27Nn85ubmEpdh8IMX"
-        "ffxirwshVrGXHBQSC/dIRvoZGuz/WkvTtHIhhCGlXABQgI2Tk5P5hw8f3uZwOGj8VjGHXnoC"
-        "HJCpJFbkLtr8FXbX+XC79HRPVVW/qqre9LtIKX/S0NDwy76+vq1lhTr/fM2NAmTk+fHv/dea"
-        "BlZkDHP0PHODH2NHg1gykw8/X7Dfb7vjTNgJqqurT3R1db0GoF2/fl0fGhqqdWca/K7RhZLO"
-        "WSBU55oGGXlVZORVkeV7nsFPDqKL+9TWJCI3n9rojX2mYhjGj4QQv5FSziunTp0qdjqd4hvl"
-        "Lnz5j49lrPMNhv7zM6b63knPuQpryMj3A9A2L++nvDaZXheqqrrXrVu3D0C5detWudPpxO/T"
-        "Hk8HYnOD3J+8yr3bH6XnZNImHg3xfsgenfHo5QAyJwFAdnb2HiGEppmmWa3rOhtKrCcalNT9"
-        "llTSwvBsXISn4nRdbJ5/czRsWvlGhQAEYtFg0kl2dnYZUKgB5U6nk5L82BMNXIU1X3uOWFH5"
-        "eWIuy/YYWcjU4qQAxQ22bWMYhgfIU1RV/UrXdWaiDyOyUiLROktoJfDtC8fZfWQbb//v75ix"
-        "MDlGnvjVC3+gflNDWiMQKPMalmVh2/a8w+HQFKAHIBR2ABCOS+uN6cTMoFstXmlwZbSba7tv"
-        "8hfzT7z+7k+ZnZ0BoK5yR1qjCBV7MoVt29i2PaWqqq0BvUIIQqYORHlrKj6R9BoVj0b04oY9"
-        "nEt+yvz3Y5yR/+Xap3XsDb/EtvV1aY1DdTA7HsW2bCKRyLiUclYBelRVldNWAfPSm4oV5ZQJ"
-        "Vn/G9Zv2oWt6Ous7e4K81XiC1wNNBO6OIWKgB7Mwp000TYuFw+GxWCw2qbS2tk7k5uae/eDD"
-        "Fn594p6SFyxRCjKLUBWF8fBoegTNMVLLm/kwdMyGGON/nePLklv0dl/Cii3gdrtvAzdg8aig"
-        "vb296uDBgwMjIyMCwFvoZXv9NvRnIKqHSckUyQdJrtfexPqm5LGVAuNdVaofcCVywfpexLYD"
-        "CsDOnTvnioqKzGXdzNQMV9tvkJEyUITyeOAjpYyAc9gxYc/GWyK2HYDF4xog6fV6h1i8FwCo"
-        "LK/EncwhkWGxEH9AXLMXM2H1CpQBifI3yeapZ+70d43+cSo4+95yL23g8XiGFUWp3bVrV/Ty"
-        "5ctZnR2ddHZ08uxzz1K9eT1GRhJls1gFlsfieK+WpJ5e/3z7pcuXzmia1rJSs3xlOg8dOvTD"
-        "8fHx7wQCgb4tW7bMm6b55/Pnz+eGw+FFGJDT5iT1XRWlfxHMZ06+/Vz9dCAQeG9kZKR1x44d"
-        "nSdPnkyuZSAArbq6eqOiKAP9/f3xlpaWgra2tlei0eiryWSyKGKa2TcaL+muwcxU5aDf9Gi+"
-        "L0Oh0BehUOiaZVlnAoHAzFr7Ih75bVnVb2pqcvf09Phi0ei6+/rUC6lw1k0p5bSUctThcIwP"
-        "Dw/HnwT4P6CDl+TMvD0JAAAAAElFTkSuQmCC")
-        
-        #----------------------------------------------------------------------
-        stopicon = PyEmbeddedImage(
-        "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABPRJ"
-        "REFUSImdlllsVGUUx3/f/e4sd5iZLjNt6XSFdtgkjWFRePABDaCBGgjamIg81CU0aoxbRHww"
-        "+EDkhWjEB5rYGEMUxQTCJg8EoQ2BbgrFCNJWltplgC63naEzd+bO50NLLVAq4STfwz3nfP/f"
-        "PSf3O98VSikmmQ94HFgDLDdNM1BfX5955swZX0tLi5FKpbSSkpJkaWlpIhQKdVdVVX2XkZFx"
-        "EpBAEGgHLgH/iSqlUEoJpVSBUqpaKXXYNM0r1dXVt6WUajx5ylVYWKi2bdvWY1nWUaXUgQ0b"
-        "NtRWVFQUjmuilEKMV5ALvAhsPHDgQFlNTU2gr69Pk1JSFMphTomfRXO8+A243i/oG9I5f6mX"
-        "K1evAxAOhykrKxs9duyYkZ+f/0lPT8/2OwXogBtYDKzYunVr0c6dO3Ns26akMIcdbxQyv0hy"
-        "rwmh8Bas5/eb89nxRR1NTU20t7cb4x1ZPjlXB2YBiw8ePJhdW1tb4DEMXng6xJtrPQhxn/Y4"
-        "QSM12o89fJnOjst3hXRdLxZCGEqpUQANmBuJRLK3bNmy1OFwUPVMPm9VTiMOqLRNYvg6+shv"
-        "rFoWwutxTcSklGEpZXDiXZRSr6xbt+6dtra2xUW5Lr7c7EUD3Flhwmu/nRKQGO7CvHaCwY7D"
-        "WNEeEmoGe0+PWnuOXHWmrBTl5eW7GxsbNwPoFy5ccHV2di7yzjD4uMqDNtFngZDOKQHurDLc"
-        "WWX4Qk/ScfRVXCLGoorU8J+z5gbjxyWGYbwshPhQKTWi7d+/P9/pdIp5xR5C2Q9uS1fDp3T+"
-        "8jo32uomfJ7cCtzZYQCOjKhYOmgxI+hBSumdOXPmegDt4sWLxU6nk3BIf7A6EB/sIBY5R/+l"
-        "nyd8yrZIRnvZ02tduxVwFQOojBQAfr9/tRBC103TLHe5XMwpSEwLKFj2EWk7gRGYOyaeTtJ4"
-        "pnZk+7UhM5FtlAhAIMYAESd+v78IyNWBYqfTSUF2fFqAJ7firufhRFSdTg36rIDhQ6XHnAI0"
-        "L1iWhWEYASBLl1L+JaWcfSuqk+u3AUikRer4ADffg/w7gt80fs35r34k3BYh2xNAarooAJ4d"
-        "vsHgaP8EWMR17GiaVo8r0+Fw6DrQDDzXO+RgQSjBUFIlPh+wB0vLZD6TrLWrkWRXB29fGAK6"
-        "pql1rNXVmrCklJYGtAgh6DXHDsuuG8k+O9M5895tq+atpSwwZ9o2TjZlWTGl1IAGNEsp1c1E"
-        "DiMqmI7nZRQJ7j/G6xZWMS/vsYcGkEzG4vF4RDt06FBfZmbmwR/27uOD3f1aVk+BljMjD6lp"
-        "/DN07a4VTYw8tL4rrQZgbNixadOm90+dOvX82cZmcbaxmWBukOVrlvJudw1R1xDp8a+kuPM6"
-        "Gx8S4LXtCIwNO1asWDGYl5dn3gneunGLc7/+gTttoAntQRrTmgMmpimAHQwGOycnlBaX4rUz"
-        "8LszMRweXLr7kWB35oMdCAT+1jRt0cqVK6Otra2+hvoGGuobWPLEEsoXzkbPkLhvR4CBRwJY"
-        "Xq/3SGVlZbq7u7utsrJyxDTNz06cOJHZ0tRCS1MLAKuRwNQT9v8AyV27dn1fXl7eqmlae11d"
-        "XXLfvn0/+Xy+l6LR6Gu2befFYjFfzrk2FzeHp7mK7jdxz2/LffGamhpvc3NzyLKsbFd3z1PG"
-        "aHyBTKdjum0POGzbFAp7qo0xVOtJZdf/C/wRDnL5FYGSAAAAAElFTkSuQmCC")
+        defaulticon = wx.Image(Bpath("images", "brz.png"))
+        starticon = wx.Image(Bpath("images", "icoplay24.png"))
+        stopicon = wx.Image(Bpath("images", "icostop24.png"))
         
         class ParamsEntryDialog(wx.TextEntryDialog):
             if wx.VERSION < (2, 6, 0):
@@ -349,12 +251,10 @@
             def OnTaskBarStartPLC(self, evt):
                 if self.pyroserver.plcobj is not None: 
                     self.pyroserver.plcobj.StartPLC()
-                evt.Skip()
             
             def OnTaskBarStopPLC(self, evt):
                 if self.pyroserver.plcobj is not None:
                     Thread(target=self.pyroserver.plcobj.StopPLC).start()
-                evt.Skip()
             
             def OnTaskBarChangeInterface(self, evt):
                 dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=self.pyroserver.ip_addr)
@@ -364,7 +264,6 @@
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.ip_addr = dlg.GetValue()
                     self.pyroserver.Stop()
-                evt.Skip()
             
             def OnTaskBarChangePort(self, evt):
                 dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port))
@@ -372,14 +271,12 @@
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.port = int(dlg.GetValue())
                     self.pyroserver.Stop()
-                evt.Skip()
             
             def OnTaskBarChangeWorkingDir(self, evt):
                 dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON)
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.workdir = dlg.GetPath()
                     self.pyroserver.Stop()
-                evt.Skip()
             
             def OnTaskBarChangeName(self, evt):
                 dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=self.pyroserver.name)
@@ -387,19 +284,17 @@
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.name = dlg.GetValue()
                     self.pyroserver.Restart()
-                evt.Skip()
             
             def _LiveShellLocals(self):
                 if self.pyroserver.plcobj is not None:
-                    return {"locals":self.pyroserver.plcobj.python_threads_vars}
+                    return {"locals":self.pyroserver.plcobj.python_runtime_vars}
                 else:
                     return {}
-
+            
             def OnTaskBarLiveShell(self, evt):
                 from wx import py
                 frame = py.crust.CrustFrame(**self._LiveShellLocals())
                 frame.Show()
-                evt.Skip()
             
             def OnTaskBarWXInspector(self, evt):
                 # Activate the widget inspection tool
@@ -409,22 +304,20 @@
 
                 wnd = wx.GetApp()
                 InspectionTool().Show(wnd, True)
-
-                evt.Skip()
             
             def OnTaskBarQuit(self, evt):
-                Thread(target=self.pyroserver.Quit).start()
+                if wx.Platform == '__WXMSW__':
+                    Thread(target=self.pyroserver.Quit).start()
                 self.RemoveIcon()
-                wx.CallAfter(wx.GetApp().Exit)
-                evt.Skip()
+                wx.CallAfter(wx.GetApp().ExitMainLoop)
             
             def UpdateIcon(self, plcstatus):
                 if plcstatus is "Started" :
-                    currenticon = self.MakeIcon(starticon.GetImage())
+                    currenticon = self.MakeIcon(starticon)
                 elif plcstatus is "Stopped":
-                    currenticon = self.MakeIcon(stopicon.GetImage())
+                    currenticon = self.MakeIcon(stopicon)
                 else:
-                    currenticon = self.MakeIcon(defaulticon.GetImage())
+                    currenticon = self.MakeIcon(defaulticon)
                 self.SetIcon(currenticon, "Beremiz Service")
 
 from runtime import PLCObject, PLCprint, ServicePublisher
@@ -436,8 +329,8 @@
 def default_evaluator(tocall, *args, **kwargs):
     try:
         res=(tocall(*args,**kwargs), None)
-    except Exception,exp:
-        res=(None, exp)
+    except Exception:
+        res=(None, sys.exc_info())
     return res
 
 class Server():
@@ -465,6 +358,8 @@
 
     def Quit(self):
         self.continueloop = False
+        if self.plcobj is not None:
+            self.plcobj.UnLoadPLC()
         self.Stop()
 
     def Start(self):
@@ -487,7 +382,7 @@
             self.servicepublisher = ServicePublisher.ServicePublisher()
             self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
         
-        if self.autostart:
+        if self.autostart and self.plcobj.GetPLCstatus()[0] != "Empty":
             self.plcobj.StartPLC()
         
         sys.stdout.flush()
@@ -495,7 +390,8 @@
         self.daemon.requestLoop()
     
     def Stop(self):
-        self.plcobj.StopPLC()
+        if self.plcobj is not None:
+            self.plcobj.StopPLC()
         if self.servicepublisher is not None:
             self.servicepublisher.UnRegisterService()
             self.servicepublisher = None
@@ -710,6 +606,31 @@
 else:
     pyroserver = Server(servicename, given_ip, port, WorkingDir, argv, autostart, website=website)
 
+# Exception hooks s
+import threading, traceback
+def LogException(*exp):
+    if pyroserver.plcobj is not None:
+        pyroserver.plcobj.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
+    else:
+        traceback.print_exception(*exp)
+
+sys.excepthook = LogException
+def installThreadExcepthook():
+    init_old = threading.Thread.__init__
+    def init(self, *args, **kwargs):
+        init_old(self, *args, **kwargs)
+        run_old = self.run
+        def run_with_except_hook(*args, **kw):
+            try:
+                run_old(*args, **kw)
+            except (KeyboardInterrupt, SystemExit):
+                raise
+            except:
+                sys.excepthook(*sys.exc_info())
+        self.run = run_with_except_hook
+    threading.Thread.__init__ = init
+installThreadExcepthook()
+
 if havetwisted or havewx:
     pyro_thread=Thread(target=pyroserver.Loop)
     pyro_thread.start()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CodeFileTreeNode.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,200 @@
+import os
+from xml.dom import minidom
+import cPickle
+
+from xmlclass import GenerateClassesFromXSDstring, UpdateXMLClassGlobals
+
+from PLCControler import UndoBuffer
+
+CODEFILE_XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <xsd:element name="%(codefile_name)s">
+    <xsd:complexType>
+      <xsd:sequence>
+        %(includes_section)s
+        <xsd:element name="variables">
+          <xsd:complexType>
+            <xsd:sequence>
+              <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
+                <xsd:complexType>
+                  <xsd:attribute name="name" type="xsd:string" use="required"/>
+                  <xsd:attribute name="type" type="xsd:string" use="required"/>
+                  <xsd:attribute name="class" use="optional">
+                    <xsd:simpleType>
+                      <xsd:restriction base="xsd:string">
+                        <xsd:enumeration value="input"/>
+                        <xsd:enumeration value="memory"/>
+                        <xsd:enumeration value="output"/>
+                      </xsd:restriction>
+                    </xsd:simpleType>
+                  </xsd:attribute>
+                  <xsd:attribute name="initial" type="xsd:string" use="optional" default=""/>
+                </xsd:complexType>
+              </xsd:element>
+            </xsd:sequence>
+          </xsd:complexType>
+        </xsd:element>
+        %(sections)s
+      </xsd:sequence>
+    </xsd:complexType>
+  </xsd:element>
+  <xsd:complexType name="CodeText">
+    <xsd:annotation>
+      <xsd:documentation>Formatted text according to parts of XHTML 1.1</xsd:documentation>
+    </xsd:annotation>
+    <xsd:sequence>
+      <xsd:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"/>
+    </xsd:sequence>
+  </xsd:complexType>
+</xsd:schema>"""
+
+SECTION_TAG_ELEMENT = "<xsd:element name=\"%s\" type=\"CodeText\"/>"
+
+class CodeFile:
+    
+    CODEFILE_NAME = "CodeFile"
+    SECTIONS_NAMES = []
+    
+    def __init__(self):
+        sections_str = {"codefile_name": self.CODEFILE_NAME}
+        if "includes" in self.SECTIONS_NAMES:
+            sections_str["includes_section"] = SECTION_TAG_ELEMENT % "includes"
+        else:
+            sections_str["includes_section"] = ""
+        sections_str["sections"] = "\n".join(
+            [SECTION_TAG_ELEMENT % name
+             for name in self.SECTIONS_NAMES if name != "includes"])
+        
+        self.CodeFileClasses = GenerateClassesFromXSDstring(
+            CODEFILE_XSD % sections_str)
+        
+        filepath = self.CodeFileName()
+        
+        self.CodeFile = self.CodeFileClasses[self.CODEFILE_NAME]()
+        if os.path.isfile(filepath):
+            xmlfile = open(filepath, 'r')
+            tree = minidom.parse(xmlfile)
+            xmlfile.close()
+            
+            for child in tree.childNodes:
+                if child.nodeType == tree.ELEMENT_NODE and child.nodeName in [self.CODEFILE_NAME]:
+                    self.CodeFile.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
+                    self.CreateCodeFileBuffer(True)
+        else:
+            self.CreateCodeFileBuffer(False)
+            self.OnCTNSave()
+
+    def GetBaseTypes(self):
+        return self.GetCTRoot().GetBaseTypes()
+
+    def GetDataTypes(self, basetypes = False):
+        return self.GetCTRoot().GetDataTypes(basetypes=basetypes)
+
+    def GenerateNewName(self, format, start_idx):
+        return self.GetCTRoot().GenerateNewName(
+            None, None, format, start_idx,
+            dict([(var.getname().upper(), True) 
+                  for var in self.CodeFile.variables.getvariable()]))
+
+    def SetVariables(self, variables):
+        self.CodeFile.variables.setvariable([])
+        for var in variables:
+            variable = self.CodeFileClasses["variables_variable"]()
+            variable.setname(var["Name"])
+            variable.settype(var["Type"])
+            variable.setinitial(var["Initial"])
+            self.CodeFile.variables.appendvariable(variable)
+    
+    def GetVariables(self):
+        datas = []
+        for var in self.CodeFile.variables.getvariable():
+            datas.append({"Name" : var.getname(), 
+                          "Type" : var.gettype(), 
+                          "Initial" : var.getinitial()})
+        return datas
+
+    def SetTextParts(self, parts):
+        for section in self.SECTIONS_NAMES:
+            section_code = parts.get(section)
+            if section_code is not None:
+                getattr(self.CodeFile, section).settext(section_code)
+    
+    def GetTextParts(self):
+        return dict([(section, getattr(self.CodeFile, section).gettext())
+                     for section in self.SECTIONS_NAMES])
+            
+    def CTNTestModified(self):
+        return self.ChangesToSave or not self.CodeFileIsSaved()    
+
+    def OnCTNSave(self, from_project_path=None):
+        filepath = self.CodeFileName()
+        
+        text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+        text += self.CodeFile.generateXMLText(self.CODEFILE_NAME, 0)
+
+        xmlfile = open(filepath,"w")
+        xmlfile.write(text.encode("utf-8"))
+        xmlfile.close()
+        
+        self.MarkCodeFileAsSaved()
+        return True
+
+    def CTNGlobalInstances(self):
+        current_location = self.GetCurrentLocation()
+        return [(variable.getname(),
+                 variable.gettype(),
+                 variable.getinitial())
+                for variable in self.CodeFile.variables.variable]
+
+#-------------------------------------------------------------------------------
+#                      Current Buffering Management Functions
+#-------------------------------------------------------------------------------
+
+    def cPickle_loads(self, str_obj):
+        UpdateXMLClassGlobals(self.CodeFileClasses)
+        return cPickle.loads(str_obj)
+
+    def cPickle_dumps(self, obj):
+        UpdateXMLClassGlobals(self.CodeFileClasses)
+        return cPickle.dumps(obj)
+
+    """
+    Return a copy of the codefile model
+    """
+    def Copy(self, model):
+        return self.cPickle_loads(self.cPickle_dumps(model))
+
+    def CreateCodeFileBuffer(self, saved):
+        self.Buffering = False
+        self.CodeFileBuffer = UndoBuffer(self.cPickle_dumps(self.CodeFile), saved)
+
+    def BufferCodeFile(self):
+        self.CodeFileBuffer.Buffering(self.cPickle_dumps(self.CodeFile))
+    
+    def StartBuffering(self):
+        self.Buffering = True
+        
+    def EndBuffering(self):
+        if self.Buffering:
+            self.CodeFileBuffer.Buffering(self.cPickle_dumps(self.CodeFile))
+            self.Buffering = False
+    
+    def MarkCodeFileAsSaved(self):
+        self.EndBuffering()
+        self.CodeFileBuffer.CurrentSaved()
+    
+    def CodeFileIsSaved(self):
+        return self.CodeFileBuffer.IsCurrentSaved() and not self.Buffering
+        
+    def LoadPrevious(self):
+        self.EndBuffering()
+        self.CodeFile = self.cPickle_loads(self.CodeFileBuffer.Previous())
+    
+    def LoadNext(self):
+        self.CodeFile = self.cPickle_loads(self.CodeFileBuffer.Next())
+    
+    def GetBufferState(self):
+        first = self.CodeFileBuffer.IsFirst() and not self.Buffering
+        last = self.CodeFileBuffer.IsLast()
+        return not first, not last
+
--- a/ConfigTreeNode.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/ConfigTreeNode.py	Wed Jul 31 10:45:07 2013 +0900
@@ -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
 
@@ -148,14 +150,16 @@
         parts = path.split(".", 1)
         if self.MandatoryParams and parts[0] == self.MandatoryParams[0]:
             self.MandatoryParams[1].setElementValue(parts[1], value)
+            value = self.MandatoryParams[1].getElementInfos(parts[0], parts[1])["value"]
         elif self.CTNParams and parts[0] == self.CTNParams[0]:
             self.CTNParams[1].setElementValue(parts[1], value)
+            value = self.CTNParams[1].getElementInfos(parts[0], parts[1])["value"]
         return value, False
 
     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 +182,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 +190,10 @@
             self.ChangesToSave = False
             # go through all children and do the same
             for CTNChild in self.IterChildren():
-                result = CTNChild.CTNRequestSave()
+                CTNChildPath = None
+                if from_project_path is not None:
+                    CTNChildPath = CTNChild.CTNPath(project_path=from_project_path)
+                result = CTNChild.CTNRequestSave(CTNChildPath)
                 if result:
                     return result
         return None
@@ -465,6 +472,8 @@
         shutil.rmtree(CTNInstance.CTNPath())
         # Remove child of Children
         self.Children[CTNInstance.CTNType].remove(CTNInstance)
+        if len(self.Children[CTNInstance.CTNType]) == 0:
+            self.Children.pop(CTNInstance.CTNType)
         # Forget it... (View have to refresh)
 
     def CTNRemove(self):
--- a/IDEFrame.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/IDEFrame.py	Wed Jul 31 10:45:07 2013 +0900
@@ -22,7 +22,8 @@
 from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
 from editors.DataTypeEditor import DataTypeEditor
 from PLCControler import *
-from controls import CustomTree, LibraryPanel, PouInstanceVariablesPanel, DebugVariablePanel, SearchResultPanel
+from controls import CustomTree, LibraryPanel, PouInstanceVariablesPanel, SearchResultPanel
+from controls.DebugVariablePanel import DebugVariablePanel
 from dialogs import ProjectDialog, PouDialog, PouTransitionDialog, PouActionDialog, FindInPouDialog, SearchInProjectDialog
 from util.BitmapLibrary import GetBitmap
 
@@ -210,15 +211,13 @@
 def GetDeleteElementFunction(remove_function, parent_type=None, check_function=None):
     def DeleteElementFunction(self, selected):
         name = self.ProjectTree.GetItemText(selected)
-        if check_function is None or not check_function(self.Controler, name):
+        if check_function is None or check_function(name):
             if parent_type is not None:
                 item_infos = self.ProjectTree.GetPyData(selected)
                 parent_name = item_infos["tagname"].split("::")[1]
                 remove_function(self.Controler, parent_name, name)
             else:
                 remove_function(self.Controler, name)
-        else:
-            self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!")%name)
     return DeleteElementFunction
 
 if wx.Platform == '__WXMSW__':
@@ -308,8 +307,6 @@
 
 class IDEFrame(wx.Frame):
     
-    Starting = False
-    
     # Compatibility function for wx versions < 2.6
     if wx.VERSION < (2, 6, 0):
         def Bind(self, event, function, id = None):
@@ -457,7 +454,6 @@
               style=wx.DEFAULT_FRAME_STYLE)
         self.SetClientSize(wx.Size(1000, 600))
         self.Bind(wx.EVT_ACTIVATE, self.OnActivated)
-        self.Bind(wx.EVT_SIZE, self.OnResize)
         
         self.TabsImageList = wx.ImageList(31, 16)
         self.TabsImageListIndexes = {}
@@ -526,22 +522,18 @@
         self.ProjectTree = CustomTree(id=ID_PLCOPENEDITORPROJECTTREE,
                   name='ProjectTree', parent=self.ProjectPanel, 
                   pos=wx.Point(0, 0), size=wx.Size(0, 0),
-                  style=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.SUNKEN_BORDER|wx.TR_EDIT_LABELS)
+                  style=wx.SUNKEN_BORDER,
+                  agwStyle=wx.TR_HAS_BUTTONS|wx.TR_SINGLE|wx.TR_EDIT_LABELS)
         self.ProjectTree.SetBackgroundBitmap(GetBitmap("custom_tree_background"),
                                              wx.ALIGN_RIGHT|wx.ALIGN_BOTTOM)
         add_menu = wx.Menu()
         self._init_coll_AddMenu_Items(add_menu)
         self.ProjectTree.SetAddMenu(add_menu)
-        if wx.Platform == '__WXMSW__':
-            self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnProjectTreeRightUp,
-                  id=ID_PLCOPENEDITORPROJECTTREE)
-            self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnProjectTreeItemSelected,
-                  id=ID_PLCOPENEDITORPROJECTTREE)
-        else:
-            self.ProjectTree.Bind(wx.EVT_RIGHT_UP, self.OnProjectTreeRightUp)
-            self.ProjectTree.Bind(wx.EVT_LEFT_UP, self.OnProjectTreeLeftUp)
-            self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnProjectTreeItemChanging,
-                  id=ID_PLCOPENEDITORPROJECTTREE)
+        self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnProjectTreeRightUp,
+              id=ID_PLCOPENEDITORPROJECTTREE)
+        self.ProjectTree.Bind(wx.EVT_LEFT_UP, self.OnProjectTreeLeftUp)
+        self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnProjectTreeItemChanging,
+              id=ID_PLCOPENEDITORPROJECTTREE)
         self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnProjectTreeBeginDrag,
               id=ID_PLCOPENEDITORPROJECTTREE)
         self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnProjectTreeItemBeginEdit,
@@ -550,6 +542,7 @@
               id=ID_PLCOPENEDITORPROJECTTREE)
         self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnProjectTreeItemActivated,
               id=ID_PLCOPENEDITORPROJECTTREE)
+        self.ProjectTree.Bind(wx.EVT_MOTION, self.OnProjectTreeMotion)
         
         #-----------------------------------------------------------------------
         #        Creating PLCopen Project POU Instance Variables Panel
@@ -677,12 +670,24 @@
         self.CurrentEditorToolBar = []
         self.CurrentMenu = None
         self.SelectedItem = None
+        self.LastToolTipItem = None
         self.SearchParams = None
         self.Highlights = {}
         self.DrawingMode = FREEDRAWING_MODE
         #self.DrawingMode = DRIVENDRAWING_MODE
         self.AuiTabCtrl = []
-        self.DefaultPerspective = None
+        
+        # Save default perspective
+        notebooks = {}
+        for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
+                                     (self.BottomNoteBook, "bottomnotebook"),
+                                     (self.RightNoteBook, "rightnotebook")]:
+            notebooks[entry_name] = self.SaveTabLayout(notebook)
+        self.DefaultPerspective = {
+            "perspective": self.AUIManager.SavePerspective(),
+            "notebooks": notebooks,
+        }
+        
         
         # Initialize Printing configuring elements
         self.PrintData = wx.PrintData()
@@ -693,13 +698,11 @@
         self.PageSetupData.SetMarginBottomRight(wx.Point(10, 20))
         
         self.SetRefreshFunctions()
+        self.SetDeleteFunctions()
     
     def __del__(self):
         self.FindDialog.Destroy()
     
-    def ResetStarting(self):
-        self.Starting = False
-    
     def Show(self):
         wx.Frame.Show(self)
         wx.CallAfter(self.RestoreLastState)
@@ -709,59 +712,21 @@
             wx.CallAfter(self._Refresh, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
         event.Skip()
 
+    def SelectTab(self, tab):
+        for notebook in [self.LeftNoteBook, self.BottomNoteBook, self.RightNoteBook]:
+            idx = notebook.GetPageIndex(tab)
+            if idx != wx.NOT_FOUND and idx != notebook.GetSelection():
+                notebook.SetSelection(idx)
+                return
+
 #-------------------------------------------------------------------------------
 #                Saving and restoring frame organization functions
 #-------------------------------------------------------------------------------
 
-    def OnResize(self, event):
-        if self.Starting:
-            self.RestoreLastLayout()
-        event.Skip()
-    
-    def EnableSaveProjectState(self):
-        return False
-    
-    def GetProjectConfiguration(self):
-        projects = {}
-        try:
-            if self.Config.HasEntry("projects"):
-                projects = cPickle.loads(str(self.Config.Read("projects")))
-        except:
-            pass
-        
-        return projects.get(
-            EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
-    
-    def SavePageState(self, page):
-        state = page.GetState()
-        if state is not None:
-            if self.Config.HasEntry("projects"):
-                projects = cPickle.loads(str(self.Config.Read("projects")))
-            else:
-                projects = {}
-            
-            project_infos = projects.setdefault(
-                 EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
-            editors_state = project_infos.setdefault("editors_state", {})
-            
-            if page.IsDebugging():
-                editors_state[page.GetInstancePath()] = state
-            else:
-                editors_state[page.GetTagName()] = state
-            
-            self.Config.Write("projects", cPickle.dumps(projects))
-            self.Config.Flush()
-    
     def GetTabInfos(self, tab):
-        if isinstance(tab, EditorPanel):
-            if tab.IsDebugging():
-                return ("debug", tab.GetInstancePath())
-            else:
-                return ("editor", tab.GetTagName())
-        else:
-            for page_name, (page_ref, page_title) in self.MainTabs.iteritems():
-                if page_ref == tab:
-                    return ("main", page_name)
+        for page_name, (page_ref, page_title) in self.MainTabs.iteritems():
+            if page_ref == tab:
+                return ("main", page_name)
         return None
     
     def SaveTabLayout(self, notebook):
@@ -854,115 +819,19 @@
         if self.Config.HasEntry("framesize"):
             frame_size = cPickle.loads(str(self.Config.Read("framesize")))
         
-        self.Starting = True
         if frame_size is None:
             self.Maximize()
         else:
             self.SetClientSize(frame_size)
-            wx.CallAfter(self.RestoreLastLayout)
-        
-    def RestoreLastLayout(self):
-        notebooks = {}
-        for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
-                                     (self.BottomNoteBook, "bottomnotebook"),
-                                     (self.RightNoteBook, "rightnotebook")]:
-            notebooks[entry_name] = self.SaveTabLayout(notebook)
-        self.DefaultPerspective = {
-            "perspective": self.AUIManager.SavePerspective(),
-            "notebooks": notebooks,
-        }
-        
-        try:
-            if self.Config.HasEntry("perspective"):
-                self.AUIManager.LoadPerspective(unicode(self.Config.Read("perspective")))
-        
-            if self.Config.HasEntry("notebooks"):
-                notebooks = cPickle.loads(str(self.Config.Read("notebooks")))
-                
-                for notebook in [self.LeftNoteBook, self.BottomNoteBook, self.RightNoteBook]:
-                    for idx in xrange(notebook.GetPageCount()):
-                        notebook.RemovePage(0)
-                        
-                for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
-                                             (self.BottomNoteBook, "bottomnotebook"),
-                                             (self.RightNoteBook, "rightnotebook")]:
-                    self.LoadTabLayout(notebook, notebooks.get(entry_name))
-        except:
-            self.ResetPerspective()
-        
-        if self.EnableSaveProjectState():
-            self.LoadProjectLayout()
-        
-        self._Refresh(EDITORTOOLBAR)
-        
-        if wx.Platform == '__WXMSW__':
-            wx.CallAfter(self.ResetStarting)
-        else:
-            self.ResetStarting()
-        wx.CallAfter(self.RefreshEditor)
-    
+        
     def SaveLastState(self):
         if not self.IsMaximized():
             self.Config.Write("framesize", cPickle.dumps(self.GetClientSize()))
         elif self.Config.HasEntry("framesize"):
             self.Config.DeleteEntry("framesize")
         
-        notebooks = {}
-        for notebook, entry_name in [(self.LeftNoteBook, "leftnotebook"),
-                                     (self.BottomNoteBook, "bottomnotebook"),
-                                     (self.RightNoteBook, "rightnotebook")]:
-            notebooks[entry_name] = self.SaveTabLayout(notebook)
-        self.Config.Write("notebooks", cPickle.dumps(notebooks))
-        
-        pane = self.AUIManager.GetPane(self.TabsOpened)
-        if pane.IsMaximized():
-            self.AUIManager.RestorePane(pane)
-        self.Config.Write("perspective", self.AUIManager.SavePerspective())
-        
-        if self.EnableSaveProjectState():
-            self.SaveProjectLayout()
-        
-            for i in xrange(self.TabsOpened.GetPageCount()):
-                self.SavePageState(self.TabsOpened.GetPage(i))
-        
         self.Config.Flush()
 
-    def SaveProjectLayout(self):
-        if self.Controler is not None:
-            tabs = []
-            
-            projects = {}
-            try:
-                 if self.Config.HasEntry("projects"):
-                    projects = cPickle.loads(str(self.Config.Read("projects")))
-            except:
-                pass
-            
-            project_infos = projects.setdefault(
-                 EncodeFileSystemPath(os.path.realpath(self.Controler.GetFilePath())), {})
-            project_infos["tabs"] = self.SaveTabLayout(self.TabsOpened)
-            if self.EnableDebug:
-                project_infos["debug_vars"] = self.DebugVariablePanel.GetDebugVariables()
-                
-            self.Config.Write("projects", cPickle.dumps(projects))
-            self.Config.Flush()
-    
-    def LoadProjectLayout(self):
-        if self.Controler is not None:
-            project = self.GetProjectConfiguration()
-            
-            try:
-                if project.has_key("tabs"):
-                    self.LoadTabLayout(self.TabsOpened, project["tabs"])
-            except:
-                self.DeleteAllPages()
-                
-            if self.EnableDebug:
-                #try:
-                self.DebugVariablePanel.SetDebugVariables(project.get("debug_vars", []))
-                #except:
-                #    self.DebugVariablePanel.ResetView()
-            
 #-------------------------------------------------------------------------------
 #                               General Functions
 #-------------------------------------------------------------------------------
@@ -998,8 +867,6 @@
             window = self.TabsOpened.GetPage(selected)
             
             if window.CheckSaveBeforeClosing():
-                if self.EnableSaveProjectState():
-                    self.SavePageState(window)
                 
                 # Refresh all window elements that have changed
                 wx.CallAfter(self._Refresh, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
@@ -1010,8 +877,12 @@
                 event.Veto()
         
 
-    def GetCopyBuffer(self):
+    def GetCopyBuffer(self, primary_selection=False):
         data = None
+        if primary_selection and wx.Platform == '__WXMSW__':
+            return data
+        else:
+            wx.TheClipboard.UsePrimarySelection(primary_selection)
         if wx.TheClipboard.Open():
             dataobj = wx.TextDataObject()
             if wx.TheClipboard.GetData(dataobj):
@@ -1019,7 +890,11 @@
             wx.TheClipboard.Close()
         return data
         
-    def SetCopyBuffer(self, text):
+    def SetCopyBuffer(self, text, primary_selection=False):
+        if primary_selection and wx.Platform == '__WXMSW__':
+            return
+        else:
+            wx.TheClipboard.UsePrimarySelection(primary_selection)
         if wx.TheClipboard.Open():
             data = wx.TextDataObject()
             data.SetText(text)
@@ -1317,20 +1192,29 @@
         elif isinstance(control, wx.ComboBox):
             control.SetMark(0, control.GetLastPosition() + 1)
     
-    DeleteFunctions = {
-        ITEM_DATATYPE: GetDeleteElementFunction(PLCControler.ProjectRemoveDataType, check_function=PLCControler.DataTypeIsUsed),
-        ITEM_POU: GetDeleteElementFunction(PLCControler.ProjectRemovePou, check_function=PLCControler.PouIsUsed),
-        ITEM_TRANSITION: GetDeleteElementFunction(PLCControler.ProjectRemovePouTransition, ITEM_POU),
-        ITEM_ACTION: GetDeleteElementFunction(PLCControler.ProjectRemovePouAction, ITEM_POU),
-        ITEM_CONFIGURATION: GetDeleteElementFunction(PLCControler.ProjectRemoveConfiguration),
-        ITEM_RESOURCE: GetDeleteElementFunction(PLCControler.ProjectRemoveConfigurationResource, ITEM_CONFIGURATION)
-    }
+    def SetDeleteFunctions(self):
+        self.DeleteFunctions = {
+            ITEM_DATATYPE: GetDeleteElementFunction(
+                    PLCControler.ProjectRemoveDataType, 
+                    check_function=self.CheckDataTypeIsUsedBeforeDeletion),
+            ITEM_POU: GetDeleteElementFunction(
+                    PLCControler.ProjectRemovePou, 
+                    check_function=self.CheckPouIsUsedBeforeDeletion),
+            ITEM_TRANSITION: GetDeleteElementFunction(
+                    PLCControler.ProjectRemovePouTransition, ITEM_POU),
+            ITEM_ACTION: GetDeleteElementFunction(
+                    PLCControler.ProjectRemovePouAction, ITEM_POU),
+            ITEM_CONFIGURATION: GetDeleteElementFunction(
+                    PLCControler.ProjectRemoveConfiguration),
+            ITEM_RESOURCE: GetDeleteElementFunction(
+                    PLCControler.ProjectRemoveConfigurationResource, ITEM_CONFIGURATION)
+        }
     
     def OnDeleteMenu(self, event):
         window = self.FindFocus()
         if window == self.ProjectTree or window is None:
             selected = self.ProjectTree.GetSelection()
-            if selected.IsOk():
+            if selected is not None and selected.IsOk():
                 function = self.DeleteFunctions.get(self.ProjectTree.GetPyData(selected)["type"], None)
                 if function is not None:
                     function(self, selected)
@@ -1372,7 +1256,7 @@
             result = self.Controler.SearchInProject(criteria)
             self.ClearSearchResults()
             self.SearchResultPanel.SetSearchResults(criteria, result)
-            self.BottomNoteBook.SetSelection(self.BottomNoteBook.GetPageIndex(self.SearchResultPanel))
+            self.SelectTab(self.SearchResultPanel)
             
 #-------------------------------------------------------------------------------
 #                             Display Menu Functions
@@ -1455,33 +1339,31 @@
         notebook.SetSelection(notebook.GetPageIndex(tab))
     
     def OnPouSelectedChanging(self, event):
-        if not self.Starting:
-            selected = self.TabsOpened.GetSelection()
-            if selected >= 0:
-                window = self.TabsOpened.GetPage(selected)
-                if not window.IsDebugging():
-                    window.ResetBuffer()
+        selected = self.TabsOpened.GetSelection()
+        if selected >= 0:
+            window = self.TabsOpened.GetPage(selected)
+            if not window.IsDebugging():
+                window.ResetBuffer()
         event.Skip()
     
     def OnPouSelectedChanged(self, event):
-        if not self.Starting:
-            selected = self.TabsOpened.GetSelection()
-            if selected >= 0:
-                window = self.TabsOpened.GetPage(selected)
-                tagname = window.GetTagName()
-                if not window.IsDebugging():
-                    wx.CallAfter(self.SelectProjectTreeItem, tagname)
-                    wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname)
-                    window.RefreshView()
-                    self.EnsureTabVisible(self.LibraryPanel)
-                else:
-                    instance_path = window.GetInstancePath()
-                    if tagname == "":
-                        instance_path = instance_path.rsplit(".", 1)[0]
-                        tagname = self.Controler.GetPouInstanceTagName(instance_path, self.EnableDebug)
-                    self.EnsureTabVisible(self.DebugVariablePanel)
-                    wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname, instance_path)
-            wx.CallAfter(self._Refresh, FILEMENU, EDITMENU, DISPLAYMENU, EDITORTOOLBAR)
+        selected = self.TabsOpened.GetSelection()
+        if selected >= 0:
+            window = self.TabsOpened.GetPage(selected)
+            tagname = window.GetTagName()
+            if not window.IsDebugging():
+                self.SelectProjectTreeItem(tagname)
+                self.PouInstanceVariablesPanel.SetPouType(tagname)
+                window.RefreshView()
+                self.EnsureTabVisible(self.LibraryPanel)
+            else:
+                instance_path = window.GetInstancePath()
+                if tagname == "":
+                    instance_path = instance_path.rsplit(".", 1)[0]
+                    tagname = self.Controler.GetPouInstanceTagName(instance_path, self.EnableDebug)
+                self.EnsureTabVisible(self.DebugVariablePanel)
+                wx.CallAfter(self.PouInstanceVariablesPanel.SetPouType, tagname, instance_path)
+        wx.CallAfter(self._Refresh, FILEMENU, EDITMENU, DISPLAYMENU, EDITORTOOLBAR)
         event.Skip()
         
     def RefreshEditor(self):
@@ -1544,30 +1426,44 @@
 #-------------------------------------------------------------------------------
 
     def RefreshProjectTree(self):
+        # Extract current selected item tagname
+        selected = self.ProjectTree.GetSelection()
+        if selected is not None and selected.IsOk():
+            item_infos = self.ProjectTree.GetPyData(selected)
+            tagname = item_infos.get("tagname", None)
+        else:
+            tagname = None
+        
+        # Refresh treectrl items according to project infos
         infos = self.Controler.GetProjectInfos()
         root = self.ProjectTree.GetRootItem()
-        if not root.IsOk():
+        if root is None or not root.IsOk():
             root = self.ProjectTree.AddRoot(infos["name"])
         self.GenerateProjectTreeBranch(root, infos)
         self.ProjectTree.Expand(root)
-
-    def ResetSelectedItem(self):
-        self.SelectedItem = None
-
-    def GenerateProjectTreeBranch(self, root, infos):
+        
+        # Select new item corresponding to previous selected item
+        if tagname is not None:
+            self.SelectProjectTreeItem(tagname)
+
+    def GenerateProjectTreeBranch(self, root, infos, item_alone=False):
         to_delete = []
         item_name = infos["name"]
         if infos["type"] in ITEMS_UNEDITABLE:
             if len(infos["values"]) == 1:
-                return self.GenerateProjectTreeBranch(root, infos["values"][0])
+                return self.GenerateProjectTreeBranch(root, infos["values"][0], True)
             item_name = _(item_name)
         self.ProjectTree.SetItemText(root, item_name)
         self.ProjectTree.SetPyData(root, infos)
         highlight_colours = self.Highlights.get(infos.get("tagname", None), (wx.WHITE, wx.BLACK))
         self.ProjectTree.SetItemBackgroundColour(root, highlight_colours[0])
         self.ProjectTree.SetItemTextColour(root, highlight_colours[1])
+        self.ProjectTree.SetItemExtraImage(root, None)
         if infos["type"] == ITEM_POU:
-            self.ProjectTree.SetItemImage(root, self.TreeImageDict[self.Controler.GetPouBodyType(infos["name"])])
+            self.ProjectTree.SetItemImage(root, 
+                self.TreeImageDict[self.Controler.GetPouBodyType(infos["name"])])
+            if item_alone:
+                self.ProjectTree.SetItemExtraImage(root, self.Controler.GetPouType(infos["name"]))
         elif infos.has_key("icon") and infos["icon"] is not None:
             icon_name = infos["icon"]
             if not self.TreeImageDict.has_key(icon_name):
@@ -1576,56 +1472,48 @@
         elif self.TreeImageDict.has_key(infos["type"]):
             self.ProjectTree.SetItemImage(root, self.TreeImageDict[infos["type"]])      
         
-        if wx.VERSION >= (2, 6, 0):
-            item, root_cookie = self.ProjectTree.GetFirstChild(root)
-        else:
-            item, root_cookie = self.ProjectTree.GetFirstChild(root, 0)
+        item, root_cookie = self.ProjectTree.GetFirstChild(root)
         for values in infos["values"]:
             if values["type"] not in ITEMS_UNEDITABLE or len(values["values"]) > 0:
-                if not item.IsOk():
+                if item is None or not item.IsOk():
                     item = self.ProjectTree.AppendItem(root, "")
-                    if wx.Platform != '__WXMSW__':
-                        item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
+                    item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
                 self.GenerateProjectTreeBranch(item, values)
                 item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
-        while item.IsOk():
+        while item is not None and item.IsOk():
             to_delete.append(item)
             item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
         for item in to_delete:
             self.ProjectTree.Delete(item)
 
+    TagNamePartsItemTypes = {
+        "D": [ITEM_DATATYPE],
+        "P": [ITEM_POU],
+        "T": [ITEM_POU, ITEM_TRANSITION],
+        "A": [ITEM_POU, ITEM_ACTION],
+        "C": [ITEM_CONFIGURATION],
+        "R": [ITEM_CONFIGURATION, ITEM_RESOURCE]}
+
     def SelectProjectTreeItem(self, tagname):
+        result = False
         if self.ProjectTree is not None:
             root = self.ProjectTree.GetRootItem()
-            if root.IsOk():
+            if root is not None and root.IsOk():
                 words = tagname.split("::")
-                if words[0] == "D":
-                    return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_DATATYPE)])
-                elif words[0] == "P":
-                    return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU)])
-                elif words[0] == "T":
-                    return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU), (words[2], ITEM_TRANSITION)])
-                elif words[0] == "A":
-                    return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_POU), (words[2], ITEM_ACTION)])
-                elif words[0] == "C":
-                    return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_CONFIGURATION)])
-                elif words[0] == "R":
-                    return self.RecursiveProjectTreeItemSelection(root, [(words[1], ITEM_CONFIGURATION), (words[2], ITEM_RESOURCE)])
-        return False
+                result = self.RecursiveProjectTreeItemSelection(root,
+                    zip(words[1:], self.TagNamePartsItemTypes.get(words[0], [])))
+        return result
 
     def RecursiveProjectTreeItemSelection(self, root, items):
         found = False
-        if wx.VERSION >= (2, 6, 0):
-            item, root_cookie = self.ProjectTree.GetFirstChild(root)
-        else:
-            item, root_cookie = self.ProjectTree.GetFirstChild(root, 0)
-        while item.IsOk() and not found:
+        item, root_cookie = self.ProjectTree.GetFirstChild(root)
+        while item is not None and item.IsOk() and not found:
             item_infos = self.ProjectTree.GetPyData(item)
             if (item_infos["name"].split(":")[-1].strip(), item_infos["type"]) == items[0]:
                 if len(items) == 1:
                     self.SelectedItem = item
-                    wx.CallAfter(self.ProjectTree.SelectItem, item)
-                    wx.CallAfter(self.ResetSelectedItem)
+                    self.ProjectTree.SelectItem(item)
+                    self.ResetSelectedItem()
                     return True
                 else:
                     found = self.RecursiveProjectTreeItemSelection(item, items[1:])
@@ -1634,11 +1522,15 @@
             item, root_cookie = self.ProjectTree.GetNextChild(root, root_cookie)
         return found
 
+    def ResetSelectedItem(self):
+        self.SelectedItem = None
+
     def OnProjectTreeBeginDrag(self, event):
-        if wx.Platform == '__WXMSW__':
-            self.SelectedItem = event.GetItem()
-        if self.SelectedItem is not None and self.ProjectTree.GetPyData(self.SelectedItem)["type"] == ITEM_POU:
-            block_name = self.ProjectTree.GetItemText(self.SelectedItem)
+        selected_item = (self.SelectedItem 
+                         if self.SelectedItem is not None
+                         else event.GetItem())
+        if selected_item.IsOk() and self.ProjectTree.GetPyData(selected_item)["type"] == ITEM_POU:
+            block_name = self.ProjectTree.GetItemText(selected_item)
             block_type = self.Controler.GetPouType(block_name)
             if block_type != "program":
                 data = wx.TextDataObject(str((block_name, block_type, "")))
@@ -1682,7 +1574,7 @@
                     if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames() if name != old_name]:
                         message = _("\"%s\" pou already exists!")%new_name
                         abort = True
-                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames()]:
                         messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
                         if messageDialog.ShowModal() == wx.ID_NO:
                             abort = True
@@ -1696,7 +1588,7 @@
                 elif item_infos["type"] == ITEM_TRANSITION:
                     if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
                         message = _("A POU named \"%s\" already exists!")%new_name
-                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables(pou_name) if name != old_name]:
+                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames(pou_name) if name != old_name]:
                         message = _("A variable with \"%s\" as name already exists in this pou!")%new_name
                     else:
                         words = item_infos["tagname"].split("::")
@@ -1707,7 +1599,7 @@
                 elif item_infos["type"] == ITEM_ACTION:
                     if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames()]:
                         message = _("A POU named \"%s\" already exists!")%new_name
-                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables(pou_name) if name != old_name]:
+                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames(pou_name) if name != old_name]:
                         message = _("A variable with \"%s\" as name already exists in this pou!")%new_name
                     else:
                         words = item_infos["tagname"].split("::")
@@ -1724,7 +1616,7 @@
                         if messageDialog.ShowModal() == wx.ID_NO:
                             abort = True
                         messageDialog.Destroy()
-                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames()]:
                         messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
                         if messageDialog.ShowModal() == wx.ID_NO:
                             abort = True
@@ -1743,7 +1635,7 @@
                         if messageDialog.ShowModal() == wx.ID_NO:
                             abort = True
                         messageDialog.Destroy()
-                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariables()]:
+                    elif new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouVariableNames()]:
                         messageDialog = wx.MessageDialog(self, _("A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?")%new_name, _("Error"), wx.YES_NO|wx.ICON_QUESTION)
                         if messageDialog.ShowModal() == wx.ID_NO:
                             abort = True
@@ -1780,23 +1672,52 @@
             event.Skip()
     
     def ProjectTreeItemSelect(self, select_item):
-        name = self.ProjectTree.GetItemText(select_item)
-        item_infos = self.ProjectTree.GetPyData(select_item)
-        if item_infos["type"] in [ITEM_DATATYPE, ITEM_POU,
-                                  ITEM_CONFIGURATION, ITEM_RESOURCE,
-                                  ITEM_TRANSITION, ITEM_ACTION]:
-            self.EditProjectElement(item_infos["type"], item_infos["tagname"], True)
-            self.PouInstanceVariablesPanel.SetPouType(item_infos["tagname"])
+        if select_item is not None and select_item.IsOk():
+            name = self.ProjectTree.GetItemText(select_item)
+            item_infos = self.ProjectTree.GetPyData(select_item)
+            if item_infos["type"] in [ITEM_DATATYPE, ITEM_POU,
+                                      ITEM_CONFIGURATION, ITEM_RESOURCE,
+                                      ITEM_TRANSITION, ITEM_ACTION]:
+                self.EditProjectElement(item_infos["type"], item_infos["tagname"], True)
+                self.PouInstanceVariablesPanel.SetPouType(item_infos["tagname"])
         
     def OnProjectTreeLeftUp(self, event):
         if self.SelectedItem is not None:
             self.ProjectTree.SelectItem(self.SelectedItem)
             self.ProjectTreeItemSelect(self.SelectedItem)
-            wx.CallAfter(self.ResetSelectedItem)
+            self.ResetSelectedItem()
         event.Skip()
     
-    def OnProjectTreeItemSelected(self, event):
-        self.ProjectTreeItemSelect(event.GetItem())
+    def OnProjectTreeMotion(self, event):
+        if not event.Dragging():
+            pt = wx.Point(event.GetX(), event.GetY())
+            item, flags = self.ProjectTree.HitTest(pt)
+            if item is not None and item.IsOk() and flags & wx.TREE_HITTEST_ONITEMLABEL:
+                item_infos = self.ProjectTree.GetPyData(item)
+                if item != self.LastToolTipItem and self.LastToolTipItem is not None:
+                    self.ProjectTree.SetToolTip(None)
+                    self.LastToolTipItem = None
+                if (self.LastToolTipItem != item and 
+                    item_infos["type"] in [ITEM_POU, ITEM_TRANSITION, ITEM_ACTION]):
+                    bodytype = self.Controler.GetEditedElementBodyType(
+                            item_infos["tagname"])
+                    if item_infos["type"] == ITEM_POU:
+                        block_type = {
+                            "program": _("Program"),
+                            "functionBlock": _("Function Block"),
+                            "function": _("Function")
+                        }[self.Controler.GetPouType(item_infos["name"])]
+                    elif item_infos["type"] == ITEM_TRANSITION:
+                        block_type = "Transition"
+                    else:
+                        block_type = "Action"
+                    self.LastToolTipItem = item
+                    wx.CallAfter(self.ProjectTree.SetToolTipString, 
+                        "%s : %s : %s" % (
+                            block_type, bodytype, item_infos["name"]))
+            elif self.LastToolTipItem is not None:
+                self.ProjectTree.SetToolTip(None)
+                self.LastToolTipItem = None
         event.Skip()
     
     def OnProjectTreeItemChanging(self, event):
@@ -1861,16 +1782,6 @@
                     new_window.SetIcon(GetBitmap("DATATYPE"))
                     self.AddPage(new_window, "")
             if new_window is not None:
-                if self.EnableSaveProjectState():
-                    project_infos = self.GetProjectConfiguration()
-                    if project_infos.has_key("editors_state"):
-                        if new_window.IsDebugging():
-                            state = project_infos["editors_state"].get(new_window.GetInstancePath())
-                        else:
-                            state = project_infos["editors_state"].get(tagname)
-                        if state is not None:
-                            wx.CallAfter(new_window.SetState, state)
-                
                 openedidx = self.IsOpened(tagname)
                 old_selected = self.TabsOpened.GetSelection()
                 if old_selected != openedidx:
@@ -1885,10 +1796,7 @@
             return new_window
     
     def OnProjectTreeRightUp(self, event):
-        if wx.Platform == '__WXMSW__':
-            item = event.GetItem()
-        else:
-            item, flags = self.ProjectTree.HitTest(wx.Point(event.GetX(), event.GetY()))
+        item = event.GetItem()
         self.ProjectTree.SelectItem(item)
         self.ProjectTreeItemSelect(item)
         name = self.ProjectTree.GetItemText(item)
@@ -1931,8 +1839,8 @@
                 menu = wx.Menu(title='')
                 new_id = wx.NewId()
                 AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Add Transition"))
-                parent = self.ProjectTree.GetItemParent(item)["type"]
-                parent_type = self.ProjectTree.GetPyData(parent)
+                parent = self.ProjectTree.GetItemParent(item)
+                parent_type = self.ProjectTree.GetPyData(parent)["type"]
                 while parent_type != ITEM_POU:
                     parent = self.ProjectTree.GetItemParent(parent)
                     parent_type = self.ProjectTree.GetPyData(parent)["type"]
@@ -1958,11 +1866,15 @@
                 while parent_type not in [ITEM_CONFIGURATION, ITEM_PROJECT]:
                     parent = self.ProjectTree.GetItemParent(parent)
                     parent_type = self.ProjectTree.GetPyData(parent)["type"]
+                parent_name = None
                 if parent_type == ITEM_PROJECT:
-                    parent_name = None
+                    config_names = self.Controler.GetProjectConfigNames()
+                    if len(config_names) > 0:
+                        parent_name = config_names[0]
                 else:
                     parent_name = self.ProjectTree.GetItemText(parent)
-                self.Bind(wx.EVT_MENU, self.GenerateAddResourceFunction(parent_name), id=new_id)
+                if parent_name is not None:
+                    self.Bind(wx.EVT_MENU, self.GenerateAddResourceFunction(parent_name), id=new_id)
             
         else:
             if item_infos["type"] == ITEM_POU:
@@ -2013,6 +1925,8 @@
             self.PopupMenu(menu)
             menu.Destroy()
         
+        self.ResetSelectedItem()
+        
         event.Skip()
 
 
@@ -2073,13 +1987,6 @@
                     icon = GetBitmap("ACTION", bodytype)
         
         if new_window is not None:
-            if self.EnableSaveProjectState():
-                project_infos = self.GetProjectConfiguration()
-                if project_infos.has_key("editors_state"):
-                    state = project_infos["editors_state"].get(instance_path)
-                    if state is not None:
-                        wx.CallAfter(new_window.SetState, state)
-            
             new_window.SetIcon(icon)
             self.AddPage(new_window, "")
             new_window.RefreshView()
@@ -2108,14 +2015,14 @@
                     elif isinstance(editor, GraphicViewer):
                         editor.ResetView(True)
                     else:
-                        editor.RefreshView()
+                        editor.SubscribeAllDataConsumers()
                 elif editor.IsDebugging():
-                    editor.RegisterVariables()
-            self.DebugVariablePanel.UnregisterObsoleteData()
-    
-    def AddDebugVariable(self, iec_path, force=False):
+                    editor.SubscribeAllDataConsumers()
+            self.DebugVariablePanel.SubscribeAllDataConsumers()
+    
+    def AddDebugVariable(self, iec_path, force=False, graph=False):
         if self.EnableDebug:
-            self.DebugVariablePanel.InsertValue(iec_path, force=force)
+            self.DebugVariablePanel.InsertValue(iec_path, force=force, graph=graph)
             self.EnsureTabVisible(self.DebugVariablePanel)
             
 #-------------------------------------------------------------------------------
@@ -2349,7 +2256,7 @@
         def OnAddPouMenu(event):
             dialog = PouDialog(self, pou_type)
             dialog.SetPouNames(self.Controler.GetProjectPouNames())
-            dialog.SetPouElementNames(self.Controler.GetProjectPouVariables())
+            dialog.SetPouElementNames(self.Controler.GetProjectPouVariableNames())
             dialog.SetValues({"pouName": self.Controler.GenerateNewName(None, None, "%s%%d" % pou_type)})
             if dialog.ShowModal() == wx.ID_OK:
                 values = dialog.GetValues()
@@ -2364,7 +2271,7 @@
         def OnAddTransitionMenu(event):
             dialog = PouTransitionDialog(self)
             dialog.SetPouNames(self.Controler.GetProjectPouNames())
-            dialog.SetPouElementNames(self.Controler.GetProjectPouVariables(pou_name))
+            dialog.SetPouElementNames(self.Controler.GetProjectPouVariableNames(pou_name))
             dialog.SetValues({"transitionName": self.Controler.GenerateNewName(None, None, "transition%d")})
             if dialog.ShowModal() == wx.ID_OK: 
                 values = dialog.GetValues()
@@ -2379,7 +2286,7 @@
         def OnAddActionMenu(event):
             dialog = PouActionDialog(self)
             dialog.SetPouNames(self.Controler.GetProjectPouNames())
-            dialog.SetPouElementNames(self.Controler.GetProjectPouVariables(pou_name))
+            dialog.SetPouElementNames(self.Controler.GetProjectPouVariableNames(pou_name))
             dialog.SetValues({"actionName": self.Controler.GenerateNewName(None, None, "action%d")})
             if dialog.ShowModal() == wx.ID_OK:
                 values = dialog.GetValues()
@@ -2437,9 +2344,7 @@
         result = self.Controler.PastePou(pou_type, pou_xml)
 
         if not isinstance(result, TupleType):
-            message = wx.MessageDialog(self, result, _("Error"), wx.OK|wx.ICON_ERROR)
-            message.ShowModal()
-            message.Destroy()
+            self.ShowErrorMessage(result)
         else:
             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, LIBRARYTREE)
             self.EditProjectElement(ITEM_POU, result[0])
@@ -2448,20 +2353,39 @@
 #                        Remove Project Elements Functions
 #-------------------------------------------------------------------------------
 
+    def CheckElementIsUsedBeforeDeletion(self, check_function, title, name):
+        if not check_function(name):
+            return True
+        
+        dialog = wx.MessageDialog(self, 
+            _("\"%s\" is used by one or more POUs. Do you wish to continue?") % name, 
+            title, wx.YES_NO|wx.ICON_QUESTION)
+        answer = dialog.ShowModal()
+        dialog.Destroy()
+        return answer == wx.ID_YES
+
+    def CheckDataTypeIsUsedBeforeDeletion(self, name):
+        return self.CheckElementIsUsedBeforeDeletion(
+            self.Controler.DataTypeIsUsed,
+            _("Remove Datatype"), name)
+    
+    def CheckPouIsUsedBeforeDeletion(self, name):
+        return self.CheckElementIsUsedBeforeDeletion(
+            self.Controler.PouIsUsed,
+            _("Remove Pou"), name)
+    
     def OnRemoveDataTypeMenu(self, event):
         selected = self.ProjectTree.GetSelection()
         if self.ProjectTree.GetPyData(selected)["type"] == ITEM_DATATYPE:
             name = self.ProjectTree.GetItemText(selected)
-            if not self.Controler.DataTypeIsUsed(name):
+            if self.CheckDataTypeIsUsedBeforeDeletion(name):
                 self.Controler.ProjectRemoveDataType(name)
                 tagname = self.Controler.ComputeDataTypeName(name)
                 idx = self.IsOpened(tagname)
                 if idx is not None:
                     self.TabsOpened.DeletePage(idx)
                 self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE)
-            else:
-                self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!"))
-
+            
     def OnRenamePouMenu(self, event):
         selected = self.ProjectTree.GetSelection()
         if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU: 
@@ -2471,15 +2395,13 @@
         selected = self.ProjectTree.GetSelection()
         if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
             name = self.ProjectTree.GetItemText(selected)
-            if not self.Controler.PouIsUsed(name):
+            if self.CheckPouIsUsedBeforeDeletion(name):
                 self.Controler.ProjectRemovePou(name)
                 tagname = self.Controler.ComputePouName(name)
                 idx = self.IsOpened(tagname)
                 if idx is not None:
                     self.TabsOpened.DeletePage(idx)
                 self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
-            else:
-                self.ShowErrorMessage(_("\"%s\" is used by one or more POUs. It can't be removed!"))
 
     def OnRemoveTransitionMenu(self, event):
         selected = self.ProjectTree.GetSelection()
--- a/NativeLib.xml	Wed Mar 13 12:34:55 2013 +0900
+++ b/NativeLib.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -74,8 +74,7 @@
           <ST>
 <![CDATA[IF TRIG AND NOT TRIG0 THEN
 {{
- extern int LogMessage(uint8_t, uint8_t*, uint32_t);
- LogMessage(GetFbVar(LEVEL),GetFbVar(MSG, .body),GetFbVar(MSG, .len));
+ LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
 }}
 END_IF;
 TRIG0:=TRIG;
--- a/PLCControler.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/PLCControler.py	Wed Jul 31 10:45:07 2013 +0900
@@ -266,8 +266,8 @@
             return [config.getname() for config in project.getconfigurations()]
         return []
     
-    # Return project pou variables
-    def GetProjectPouVariables(self, pou_name = None, debug = False):
+    # Return project pou variable names
+    def GetProjectPouVariableNames(self, pou_name = None, debug = False):
         variables = []
         project = self.GetProject(debug)
         if project is not None:
@@ -561,7 +561,7 @@
                         instances.append(var_path)
                     else:
                         pou = project.getpou(var_type)
-                        if pou is not None:
+                        if pou is not None and project.ElementIsUsedBy(pou_type, var_type):
                             instances.extend(
                                 self.RecursiveSearchPouInstances(
                                     project, pou_type, var_path, 
@@ -596,7 +596,7 @@
                             if pou_type == words[1]:
                                 instances.append(pou_path)
                             pou = project.getpou(pou_type)
-                            if pou is not None:
+                            if pou is not None and project.ElementIsUsedBy(words[1], pou_type):
                                 instances.extend(
                                     self.RecursiveSearchPouInstances(
                                         project, words[1], pou_path, 
@@ -649,14 +649,38 @@
                         return self.RecursiveGetPouInstanceTagName(project, vartype, parts[1:], debug)
         return None
     
+    def GetGlobalInstanceTagName(self, project, element, parts, debug = False):
+        for varlist in element.getglobalVars():
+            for variable in varlist.getvariable():
+                if variable.getname() == parts[0]:
+                    vartype_content = variable.gettype().getcontent()
+                    if vartype_content["name"] == "derived":                        
+                        if len(parts) == 1:
+                            return self.ComputePouName(
+                                        vartype_content["value"].getname())
+                        else:
+                            return self.RecursiveGetPouInstanceTagName(
+                                        project, 
+                                        vartype_content["value"].getname(),
+                                        parts[1:], debug)
+        return None
+    
     def GetPouInstanceTagName(self, instance_path, debug = False):
+        project = self.GetProject(debug)
         parts = instance_path.split(".")
         if len(parts) == 1:
             return self.ComputeConfigurationName(parts[0])
         elif len(parts) == 2:
+            for config in project.getconfigurations():
+                if config.getname() == parts[0]:
+                    result = self.GetGlobalInstanceTagName(project, 
+                                                           config, 
+                                                           parts[1:],
+                                                           debug)
+                    if result is not None:
+                        return result
             return self.ComputeConfigurationResourceName(parts[0], parts[1])
         else:
-            project = self.GetProject(debug)
             for config in project.getconfigurations():
                 if config.getname() == parts[0]:
                     for resource in config.getresource():
@@ -674,6 +698,14 @@
                                                     project,
                                                     pou_instance.gettypeName(),
                                                     parts[3:], debug)
+                            return self.GetGlobalInstanceTagName(project, 
+                                                                 resource, 
+                                                                 parts[2:], 
+                                                                 debug)
+                    return self.GetGlobalInstanceTagName(project, 
+                                                         config, 
+                                                         parts[1:],
+                                                         debug)
         return None
     
     def GetInstanceInfos(self, instance_path, debug = False):
@@ -1261,6 +1293,16 @@
             tempvar["Documentation"] = ""
 
         return tempvar
+    
+    # Add a global var to configuration to configuration
+    def AddConfigurationGlobalVar(self, config_name, type, var_name, 
+                                           location="", description=""):
+        if self.Project is not None:
+            # Found the configuration corresponding to name
+            configuration = self.Project.getconfiguration(config_name)
+            if configuration is not None:
+                # Set configuration global vars
+                configuration.addglobalVar(type, var_name, location, description)
 
     # Replace the configuration globalvars by those given
     def SetConfigurationGlobalVars(self, name, vars):
@@ -1289,6 +1331,20 @@
                         vars.append(tempvar)
         return vars
 
+    # Return configuration variable names
+    def GetConfigurationVariableNames(self, config_name = None, debug = False):
+        variables = []
+        project = self.GetProject(debug)
+        if project is not None:
+            for configuration in self.Project.getconfigurations():
+                if config_name is None or config_name == configuration.getname():
+                    variables.extend(
+                        [var.getname() for var in reduce(
+                            lambda x, y: x + y, [varlist.getvariable() 
+                                for varlist in configuration.globalVars],
+                            [])])
+        return variables
+
     # Replace the resource globalvars by those given
     def SetConfigurationResourceGlobalVars(self, config_name, name, vars):
         if self.Project is not None:
@@ -1316,6 +1372,23 @@
                         vars.append(tempvar)
         return vars
     
+    # Return resource variable names
+    def GetConfigurationResourceVariableNames(self, 
+                config_name = None, resource_name = None, debug = False):
+        variables = []
+        project = self.GetProject(debug)
+        if project is not None:
+            for configuration in self.Project.getconfigurations():
+                if config_name is None or config_name == configuration.getname():
+                    for resource in configuration.getresource():
+                        if resource_name is None or resource.getname() == resource_name:
+                            variables.extend(
+                                [var.getname() for var in reduce(
+                                    lambda x, y: x + y, [varlist.getvariable() 
+                                        for varlist in resource.globalVars],
+                                    [])])
+        return variables
+    
     # Recursively generate element name tree for a structured variable
     def GenerateVarTree(self, typename, debug = False):
         project = self.GetProject(debug)
@@ -1492,24 +1565,29 @@
 
     def GetConfigurationExtraVariables(self):
         global_vars = []
-        for var_name, var_type in self.GetConfNodeGlobalInstances():
+        for var_name, var_type, var_initial in self.GetConfNodeGlobalInstances():
             tempvar = plcopen.varListPlain_variable()
             tempvar.setname(var_name)
             
             tempvartype = plcopen.dataType()
             if var_type in self.GetBaseTypes():
                 if var_type == "STRING":
-                    var_type.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
+                    tempvartype.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()})
                 elif var_type == "WSTRING":
-                    var_type.setcontent({"name" : "wstring", "value" : plcopen.elementaryTypes_wstring()})
+                    tempvartype.setcontent({"name" : "wstring", "value" : plcopen.elementaryTypes_wstring()})
                 else:
-                    var_type.setcontent({"name" : var_type, "value" : None})
+                    tempvartype.setcontent({"name" : var_type, "value" : None})
             else:
                 tempderivedtype = plcopen.derivedTypes_derived()
                 tempderivedtype.setname(var_type)
                 tempvartype.setcontent({"name" : "derived", "value" : tempderivedtype})
             tempvar.settype(tempvartype)
             
+            if var_initial != "":
+                value = plcopen.value()
+                value.setvalue(var_initial)
+                tempvar.setinitialValue(value)
+            
             global_vars.append(tempvar)
         return global_vars
 
@@ -1531,7 +1609,7 @@
                                 result_blocktype["inputs"] = [(i[0], "ANY", i[2]) for i in result_blocktype["inputs"]]
                                 result_blocktype["outputs"] = [(o[0], "ANY", o[2]) for o in result_blocktype["outputs"]]
                                 return result_blocktype
-                        result_blocktype = blocktype
+                        result_blocktype = blocktype.copy()
         if result_blocktype is not None:
             return result_blocktype
         project = self.GetProject(debug)
@@ -2071,7 +2149,13 @@
     def GetEditedElementVariables(self, tagname, debug = False):
         words = tagname.split("::")
         if words[0] in ["P","T","A"]:
-            return self.GetProjectPouVariables(words[1], debug)
+            return self.GetProjectPouVariableNames(words[1], debug)
+        elif words[0] in ["C", "R"]:
+            names = self.GetConfigurationVariableNames(words[1], debug)
+            if words[0] == "R":
+                names.extend(self.GetConfigurationResourceVariableNames(
+                    words[1], words[2], debug))
+            return names
         return []
 
     def GetEditedElementCopy(self, tagname, debug = False):
@@ -2095,15 +2179,20 @@
                     text += instance_copy.generateXMLText(name.split("_")[-1], 0)
         return text
     
-    def GenerateNewName(self, tagname, name, format, exclude={}, debug=False):
+    def GenerateNewName(self, tagname, name, format, start_idx=0, exclude={}, debug=False):
         names = exclude.copy()
         if tagname is not None:
-            names.update(dict([(varname.upper(), True) for varname in self.GetEditedElementVariables(tagname, debug)]))
-            element = self.GetEditedElement(tagname, debug)
-            if element is not None:
-                for instance in element.getinstances():
-                    if isinstance(instance, (plcopen.sfcObjects_step, plcopen.commonObjects_connector, plcopen.commonObjects_continuation)):
-                        names[instance.getname().upper()] = True
+            names.update(dict([(varname.upper(), True) 
+                               for varname in self.GetEditedElementVariables(tagname, debug)]))
+            words = tagname.split("::")
+            if words[0] in ["P","T","A"]:
+                element = self.GetEditedElement(tagname, debug)
+                if element is not None and element.getbodyType() not in ["ST", "IL"]:
+                    for instance in element.getinstances():
+                        if isinstance(instance, (plcopen.sfcObjects_step, 
+                                                 plcopen.commonObjects_connector, 
+                                                 plcopen.commonObjects_continuation)):
+                            names[instance.getname().upper()] = True
         else:
             project = self.GetProject(debug)
             if project is not None:
@@ -2122,7 +2211,7 @@
                     for resource in config.getresource():
                         names[resource.getname().upper()] = True
             
-        i = 0
+        i = start_idx
         while name is None or names.get(name.upper(), False):
             name = (format%i)
             i += 1
@@ -2152,7 +2241,7 @@
             text = "<paste>%s</paste>"%text
             
             try:
-                tree = minidom.parseString(text)
+                tree = minidom.parseString(text.encode("utf-8"))
             except:
                 return _("Invalid plcopen element(s)!!!")
             instances = []
@@ -2178,12 +2267,19 @@
                                         blocktype = instance.gettypeName()
                                         if element_type == "function":
                                             return _("FunctionBlock \"%s\" can't be pasted in a Function!!!")%blocktype
-                                        blockname = self.GenerateNewName(tagname, blockname, "%s%%d"%blocktype, debug=debug)
+                                        blockname = self.GenerateNewName(tagname, 
+                                                                         blockname, 
+                                                                         "%s%%d"%blocktype, 
+                                                                         debug=debug)
                                         exclude[blockname] = True
                                         instance.setinstanceName(blockname)
                                         self.AddEditedElementPouVar(tagname, blocktype, blockname)
                                 elif child.nodeName == "step":
-                                    stepname = self.GenerateNewName(tagname, instance.getname(), "Step%d", exclude, debug)
+                                    stepname = self.GenerateNewName(tagname, 
+                                                                    instance.getname(), 
+                                                                    "Step%d", 
+                                                                    exclude=exclude, 
+                                                                    debug=debug)
                                     exclude[stepname] = True
                                     instance.setname(stepname)
                                 localid = instance.getlocalId()
--- a/PLCGenerator.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/PLCGenerator.py	Wed Jul 31 10:45:07 2013 +0900
@@ -106,9 +106,9 @@
     # Compute value according to type given
     def ComputeValue(self, value, var_type):
         base_type = self.Controler.GetBaseType(var_type)
-        if base_type == "STRING":
+        if base_type == "STRING" and not value.startswith("'") and not value.endswith("'"):
             return "'%s'"%value
-        elif base_type == "WSTRING":
+        elif base_type == "WSTRING" and not value.startswith('"') and not value.endswith('"'):
             return "\"%s\""%value
         return value
 
@@ -654,7 +654,16 @@
                     self.Interface.append((varTypeNames[varlist["name"]], option, False, variables))
                 if len(located) > 0:
                     self.Interface.append((varTypeNames[varlist["name"]], option, True, located))
-        
+    
+    LITERAL_TYPES = {
+        "T": "TIME",
+        "D": "DATE",
+        "TOD": "TIME_OF_DAY",
+        "DT": "DATE_AND_TIME",
+        "2": None,
+        "8": None,
+        "16": None,
+    }
     def ComputeConnectionTypes(self, pou):
         body = pou.getbody()
         if isinstance(body, ListType):
@@ -681,7 +690,9 @@
                     elif var_type is None:
                         parts = expression.split("#")
                         if len(parts) > 1:
-                            var_type = parts[0]
+                            literal_prefix = parts[0].upper()
+                            var_type = self.LITERAL_TYPES.get(literal_prefix, 
+                                                              literal_prefix)
                         elif expression.startswith("'"):
                             var_type = "STRING"
                         elif expression.startswith('"'):
@@ -883,17 +894,18 @@
             otherInstances["outVariables&coils"].sort(SortInstances)
             otherInstances["blocks"].sort(SortInstances)
             instances = [instance for (executionOrderId, instance) in orderedInstances]
-            instances.extend(otherInstances["connectors"] + otherInstances["outVariables&coils"] + otherInstances["blocks"])
+            instances.extend(otherInstances["outVariables&coils"] + otherInstances["blocks"] + otherInstances["connectors"])
             for instance in instances:
                 if isinstance(instance, (plcopen.fbdObjects_outVariable, plcopen.fbdObjects_inOutVariable)):
                     connections = instance.connectionPointIn.getconnections()
                     if connections is not None:
                         expression = self.ComputeExpression(body, connections)
-                        self.Program += [(self.CurrentIndent, ()),
-                                         (instance.getexpression(), (self.TagName, "io_variable", instance.getlocalId(), "expression")),
-                                         (" := ", ())]
-                        self.Program += expression
-                        self.Program += [(";\n", ())]
+                        if expression is not None:
+                            self.Program += [(self.CurrentIndent, ()),
+                                             (instance.getexpression(), (self.TagName, "io_variable", instance.getlocalId(), "expression")),
+                                             (" := ", ())]
+                            self.Program += expression
+                            self.Program += [(";\n", ())]
                 elif isinstance(instance, plcopen.fbdObjects_block):
                     block_type = instance.gettypeName()
                     self.ParentGenerator.GeneratePouProgram(block_type)
@@ -902,20 +914,27 @@
                         block_infos = self.GetBlockType(block_type)
                     if block_infos is None:
                         raise PLCGenException, _("Undefined block type \"%s\" in \"%s\" POU")%(block_type, self.Name)
-                    block_infos["generate"](self, instance, block_infos, body, None)
+                    try:
+                        block_infos["generate"](self, instance, block_infos, body, None)
+                    except ValueError, e:
+                        raise PLCGenException, e.message
                 elif isinstance(instance, plcopen.commonObjects_connector):
                     connector = instance.getname()
                     if self.ComputedConnectors.get(connector, None):
-                        continue 
-                    self.ComputedConnectors[connector] = self.ComputeExpression(body, instance.connectionPointIn.getconnections())
+                        continue
+                    expression = self.ComputeExpression(body, instance.connectionPointIn.getconnections())
+                    if expression is not None:
+                        self.ComputedConnectors[connector] = expression
                 elif isinstance(instance, plcopen.ldObjects_coil):
                     connections = instance.connectionPointIn.getconnections()
                     if connections is not None:
                         coil_info = (self.TagName, "coil", instance.getlocalId())
-                        expression = self.ExtractModifier(instance, self.ComputeExpression(body, connections), coil_info)
-                        self.Program += [(self.CurrentIndent, ())]
-                        self.Program += [(instance.getvariable(), coil_info + ("reference",))]
-                        self.Program += [(" := ", ())] + expression + [(";\n", ())]
+                        expression = self.ComputeExpression(body, connections)
+                        if expression is not None:
+                            expression = self.ExtractModifier(instance, expression, coil_info)
+                            self.Program += [(self.CurrentIndent, ())]
+                            self.Program += [(instance.getvariable(), coil_info + ("reference",))]
+                            self.Program += [(" := ", ())] + expression + [(";\n", ())]
                         
     def FactorizePaths(self, paths):
         same_paths = {}
@@ -961,7 +980,10 @@
                     block_infos = self.GetBlockType(block_type)
                 if block_infos is None:
                     raise PLCGenException, _("Undefined block type \"%s\" in \"%s\" POU")%(block_type, self.Name)
-                paths.append(str(block_infos["generate"](self, next, block_infos, body, connection, order, to_inout)))
+                try:
+                    paths.append(str(block_infos["generate"](self, next, block_infos, body, connection, order, to_inout)))
+                except ValueError, e:
+                    raise PLCGenException, e.message
             elif isinstance(next, plcopen.commonObjects_continuation):
                 name = next.getname()
                 computed_value = self.ComputedConnectors.get(name, None)
@@ -978,8 +1000,9 @@
                         connections = connector.connectionPointIn.getconnections()
                         if connections is not None:
                             expression = self.ComputeExpression(body, connections, order)
-                            self.ComputedConnectors[name] = expression
-                            paths.append(str(expression))
+                            if expression is not None:
+                                self.ComputedConnectors[name] = expression
+                                paths.append(str(expression))
                     else:
                         raise PLCGenException, _("No connector found corresponding to \"%s\" continuation in \"%s\" POU")%(name, self.Name)
             elif isinstance(next, plcopen.ldObjects_contact):
@@ -1022,6 +1045,8 @@
 
     def ComputeExpression(self, body, connections, order = False, to_inout = False):
         paths = self.GeneratePaths(connections, body, order, to_inout)
+        if len(paths) == 0:
+            return None
         if len(paths) > 1:
             factorized_paths = self.FactorizePaths(paths)
             if len(factorized_paths) > 1:
@@ -1245,9 +1270,10 @@
                             connections = instance.connectionPointIn.getconnections()
                             if connections is not None:
                                 expression = self.ComputeExpression(transitionBody, connections)
-                                transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
-                                self.SFCComputedBlocks += self.Program
-                                self.Program = []
+                                if expression is not None:
+                                    transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
+                                    self.SFCComputedBlocks += self.Program
+                                    self.Program = []
                     if not transition_infos.has_key("content"):
                         raise PLCGenException, _("Transition \"%s\" body must contain an output variable or coil referring to its name") % transitionValues["value"]
                 self.TagName = previous_tagname
@@ -1258,9 +1284,10 @@
                 connections = transition.getconnections()
                 if connections is not None:
                     expression = self.ComputeExpression(body, connections)
-                    transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
-                    self.SFCComputedBlocks += self.Program
-                    self.Program = []
+                    if expression is not None:
+                        transition_infos["content"] = [("\n%s:= "%self.CurrentIndent, ())] + expression + [(";\n", ())]
+                        self.SFCComputedBlocks += self.Program
+                        self.Program = []
             for step in steps:
                 self.GenerateSFCStep(step, pou)
                 step_name = step.getname()
--- a/PLCOpenEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/PLCOpenEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -82,6 +82,7 @@
 from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
 from editors.Viewer import Viewer
 from PLCControler import PLCControler
+from dialogs import ProjectDialog
 
 #-------------------------------------------------------------------------------
 #                            PLCOpenEditor Main Class
@@ -303,7 +304,6 @@
                     self.LibraryPanel.SetController(controler)
                     self.ProjectTree.Enable(True)
                     self.PouInstanceVariablesPanel.SetController(controler)
-                    self.LoadProjectLayout()
                     self._Refresh(PROJECTTREE, LIBRARYTREE)
             self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
         dialog.Destroy()
@@ -314,7 +314,6 @@
     def OnCloseProjectMenu(self, event):
         if not self.CheckSaveBeforeClosing():
             return
-        self.SaveProjectLayout()
         self.ResetView()
         self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
 
--- a/ProjectController.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/ProjectController.py	Wed Jul 31 10:45:07 2013 +0900
@@ -21,7 +21,7 @@
 from editors.FileManagementPanel import FileManagementPanel
 from editors.ProjectNodeEditor import ProjectNodeEditor
 from editors.IECCodeViewer import IECCodeViewer
-from graphics import DebugViewer
+from editors.DebugViewer import DebugViewer
 from dialogs import DiscoveryDialog
 from PLCControler import PLCControler
 from plcopen.structures import IEC_KEYWORDS
@@ -85,9 +85,9 @@
         PLCControler.__init__(self)
 
         self.MandatoryParams = None
-        self.SetAppFrame(frame, logger)
         self._builder = None
         self._connector = None
+        self.SetAppFrame(frame, logger)
         
         self.iec2c_path = os.path.join(base_folder, "matiec", "iec2c"+(".exe" if wx.Platform == '__WXMSW__' else ""))
         self.ieclib_path = os.path.join(base_folder, "matiec", "lib")
@@ -113,7 +113,6 @@
         self.DebugThread = None
         self.debug_break = False
         self.previous_plcstate = None
-        self.previous_log_count = [None]*LogLevelsCount
         # copy ConfNodeMethods so that it can be later customized
         self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
 
@@ -137,6 +136,8 @@
         self.StatusTimer = None
         
         if frame is not None:
+            frame.LogViewer.SetLogSource(self._connector)
+            
             # Timer to pull PLC status
             ID_STATUSTIMER = wx.NewId()
             self.StatusTimer = wx.Timer(self.AppFrame, ID_STATUSTIMER)
@@ -213,27 +214,33 @@
     def SetParamsAttribute(self, path, value):
         if path.startswith("BeremizRoot.TargetType.") and self.BeremizRoot.getTargetType().getcontent() is None:
             self.BeremizRoot.setTargetType(self.GetTarget())
-        return ConfigTreeNode.SetParamsAttribute(self, path, value)
+        res = ConfigTreeNode.SetParamsAttribute(self, path, value)
+        if path.startswith("BeremizRoot.Libraries."):
+            wx.CallAfter(self.RefreshConfNodesBlockLists)
+        return res
         
     # helper func to check project path write permission
     def CheckProjectPathPerm(self, dosave=True):
         if CheckPathPerm(self.ProjectPath):
             return True
-        dialog = wx.MessageDialog(self.AppFrame, 
-                    _('You must have permission to work on the project\nWork on a project copy ?'),
-                    _('Error'), 
-                    wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
-        answer = dialog.ShowModal()
-        dialog.Destroy()
-        if answer == wx.ID_YES:
-            if self.SaveProjectAs():
-                self.AppFrame.RefreshTitle()
-                self.AppFrame.RefreshFileMenu()
-                self.AppFrame.RefreshPageTitles()
-                return True
+        if self.AppFrame is not None:
+            dialog = wx.MessageDialog(self.AppFrame, 
+                        _('You must have permission to work on the project\nWork on a project copy ?'),
+                        _('Error'), 
+                        wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
+            answer = dialog.ShowModal()
+            dialog.Destroy()
+            if answer == wx.ID_YES:
+                if self.SaveProjectAs():
+                    self.AppFrame.RefreshTitle()
+                    self.AppFrame.RefreshFileMenu()
+                    self.AppFrame.RefreshPageTitles()
+                    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)
@@ -281,7 +288,7 @@
         """
         if os.path.basename(ProjectPath) == "":
             ProjectPath = os.path.dirname(ProjectPath)
-		# Verify that project contains a PLCOpen program
+        # Verify that project contains a PLCOpen program
         plc_file = os.path.join(ProjectPath, "plc.xml")
         if not os.path.isfile(plc_file):
             return _("Chosen folder doesn't contain a program. It's not a valid project!")
@@ -310,9 +317,11 @@
         if os.path.exists(self._getBuildPath()):
             self.EnableMethod("_Clean", True)
 
-        if os.path.isfile(self._getIECrawcodepath()):
+        if os.path.isfile(self._getIECcodepath()):
             self.ShowMethod("_showIECcode", True)
-
+        
+        self.UpdateMethodsFromPLCStatus()
+        
         return None
     
     def RecursiveConfNodeInfos(self, confnode):
@@ -321,6 +330,7 @@
             values.append(
                 {"name": "%s: %s" % (CTNChild.GetFullIEC_Channel(),
                                      CTNChild.CTNName()), 
+                 "tagname": CTNChild.CTNFullName(),
                  "type": ITEM_CONFNODE, 
                  "confnode": CTNChild,
                  "icon": CTNChild.GetIconName(),
@@ -345,14 +355,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")
@@ -364,9 +379,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
@@ -599,6 +613,15 @@
             return False
         # transform those base names to full names with path
         C_files = map(lambda filename:os.path.join(buildpath, filename), C_files)
+
+        # prepend beremiz include to configuration header
+        H_files = [ fname for fname in result.splitlines() if fname[-2:]==".h" or fname[-2:]==".H" ]
+        H_files.remove("LOCATED_VARIABLES.h")
+        H_files = map(lambda filename:os.path.join(buildpath, filename), H_files)
+        for H_file in H_files:
+            with file(H_file, 'r') as original: data = original.read()
+            with file(H_file, 'w') as modified: modified.write('#include "beremiz.h"\n' + data)
+
         self.logger.write(_("Extracting Located Variables...\n"))
         # Keep track of generated located variables for later use by self._Generate_C
         self.PLCGeneratedLocatedVars = self.GetLocations()
@@ -776,7 +799,7 @@
         
         return debug_code
         
-    def Generate_plc_common_main(self):
+    def Generate_plc_main(self):
         """
         Use confnodes layout given in LocationCFilesAndCFLAGS to
         generate glue code that dispatch calls to all confnodes
@@ -785,19 +808,19 @@
         # in retreive, publish, init, cleanup
         locstrs = map(lambda x:"_".join(map(str,x)),
            [loc for loc,Cfiles,DoCalls in self.LocationCFilesAndCFLAGS if loc and DoCalls])
-
+        
         # Generate main, based on template
         if not self.BeremizRoot.getDisable_Extensions():
-            plc_main_code = targets.GetCode("plc_common_main") % {
+            plc_main_code = targets.GetCode("plc_main_head") % {
                 "calls_prototypes":"\n".join([(
                       "int __init_%(s)s(int argc,char **argv);\n"+
                       "void __cleanup_%(s)s(void);\n"+
                       "void __retrieve_%(s)s(void);\n"+
                       "void __publish_%(s)s(void);")%{'s':locstr} for locstr in locstrs]),
                 "retrieve_calls":"\n    ".join([
-                      "__retrieve_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
+                      "__retrieve_%s();"%locstr for locstr in locstrs]),
                 "publish_calls":"\n    ".join([ #Call publish in reverse order
-                      "__publish_%s();"%locstr for locstr in locstrs]),
+                      "__publish_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
                 "init_calls":"\n    ".join([
                       "init_level=%d; "%(i+1)+
                       "if((res = __init_%s(argc,argv))){"%locstr +
@@ -808,7 +831,7 @@
                       "__cleanup_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)])
                 }
         else:
-            plc_main_code = targets.GetCode("plc_common_main") % {
+            plc_main_code = targets.GetCode("plc_main_head") % {
                 "calls_prototypes":"\n",
                 "retrieve_calls":"\n",
                 "publish_calls":"\n",
@@ -816,6 +839,7 @@
                 "cleanup_calls":"\n"
                 }
         plc_main_code += targets.GetTargetCode(self.GetTarget().getcontent()["name"])
+        plc_main_code += targets.GetCode("plc_main_tail")
         return plc_main_code
 
         
@@ -890,13 +914,16 @@
         # Now we can forget ExtraFiles (will close files object)
         del ExtraFiles
         
+        # Header file for extensions
+        open(os.path.join(buildpath,"beremiz.h"), "w").write(targets.GetHeader())
+
         # Template based part of C code generation
         # files are stacked at the beginning, as files of confnode tree root
         for generator, filename, name in [
            # debugger code
            (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"),
            # init/cleanup/retrieve/publish, run and align code
-           (self.Generate_plc_common_main,"plc_common_main.c","Common runtime")]:
+           (self.Generate_plc_main,"plc_main.c","Common runtime")]:
             try:
                 # Do generate
                 code = generator()
@@ -966,7 +993,7 @@
             if self._IECCodeView is None:
                 plc_file = self._getIECcodepath()
             
-                self._IECCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", None, None, instancepath=name)
+                self._IECCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name)
                 self._IECCodeView.SetTextSyntax("ALL")
                 self._IECCodeView.SetKeywords(IEC_KEYWORDS)
                 try:
@@ -986,7 +1013,7 @@
             if self._IECRawCodeView is None:
                 controler = MiniTextControler(self._getIECrawcodepath(), self)
                 
-                self._IECRawCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", None, controler, instancepath=name)
+                self._IECRawCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, controler, instancepath=name)
                 self._IECRawCodeView.SetTextSyntax("ALL")
                 self._IECRawCodeView.SetKeywords(IEC_KEYWORDS)
                 self._IECRawCodeView.RefreshView()
@@ -1077,36 +1104,10 @@
         self.CompareLocalAndRemotePLC()
 
     def UpdatePLCLog(self, log_count):
-        if log_count :
-            to_console = []
-            for level, count, prev in zip(xrange(LogLevelsCount), log_count,self.previous_log_count):
-                if count is not None and prev != count:
-                    # XXX replace dump to console with dedicated log panel.
-                    dump_end = max( # request message sent after the last one we already got
-                        prev - 1 if prev is not None else -1,
-                        count - 100) # 100 is purely arbitrary number
-                        # dedicated panel should only ask for a small range, 
-                        # depending on how user navigate in the panel
-                        # and only ask for last one in follow mode
-                    for msgidx in xrange(count-1, dump_end,-1):
-                        answer = self._connector.GetLogMessage(level, msgidx)
-                        if answer is not None :
-                            msg, tick, tv_sec, tv_nsec = answer 
-                            to_console.insert(0,(
-                                (tv_sec, tv_nsec),
-                                '%d|%s.%9.9d|%s(%s)'%(
-                                    int(tick),
-                                    str(datetime.fromtimestamp(tv_sec)),
-                                    tv_nsec,
-                                    msg,
-                                    LogLevels[level])))
-                        else:
-                            break;
-                    self.previous_log_count[level] = count
-            if to_console:
-                to_console.sort()
-                self.logger.write("\n".join(zip(*to_console)[1]+('',)))
-
+        if log_count:
+            if self.AppFrame is not None:
+                self.AppFrame.LogViewer.SetLogCounters(log_count)
+    
     def UpdateMethodsFromPLCStatus(self):
         status = None
         if self._connector is not None:
@@ -1115,7 +1116,7 @@
                 status, log_count = PLCstatus
                 self.UpdatePLCLog(log_count)
         if status is None:
-            self._connector = None
+            self._SetConnector(None, False)
             status = "Disconnected"
         if(self.previous_plcstate != status):
             for args in {
@@ -1133,16 +1134,18 @@
                                       ("_Disconnect", False)],
                    }.get(status,[]):
                 self.ShowMethod(*args)
-            {"Broken": self.logger.write_error,
-             None: lambda x: None}.get(
-                status, self.logger.write)(_("PLC state is \"%s\"\n")%_(status))
             self.previous_plcstate = status
             if self.AppFrame is not None:
                 self.AppFrame.RefreshStatusToolBar()
-    
+                if status == "Disconnected":
+                    self.AppFrame.ConnectionStatusBar.SetStatusText(_(status), 1)
+                    self.AppFrame.ConnectionStatusBar.SetStatusText('', 2)
+                else:
+                    self.AppFrame.ConnectionStatusBar.SetStatusText(
+                        _("Connected to URI: %s") % self.BeremizRoot.getURI_location().strip(), 1)
+                    self.AppFrame.ConnectionStatusBar.SetStatusText(_(status), 2)
+                
     def PullPLCStatusProc(self, event):
-        if self._connector is None:
-            self.StatusTimer.Stop()
         self.UpdateMethodsFromPLCStatus()
         
     def RegisterDebugVarToConnector(self):
@@ -1179,16 +1182,23 @@
                 self.TracedIECPath = []
                 self._connector.SetTraceVariablesList([])
             self.IECdebug_lock.release()
-
+    
+    def IsPLCStarted(self):
+        return self.previous_plcstate == "Started"
+     
     def ReArmDebugRegisterTimer(self):
         if self.DebugTimer is not None:
             self.DebugTimer.cancel()
-
-        # Timer to prevent rapid-fire when registering many variables
-        # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
-        self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector])
-        # Rearm anti-rapid-fire timer
-        self.DebugTimer.start()
+        
+        # Prevent to call RegisterDebugVarToConnector when PLC is not started
+        # If an output location var is forced it's leads to segmentation fault in runtime
+        # Links between PLC located variables and real variables are not ready
+        if self.IsPLCStarted():
+            # Timer to prevent rapid-fire when registering many variables
+            # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
+            self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector])
+            # Rearm anti-rapid-fire timer
+            self.DebugTimer.start()
 
     def GetDebugIECVariableType(self, IECPath):
         Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None))
@@ -1227,13 +1237,15 @@
         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
         if IECdebug_data is not None:
             IECdebug_data[0].pop(callableobj,None)
+            if len(IECdebug_data[0]) == 0:
+                self.IECdebug_datas.pop(IECPath)
         self.IECdebug_lock.release()
 
         self.ReArmDebugRegisterTimer()
 
     def UnsubscribeAllDebugIECVariable(self):
         self.IECdebug_lock.acquire()
-        IECdebug_data = {}
+        self.IECdebug_datas = {}
         self.IECdebug_lock.release()
 
         self.ReArmDebugRegisterTimer()
@@ -1303,6 +1315,7 @@
             else:
                 plc_status = None
             debug_getvar_retry += 1
+            #print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()]
             if plc_status == "Started":
                 self.IECdebug_lock.acquire()
                 if len(debug_vars) == len(self.TracedIECPath):
@@ -1341,7 +1354,6 @@
 
     def _connect_debug(self): 
         self.previous_plcstate = None
-        self.previous_log_count = [None]*LogLevelsCount
         if self.AppFrame:
             self.AppFrame.ResetGraphicViewers()
         self.RegisterDebugVarToConnector()
@@ -1373,6 +1385,19 @@
         
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
+    def _SetConnector(self, connector, update_status=True):
+        self._connector = connector
+        if self.AppFrame is not None:
+            self.AppFrame.LogViewer.SetLogSource(connector)
+        if connector is not None:
+            # Start the status Timer
+            self.StatusTimer.Start(milliseconds=500, oneShot=False)
+        else:
+            # Stop the status Timer
+            self.StatusTimer.Stop()
+            if update_status:
+                wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+
     def _Connect(self):
         # don't accept re-connetion if already connected
         if self._connector is not None:
@@ -1417,7 +1442,7 @@
                            
         # Get connector from uri
         try:
-            self._connector = connectors.ConnectorFactory(uri, self)
+            self._SetConnector(connectors.ConnectorFactory(uri, self))
         except Exception, msg:
             self.logger.write_error(_("Exception while connecting %s!\n")%uri)
             self.logger.write_error(traceback.format_exc())
@@ -1442,9 +1467,6 @@
 
             #self.logger.write(_("PLC is %s\n")%status)
             
-            # Start the status Timer
-            self.StatusTimer.Start(milliseconds=500, oneShot=False)
-            
             if self.previous_plcstate in ["Started","Stopped"]:
                 if self.DebugAvailable() and self.GetIECProgramsAndVariables():
                     self.logger.write(_("Debugger ready\n"))
@@ -1477,9 +1499,7 @@
 
 
     def _Disconnect(self):
-        self._connector = None
-        self.StatusTimer.Stop()
-        wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+        self._SetConnector(None)
         
     def _Transfer(self):
         # Get the last build PLC's 
@@ -1522,8 +1542,6 @@
             else:
                 self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n"))
 
-        self.previous_log_count = [None]*LogLevelsCount
-
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
     StatusMethods = [
--- a/c_ext/CFileEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/c_ext/CFileEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,839 +1,44 @@
-import keyword
 
-import wx
-import wx.grid
 import wx.stc as stc
-import wx.lib.buttons
 
-from controls import CustomGrid, CustomTable
-from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT
-from util.BitmapLibrary import GetBitmap
+from controls.CustomStyledTextCtrl import faces
+from editors.CodeFileEditor import CodeFileEditor, CodeEditor
 
-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,
-             }
+class CppEditor(CodeEditor):
 
+    KEYWORDS = ["asm", "auto", "bool", "break", "case", "catch", "char", "class", 
+        "const", "const_cast", "continue", "default", "delete", "do", "double", 
+        "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", 
+        "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", 
+        "namespace", "new", "operator", "private", "protected", "public", "register", 
+        "reinterpret_cast", "return", "short", "signed", "sizeof", "static", 
+        "static_cast", "struct", "switch", "template", "this", "throw", "true", "try",
+        "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", 
+        "void", "volatile", "wchar_t", "while"]
+    COMMENT_HEADER = "/"
+    
+    def SetCodeLexer(self):
+        self.SetLexer(stc.STC_LEX_CPP)
+        
+        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)
 
-def AppendMenu(parent, help, id, kind, text):
-    if wx.VERSION >= (2, 6, 0):
-        parent.Append(help=help, id=id, kind=kind, text=text)
-    else:
-        parent.Append(helpString=help, id=id, kind=kind, item=text)
+#-------------------------------------------------------------------------------
+#                          CFileEditor Main Frame Class
+#-------------------------------------------------------------------------------
 
-
-[ID_CPPEDITOR,
-] = [wx.NewId() for _init_ctrls in range(1)]
-
-CPP_KEYWORDS = ["asm", "auto", "bool", "break", "case", "catch", "char", "class", 
-    "const", "const_cast", "continue", "default", "delete", "do", "double", 
-    "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", 
-    "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", 
-    "namespace", "new", "operator", "private", "protected", "public", "register", 
-    "reinterpret_cast", "return", "short", "signed", "sizeof", "static", 
-    "static_cast", "struct", "switch", "template", "this", "throw", "true", "try",
-    "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
+class CFileEditor(CodeFileEditor):
     
-    def __init__(self, parent, name, window, controler):
-        stc.StyledTextCtrl.__init__(self, parent, ID_CPPEDITOR, wx.DefaultPosition, 
-                 wx.Size(-1, 300), 0)
-
-        self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
-        self.SetMarginWidth(1, 25)
-
-        self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
-        self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
-
-        self.SetLexer(stc.STC_LEX_CPP)
-        self.SetKeyWords(0, " ".join(CPP_KEYWORDS))
-
-        self.SetProperty("fold", "1")
-        self.SetProperty("tab.timmy.whinge.level", "1")
-        self.SetMargins(0,0)
-
-        self.SetViewWhiteSpace(False)
-        #self.SetBufferedDraw(False)
-        #self.SetViewEOL(True)
-        #self.SetEOLMode(stc.STC_EOL_CRLF)
-        #self.SetUseAntiAliasing(True)
-        
-        self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
-        self.SetEdgeColumn(78)
-
-        # Setup a margin to hold fold markers
-        #self.SetFoldFlags(16)  ###  WHAT IS THIS VALUE?  WHAT ARE THE OTHER FLAGS?  DOES IT MATTER?
-        self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
-        self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
-        self.SetMarginSensitive(2, True)
-        self.SetMarginWidth(2, 12)
-
-        if self.fold_symbols == 0:
-            # Arrow pointing right for contracted folders, arrow pointing down for expanded
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_ARROWDOWN, "black", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_ARROW, "black", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_EMPTY, "black", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_EMPTY, "black", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_EMPTY,     "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY,     "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY,     "white", "black")
-            
-        elif self.fold_symbols == 1:
-            # Plus for contracted folders, minus for expanded
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_MINUS, "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_PLUS,  "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_EMPTY, "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_EMPTY, "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_EMPTY, "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
-
-        elif self.fold_symbols == 2:
-            # Like a flattened tree control using circular headers and curved joins
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_CIRCLEMINUS,          "white", "#404040")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_CIRCLEPLUS,           "white", "#404040")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,                "white", "#404040")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNERCURVE,         "white", "#404040")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_CIRCLEPLUSCONNECTED,  "white", "#404040")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE,         "white", "#404040")
-
-        elif self.fold_symbols == 3:
-            # Like a flattened tree control using square headers
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_BOXMINUS,          "white", "#808080")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_BOXPLUS,           "white", "#808080")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,             "white", "#808080")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNER,           "white", "#808080")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_BOXPLUSCONNECTED,  "white", "#808080")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
-            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,           "white", "#808080")
-
-
-        self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
-        self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
-        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
-
-        # Make some styles,  The lexer defines what each style is used for, we
-        # just have to define what each style looks like.  This set is adapted from
-        # Scintilla sample property files.
-
-        # Global default styles for all languages
-        self.StyleSetSpec(stc.STC_STYLE_DEFAULT,     "face:%(mono)s,size:%(size)d" % faces)
-        self.StyleClearAll()  # Reset all to be like the default
-
-        # Global default styles for all languages
-        self.StyleSetSpec(stc.STC_STYLE_DEFAULT,     "face:%(mono)s,size:%(size)d" % faces)
-        self.StyleSetSpec(stc.STC_STYLE_LINENUMBER,  "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
-        self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces)
-        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')
-        
-        # register some images for use in the AutoComplete box.
-        #self.RegisterImage(1, images.getSmilesBitmap())
-        self.RegisterImage(1, 
-            wx.ArtProvider.GetBitmap(wx.ART_DELETE, size=(16,16)))
-        self.RegisterImage(2, 
-            wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
-        self.RegisterImage(3, 
-            wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))
-
-        # Indentation size
-        self.SetTabWidth(2)
-        self.SetUseTabs(0)
-
-        self.Controler = controler
-        self.ParentWindow = window
-        
-        self.DisableEvents = True
-        self.Name = name
-        self.CurrentAction = None
-        
-        self.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE)
-
-        self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_CPPEDITOR)
-        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
-        self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_CPPEDITOR)
-    
-    def OnModification(self, event):
-        if not self.DisableEvents:
-            mod_type = event.GetModificationType()
-            if not (mod_type&wx.stc.STC_PERFORMED_UNDO or mod_type&wx.stc.STC_PERFORMED_REDO):
-                if mod_type&wx.stc.STC_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())
-                    wx.CallAfter(self.RefreshModel)
-                elif mod_type&wx.stc.STC_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())
-                    wx.CallAfter(self.RefreshModel)
-        event.Skip()
-    
-    def OnDoDrop(self, event):
-        self.ResetBuffer()
-        wx.CallAfter(self.RefreshModel)
-        event.Skip()
-
-    # Buffer the last model state
-    def RefreshBuffer(self):
-        self.Controler.BufferCFile()
-        if self.ParentWindow is not None:
-            self.ParentWindow.RefreshTitle()
-            self.ParentWindow.RefreshFileMenu()
-            self.ParentWindow.RefreshEditMenu()
-            self.ParentWindow.RefreshPageTitles()
-    
-    def StartBuffering(self):
-        self.Controler.StartBuffering()
-        if self.ParentWindow is not None:
-            self.ParentWindow.RefreshTitle()
-            self.ParentWindow.RefreshFileMenu()
-            self.ParentWindow.RefreshEditMenu()
-            self.ParentWindow.RefreshPageTitles()
-    
-    def ResetBuffer(self):
-        if self.CurrentAction != None:
-            self.Controler.EndBuffering()
-            self.CurrentAction = None
-
-    def RefreshView(self):
-        self.ResetBuffer()
-        self.DisableEvents = True
-        old_cursor_pos = self.GetCurrentPos()
-        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()
-        self.DisableEvents = False
-        
-        self.Colourise(0, -1)
-
-    def DoGetBestSize(self):
-        return self.ParentWindow.GetPanelBestSize()
-
-    def RefreshModel(self):
-        self.Controler.SetPartText(self.Name, self.GetText())
-
-    def OnKeyPressed(self, event):
-        if self.CallTipActive():
-            self.CallTipCancel()
-        key = event.GetKeyCode()
-
-        if key == 32 and event.ControlDown():
-            pos = self.GetCurrentPos()
-
-            # Tips
-            if event.ShiftDown():
-                pass
-##                self.CallTipSetBackground("yellow")
-##                self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n'
-##                                 'show some suff, maybe parameters..\n\n'
-##                                 'fubar(param1, param2)')
-            # Code completion
-            else:
-                self.AutoCompSetIgnoreCase(False)  # so this needs to match
-
-                # Images are specified with a appended "?type"
-                self.AutoCompShow(0, " ".join([word + "?1" for word in CPP_KEYWORDS]))
-        else:
-            event.Skip()
-
-    def OnKillFocus(self, event):
-        self.AutoCompCancel()
-        event.Skip()
-
-    def OnUpdateUI(self, evt):
-        # check for matching braces
-        braceAtCaret = -1
-        braceOpposite = -1
-        charBefore = None
-        caretPos = self.GetCurrentPos()
-
-        if caretPos > 0:
-            charBefore = self.GetCharAt(caretPos - 1)
-            styleBefore = self.GetStyleAt(caretPos - 1)
-
-        # check before
-        if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
-            braceAtCaret = caretPos - 1
-
-        # check after
-        if braceAtCaret < 0:
-            charAfter = self.GetCharAt(caretPos)
-            styleAfter = self.GetStyleAt(caretPos)
-
-            if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
-                braceAtCaret = caretPos
-
-        if braceAtCaret >= 0:
-            braceOpposite = self.BraceMatch(braceAtCaret)
-
-        if braceAtCaret != -1  and braceOpposite == -1:
-            self.BraceBadLight(braceAtCaret)
-        else:
-            self.BraceHighlight(braceAtCaret, braceOpposite)
-            #pt = self.PointFromPosition(braceOpposite)
-            #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
-            #print pt
-            #self.Refresh(False)
-
-
-    def OnMarginClick(self, evt):
-        # fold and unfold as needed
-        if evt.GetMargin() == 2:
-            if evt.GetShift() and evt.GetControl():
-                self.FoldAll()
-            else:
-                lineClicked = self.LineFromPosition(evt.GetPosition())
-
-                if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
-                    if evt.GetShift():
-                        self.SetFoldExpanded(lineClicked, True)
-                        self.Expand(lineClicked, True, True, 1)
-                    elif evt.GetControl():
-                        if self.GetFoldExpanded(lineClicked):
-                            self.SetFoldExpanded(lineClicked, False)
-                            self.Expand(lineClicked, False, True, 0)
-                        else:
-                            self.SetFoldExpanded(lineClicked, True)
-                            self.Expand(lineClicked, True, True, 100)
-                    else:
-                        self.ToggleFold(lineClicked)
-
-
-    def FoldAll(self):
-        lineCount = self.GetLineCount()
-        expanding = True
-
-        # find out if we are folding or unfolding
-        for lineNum in range(lineCount):
-            if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
-                expanding = not self.GetFoldExpanded(lineNum)
-                break
-
-        lineNum = 0
-
-        while lineNum < lineCount:
-            level = self.GetFoldLevel(lineNum)
-            if level & stc.STC_FOLDLEVELHEADERFLAG and \
-               (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
-
-                if expanding:
-                    self.SetFoldExpanded(lineNum, True)
-                    lineNum = self.Expand(lineNum, True)
-                    lineNum = lineNum - 1
-                else:
-                    lastChild = self.GetLastChild(lineNum, -1)
-                    self.SetFoldExpanded(lineNum, False)
-
-                    if lastChild > lineNum:
-                        self.HideLines(lineNum+1, lastChild)
-
-            lineNum = lineNum + 1
+    CONFNODEEDITOR_TABS = [
+        (_("C code"), "_create_CodePanel")]
+    CODE_EDITOR = CppEditor
 
 
 
-    def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
-        lastChild = self.GetLastChild(line, level)
-        line = line + 1
-
-        while line <= lastChild:
-            if force:
-                if visLevels > 0:
-                    self.ShowLines(line, line)
-                else:
-                    self.HideLines(line, line)
-            else:
-                if doExpand:
-                    self.ShowLines(line, line)
-
-            if level == -1:
-                level = self.GetFoldLevel(line)
-
-            if level & stc.STC_FOLDLEVELHEADERFLAG:
-                if force:
-                    if visLevels > 1:
-                        self.SetFoldExpanded(line, True)
-                    else:
-                        self.SetFoldExpanded(line, False)
-
-                    line = self.Expand(line, doExpand, force, visLevels-1)
-
-                else:
-                    if doExpand and self.GetFoldExpanded(line):
-                        line = self.Expand(line, True, force, visLevels-1)
-                    else:
-                        line = self.Expand(line, False, force, visLevels-1)
-            else:
-                line = line + 1
-
-        return line
-
-    def Cut(self):
-        self.ResetBuffer()
-        self.DisableEvents = True
-        self.CmdKeyExecute(wx.stc.STC_CMD_CUT)
-        self.DisableEvents = False
-        self.RefreshModel()
-        self.RefreshBuffer()
-    
-    def Copy(self):
-        self.CmdKeyExecute(wx.stc.STC_CMD_COPY)
-    
-    def Paste(self):
-        self.ResetBuffer()
-        self.DisableEvents = True
-        self.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
-        self.DisableEvents = False
-        self.RefreshModel()
-        self.RefreshBuffer()
-
-
-#-------------------------------------------------------------------------------
-#                         Helper for VariablesGrid values
-#-------------------------------------------------------------------------------
-
-class VariablesTable(CustomTable):
-    
-    def GetValue(self, row, col):
-        if row < self.GetNumberRows():
-            if col == 0:
-                return row + 1
-            else:
-                return str(self.data[row].get(self.GetColLabelValue(col, False), ""))
-    
-    def _updateColAttrs(self, grid):
-        """
-        wxGrid -> update the column attributes to add the
-        appropriate renderer given the column name.
-
-        Otherwise default to the default renderer.
-        """
-        
-        typelist = None
-        accesslist = None
-        for row in range(self.GetNumberRows()):
-            for col in range(self.GetNumberCols()):
-                editor = None
-                renderer = None
-                colname = self.GetColLabelValue(col, False)
-                
-                if colname == "Name":
-                    editor = wx.grid.GridCellTextEditor()
-                elif colname == "Class":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters("input,memory,output")
-                elif colname == "Type":
-                    pass
-                else:
-                    grid.SetReadOnly(row, col, True)
-                
-                grid.SetCellEditor(row, col, editor)
-                grid.SetCellRenderer(row, col, renderer)
-                
-                grid.SetCellBackgroundColour(row, col, wx.WHITE)
-            self.ResizeRow(grid, row)
-
-
-class VariablesEditor(wx.Panel):
-    
-    def __init__(self, parent, window, controler):
-        wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
-        
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=4)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
-        
-        self.VariablesGrid = CustomGrid(self, size=wx.Size(-1, 300), style=wx.VSCROLL)
-        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
-        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
-        self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
-        main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
-        
-        controls_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(controls_sizer, border=5, flag=wx.TOP|wx.ALIGN_RIGHT)
-        
-        for name, bitmap, help in [
-                ("AddVariableButton", "add_element", _("Add variable")),
-                ("DeleteVariableButton", "remove_element", _("Remove variable")),
-                ("UpVariableButton", "up", _("Move variable up")),
-                ("DownVariableButton", "down", _("Move variable down"))]:
-            button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), 
-                  size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
-            setattr(self, name, button)
-            controls_sizer.AddWindow(button, border=5, flag=wx.LEFT)
-        
-        self.SetSizer(main_sizer)
-                
-        self.ParentWindow = window
-        self.Controler = controler
-        
-        self.VariablesDefaultValue = {"Name" : "", "Class" : "input", "Type" : ""}
-        self.Table = VariablesTable(self, [], ["#", "Name", "Class", "Type"])
-        self.ColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
-        self.ColSizes = [40, 200, 150, 150]
-        self.VariablesGrid.SetTable(self.Table)
-        self.VariablesGrid.SetButtons({"Add": self.AddVariableButton,
-                                       "Delete": self.DeleteVariableButton,
-                                       "Up": self.UpVariableButton,
-                                       "Down": self.DownVariableButton})
-        
-        def _AddVariable(new_row):
-            self.Table.InsertRow(new_row, self.VariablesDefaultValue.copy())
-            self.RefreshModel()
-            self.RefreshView()
-            return new_row
-        setattr(self.VariablesGrid, "_AddRow", _AddVariable)
-        
-        def _DeleteVariable(row):
-            self.Table.RemoveRow(row)
-            self.RefreshModel()
-            self.RefreshView()
-        setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
-        
-        def _MoveVariable(row, move):
-            new_row = self.Table.MoveRow(row, move)
-            if new_row != row:
-                self.RefreshModel()
-                self.RefreshView()
-            return new_row
-        setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
-        
-        self.VariablesGrid.SetRowLabelSize(0)
-        for col in range(self.Table.GetNumberCols()):
-            attr = wx.grid.GridCellAttr()
-            attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
-            self.VariablesGrid.SetColAttr(col, attr)
-            self.VariablesGrid.SetColSize(col, self.ColSizes[col])
-        self.Table.ResetView(self.VariablesGrid)
-
-    def RefreshModel(self):
-        self.Controler.SetVariables(self.Table.GetData())
-        self.RefreshBuffer()
-        
-    # Buffer the last model state
-    def RefreshBuffer(self):
-        self.Controler.BufferCFile()
-        self.ParentWindow.RefreshTitle()
-        self.ParentWindow.RefreshFileMenu()
-        self.ParentWindow.RefreshEditMenu()
-        self.ParentWindow.RefreshPageTitles()
-
-    def RefreshView(self):
-        self.Table.SetData(self.Controler.GetVariables())
-        self.Table.ResetView(self.VariablesGrid)
-        self.VariablesGrid.RefreshButtons()
-    
-    def DoGetBestSize(self):
-        return self.ParentWindow.GetPanelBestSize()
-    
-    def OnVariablesGridCellChange(self, event):
-        self.RefreshModel()
-        wx.CallAfter(self.RefreshView)
-        event.Skip()
-
-    def OnVariablesGridEditorShown(self, event):
-        row, col = event.GetRow(), event.GetCol() 
-        if self.Table.GetColLabelValue(col, False) == "Type":
-            type_menu = wx.Menu(title='')
-            base_menu = wx.Menu(title='')
-            for base_type in self.Controler.GetBaseTypes():
-                new_id = wx.NewId()
-                AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
-                self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
-            type_menu.AppendMenu(wx.NewId(), "Base Types", base_menu)
-            datatype_menu = wx.Menu(title='')
-            for datatype in self.Controler.GetDataTypes(basetypes=False, only_locatables=True):
-                new_id = wx.NewId()
-                AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
-                self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
-            type_menu.AppendMenu(wx.NewId(), "User Data Types", datatype_menu)
-            rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
-            
-            self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
-            type_menu.Destroy()
-            event.Veto()
-        else:
-            event.Skip()
-
-    def GetVariableTypeFunction(self, base_type):
-        def VariableTypeFunction(event):
-            row = self.VariablesGrid.GetGridCursorRow()
-            self.Table.SetValueByName(row, "Type", base_type)
-            self.Table.ResetView(self.VariablesGrid)
-            self.RefreshModel()
-            self.RefreshView()
-            event.Skip()
-        return VariableTypeFunction
-
-    def OnVariablesGridCellLeftClick(self, event):
-        if event.GetCol() == 0:
-            row = event.GetRow()
-            num = 0
-            if self.Table.GetValueByName(row, "Class") == "input":
-                dir = "%I"
-                for i in xrange(row):
-                    if self.Table.GetValueByName(i, "Class") == "input":
-                        num += 1
-            elif self.Table.GetValueByName(row, "Class") == "memory":
-                dir = "%M"
-                for i in xrange(row):
-                    if self.Table.GetValueByName(i, "Class") == "memory":
-                        num += 1
-            else:
-                dir = "%Q"
-                for i in xrange(row):
-                    if self.Table.GetValueByName(i, "Class") == "output":
-                        num += 1
-            data_type = self.Table.GetValueByName(row, "Type")
-            var_name = self.Table.GetValueByName(row, "Name")
-            base_location = ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation()))
-            location = "%s%s%s.%d"%(dir, self.Controler.GetSizeOfType(data_type), base_location, num)
-            data = wx.TextDataObject(str((location, "location", data_type, var_name, "")))
-            dragSource = wx.DropSource(self.VariablesGrid)
-            dragSource.SetData(data)
-            dragSource.DoDragDrop()
-            return
-        event.Skip()
-    
-
-#-------------------------------------------------------------------------------
-#                          SVGUIEditor Main Frame Class
-#-------------------------------------------------------------------------------
-
-CFILE_PARTS = [
-    ("Includes", CppEditor), 
-    ("Variables", VariablesEditor), 
-    ("Globals", CppEditor), 
-    ("Init", CppEditor), 
-    ("CleanUp", CppEditor), 
-    ("Retrieve", CppEditor), 
-    ("Publish", CppEditor),
-]
-
-class FoldPanelCaption(wx.lib.buttons.GenBitmapTextToggleButton):
-    
-    def GetBackgroundBrush(self, dc):
-        colBg = self.GetBackgroundColour()
-        brush = wx.Brush(colBg, wx.SOLID)
-        if self.style & wx.BORDER_NONE:
-            myAttr = self.GetDefaultAttributes()
-            parAttr = self.GetParent().GetDefaultAttributes()
-            myDef = colBg == myAttr.colBg
-            parDef = self.GetParent().GetBackgroundColour() == parAttr.colBg
-            if myDef and parDef:
-                if wx.Platform == "__WXMAC__":
-                    brush.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
-                elif wx.Platform == "__WXMSW__":
-                    if self.DoEraseBackground(dc):
-                        brush = None
-            elif myDef and not parDef:
-                colBg = self.GetParent().GetBackgroundColour()
-                brush = wx.Brush(colBg, wx.SOLID)
-        return brush
-    
-    def DrawLabel(self, dc, width, height, dx=0, dy=0):
-        bmp = self.bmpLabel
-        if bmp is not None:     # if the bitmap is used
-            if self.bmpDisabled and not self.IsEnabled():
-                bmp = self.bmpDisabled
-            if self.bmpFocus and self.hasFocus:
-                bmp = self.bmpFocus
-            if self.bmpSelected and not self.up:
-                bmp = self.bmpSelected
-            bw,bh = bmp.GetWidth(), bmp.GetHeight()
-            hasMask = bmp.GetMask() is not None
-        else:
-            bw = bh = 0     # no bitmap -> size is zero
-        
-        dc.SetFont(self.GetFont())
-        if self.IsEnabled():
-            dc.SetTextForeground(self.GetForegroundColour())
-        else:
-            dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
-
-        label = self.GetLabel()
-        tw, th = dc.GetTextExtent(label)        # size of text
-        
-        if bmp is not None:
-            dc.DrawBitmap(bmp, width - bw - 2, (height-bh)/2, hasMask) # draw bitmap if available
-        
-        dc.DrawText(label, 2, (height-th)/2)      # draw the text
-
-        dc.SetPen(wx.Pen(self.GetForegroundColour()))
-        dc.SetBrush(wx.TRANSPARENT_BRUSH)
-        dc.DrawRectangle(0, 0, width, height)
-
-class CFileEditor(ConfTreeNodeEditor):
-    
-    CONFNODEEDITOR_TABS = [
-        (_("C code"), "_create_CCodeEditor")]
-    
-    def _create_CCodeEditor(self, prnt):
-        self.CCodeEditor = wx.ScrolledWindow(prnt, 
-              style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
-        self.CCodeEditor.Bind(wx.EVT_SIZE, self.OnCCodeEditorResize)
-        
-        self.Panels = {}
-        self.MainSizer = wx.BoxSizer(wx.VERTICAL)
-        
-        for idx, (name, panel_class) in enumerate(CFILE_PARTS):
-            button_id = wx.NewId()
-            button = FoldPanelCaption(id=button_id, name='FoldPanelCaption_%s' % name, 
-                  label=name, bitmap=GetBitmap("CollapsedIconData"), 
-                  parent=self.CCodeEditor, pos=wx.Point(0, 0),
-                  size=wx.Size(0, 20), style=wx.NO_BORDER|wx.ALIGN_LEFT)
-            button.SetBitmapSelected(GetBitmap("ExpandedIconData"))
-            button.Bind(wx.EVT_BUTTON, self.GenPanelButtonCallback(name), id=button_id)
-            self.MainSizer.AddWindow(button, 0, border=0, flag=wx.TOP|wx.GROW)
-            
-            if panel_class == VariablesEditor:
-                panel = VariablesEditor(self.CCodeEditor, self.ParentWindow, self.Controler)
-            else:
-                panel = panel_class(self.CCodeEditor, name, self.ParentWindow, self.Controler)
-            self.MainSizer.AddWindow(panel, 0, border=0, flag=wx.BOTTOM|wx.GROW)
-            panel.Hide()
-            
-            self.Panels[name] = {"button": button, "panel": panel, "expanded": False, "row": 2 * idx + 1}
-        
-        self.CCodeEditor.SetSizer(self.MainSizer)
-        
-        return self.CCodeEditor
-    
-    def __init__(self, parent, controler, window):
-        ConfTreeNodeEditor.__init__(self, parent, controler, window)
-    
-    def GetBufferState(self):
-        return self.Controler.GetBufferState()
-        
-    def Undo(self):
-        self.Controler.LoadPrevious()
-        self.RefreshView()
-            
-    def Redo(self):
-        self.Controler.LoadNext()
-        self.RefreshView()
-    
-    def RefreshView(self):
-        ConfTreeNodeEditor.RefreshView(self)
-        
-        for infos in self.Panels.itervalues():
-            infos["panel"].RefreshView()
-        
-        self.RefreshCCodeEditorScrollbars()
-
-    def GenPanelButtonCallback(self, name):
-        def PanelButtonCallback(event):
-            self.TogglePanel(name)
-        return PanelButtonCallback
-
-    def ExpandPanel(self, name):
-        infos = self.Panels.get(name, None)
-        if infos is not None and not infos["expanded"]:
-            infos["expanded"] = True
-            infos["button"].SetToggle(True)
-            infos["panel"].Show()
-            
-            self.RefreshSizerLayout()
-    
-    def CollapsePanel(self, name):
-        infos = self.Panels.get(name, None)
-        if infos is not None and infos["expanded"]:
-            infos["expanded"] = False
-            infos["button"].SetToggle(False)
-            infos["panel"].Hide()
-            
-            self.RefreshSizerLayout()
-        
-    def TogglePanel(self, name):
-        infos = self.Panels.get(name, None)
-        if infos is not None:
-            infos["expanded"] = not infos["expanded"]
-            infos["button"].SetToggle(infos["expanded"])
-            if infos["expanded"]:
-                infos["panel"].Show()
-            else:
-                infos["panel"].Hide()
-            
-            self.RefreshSizerLayout()
-    
-    def RefreshSizerLayout(self):
-        self.MainSizer.Layout()
-        self.RefreshCCodeEditorScrollbars()
-    
-    def RefreshCCodeEditorScrollbars(self):
-        self.CCodeEditor.GetBestSize()
-        xstart, ystart = self.CCodeEditor.GetViewStart()
-        window_size = self.CCodeEditor.GetClientSize()
-        maxx, maxy = self.MainSizer.GetMinSize()
-        posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
-        posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
-        self.CCodeEditor.Scroll(posx, posy)
-        self.CCodeEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
-                maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
-    
-    def OnCCodeEditorResize(self, event):
-        self.RefreshCCodeEditorScrollbars()
-        event.Skip()
-    
--- a/c_ext/c_ext.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/c_ext/c_ext.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,19 +1,10 @@
+
 import os
-from xml.dom import minidom
-import cPickle
-
-from xmlclass import *
 
 from CFileEditor import CFileEditor
-from PLCControler import UndoBuffer, LOCATION_CONFNODE, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT 
+from CodeFileTreeNode import CodeFile
 
-CFileClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "cext_xsd.xsd"))
-
-TYPECONVERSION = {"BOOL" : "X", "SINT" : "B", "INT" : "W", "DINT" : "D", "LINT" : "L",
-    "USINT" : "B", "UINT" : "W", "UDINT" : "D", "ULINT" : "L", "REAL" : "D", "LREAL" : "L",
-    "STRING" : "B", "BYTE" : "B", "WORD" : "W", "DWORD" : "D", "LWORD" : "L", "WSTRING" : "W"}
-
-class CFile:
+class CFile(CodeFile):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <xsd:element name="CExtension">
@@ -24,142 +15,25 @@
       </xsd:element>
     </xsd:schema>
     """
+    CODEFILE_NAME = "CFile"
+    SECTIONS_NAMES = [
+        "includes",
+        "globals",
+        "initFunction",
+        "cleanUpFunction",
+        "retrieveFunction",
+        "publishFunction"]
     EditorType = CFileEditor
     
-    def __init__(self):
-        filepath = self.CFileName()
-        
-        self.CFile = CFileClasses["CFile"]()
-        if os.path.isfile(filepath):
-            xmlfile = open(filepath, 'r')
-            tree = minidom.parse(xmlfile)
-            xmlfile.close()
-            
-            for child in tree.childNodes:
-                if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "CFile":
-                    self.CFile.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
-                    self.CreateCFileBuffer(True)
-        else:
-            self.CreateCFileBuffer(False)
-            self.OnCTNSave()
-
+    def GenerateClassesFromXSDstring(self, xsd_string):
+        return GenerateClassesFromXSDstring(xsd_string)
+    
     def GetIconName(self):
         return "Cfile"
 
-    def CFileName(self):
+    def CodeFileName(self):
         return os.path.join(self.CTNPath(), "cfile.xml")
-
-    def GetBaseTypes(self):
-        return self.GetCTRoot().GetBaseTypes()
-
-    def GetDataTypes(self, basetypes = False, only_locatables = False):
-        return self.GetCTRoot().GetDataTypes(basetypes=basetypes, only_locatables=only_locatables)
-
-    def GetSizeOfType(self, type):
-        return TYPECONVERSION.get(self.GetCTRoot().GetBaseType(type), None)
-
-    def SetVariables(self, variables):
-        self.CFile.variables.setvariable([])
-        for var in variables:
-            variable = CFileClasses["variables_variable"]()
-            variable.setname(var["Name"])
-            variable.settype(var["Type"])
-            variable.setclass(var["Class"])
-            self.CFile.variables.appendvariable(variable)
     
-    def GetVariables(self):
-        datas = []
-        for var in self.CFile.variables.getvariable():
-            datas.append({"Name" : var.getname(), "Type" : var.gettype(), "Class" : var.getclass()})
-        return datas
-
-    def GetVariableLocationTree(self):
-        '''See ConfigTreeNode.GetVariableLocationTree() for a description.'''
-
-        current_location = ".".join(map(str, self.GetCurrentLocation()))
-        
-        vars = []
-        input = memory = output = 0
-        for var in self.CFile.variables.getvariable():
-            var_size = self.GetSizeOfType(var.gettype())
-            var_location = ""
-            if var.getclass() == "input":
-                var_class = LOCATION_VAR_INPUT
-                if var_size is not None:
-                    var_location = "%%I%s%s.%d"%(var_size, current_location, input)
-                input += 1
-            elif var.getclass() == "memory":
-                var_class = LOCATION_VAR_INPUT
-                if var_size is not None:
-                    var_location = "%%M%s%s.%d"%(var_size, current_location, memory)
-                memory += 1
-            else:
-                var_class = LOCATION_VAR_OUTPUT
-                if var_size is not None:
-                    var_location = "%%Q%s%s.%d"%(var_size, current_location, output)
-                output += 1
-            vars.append({"name": var.getname(),
-                         "type": var_class,
-                         "size": var_size,
-                         "IEC_type": var.gettype(),
-                         "var_name": var.getname(),
-                         "location": var_location,
-                         "description": "",
-                         "children": []})
-                
-        return  {"name": self.BaseParams.getName(),
-                "type": LOCATION_CONFNODE,
-                "location": self.GetFullIEC_Channel(),
-                "children": vars}
-
-    def SetPartText(self, name, text):
-        if name == "Includes":
-            self.CFile.includes.settext(text)
-        elif name == "Globals":
-            self.CFile.globals.settext(text)
-        elif name == "Init":
-            self.CFile.initFunction.settext(text)
-        elif name == "CleanUp":
-            self.CFile.cleanUpFunction.settext(text)
-        elif name == "Retrieve":
-            self.CFile.retrieveFunction.settext(text)
-        elif name == "Publish":
-            self.CFile.publishFunction.settext(text)
-        
-    def GetPartText(self, name):
-        if name == "Includes":
-            return self.CFile.includes.gettext()
-        elif name == "Globals":
-            return self.CFile.globals.gettext()
-        elif name == "Init":
-            return self.CFile.initFunction.gettext()
-        elif name == "CleanUp":
-            return self.CFile.cleanUpFunction.gettext()
-        elif name == "Retrieve":
-            return self.CFile.retrieveFunction.gettext()
-        elif name == "Publish":
-            return self.CFile.publishFunction.gettext()
-        return ""
-                
-    def CTNTestModified(self):
-        return self.ChangesToSave or not self.CFileIsSaved()    
-
-    def OnCTNSave(self):
-        filepath = self.CFileName()
-        
-        text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
-        extras = {"xmlns":"http://www.w3.org/2001/XMLSchema",
-                  "xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance",
-                  "xsi:schemaLocation" : "cext_xsd.xsd"}
-        text += self.CFile.generateXMLText("CFile", 0, extras)
-
-        xmlfile = open(filepath,"w")
-        xmlfile.write(text.encode("utf-8"))
-        xmlfile.close()
-        
-        self.MarkCFileAsSaved()
-        return True
-
     def CTNGenerate_C(self, buildpath, locations):
         """
         Generate C code
@@ -178,64 +52,49 @@
         location_str = "_".join(map(str, current_location))
         
         text = "/* Code generated by Beremiz c_ext confnode */\n\n"
+        text += "#include <stdio.h>\n\n"
         
         # Adding includes
         text += "/* User includes */\n"
-        text += self.CFile.includes.gettext()
+        text += self.CodeFile.includes.gettext().strip()
         text += "\n"
         
-        text += '#include "iec_types.h"'
-
+        text += '#include "iec_types_all.h"\n\n'
+        
         # Adding variables
-        vars = []
-        inputs = memories = outputs = 0
-        for variable in self.CFile.variables.variable:
-            var = {"Name" : variable.getname(), "Type" : variable.gettype()}
-            if variable.getclass() == "input":
-                var["location"] = "__I%s%s_%d"%(self.GetSizeOfType(var["Type"]), location_str, inputs)
-                inputs += 1
-            elif variable.getclass() == "memory":
-                var["location"] = "__M%s%s_%d"%(self.GetSizeOfType(var["Type"]), location_str, memories)
-                memories += 1
-            else:
-                var["location"] = "__Q%s%s_%d"%(self.GetSizeOfType(var["Type"]), location_str, outputs)
-                outputs += 1
-            vars.append(var)
-        text += "/* Beremiz c_ext confnode user variables definition */\n"
-        base_types = self.GetCTRoot().GetBaseTypes()
-        for var in vars:
-            if var["Type"] in base_types:
-                prefix = "IEC_"
-            else:
-                prefix = ""
-            text += "%s%s beremiz%s;\n"%(prefix, var["Type"], var["location"])
-            text += "%s%s *%s = &beremiz%s;\n"%(prefix, var["Type"], var["location"], var["location"])
+        config = self.GetCTRoot().GetProjectConfigNames()[0]
         text += "/* User variables reference */\n"
-        for var in vars:
-            text += "#define %s beremiz%s\n"%(var["Name"], var["location"])
+        for variable in self.CodeFile.variables.variable:
+            var_infos = {
+                "name": variable.getname(),
+                "global": "%s__%s" % (config.upper(),
+                                      variable.getname().upper()),
+                "type": "__IEC_%s_t" % variable.gettype()}
+            text += "extern %(type)s %(global)s;\n" % var_infos
+            text += "#define %(name)s %(global)s.value\n" % var_infos
         text += "\n"
         
         # Adding user global variables and routines
         text += "/* User internal user variables and routines */\n"
-        text += self.CFile.globals.gettext()
+        text += self.CodeFile.globals.gettext().strip()
+        text += "\n"
         
         # Adding Beremiz confnode functions
         text += "/* Beremiz confnode functions */\n"
         text += "int __init_%s(int argc,char **argv)\n{\n"%location_str
-        text += self.CFile.initFunction.gettext()
-        text += "  return 0;\n"
-        text += "\n}\n\n"
+        text += self.CodeFile.initFunction.gettext().strip()
+        text += "  return 0;\n}\n\n"
         
         text += "void __cleanup_%s(void)\n{\n"%location_str
-        text += self.CFile.cleanUpFunction.gettext()
+        text += self.CodeFile.cleanUpFunction.gettext().strip()
         text += "\n}\n\n"
         
         text += "void __retrieve_%s(void)\n{\n"%location_str
-        text += self.CFile.retrieveFunction.gettext()
+        text += self.CodeFile.retrieveFunction.gettext().strip()
         text += "\n}\n\n"
         
         text += "void __publish_%s(void)\n{\n"%location_str
-        text += self.CFile.publishFunction.gettext()
+        text += self.CodeFile.publishFunction.gettext().strip()
         text += "\n}\n\n"
         
         Gen_Cfile_path = os.path.join(buildpath, "CFile_%s.c"%location_str)
@@ -247,48 +106,3 @@
         
         return [(Gen_Cfile_path, str(self.CExtension.getCFLAGS() + matiec_flags))],str(self.CExtension.getLDFLAGS()),True
 
-
-#-------------------------------------------------------------------------------
-#                      Current Buffering Management Functions
-#-------------------------------------------------------------------------------
-
-    """
-    Return a copy of the cfile model
-    """
-    def Copy(self, model):
-        return cPickle.loads(cPickle.dumps(model))
-
-    def CreateCFileBuffer(self, saved):
-        self.Buffering = False
-        self.CFileBuffer = UndoBuffer(cPickle.dumps(self.CFile), saved)
-
-    def BufferCFile(self):
-        self.CFileBuffer.Buffering(cPickle.dumps(self.CFile))
-    
-    def StartBuffering(self):
-        self.Buffering = True
-        
-    def EndBuffering(self):
-        if self.Buffering:
-            self.CFileBuffer.Buffering(cPickle.dumps(self.CFile))
-            self.Buffering = False
-    
-    def MarkCFileAsSaved(self):
-        self.EndBuffering()
-        self.CFileBuffer.CurrentSaved()
-    
-    def CFileIsSaved(self):
-        return self.CFileBuffer.IsCurrentSaved() and not self.Buffering
-        
-    def LoadPrevious(self):
-        self.EndBuffering()
-        self.CFile = cPickle.loads(self.CFileBuffer.Previous())
-    
-    def LoadNext(self):
-        self.CFile = cPickle.loads(self.CFileBuffer.Next())
-    
-    def GetBufferState(self):
-        first = self.CFileBuffer.IsFirst() and not self.Buffering
-        last = self.CFileBuffer.IsLast()
-        return not first, not last
-
--- a/c_ext/cext_xsd.xsd	Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1" ?>
-<xsd:schema targetNamespace="cext_xsd.xsd" 
-            xmlns:cext="cext_xsd.xsd" 
-            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-            elementFormDefault="qualified" 
-            attributeFormDefault="unqualified">
-
-  <xsd:element name="CFile">
-    <xsd:complexType>
-      <xsd:sequence>
-        <xsd:element name="includes" type="cext:CCode"/>
-        <xsd:element name="variables">
-          <xsd:complexType>
-            <xsd:sequence>
-              <xsd:element name="variable" minOccurs="0" maxOccurs="unbounded">
-                <xsd:complexType>
-                  <xsd:attribute name="name" type="xsd:string" use="required"/>
-                  <xsd:attribute name="type" type="xsd:string" use="required"/>
-                  <xsd:attribute name="class" use="required">
-                    <xsd:simpleType>
-                      <xsd:restriction base="xsd:string">
-                        <xsd:enumeration value="input"/>
-                        <xsd:enumeration value="memory"/>
-                        <xsd:enumeration value="output"/>
-                      </xsd:restriction>
-                    </xsd:simpleType>
-                  </xsd:attribute>
-                </xsd:complexType>
-              </xsd:element>
-            </xsd:sequence>
-          </xsd:complexType>
-        </xsd:element>
-        <xsd:element name="globals" type="cext:CCode"/>
-        <xsd:element name="initFunction" type="cext:CCode"/>
-        <xsd:element name="cleanUpFunction" type="cext:CCode"/>
-        <xsd:element name="retrieveFunction" type="cext:CCode"/>
-        <xsd:element name="publishFunction" type="cext:CCode"/>
-      </xsd:sequence>
-    </xsd:complexType>
-  </xsd:element>
-  <xsd:complexType name="CCode">
-    <xsd:annotation>
-      <xsd:documentation>Formatted text according to parts of XHTML 1.1</xsd:documentation>
-    </xsd:annotation>
-    <xsd:sequence>
-      <xsd:any namespace="http://www.w3.org/1999/xhtml" processContents="lax"/>
-    </xsd:sequence>
-  </xsd:complexType>
-</xsd:schema>
--- a/canfestival/NetworkEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/NetworkEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -2,7 +2,7 @@
 import wx
 
 from subindextable import EditingPanel
-from networkedit import NetworkEditorTemplate
+from networkeditortemplate import NetworkEditorTemplate
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
 
 [ID_NETWORKEDITOR, 
--- a/canfestival/SlaveEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/SlaveEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -2,7 +2,7 @@
 import wx
 
 from subindextable import EditingPanel
-from nodeeditor import NodeEditorTemplate
+from nodeeditortemplate import NodeEditorTemplate
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
 
 [ID_SLAVEEDITORCONFNODEMENUNODEINFOS, ID_SLAVEEDITORCONFNODEMENUDS301PROFILE,
--- a/canfestival/canfestival.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/canfestival.py	Wed Jul 31 10:45:07 2013 +0900
@@ -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")
@@ -9,12 +9,12 @@
 from nodelist import NodeList
 from nodemanager import NodeManager
 import config_utils, gen_cfile, eds_utils
-from networkedit import networkedit
-from objdictedit import objdictedit
 import canfestival_config as local_canfestival_config
 from ConfigTreeNode import ConfigTreeNode
 from commondialogs import CreateNodeDialog
-
+from subindextable import IECTypeConversion, SizeConversion
+
+from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
 from SlaveEditor import SlaveEditor, MasterViewer
 from NetworkEditor import NetworkEditor
 
@@ -26,6 +26,32 @@
 AddCatalog(os.path.join(CanFestivalPath, "objdictgen", "locale"))
 
 #--------------------------------------------------
+#              Location Tree Helper
+#--------------------------------------------------
+
+def GetSlaveLocationTree(slave_node, current_location, name):
+    entries = []
+    for index, subindex, size, entry_name in slave_node.GetMapVariableList():
+        subentry_infos = slave_node.GetSubentryInfos(index, subindex)
+        typeinfos = slave_node.GetEntryInfos(subentry_infos["type"])
+        if typeinfos:
+            entries.append({
+                "name": "0x%4.4x-0x%2.2x: %s" % (index, subindex, entry_name),
+                "type": LOCATION_VAR_MEMORY,
+                "size": size,
+                "IEC_type": IECTypeConversion.get(typeinfos["name"]),
+                "var_name": "%s_%4.4x_%2.2x" % ("_".join(name.split()), index, subindex),
+                "location": "%s%s"%(SizeConversion[size], ".".join(map(str, current_location + 
+                                                                                (index, subindex)))),
+                "description": "",
+                "children": []})
+    return  {"name": name,
+             "type": LOCATION_CONFNODE,
+             "location": ".".join([str(i) for i in current_location]) + ".x",
+             "children": entries
+    }
+
+#--------------------------------------------------
 #                    SLAVE
 #--------------------------------------------------
 
@@ -128,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):
@@ -139,7 +165,13 @@
             self._View.SetBusId(self.GetCurrentLocation())
         
         return result
-        
+    
+    def GetVariableLocationTree(self):
+        current_location = self.GetCurrentLocation()
+        return GetSlaveLocationTree(self.CurrentNode, 
+                                    self.GetCurrentLocation(), 
+                                    self.BaseParams.getName())
+    
     def CTNGenerate_C(self, buildpath, locations):
         """
         Generate C code
@@ -277,7 +309,23 @@
         if refresh_network and self._View is not None:
             wx.CallAfter(self._View.RefreshBufferState)
         return value, refresh
-        
+    
+    def GetVariableLocationTree(self):
+        current_location = self.GetCurrentLocation()
+        nodeindexes = self.SlaveNodes.keys()
+        nodeindexes.sort()
+        return {"name": self.BaseParams.getName(),
+                 "type": LOCATION_CONFNODE,
+                 "location": self.GetFullIEC_Channel(),
+                 "children": [GetSlaveLocationTree(self.Manager.GetCurrentNodeCopy(),
+                                                   current_location,
+                                                   _("Local entries"))] + 
+                             [GetSlaveLocationTree(self.SlaveNodes[nodeid]["Node"], 
+                                                   current_location + (nodeid,), 
+                                                   self.SlaveNodes[nodeid]["Name"])
+                              for nodeid in nodeindexes]
+        }
+    
     _GeneratedMasterView = None
     def _ShowGeneratedMaster(self):
         self._OpenView("Generated master")
@@ -330,8 +378,11 @@
     def CTNTestModified(self):
         return self.ChangesToSave or self.HasChanged()
         
-    def OnCTNSave(self):
+    def OnCTNSave(self, from_project_path=None):
         self.SetRoot(self.CTNPath())
+        if from_project_path is not None:
+            shutil.copytree(self.GetEDSFolder(from_project_path), 
+                            self.GetEDSFolder())
         return self.SaveProject() is None
 
     def CTNGenerate_C(self, buildpath, locations):
@@ -412,7 +463,8 @@
             if can_driver not in can_drivers :
                 can_driver = can_drivers[0]
             can_drv_ext = self.GetCTRoot().GetBuilder().extension
-            can_driver_name = "libcanfestival_" + can_driver + can_drv_ext
+            can_drv_prefix = self.GetCTRoot().GetBuilder().dlopen_prefix
+            can_driver_name = can_drv_prefix + "libcanfestival_" + can_driver + can_drv_ext
         else:
             can_driver_name = ""
 
--- a/canfestival/config_utils.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/canfestival/config_utils.py	Wed Jul 31 10:45:07 2013 +0900
@@ -118,23 +118,24 @@
     @return: a tuple of value and number of parameters to add to DCF 
     """
     
+    dcfdata=[]
     # Create entry for RPDO or TPDO parameters and Disable PDO
-    dcfdata = LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(0x80000000 + cobid, 4)
-    # Set Transmit type synchrone
-    dcfdata += LE_to_BE(idx, 2) + LE_to_BE(0x02, 1) + LE_to_BE(0x01, 4) + LE_to_BE(transmittype, 1)
-    # Re-Enable PDO
-    #         ---- INDEX -----   --- SUBINDEX ----   ----- SIZE ------   ------ DATA ------
-    dcfdata += LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(cobid, 4)
-    nbparams = 3
+    #           ---- INDEX -----   --- SUBINDEX ----   ----- SIZE ------   ------ DATA ------
+    dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(0x80000000 + cobid, 4)]
+    # Set Transmit type
+    dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x02, 1) + LE_to_BE(0x01, 4) + LE_to_BE(transmittype, 1)]
     if len(pdomapping) > 0:
-        dcfdata += LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(len(pdomapping), 1)
-        nbparams += 1
+        # Disable Mapping
+        dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(0x00, 1)]
         # Map Variables
         for subindex, (name, loc_infos) in enumerate(pdomapping):
             value = (loc_infos["index"] << 16) + (loc_infos["subindex"] << 8) + loc_infos["size"]
-            dcfdata += LE_to_BE(idx + 0x200, 2) + LE_to_BE(subindex + 1, 1) + LE_to_BE(0x04, 4) + LE_to_BE(value, 4)
-            nbparams += 1
-    return dcfdata, nbparams
+            dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(subindex + 1, 1) + LE_to_BE(0x04, 4) + LE_to_BE(value, 4)]
+        # Re-enable Mapping
+        dcfdata += [LE_to_BE(idx + 0x200, 2) + LE_to_BE(0x00, 1) + LE_to_BE(0x01, 4) + LE_to_BE(len(pdomapping), 1)]
+    # Re-Enable PDO
+    dcfdata += [LE_to_BE(idx, 2) + LE_to_BE(0x01, 1) + LE_to_BE(0x04, 4) + LE_to_BE(cobid, 4)]
+    return "".join(dcfdata), len(dcfdata)
 
 class ConciseDCFGenerator:
 
--- a/connectors/PYRO/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/connectors/PYRO/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -45,6 +45,7 @@
             from util.Zeroconf import Zeroconf
             r = Zeroconf()
             i=r.getServiceInfo(service_type, location)
+            if i is None : raise Exception, "'%s' not found"%location
             ip = str(socket.inet_ntoa(i.getAddress()))
             port = str(i.getPort())
             newlocation = ip+':'+port
@@ -72,18 +73,18 @@
         def catcher_func(*args,**kwargs):
             try:
                 return func(*args,**kwargs)
-            except Pyro.errors.ProtocolError, e:
-                pass
             except Pyro.errors.ConnectionClosedError, e:
                 confnodesroot.logger.write_error("Connection lost!\n")
-                confnodesroot._connector = None
+                confnodesroot._SetConnector(None)
+            except Pyro.errors.ProtocolError, e:
+                confnodesroot.logger.write_error("Pyro exception: "+str(e)+"\n")
             except Exception,e:
                 #confnodesroot.logger.write_error(traceback.format_exc())
                 errmess = ''.join(Pyro.util.getPyroTraceback(e))
                 confnodesroot.logger.write_error(errmess+"\n")
                 print errmess
-                confnodesroot._connector = None
-                return default
+                confnodesroot._SetConnector(None)
+            return default
         return catcher_func
 
     # Check connection is effective. 
--- a/controls/CustomGrid.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/CustomGrid.py	Wed Jul 31 10:45:07 2013 +0900
@@ -93,7 +93,7 @@
             self.Table.InsertRow(new_row, self.DefaultValue.copy())
             self.Table.ResetView(self)
         if new_row is not None:
-            self.SetSelectedRow(new_row)
+            self.SetSelectedCell(new_row, col)
 
     def DeleteRow(self):
         self.CloseEditControl()
@@ -105,7 +105,8 @@
             else:
                 self.Table.RemoveRow(row)
                 self.Table.ResetView(self)
-            self.SetSelectedRow(min(row, self.Table.GetNumberRows() - 1))
+            if self.Table.GetNumberRows() > 0:
+                self.SetSelectedCell(min(row, self.Table.GetNumberRows() - 1), col)
 
     def MoveRow(self, row, move):
         self.CloseEditControl()
@@ -117,10 +118,9 @@
             if new_row != row:
                 self.Table.ResetView(self)
         if new_row != row:
-            self.SetSelectedRow(new_row)
+            self.SetSelectedCell(new_row, col)
     
-    def SetSelectedRow(self, row):
-        col = self.GetGridCursorCol()
+    def SetSelectedCell(self, row, col):
         self.SetGridCursor(row, col)
         self.MakeCellVisible(row, col)
         self.RefreshButtons()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomStyledTextCtrl.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import wx
+import wx.stc
+
+if wx.Platform == '__WXMSW__':
+    faces = { 'times': 'Times New Roman',
+              'mono' : 'Courier New',
+              'helv' : 'Arial',
+              'other': 'Comic Sans MS',
+              'size' : 10,
+             }
+else:
+    faces = { 'times': 'Times',
+              'mono' : 'Courier',
+              'helv' : 'Helvetica',
+              'other': 'new century schoolbook',
+              'size' : 12,
+             }
+
+NAVIGATION_KEYS = [
+    wx.WXK_END,
+    wx.WXK_HOME,
+    wx.WXK_LEFT,
+    wx.WXK_UP,
+    wx.WXK_RIGHT,
+    wx.WXK_DOWN,
+    wx.WXK_PAGEUP,
+    wx.WXK_PAGEDOWN,
+    wx.WXK_NUMPAD_HOME,
+    wx.WXK_NUMPAD_LEFT,
+    wx.WXK_NUMPAD_UP,
+    wx.WXK_NUMPAD_RIGHT,
+    wx.WXK_NUMPAD_DOWN,
+    wx.WXK_NUMPAD_PAGEUP,
+    wx.WXK_NUMPAD_PAGEDOWN,
+    wx.WXK_NUMPAD_END]
+
+def GetCursorPos(old, new):
+    if old == "":
+        return 0
+    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 CustomStyledTextCtrl(wx.stc.StyledTextCtrl):
+    
+    def __init__(self, *args, **kwargs):
+        wx.stc.StyledTextCtrl.__init__(self, *args, **kwargs)
+        
+        self.Bind(wx.EVT_MOTION, self.OnMotion)
+        
+    def OnMotion(self, event):
+        if wx.Platform == '__WXMSW__':
+            if not event.Dragging():
+                x, y = event.GetPosition()
+                margin_width = reduce(
+                        lambda x, y: x + y,
+                        [self.GetMarginWidth(i)
+                         for i in xrange(3)],
+                        0)
+                if x <= margin_width:
+                    self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
+                else:
+                    self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM))
+            else:
+                event.Skip()
+        else:
+            event.Skip()
+
+    def AppendText(self, text):
+        self.GotoPos(self.GetLength())
+        self.AddText(text)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/CustomToolTip.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import wx
+
+from controls.CustomStyledTextCtrl import faces
+
+TOOLTIP_MAX_CHARACTERS = 30 # Maximum number of characters by line in ToolTip
+TOOLTIP_MAX_LINE = 5        # Maximum number of line in ToolTip
+TOOLTIP_WAIT_PERIOD = 0.5   # Wait period before displaying tooltip in second
+
+#-------------------------------------------------------------------------------
+#                               Custom ToolTip
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom tool tip
+"""
+
+class CustomToolTip(wx.PopupWindow):
+    
+    def __init__(self, parent, tip, restricted=True):
+        """
+        Constructor
+        @param parent: Parent window
+        @param tip: Tip text (may be multiline)
+        @param restricted: Tool tip must follow size restriction in line and 
+            characters number defined (default True)
+        """
+        wx.PopupWindow.__init__(self, parent)
+        
+        self.Restricted = restricted
+        
+        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+        self.SetTip(tip)
+        
+        # Initialize text font style
+        self.Font = wx.Font(
+            faces["size"], 
+            wx.SWISS, 
+            wx.NORMAL, 
+            wx.NORMAL, 
+            faceName = faces["mono"])
+        
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+    
+    def SetFont(self, font):
+        """
+        Set tool tip text font style
+        @param font: wx.Font object containing font style
+        """
+        self.Font = font
+        self.RefreshTip()
+    
+    def SetTip(self, tip):
+        """
+        Set tool tip text
+        @param tip: Tool tip text
+        """
+        if self.Restricted:
+            # Compute tip text line return
+            self.Tip = []
+            for line in tip.splitlines():
+                if line != "":
+                    words = line.split()
+                    new_line = words[0]
+                    for word in words[1:]:
+                        # Add word to line
+                        if len(new_line + " " + word) <= \
+                            TOOLTIP_MAX_CHARACTERS:
+                            new_line += " " + word
+                        # Create new line
+                        else:
+                            self.Tip.append(new_line)
+                            new_line = word
+                    self.Tip.append(new_line)
+                else:
+                    self.Tip.append(line)
+            
+            # Restrict number of lines
+            if len(self.Tip) > TOOLTIP_MAX_LINE:
+                self.Tip = self.Tip[:TOOLTIP_MAX_LINE]
+                
+                # Add ... to the end of last line to indicate that tool tip
+                # text is too long
+                if len(self.Tip[-1]) < TOOLTIP_MAX_CHARACTERS - 3:
+                    self.Tip[-1] += "..."
+                else:
+                    self.Tip[-1] = self.Tip[-1]\
+                        [:TOOLTIP_MAX_CHARACTERS - 3] + "..."
+        else:
+            self.Tip = tip.splitlines()
+        
+        # Prevent to call wx method in non-wx threads
+        wx.CallAfter(self.RefreshTip)
+    
+    def SetToolTipPosition(self, pos):
+        """
+        Set tool tip position
+        @param pos: New tool tip position
+        """
+        # Get screen size to prevent tool tip to go out of the screen
+        screen_width, screen_height = wx.GetDisplaySize()
+        
+        # Calculate position of tool tip to stay in screen limits
+        tip_width, tip_height = self.GetToolTipSize()
+        self.SetPosition(wx.Point(
+            max(0, min(pos.x, screen_width - tip_width)),
+            max(0, min(pos.y, screen_height - tip_height))))
+    
+    def GetToolTipSize(self):
+        """
+        Get tool tip size according to tip text and restriction
+        @return: wx.Size(tool_tip_width, tool_tip_height)
+        """
+        max_width = max_height = 0
+        
+        # Create a memory DC for calculating text extent
+        dc = wx.MemoryDC()
+        dc.SetFont(self.Font)
+        
+        # Compute max tip text size
+        for line in self.Tip:
+            w, h = dc.GetTextExtent(line)
+            max_width = max(max_width, w)
+            max_height += h
+        
+        return wx.Size(max_width + 4, max_height + 4)
+    
+    def RefreshTip(self):
+        """
+        Refresh tip on screen
+        """
+        # Prevent to call this function if tool tip destroyed
+        if self:
+            # Refresh tool tip size and position
+            self.SetClientSize(self.GetToolTipSize())
+            
+            # Redraw tool tip
+            self.Refresh()
+    
+    def OnPaint(self, event):
+        """
+        Callback for Paint Event
+        @param event: Paint event
+        """
+        # Get buffered paint DC for tool tip
+        dc = wx.AutoBufferedPaintDC(self)
+        dc.Clear()
+        
+        # Set DC drawing style
+        dc.SetPen(wx.BLACK_PEN)
+        dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
+        dc.SetFont(self.Font)
+        
+        # Draw Tool tip
+        dc.BeginDrawing()
+        tip_width, tip_height = self.GetToolTipSize()
+        
+        # Draw background rectangle
+        dc.DrawRectangle(0, 0, tip_width, tip_height)
+        
+        # Draw tool tip text
+        line_offset = 0
+        for line in self.Tip:
+            dc.DrawText(line, 2, line_offset + 2)
+            line_width, line_height = dc.GetTextExtent(line)
+            line_offset += line_height
+        
+        dc.EndDrawing()
+        
+        event.Skip()
--- a/controls/CustomTree.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/CustomTree.py	Wed Jul 31 10:45:07 2013 +0900
@@ -16,12 +16,30 @@
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 import wx
+import wx.lib.agw.customtreectrl as CT
 
-class CustomTree(wx.TreeCtrl):
+from util.BitmapLibrary import GetBitmap
+
+# Customize CustomTreeItem for adding icon on item left
+CT.GenericTreeItem._ExtraImage = None
+
+def SetExtraImage(self, image):
+    self._type = (1 if image is not None else 0)
+    self._ExtraImage = image
+
+CT.GenericTreeItem.SetExtraImage = SetExtraImage
+
+_DefaultGetCurrentCheckedImage = CT.GenericTreeItem.GetCurrentCheckedImage
+def GetCurrentCheckedImage(self):
+    if self._ExtraImage is not None:
+        return self._ExtraImage
+    return _DefaultGetCurrentCheckedImage(self)
+CT.GenericTreeItem.GetCurrentCheckedImage = GetCurrentCheckedImage
+
+class CustomTree(CT.CustomTreeCtrl):
     
     def __init__(self, *args, **kwargs):
-        wx.TreeCtrl.__init__(self, *args, **kwargs)
-        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+        CT.CustomTreeCtrl.__init__(self, *args, **kwargs)
         
         self.BackgroundBitmap = None
         self.BackgroundAlign = wx.ALIGN_LEFT|wx.ALIGN_TOP
@@ -29,18 +47,30 @@
         self.AddMenu = None
         self.Enabled = False
         
-        if wx.Platform == '__WXMSW__':
-            self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
-        else:
-            self.Bind(wx.EVT_PAINT, self.OnPaint)
-            self.Bind(wx.EVT_SIZE, self.OnResize)
-            self.Bind(wx.EVT_SCROLL, self.OnScroll)
+        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
     
     def SetBackgroundBitmap(self, bitmap, align):
         self.BackgroundBitmap = bitmap
         self.BackgroundAlign = align
     
+    def SetImageListCheck(self, sizex, sizey, imglist=None):
+        CT.CustomTreeCtrl.SetImageListCheck(self, sizex, sizey, imglist=None)
+        
+        self.ExtraImages = {}
+        for image in ["function", "functionBlock", "program"]:
+            self.ExtraImages[image] = self._imageListCheck.Add(GetBitmap(image.upper()))
+    
+    def SetItemExtraImage(self, item, bitmap):
+        dc = wx.ClientDC(self)
+        image = self.ExtraImages.get(bitmap)
+        if image is not None:
+            item.SetExtraImage(image)
+        else:
+            item.SetExtraImage(None)
+        self.CalculateSize(item, dc)
+        self.RefreshLine(item)   
+        
     def SetAddMenu(self, add_menu):
         self.AddMenu = add_menu
     
@@ -67,19 +97,6 @@
         
         return wx.Rect(x, y, bitmap_size[0], bitmap_size[1])
     
-    def RefreshBackground(self, refresh_base=False):
-        dc = wx.ClientDC(self)
-        dc.Clear()
-        
-        bitmap_rect = self.GetBitmapRect()
-        dc.DrawBitmap(self.BackgroundBitmap, bitmap_rect.x, bitmap_rect.y)
-        
-        if refresh_base:
-            self.Refresh(False)
-    
-    def OnEraseBackground(self, event):
-        self.RefreshBackground(True)
-    
     def OnLeftUp(self, event):
         if self.Enabled:
             pos = event.GetPosition()
@@ -88,17 +105,26 @@
             bitmap_rect = self.GetBitmapRect()
             if (bitmap_rect.InsideXY(pos.x, pos.y) or 
                 flags & wx.TREE_HITTEST_NOWHERE) and self.AddMenu is not None:
-                self.PopupMenuXY(self.AddMenu, pos.x, pos.y)
+                wx.CallAfter(self.PopupMenuXY, self.AddMenu, pos.x, pos.y)
+        event.Skip()
+    
+    def OnEraseBackground(self, event):
+        dc = event.GetDC()
+
+        if not dc:
+            dc = wx.ClientDC(self)
+            rect = self.GetUpdateRegion().GetBox()
+            dc.SetClippingRect(rect)
+        
+        dc.Clear()
+        
+        bitmap_rect = self.GetBitmapRect()
+        dc.DrawBitmap(self.BackgroundBitmap, bitmap_rect.x, bitmap_rect.y)
+    
+    def OnScroll(self, event):
+        wx.CallAfter(self.Refresh)
         event.Skip()
 
-    def OnScroll(self, event):
-        self.RefreshBackground(True)
-        event.Skip()
-
-    def OnResize(self, event):
-        self.RefreshBackground(True)
-        event.Skip()
-    
-    def OnPaint(self, event):
-        self.RefreshBackground()
-        event.Skip()
\ No newline at end of file
+    def OnSize(self, event):
+        CT.CustomTreeCtrl.OnSize(self, event)
+        self.Refresh()
--- a/controls/DebugVariablePanel.py	Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2261 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
-#based on the plcopen standard. 
-#
-#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
-#
-#See COPYING file for copyrights details.
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU General Public
-#License as published by the Free Software Foundation; either
-#version 2.1 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-#General Public License for more details.
-#
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-from types import TupleType, ListType, FloatType
-from time import time as gettime
-import math
-import numpy
-import binascii
-
-import wx
-import wx.lib.buttons
-
-try:
-    import matplotlib
-    matplotlib.use('WX')
-    import matplotlib.pyplot
-    from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
-    from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
-    from matplotlib.backends.backend_agg import FigureCanvasAgg
-    from mpl_toolkits.mplot3d import Axes3D
-    color_cycle = ['r', 'b', 'g', 'm', 'y', 'k']
-    cursor_color = '#800080'
-    USE_MPL = True
-except:
-    USE_MPL = False
-
-from graphics import DebugDataConsumer, DebugViewer, REFRESH_PERIOD
-from controls import CustomGrid, CustomTable
-from dialogs.ForceVariableDialog import ForceVariableDialog
-from util.BitmapLibrary import GetBitmap
-
-def AppendMenu(parent, help, id, kind, text):
-    parent.Append(help=help, id=id, kind=kind, text=text)
-
-def GetDebugVariablesTableColnames():
-    _ = lambda x : x
-    return [_("Variable"), _("Value")]
-
-CRC_SIZE = 8
-CRC_MASK = 2 ** CRC_SIZE - 1
-
-class VariableTableItem(DebugDataConsumer):
-    
-    def __init__(self, parent, variable):
-        DebugDataConsumer.__init__(self)
-        self.Parent = parent
-        self.Variable = variable
-        self.RefreshVariableType()
-        self.Value = ""
-        
-    def __del__(self):
-        self.Parent = None
-    
-    def SetVariable(self, variable):
-        if self.Parent and self.Variable != variable:
-            self.Variable = variable
-            self.RefreshVariableType()
-            self.Parent.RefreshView()
-    
-    def GetVariable(self, mask=None):
-        variable = self.Variable
-        if mask is not None:
-            parts = variable.split('.')
-            mask = mask + ['*'] * max(0, len(parts) - len(mask))
-            last = None
-            variable = ""
-            for m, v in zip(mask, parts):
-                if m == '*':
-                    if last == '*':
-                        variable += '.'
-                    variable += v
-                elif last is None or last == '*':
-                    variable += '..'
-                last = m
-        return variable
-    
-    def RefreshVariableType(self):
-        self.VariableType = self.Parent.GetDataType(self.Variable)
-        if USE_MPL:
-            self.ResetData()
-    
-    def GetVariableType(self):
-        return self.VariableType
-    
-    def GetData(self, start_tick=None, end_tick=None):
-        if USE_MPL and self.IsNumVariable():
-            if len(self.Data) == 0:
-                return self.Data
-            
-            start_idx = end_idx = None
-            if start_tick is not None:
-                start_idx = self.GetNearestData(start_tick, -1)
-            if end_tick is not None:
-                end_idx = self.GetNearestData(end_tick, 1)
-            if start_idx is None:
-                start_idx = 0
-            if end_idx is not None:
-                return self.Data[start_idx:end_idx + 1]
-            else:
-                return self.Data[start_idx:]
-            
-        return None
-    
-    def GetRawValue(self, idx):
-        if self.VariableType in ["STRING", "WSTRING"] and idx < len(self.RawData):
-            return self.RawData[idx][0]
-        return ""
-    
-    def GetRange(self):
-        return self.MinValue, self.MaxValue
-    
-    def ResetData(self):
-        if self.IsNumVariable():
-            self.Data = numpy.array([]).reshape(0, 3)
-            if self.VariableType in ["STRING", "WSTRING"]:
-                self.RawData = []
-            self.MinValue = None
-            self.MaxValue = None
-        else:
-            self.Data = None
-    
-    def IsNumVariable(self):
-        return (self.Parent.IsNumType(self.VariableType) or 
-                self.VariableType in ["STRING", "WSTRING"])
-    
-    def NewValue(self, tick, value, forced=False):
-        if USE_MPL and self.IsNumVariable():
-            if self.VariableType in ["STRING", "WSTRING"]:
-                num_value = binascii.crc32(value) & CRC_MASK
-            else:
-                num_value = float(value)
-            if self.MinValue is None:
-                self.MinValue = num_value
-            else:
-                self.MinValue = min(self.MinValue, num_value)
-            if self.MaxValue is None:
-                self.MaxValue = num_value
-            else:
-                self.MaxValue = max(self.MaxValue, num_value)
-            forced_value = float(forced)
-            if self.VariableType in ["STRING", "WSTRING"]:
-                raw_data = (value, forced_value)
-                if len(self.RawData) == 0 or self.RawData[-1] != raw_data:
-                    extra_value = len(self.RawData)
-                    self.RawData.append(raw_data)
-                else:
-                    extra_value = len(self.RawData) - 1
-            else:
-                extra_value = forced_value
-            self.Data = numpy.append(self.Data, [[float(tick), num_value, extra_value]], axis=0)
-            self.Parent.HasNewData = True
-        DebugDataConsumer.NewValue(self, tick, value, forced)
-    
-    def SetForced(self, forced):
-        if self.Forced != forced:
-            self.Forced = forced
-            self.Parent.HasNewData = True
-    
-    def SetValue(self, value):
-        if (self.VariableType == "STRING" and value.startswith("'") and value.endswith("'") or
-            self.VariableType == "WSTRING" and value.startswith('"') and value.endswith('"')):
-            value = value[1:-1]
-        if self.Value != value:
-            self.Value = value
-            self.Parent.HasNewData = True
-            
-    def GetValue(self, tick=None, raw=False):
-        if tick is not None and self.IsNumVariable():
-            if len(self.Data) > 0:
-                idx = numpy.argmin(abs(self.Data[:, 0] - tick))
-                if self.VariableType in ["STRING", "WSTRING"]:
-                    value, forced = self.RawData[int(self.Data[idx, 2])]
-                    if not raw:
-                        if self.VariableType == "STRING":
-                            value = "'%s'" % value
-                        else:
-                            value = '"%s"' % value
-                    return value, forced
-                else:
-                    value = self.Data[idx, 1]
-                    if not raw and isinstance(value, FloatType):
-                        value = "%.6g" % value
-                    return value, self.Data[idx, 2]
-            return self.Value, self.IsForced()
-        elif not raw:
-            if self.VariableType == "STRING":
-                return "'%s'" % self.Value
-            elif self.VariableType == "WSTRING":
-                return '"%s"' % self.Value
-            elif isinstance(self.Value, FloatType):
-                return "%.6g" % self.Value
-        return self.Value
-
-    def GetNearestData(self, tick, adjust):
-        if USE_MPL and self.IsNumVariable():
-            ticks = self.Data[:, 0]
-            new_cursor = numpy.argmin(abs(ticks - tick))
-            if adjust == -1 and ticks[new_cursor] > tick and new_cursor > 0:
-                new_cursor -= 1
-            elif adjust == 1 and ticks[new_cursor] < tick and new_cursor < len(ticks):
-                new_cursor += 1
-            return new_cursor
-        return None
-
-class DebugVariableTable(CustomTable):
-    
-    def GetValue(self, row, col):
-        if row < self.GetNumberRows():
-            return self.GetValueByName(row, self.GetColLabelValue(col, False))
-        return ""
-    
-    def SetValue(self, row, col, value):
-        if col < len(self.colnames):
-            self.SetValueByName(row, self.GetColLabelValue(col, False), value)
-            
-    def GetValueByName(self, row, colname):
-        if row < self.GetNumberRows():
-            if colname == "Variable":
-                return self.data[row].GetVariable()
-            elif colname == "Value":
-                return self.data[row].GetValue()
-        return ""
-
-    def SetValueByName(self, row, colname, value):
-        if row < self.GetNumberRows():
-            if colname == "Variable":
-                self.data[row].SetVariable(value)
-            elif colname == "Value":
-                self.data[row].SetValue(value)
-    
-    def IsForced(self, row):
-        if row < self.GetNumberRows():
-            return self.data[row].IsForced()
-        return False
-    
-    def IsNumVariable(self, row):
-        if row < self.GetNumberRows():
-            return self.data[row].IsNumVariable()
-        return False
-    
-    def _updateColAttrs(self, grid):
-        """
-        wx.grid.Grid -> update the column attributes to add the
-        appropriate renderer given the column name.
-
-        Otherwise default to the default renderer.
-        """
-        
-        for row in range(self.GetNumberRows()):
-            for col in range(self.GetNumberCols()):
-                colname = self.GetColLabelValue(col, False)
-                if colname == "Value":
-                    if self.IsForced(row):
-                        grid.SetCellTextColour(row, col, wx.BLUE)
-                    else:
-                        grid.SetCellTextColour(row, col, wx.BLACK)
-                grid.SetReadOnly(row, col, True)
-            self.ResizeRow(grid, row)
-                
-    def AppendItem(self, data):
-        self.data.append(data)
-    
-    def InsertItem(self, idx, data):
-        self.data.insert(idx, data)
-    
-    def RemoveItem(self, idx):
-        self.data.pop(idx)
-    
-    def MoveItem(self, idx, new_idx):
-        self.data.insert(new_idx, self.data.pop(idx))
-        
-    def GetItem(self, idx):
-        return self.data[idx]
-
-class DebugVariableDropTarget(wx.TextDropTarget):
-    
-    def __init__(self, parent, control=None):
-        wx.TextDropTarget.__init__(self)
-        self.ParentWindow = parent
-        self.ParentControl = control
-    
-    def __del__(self):
-        self.ParentWindow = None
-        self.ParentControl = None
-    
-    def OnDragOver(self, x, y, d):
-        if self.ParentControl is not None:
-            self.ParentControl.OnMouseDragging(x, y)
-        else:
-            self.ParentWindow.RefreshHighlight(x, y)
-        return wx.TextDropTarget.OnDragOver(self, x, y, d)
-        
-    def OnDropText(self, x, y, data):
-        message = None
-        try:
-            values = eval(data)
-        except:
-            message = _("Invalid value \"%s\" for debug variable")%data
-            values = None
-        if not isinstance(values, TupleType):
-            message = _("Invalid value \"%s\" for debug variable")%data
-            values = None
-        
-        if message is not None:
-            wx.CallAfter(self.ShowMessage, message)
-        elif values is not None and values[1] == "debug":
-            if isinstance(self.ParentControl, CustomGrid):
-                x, y = self.ParentControl.CalcUnscrolledPosition(x, y)
-                row = self.ParentControl.YToRow(y - self.ParentControl.GetColLabelSize())
-                if row == wx.NOT_FOUND:
-                    row = self.ParentWindow.Table.GetNumberRows()
-                self.ParentWindow.InsertValue(values[0], row, force=True)
-            elif self.ParentControl is not None:
-                width, height = self.ParentControl.GetSize()
-                target_idx = self.ParentControl.GetIndex()
-                merge_type = GRAPH_PARALLEL
-                if isinstance(self.ParentControl, DebugVariableGraphic):
-                    if self.ParentControl.Is3DCanvas():
-                        if y > height / 2:
-                            target_idx += 1
-                        if len(values) > 1 and values[2] == "move":
-                            self.ParentWindow.MoveValue(values[0], target_idx)
-                        else:
-                            self.ParentWindow.InsertValue(values[0], target_idx, force=True)
-                        
-                    else:
-                        rect = self.ParentControl.GetAxesBoundingBox()
-                        if rect.InsideXY(x, y):
-                            merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
-                            if merge_rect.InsideXY(x, y):
-                                merge_type = GRAPH_ORTHOGONAL
-                            wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
-                        else:
-                            if y > height / 2:
-                                target_idx += 1
-                            if len(values) > 2 and values[2] == "move":
-                                self.ParentWindow.MoveValue(values[0], target_idx)
-                            else:
-                                self.ParentWindow.InsertValue(values[0], target_idx, force=True)
-                else:
-                    if y > height / 2:
-                        target_idx += 1
-                    if len(values) > 2 and values[2] == "move":
-                        self.ParentWindow.MoveValue(values[0], target_idx)
-                    else:
-                        self.ParentWindow.InsertValue(values[0], target_idx, force=True)
-                    
-            elif len(values) > 2 and values[2] == "move":
-                self.ParentWindow.MoveValue(values[0])
-            else:
-                self.ParentWindow.InsertValue(values[0], force=True)
-    
-    def OnLeave(self):
-        self.ParentWindow.ResetHighlight()
-        return wx.TextDropTarget.OnLeave(self)
-    
-    def ShowMessage(self, message):
-        dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
-        dialog.ShowModal()
-        dialog.Destroy()
-
-if USE_MPL:
-    MILLISECOND = 1000000
-    SECOND = 1000 * MILLISECOND
-    MINUTE = 60 * SECOND
-    HOUR = 60 * MINUTE
-    DAY = 24 * HOUR
-    
-    ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)])
-    RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)])
-    TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
-                        [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
-                        [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
-    
-    GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
-    
-    SCROLLBAR_UNIT = 10
-    
-    #CANVAS_HIGHLIGHT_TYPES
-    [HIGHLIGHT_NONE,
-     HIGHLIGHT_BEFORE,
-     HIGHLIGHT_AFTER,
-     HIGHLIGHT_LEFT,
-     HIGHLIGHT_RIGHT,
-     HIGHLIGHT_RESIZE] = range(6)
-    
-    HIGHLIGHT_DROP_PEN = wx.Pen(wx.Colour(0, 128, 255))
-    HIGHLIGHT_DROP_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128))
-    HIGHLIGHT_RESIZE_PEN = wx.Pen(wx.Colour(200, 200, 200))
-    HIGHLIGHT_RESIZE_BRUSH = wx.Brush(wx.Colour(200, 200, 200))
-    
-    #CANVAS_SIZE_TYPES
-    [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
-    
-    DEFAULT_CANVAS_HEIGHT = 200.
-    CANVAS_BORDER = (20., 10.)
-    CANVAS_PADDING = 8.5
-    VALUE_LABEL_HEIGHT = 17.
-    AXES_LABEL_HEIGHT = 12.75
-    
-    def compute_mask(x, y):
-        mask = []
-        for xp, yp in zip(x, y):
-            if xp == yp:
-                mask.append(xp)
-            else:
-                mask.append("*")
-        return mask
-    
-    def NextTick(variables):
-        next_tick = None
-        for item, data in variables:
-            if len(data) > 0:
-                if next_tick is None:
-                    next_tick = data[0][0]
-                else:
-                    next_tick = min(next_tick, data[0][0])
-        return next_tick
-    
-    def OrthogonalData(item, start_tick, end_tick):
-        data = item.GetData(start_tick, end_tick)
-        min_value, max_value = item.GetRange()
-        if min_value is not None and max_value is not None:
-            center = (min_value + max_value) / 2.
-            range = max(1.0, max_value - min_value)
-        else:
-            center = 0.5
-            range = 1.0
-        return data, center - range * 0.55, center + range * 0.55
-    
-    class GraphButton():
-        
-        def __init__(self, x, y, bitmap, callback):
-            self.Position = wx.Point(x, y)
-            self.Bitmap = bitmap
-            self.Shown = True
-            self.Enabled = True
-            self.Callback = callback
-        
-        def __del__(self):
-            self.callback = None
-        
-        def GetSize(self):
-            return self.Bitmap.GetSize()
-        
-        def SetPosition(self, x, y):
-            self.Position = wx.Point(x, y)
-        
-        def Show(self):
-            self.Shown = True
-            
-        def Hide(self):
-            self.Shown = False
-        
-        def IsShown(self):
-            return self.Shown
-        
-        def Enable(self):
-            self.Enabled = True
-        
-        def Disable(self):
-            self.Enabled = False
-        
-        def IsEnabled(self):
-            return self.Enabled
-        
-        def HitTest(self, x, y):
-            if self.Shown and self.Enabled:
-                w, h = self.Bitmap.GetSize()
-                rect = wx.Rect(self.Position.x, self.Position.y, w, h)
-                if rect.InsideXY(x, y):
-                    return True
-            return False
-        
-        def ProcessCallback(self):
-            if self.Callback is not None:
-                wx.CallAfter(self.Callback)
-                
-        def Draw(self, dc):
-            if self.Shown and self.Enabled:
-                dc.DrawBitmap(self.Bitmap, self.Position.x, self.Position.y, True)
-    
-    class DebugVariableViewer:
-        
-        def __init__(self, window, items=[]):
-            self.ParentWindow = window
-            self.Items = items
-            
-            self.Highlight = HIGHLIGHT_NONE
-            self.Buttons = []
-        
-        def __del__(self):
-            self.ParentWindow = None
-        
-        def GetIndex(self):
-            return self.ParentWindow.GetViewerIndex(self)
-        
-        def GetItem(self, variable):
-            for item in self.Items:
-                if item.GetVariable() == variable:
-                    return item
-            return None
-        
-        def GetItems(self):
-            return self.Items
-        
-        def GetVariables(self):
-            if len(self.Items) > 1:
-                variables = [item.GetVariable() for item in self.Items]
-                if self.GraphType == GRAPH_ORTHOGONAL:
-                    return tuple(variables)
-                return variables
-            return self.Items[0].GetVariable()
-        
-        def AddItem(self, item):
-            self.Items.append(item)
-            
-        def RemoveItem(self, item):
-            if item in self.Items:
-                self.Items.remove(item)
-            
-        def Clear(self):
-            for item in self.Items:
-                self.ParentWindow.RemoveDataConsumer(item)
-            self.Items = []
-            
-        def IsEmpty(self):
-            return len(self.Items) == 0
-        
-        def UnregisterObsoleteData(self):
-            for item in self.Items[:]:
-                iec_path = item.GetVariable().upper()
-                if self.ParentWindow.GetDataType(iec_path) is None:
-                    self.ParentWindow.RemoveDataConsumer(item)
-                    self.RemoveItem(item)
-                else:
-                    self.ParentWindow.AddDataConsumer(iec_path, item)
-                    item.RefreshVariableType()
-            
-        def ResetData(self):
-            for item in self.Items:
-                item.ResetData()
-        
-        def RefreshViewer(self):
-            pass
-        
-        def SetHighlight(self, highlight):
-            if self.Highlight != highlight:
-                self.Highlight = highlight
-                return True
-            return False
-        
-        def GetButtons(self):
-            return self.Buttons
-        
-        def HandleButtons(self, x, y):
-            for button in self.GetButtons():
-                if button.HitTest(x, y):
-                    button.ProcessCallback()
-                    return True
-            return False
-        
-        def IsOverButton(self, x, y):
-            for button in self.GetButtons():
-                if button.HitTest(x, y):
-                    return True
-            return False
-        
-        def ShowButtons(self, show):
-            for button in self.Buttons:
-                if show:
-                    button.Show()
-                else:
-                    button.Hide()
-            self.RefreshButtonsState()
-            self.ParentWindow.ForceRefresh()
-        
-        def RefreshButtonsState(self, refresh_positions=False):
-            if self:
-                width, height = self.GetSize()
-                if refresh_positions:
-                    offset = 0
-                    buttons = self.Buttons[:]
-                    buttons.reverse()
-                    for button in buttons:
-                        if button.IsShown() and button.IsEnabled():
-                            w, h = button.GetSize()
-                            button.SetPosition(width - 5 - w - offset, 5)
-                            offset += w + 2
-                    self.ParentWindow.ForceRefresh()
-        
-        def DrawCommonElements(self, dc, buttons=None):
-            width, height = self.GetSize()
-            
-            dc.SetPen(HIGHLIGHT_DROP_PEN)
-            dc.SetBrush(HIGHLIGHT_DROP_BRUSH)
-            if self.Highlight in [HIGHLIGHT_BEFORE]:
-                dc.DrawLine(0, 1, width - 1, 1)
-            elif self.Highlight in [HIGHLIGHT_AFTER]:
-                dc.DrawLine(0, height - 1, width - 1, height - 1)
-            
-            if buttons is None:
-                buttons = self.Buttons
-            for button in buttons:
-                button.Draw(dc)
-                
-            if self.ParentWindow.IsDragging():
-                destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
-                srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
-                if destBBox.width > 0 and destBBox.height > 0:
-                    srcPanel = self.ParentWindow.DraggingAxesPanel
-                    srcBBox = srcPanel.GetAxesBoundingBox()
-                    
-                    if destBBox.x == 0:
-                        srcX = srcBBox.x - srcPos.x
-                    else:
-                        srcX = srcBBox.x
-                    if destBBox.y == 0:
-                        srcY = srcBBox.y - srcPos.y
-                    else:
-                        srcY = srcBBox.y
-                    
-                    srcBmp = _convert_agg_to_wx_bitmap(srcPanel.get_renderer(), None)
-                    srcDC = wx.MemoryDC()
-                    srcDC.SelectObject(srcBmp)
-                    
-                    dc.Blit(destBBox.x, destBBox.y, 
-                            int(destBBox.width), int(destBBox.height), 
-                            srcDC, srcX, srcY)
-        
-        def OnEnter(self, event):
-            self.ShowButtons(True)
-            event.Skip()
-            
-        def OnLeave(self, event):
-            if self.Highlight != HIGHLIGHT_RESIZE or self.CanvasStartSize is None:
-                x, y = event.GetPosition()
-                width, height = self.GetSize()
-                if (x <= 0 or x >= width - 1 or
-                    y <= 0 or y >= height - 1):
-                    self.ShowButtons(False)
-            event.Skip()
-        
-        def OnCloseButton(self):
-            wx.CallAfter(self.ParentWindow.DeleteValue, self)
-        
-        def OnForceButton(self):
-            wx.CallAfter(self.ForceValue, self.Items[0])
-            
-        def OnReleaseButton(self):
-            wx.CallAfter(self.ReleaseValue, self.Items[0])
-        
-        def OnResizeWindow(self, event):
-            wx.CallAfter(self.RefreshButtonsState, True)
-            event.Skip()
-        
-        def OnMouseDragging(self, x, y):
-            xw, yw = self.GetPosition()
-            self.ParentWindow.RefreshHighlight(x + xw, y + yw)
-        
-        def OnDragging(self, x, y):
-            width, height = self.GetSize()
-            if y < height / 2:
-                if self.ParentWindow.IsViewerFirst(self):
-                    self.SetHighlight(HIGHLIGHT_BEFORE)
-                else:
-                    self.SetHighlight(HIGHLIGHT_NONE)
-                    self.ParentWindow.HighlightPreviousViewer(self)
-            else:
-                self.SetHighlight(HIGHLIGHT_AFTER)
-        
-        def OnResize(self, event):
-            wx.CallAfter(self.RefreshButtonsState, True)
-            event.Skip()
-        
-        def ForceValue(self, item):
-            iec_path = item.GetVariable().upper()
-            iec_type = self.ParentWindow.GetDataType(iec_path)
-            if iec_type is not None:
-                dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
-                if dialog.ShowModal() == wx.ID_OK:
-                    self.ParentWindow.ForceDataValue(iec_path, dialog.GetValue())
-        
-        def ReleaseValue(self, item):
-            iec_path = item.GetVariable().upper()
-            self.ParentWindow.ReleaseDataValue(iec_path)
-    
-    
-    class DebugVariableText(DebugVariableViewer, wx.Panel):
-        
-        def __init__(self, parent, window, items=[]):
-            DebugVariableViewer.__init__(self, window, items)
-            
-            wx.Panel.__init__(self, parent)
-            self.SetBackgroundColour(wx.WHITE)
-            self.SetDropTarget(DebugVariableDropTarget(window, self))
-            self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
-            self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
-            self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
-            self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
-            self.Bind(wx.EVT_SIZE, self.OnResize)
-            self.Bind(wx.EVT_PAINT, self.OnPaint)
-            
-            self.SetMinSize(wx.Size(0, 25))
-            
-            self.Buttons.append(
-                GraphButton(0, 0, GetBitmap("force"), self.OnForceButton))
-            self.Buttons.append(
-                GraphButton(0, 0, GetBitmap("release"), self.OnReleaseButton))
-            self.Buttons.append(
-                GraphButton(0, 0, GetBitmap("delete_graph"), self.OnCloseButton))
-            
-            self.ShowButtons(False)
-            
-        def RefreshViewer(self):
-            width, height = self.GetSize()
-            bitmap = wx.EmptyBitmap(width, height)
-            
-            dc = wx.BufferedDC(wx.ClientDC(self), bitmap)
-            dc.Clear()
-            dc.BeginDrawing()
-            
-            gc = wx.GCDC(dc)
-            
-            item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
-            w, h = gc.GetTextExtent(item_name)
-            gc.DrawText(item_name, 20, (height - h) / 2)
-            
-            if self.Items[0].IsForced():
-                gc.SetTextForeground(wx.BLUE)
-                self.Buttons[0].Disable()
-                self.Buttons[1].Enable()
-            else:
-                self.Buttons[1].Disable()
-                self.Buttons[0].Enable()
-            self.RefreshButtonsState(True)
-            
-            item_value = self.Items[0].GetValue()
-            w, h = gc.GetTextExtent(item_value)
-            gc.DrawText(item_value, width - 40 - w, (height - h) / 2)
-            
-            self.DrawCommonElements(gc)
-            
-            gc.EndDrawing()
-        
-        def OnLeftDown(self, event):
-            width, height = self.GetSize()
-            item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
-            w, h = self.GetTextExtent(item_name)
-            x, y = event.GetPosition()
-            rect = wx.Rect(20, (height - h) / 2, w, h)
-            if rect.InsideXY(x, y):
-                data = wx.TextDataObject(str((self.Items[0].GetVariable(), "debug", "move")))
-                dragSource = wx.DropSource(self)
-                dragSource.SetData(data)
-                dragSource.DoDragDrop()
-            else:
-                event.Skip()
-        
-        def OnLeftUp(self, event):
-            x, y = event.GetPosition()
-            wx.CallAfter(self.HandleButtons, x, y)
-            event.Skip()
-        
-        def OnPaint(self, event):
-            self.RefreshViewer()
-            event.Skip()
-    
-    class DebugVariableGraphic(DebugVariableViewer, FigureCanvas):
-        
-        def __init__(self, parent, window, items, graph_type):
-            DebugVariableViewer.__init__(self, window, items)
-            
-            self.CanvasSize = SIZE_MAXI
-            self.GraphType = graph_type
-            self.CursorTick = None
-            self.MouseStartPos = None
-            self.StartCursorTick = None
-            self.CanvasStartSize = None
-            self.ContextualButtons = []
-            self.ContextualButtonsItem = None
-            
-            self.Figure = matplotlib.figure.Figure(facecolor='w')
-            self.Figure.subplotpars.update(top=0.95, left=0.1, bottom=0.1, right=0.95)
-            
-            FigureCanvas.__init__(self, parent, -1, self.Figure)
-            self.SetBackgroundColour(wx.WHITE)
-            self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
-            self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
-            self.Bind(wx.EVT_SIZE, self.OnResize)
-            
-            self.SetMinSize(wx.Size(200, 200))
-            self.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self))
-            self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
-            self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
-            self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
-            self.mpl_connect('scroll_event', self.OnCanvasScroll)
-            
-            for size, bitmap in zip([SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
-                                    ["minimize_graph", "middle_graph", "maximize_graph"]):
-                self.Buttons.append(GraphButton(0, 0, GetBitmap(bitmap), self.GetOnChangeSizeButton(size)))
-            self.Buttons.append(
-                GraphButton(0, 0, GetBitmap("export_graph_mini"), self.OnExportGraphButton))
-            self.Buttons.append(
-                GraphButton(0, 0, GetBitmap("delete_graph"), self.OnCloseButton))
-            
-            self.ResetGraphics()
-            self.RefreshLabelsPosition(200)
-            self.ShowButtons(False)
-        
-        def draw(self, drawDC=None):
-            FigureCanvasAgg.draw(self)
-    
-            self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
-            self.bitmap.UseAlpha() 
-            width, height = self.GetSize()
-            bbox = self.GetAxesBoundingBox()
-            
-            destDC = wx.MemoryDC()
-            destDC.SelectObject(self.bitmap)
-            
-            destGC = wx.GCDC(destDC)
-            
-            destGC.BeginDrawing()
-            if self.Highlight == HIGHLIGHT_RESIZE:
-                destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
-                destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
-                destGC.DrawRectangle(0, height - 5, width, 5)
-            else:
-                destGC.SetPen(HIGHLIGHT_DROP_PEN)
-                destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
-                if self.Highlight == HIGHLIGHT_LEFT:
-                    destGC.DrawRectangle(bbox.x, bbox.y, 
-                                         bbox.width / 2, bbox.height)
-                elif self.Highlight == HIGHLIGHT_RIGHT:
-                    destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y, 
-                                         bbox.width / 2, bbox.height)
-            
-            self.DrawCommonElements(destGC, self.Buttons + self.ContextualButtons)
-            
-            destGC.EndDrawing()
-            
-            self._isDrawn = True
-            self.gui_repaint(drawDC=drawDC)
-        
-        def GetButtons(self):
-            return self.Buttons + self.ContextualButtons
-        
-        def PopupContextualButtons(self, item, rect, style=wx.RIGHT):
-            if self.ContextualButtonsItem is not None and item != self.ContextualButtonsItem:
-                self.DismissContextualButtons()
-            
-            if self.ContextualButtonsItem is None:
-                self.ContextualButtonsItem = item
-                
-                if self.ContextualButtonsItem.IsForced():
-                    self.ContextualButtons.append(
-                        GraphButton(0, 0, GetBitmap("release"), self.OnReleaseButton))
-                else:
-                    self.ContextualButtons.append(
-                        GraphButton(0, 0, GetBitmap("force"), self.OnForceButton))
-                self.ContextualButtons.append(
-                    GraphButton(0, 0, GetBitmap("export_graph_mini"), self.OnExportItemGraphButton))
-                self.ContextualButtons.append(
-                    GraphButton(0, 0, GetBitmap("delete_graph"), self.OnRemoveItemButton))
-                
-                offset = 0
-                buttons = self.ContextualButtons[:]
-                if style in [wx.TOP, wx.LEFT]:
-                     buttons.reverse()
-                for button in buttons:
-                    w, h = button.GetSize()
-                    if style in [wx.LEFT, wx.RIGHT]:
-                        if style == wx.LEFT:
-                            x = rect.x - w - offset
-                        else:
-                            x = rect.x + rect.width + offset
-                        y = rect.y + (rect.height - h) / 2
-                        offset += w
-                    else:
-                        x = rect.x + (rect.width - w ) / 2
-                        if style == wx.TOP:
-                            y = rect.y - h - offset
-                        else:
-                            y = rect.y + rect.height + offset
-                        offset += h
-                    button.SetPosition(x, y)
-                self.ParentWindow.ForceRefresh()
-        
-        def DismissContextualButtons(self):
-            if self.ContextualButtonsItem is not None:
-                self.ContextualButtonsItem = None
-                self.ContextualButtons = []
-                self.ParentWindow.ForceRefresh()
-        
-        def IsOverContextualButton(self, x, y):
-            for button in self.ContextualButtons:
-                if button.HitTest(x, y):
-                    return True
-            return False
-        
-        def SetMinSize(self, size):
-            wx.Window.SetMinSize(self, size)
-            wx.CallAfter(self.RefreshButtonsState)
-        
-        def GetOnChangeSizeButton(self, size):
-            def OnChangeSizeButton():
-                self.CanvasSize = size
-                self.SetCanvasSize(200, self.CanvasSize)
-            return OnChangeSizeButton
-        
-        def OnExportGraphButton(self):
-            self.ExportGraph()
-        
-        def OnForceButton(self):
-            wx.CallAfter(self.ForceValue, 
-                         self.ContextualButtonsItem)
-            self.DismissContextualButtons()
-            
-        def OnReleaseButton(self):
-            wx.CallAfter(self.ReleaseValue, 
-                         self.ContextualButtonsItem)
-            self.DismissContextualButtons()
-        
-        def OnExportItemGraphButton(self):
-            wx.CallAfter(self.ExportGraph, 
-                         self.ContextualButtonsItem)
-            self.DismissContextualButtons()
-            
-        def OnRemoveItemButton(self):            
-            wx.CallAfter(self.ParentWindow.DeleteValue, self, 
-                         self.ContextualButtonsItem)
-            self.DismissContextualButtons()
-        
-        def RefreshButtonsState(self, refresh_positions=False):
-            if self:
-                width, height = self.GetSize()
-                min_width, min_height = self.GetCanvasMinSize()
-                for button, size in zip(self.Buttons, 
-                                        [min_height, SIZE_MIDDLE, SIZE_MAXI]):
-                    if size == height and button.IsEnabled():
-                        button.Disable()
-                        refresh_positions = True
-                    elif not button.IsEnabled():
-                        button.Enable()
-                        refresh_positions = True
-                if refresh_positions:
-                    offset = 0
-                    buttons = self.Buttons[:]
-                    buttons.reverse()
-                    for button in buttons:
-                        if button.IsShown() and button.IsEnabled():
-                            w, h = button.GetSize()
-                            button.SetPosition(width - 5 - w - offset, 5)
-                            offset += w + 2
-                    self.ParentWindow.ForceRefresh()
-        
-        def RefreshLabelsPosition(self, height):
-            canvas_ratio = 1. / height
-            graph_ratio = 1. / ((1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) * height)
-            
-            self.Figure.subplotpars.update(
-                top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
-                bottom= CANVAS_BORDER[0] * canvas_ratio)
-            
-            if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
-                num_item = len(self.Items)
-                for idx in xrange(num_item):
-                    if not self.Is3DCanvas():
-                        self.AxesLabels[idx].set_position(
-                            (0.05, 
-                             1.0 - (CANVAS_PADDING + AXES_LABEL_HEIGHT * idx) * graph_ratio))
-                    self.Labels[idx].set_position(
-                        (0.95, 
-                         CANVAS_PADDING * graph_ratio + 
-                         (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
-            else:
-                self.AxesLabels[0].set_position((0.1, CANVAS_PADDING * graph_ratio))
-                self.Labels[0].set_position((0.95, CANVAS_PADDING * graph_ratio))
-                self.AxesLabels[1].set_position((0.05, 2 * CANVAS_PADDING * graph_ratio))
-                self.Labels[1].set_position((0.05, 1.0 - CANVAS_PADDING * graph_ratio))
-        
-            self.Figure.subplots_adjust()
-        
-        def GetCanvasMinSize(self):
-            return wx.Size(200, 
-                           CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
-                           2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
-        
-        def SetCanvasSize(self, width, height):
-            height = max(height, self.GetCanvasMinSize()[1])
-            self.SetMinSize(wx.Size(width, height))
-            self.RefreshLabelsPosition(height)
-            self.ParentWindow.RefreshGraphicsSizer()
-            
-        def GetAxesBoundingBox(self, absolute=False):
-            width, height = self.GetSize()
-            ax, ay, aw, ah = self.figure.gca().get_position().bounds
-            bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
-                           aw * width + 2, ah * height + 1)
-            if absolute:
-                xw, yw = self.GetPosition()
-                bbox.x += xw
-                bbox.y += yw
-            return bbox
-        
-        def OnCanvasButtonPressed(self, event):
-            width, height = self.GetSize()
-            x, y = event.x, height - event.y
-            if not self.IsOverButton(x, y):
-                if event.inaxes == self.Axes:
-                    item_idx = None
-                    for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
-                                 [pair for pair in enumerate(self.Labels)]):
-                        (x0, y0), (x1, y1) = t.get_window_extent().get_points()
-                        rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
-                        if rect.InsideXY(x, y):
-                            item_idx = i
-                            break
-                    if item_idx is not None:
-                        self.ShowButtons(False)
-                        self.DismissContextualButtons()
-                        xw, yw = self.GetPosition()
-                        self.ParentWindow.StartDragNDrop(self, 
-                            self.Items[item_idx], x + xw, y + yw, x + xw, y + yw)
-                    elif not self.Is3DCanvas():
-                        self.MouseStartPos = wx.Point(x, y)
-                        if event.button == 1 and event.inaxes == self.Axes:
-                            self.StartCursorTick = self.CursorTick
-                            self.HandleCursorMove(event)
-                        elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
-                            width, height = self.GetSize()
-                            start_tick, end_tick = self.ParentWindow.GetRange()
-                            self.StartCursorTick = start_tick
-                
-                elif event.button == 1 and event.y <= 5:
-                    self.MouseStartPos = wx.Point(x, y)
-                    self.CanvasStartSize = self.GetSize()
-        
-        def OnCanvasButtonReleased(self, event):
-            if self.ParentWindow.IsDragging():
-                width, height = self.GetSize()
-                xw, yw = self.GetPosition()
-                self.ParentWindow.StopDragNDrop(
-                    self.ParentWindow.DraggingAxesPanel.Items[0].GetVariable(),
-                    xw + event.x, 
-                    yw + height - event.y)
-            else:
-                self.MouseStartPos = None
-                self.StartCursorTick = None
-                self.CanvasStartSize = None
-                width, height = self.GetSize()
-                self.HandleButtons(event.x, height - event.y)
-                if event.y > 5 and self.SetHighlight(HIGHLIGHT_NONE):
-                    self.SetCursor(wx.NullCursor)
-                    self.ParentWindow.ForceRefresh()
-        
-        def OnCanvasMotion(self, event):
-            width, height = self.GetSize()
-            if self.ParentWindow.IsDragging():
-                xw, yw = self.GetPosition()
-                self.ParentWindow.MoveDragNDrop(
-                    xw + event.x, 
-                    yw + height - event.y)
-            else:
-                if not self.Is3DCanvas():
-                    if event.button == 1 and self.CanvasStartSize is None:
-                        if event.inaxes == self.Axes:
-                            if self.MouseStartPos is not None:
-                                self.HandleCursorMove(event)
-                        elif self.MouseStartPos is not None and len(self.Items) == 1:
-                            xw, yw = self.GetPosition()
-                            self.ParentWindow.SetCursorTick(self.StartCursorTick)
-                            self.ParentWindow.StartDragNDrop(self, 
-                                self.Items[0],
-                                event.x + xw, height - event.y + yw, 
-                                self.MouseStartPos.x + xw, self.MouseStartPos.y + yw)
-                    elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
-                        start_tick, end_tick = self.ParentWindow.GetRange()
-                        rect = self.GetAxesBoundingBox()
-                        self.ParentWindow.SetCanvasPosition(
-                            self.StartCursorTick + (self.MouseStartPos.x - event.x) *
-                            (end_tick - start_tick) / rect.width)    
-                
-                if event.button == 1 and self.CanvasStartSize is not None:
-                    width, height = self.GetSize()
-                    self.SetCanvasSize(width, 
-                        self.CanvasStartSize.height + height - event.y - self.MouseStartPos.y)
-                    
-                elif event.button in [None, "up", "down"]:
-                    if self.GraphType == GRAPH_PARALLEL:
-                        orientation = [wx.RIGHT] * len(self.AxesLabels) + [wx.LEFT] * len(self.Labels)
-                    elif len(self.AxesLabels) > 0:
-                        orientation = [wx.RIGHT, wx.TOP, wx.LEFT, wx.BOTTOM]
-                    else:
-                        orientation = [wx.LEFT] * len(self.Labels)
-                    item_idx = None
-                    item_style = None
-                    for (i, t), style in zip([pair for pair in enumerate(self.AxesLabels)] + 
-                                             [pair for pair in enumerate(self.Labels)], 
-                                             orientation):
-                        (x0, y0), (x1, y1) = t.get_window_extent().get_points()
-                        rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
-                        if rect.InsideXY(event.x, height - event.y):
-                            item_idx = i
-                            item_style = style
-                            break
-                    if item_idx is not None:
-                        self.PopupContextualButtons(self.Items[item_idx], rect, item_style)
-                        return 
-                    if not self.IsOverContextualButton(event.x, height - event.y):
-                        self.DismissContextualButtons()
-                    
-                    if event.y <= 5:
-                        if self.SetHighlight(HIGHLIGHT_RESIZE):
-                            self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
-                            self.ParentWindow.ForceRefresh()
-                    else:
-                        if self.SetHighlight(HIGHLIGHT_NONE):
-                            self.SetCursor(wx.NullCursor)
-                            self.ParentWindow.ForceRefresh()
-        
-        def OnCanvasScroll(self, event):
-            if event.inaxes is not None:
-                if self.GraphType == GRAPH_ORTHOGONAL:
-                    start_tick, end_tick = self.ParentWindow.GetRange()
-                    tick = (start_tick + end_tick) / 2.
-                else:
-                    tick = event.xdata
-                self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
-        
-        def OnDragging(self, x, y):
-            width, height = self.GetSize()
-            bbox = self.GetAxesBoundingBox()
-            if bbox.InsideXY(x, y) and not self.Is3DCanvas():
-                rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
-                if rect.InsideXY(x, y):
-                    self.SetHighlight(HIGHLIGHT_LEFT)
-                else:
-                    self.SetHighlight(HIGHLIGHT_RIGHT)
-            elif y < height / 2:
-                if self.ParentWindow.IsViewerFirst(self):
-                    self.SetHighlight(HIGHLIGHT_BEFORE)
-                else:
-                    self.SetHighlight(HIGHLIGHT_NONE)
-                    self.ParentWindow.HighlightPreviousViewer(self)
-            else:
-                self.SetHighlight(HIGHLIGHT_AFTER)
-        
-        def OnLeave(self, event):
-            if self.CanvasStartSize is None and self.SetHighlight(HIGHLIGHT_NONE):
-                self.SetCursor(wx.NullCursor)
-                self.ParentWindow.ForceRefresh()
-            DebugVariableViewer.OnLeave(self, event)
-        
-        def HandleCursorMove(self, event):
-            start_tick, end_tick = self.ParentWindow.GetRange()
-            cursor_tick = None
-            if self.GraphType == GRAPH_ORTHOGONAL:
-                x_data = self.Items[0].GetData(start_tick, end_tick)
-                y_data = self.Items[1].GetData(start_tick, end_tick)
-                if len(x_data) > 0 and len(y_data) > 0:
-                    length = min(len(x_data), len(y_data))
-                    d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
-                    cursor_tick = x_data[numpy.argmin(d), 0]
-            else:
-                data = self.Items[0].GetData(start_tick, end_tick)
-                if len(data) > 0:
-                    cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0]
-            if cursor_tick is not None:
-                self.ParentWindow.SetCursorTick(cursor_tick)
-        
-        def OnAxesMotion(self, event):
-            if self.Is3DCanvas():
-                current_time = gettime()
-                if current_time - self.LastMotionTime > REFRESH_PERIOD:
-                    self.LastMotionTime = current_time
-                    Axes3D._on_move(self.Axes, event)
-        
-        def ResetGraphics(self):
-            self.Figure.clear()
-            if self.Is3DCanvas():
-                self.Axes = self.Figure.gca(projection='3d')
-                self.Axes.set_color_cycle(['b'])
-                self.LastMotionTime = gettime()
-                setattr(self.Axes, "_on_move", self.OnAxesMotion)
-                self.Axes.mouse_init()
-                self.Axes.tick_params(axis='z', labelsize='small')
-            else:
-                self.Axes = self.Figure.gca()
-                self.Axes.set_color_cycle(color_cycle)
-            self.Axes.tick_params(axis='x', labelsize='small')
-            self.Axes.tick_params(axis='y', labelsize='small')
-            self.Plots = []
-            self.VLine = None
-            self.HLine = None
-            self.Labels = []
-            self.AxesLabels = []
-            if not self.Is3DCanvas():
-                text_func = self.Axes.text
-            else:
-                text_func = self.Axes.text2D
-            if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
-                num_item = len(self.Items)
-                for idx in xrange(num_item):
-                    if num_item == 1:
-                        color = 'k'
-                    else:
-                        color = color_cycle[idx % len(color_cycle)]
-                    if not self.Is3DCanvas():
-                        self.AxesLabels.append(
-                            text_func(0, 0, "", size='small',
-                                      verticalalignment='top', 
-                                      color=color,
-                                      transform=self.Axes.transAxes))
-                    self.Labels.append(
-                        text_func(0, 0, "", size='large', 
-                                  horizontalalignment='right',
-                                  color=color,
-                                  transform=self.Axes.transAxes))
-            else:
-                self.AxesLabels.append(
-                    self.Axes.text(0, 0, "", size='small',
-                                   transform=self.Axes.transAxes))
-                self.Labels.append(
-                    self.Axes.text(0, 0, "", size='large',
-                                   horizontalalignment='right',
-                                   transform=self.Axes.transAxes))
-                self.AxesLabels.append(
-                    self.Axes.text(0, 0, "", size='small',
-                                   rotation='vertical',
-                                   verticalalignment='bottom',
-                                   transform=self.Axes.transAxes))
-                self.Labels.append(
-                    self.Axes.text(0, 0, "", size='large',
-                                   rotation='vertical',
-                                   verticalalignment='top',
-                                   transform=self.Axes.transAxes))
-            width, height = self.GetSize()
-            self.RefreshLabelsPosition(height)
-            
-        def AddItem(self, item):
-            DebugVariableViewer.AddItem(self, item)
-            self.ResetGraphics()
-            
-        def RemoveItem(self, item):
-            DebugVariableViewer.RemoveItem(self, item)
-            if not self.IsEmpty():
-                if len(self.Items) == 1:
-                    self.GraphType = GRAPH_PARALLEL
-                self.ResetGraphics()
-        
-        def UnregisterObsoleteData(self):
-            DebugVariableViewer.UnregisterObsoleteData(self)
-            if not self.IsEmpty():
-                self.ResetGraphics()
-        
-        def Is3DCanvas(self):
-            return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
-        
-        def SetCursorTick(self, cursor_tick):
-            self.CursorTick = cursor_tick
-            
-        def RefreshViewer(self, refresh_graphics=True):
-            
-            if refresh_graphics:
-                start_tick, end_tick = self.ParentWindow.GetRange()
-                
-                if self.GraphType == GRAPH_PARALLEL:    
-                    min_value = max_value = None
-                    
-                    for idx, item in enumerate(self.Items):
-                        data = item.GetData(start_tick, end_tick)
-                        if data is not None:
-                            item_min_value, item_max_value = item.GetRange()
-                            if min_value is None:
-                                min_value = item_min_value
-                            elif item_min_value is not None:
-                                min_value = min(min_value, item_min_value)
-                            if max_value is None:
-                                max_value = item_max_value
-                            elif item_max_value is not None:
-                                max_value = max(max_value, item_max_value)
-                            
-                            if len(self.Plots) <= idx:
-                                self.Plots.append(
-                                    self.Axes.plot(data[:, 0], data[:, 1])[0])
-                            else:
-                                self.Plots[idx].set_data(data[:, 0], data[:, 1])
-                        
-                    if min_value is not None and max_value is not None:
-                        y_center = (min_value + max_value) / 2.
-                        y_range = max(1.0, max_value - min_value)
-                    else:
-                        y_center = 0.5
-                        y_range = 1.0
-                    x_min, x_max = start_tick, end_tick
-                    y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
-                    
-                    if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
-                        if self.VLine is None:
-                            self.VLine = self.Axes.axvline(self.CursorTick, color=cursor_color)
-                        else:
-                            self.VLine.set_xdata((self.CursorTick, self.CursorTick))
-                        self.VLine.set_visible(True)
-                    else:
-                        if self.VLine is not None:
-                            self.VLine.set_visible(False)
-                else:
-                    min_start_tick = reduce(max, [item.GetData()[0, 0] 
-                                                  for item in self.Items
-                                                  if len(item.GetData()) > 0], 0)
-                    start_tick = max(start_tick, min_start_tick)
-                    end_tick = max(end_tick, min_start_tick)
-                    x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick)
-                    y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick)
-                    if self.CursorTick is not None:
-                        x_cursor, x_forced = self.Items[0].GetValue(self.CursorTick, raw=True)
-                        y_cursor, y_forced = self.Items[1].GetValue(self.CursorTick, raw=True)
-                    length = 0
-                    if x_data is not None and y_data is not None:  
-                        length = min(len(x_data), len(y_data))
-                    if len(self.Items) < 3:
-                        if x_data is not None and y_data is not None:
-                            if len(self.Plots) == 0:
-                                self.Plots.append(
-                                    self.Axes.plot(x_data[:, 1][:length], 
-                                                   y_data[:, 1][:length])[0])
-                            else:
-                                self.Plots[0].set_data(
-                                    x_data[:, 1][:length], 
-                                    y_data[:, 1][:length])
-                        
-                        if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
-                            if self.VLine is None:
-                                self.VLine = self.Axes.axvline(x_cursor, color=cursor_color)
-                            else:
-                                self.VLine.set_xdata((x_cursor, x_cursor))
-                            if self.HLine is None:
-                                self.HLine = self.Axes.axhline(y_cursor, color=cursor_color)
-                            else:
-                                self.HLine.set_ydata((y_cursor, y_cursor))
-                            self.VLine.set_visible(True)
-                            self.HLine.set_visible(True)
-                        else:
-                            if self.VLine is not None:
-                                self.VLine.set_visible(False)
-                            if self.HLine is not None:
-                                self.HLine.set_visible(False)
-                    else:
-                        while len(self.Axes.lines) > 0:
-                            self.Axes.lines.pop()
-                        z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick)
-                        if self.CursorTick is not None:
-                            z_cursor, z_forced = self.Items[2].GetValue(self.CursorTick, raw=True)
-                        if x_data is not None and y_data is not None and z_data is not None:
-                            length = min(length, len(z_data))
-                            self.Axes.plot(x_data[:, 1][:length],
-                                           y_data[:, 1][:length],
-                                           zs = z_data[:, 1][:length])
-                        self.Axes.set_zlim(z_min, z_max)
-                        if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
-                            for kwargs in [{"xs": numpy.array([x_min, x_max])},
-                                           {"ys": numpy.array([y_min, y_max])},
-                                           {"zs": numpy.array([z_min, z_max])}]:
-                                for param, value in [("xs", numpy.array([x_cursor, x_cursor])),
-                                                     ("ys", numpy.array([y_cursor, y_cursor])),
-                                                     ("zs", numpy.array([z_cursor, z_cursor]))]:
-                                    kwargs.setdefault(param, value)
-                                kwargs["color"] = cursor_color
-                                self.Axes.plot(**kwargs)
-                    
-                self.Axes.set_xlim(x_min, x_max)
-                self.Axes.set_ylim(y_min, y_max)
-            
-            variable_name_mask = self.ParentWindow.GetVariableNameMask()
-            if self.CursorTick is not None:
-                values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items])
-            else:
-                values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items])
-            labels = [item.GetVariable(variable_name_mask) for item in self.Items]
-            styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
-            if self.Is3DCanvas():
-                for idx, label_func in enumerate([self.Axes.set_xlabel, 
-                                                  self.Axes.set_ylabel,
-                                                  self.Axes.set_zlabel]):
-                    label_func(labels[idx], fontdict={'size': 'small','color': color_cycle[idx]})
-            else:
-                for label, text in zip(self.AxesLabels, labels):
-                    label.set_text(text)
-            for label, value, style in zip(self.Labels, values, styles):
-                label.set_text(value)
-                label.set_style(style)
-            
-            self.draw()
-    
-        def ExportGraph(self, item=None):
-            if item is not None:
-                variables = [(item, [entry for entry in item.GetData()])]
-            else:
-                variables = [(item, [entry for entry in item.GetData()])
-                             for item in self.Items]
-            self.ParentWindow.CopyDataToClipboard(variables)
-    
-class DebugVariablePanel(wx.Panel, DebugViewer):
-    
-    def __init__(self, parent, producer, window):
-        wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
-        self.SetBackgroundColour(wx.WHITE)
-        
-        self.ParentWindow = window
-        
-        DebugViewer.__init__(self, producer, True)
-        
-        self.HasNewData = False
-        
-        if USE_MPL:
-            main_sizer = wx.BoxSizer(wx.VERTICAL)
-            
-            self.Ticks = numpy.array([])
-            self.RangeValues = None
-            self.StartTick = 0
-            self.Fixed = False
-            self.Force = False
-            self.CursorTick = None
-            self.DraggingAxesPanel = None
-            self.DraggingAxesBoundingBox = None
-            self.DraggingAxesMousePos = None
-            self.VariableNameMask = []
-            
-            self.GraphicPanels = []
-            
-            graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
-            main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
-            
-            range_label = wx.StaticText(self, label=_('Range:'))
-            graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
-            
-            self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
-            self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
-            graphics_button_sizer.AddWindow(self.CanvasRange, 1, 
-                  border=5, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
-            
-            for name, bitmap, help in [
-                ("CurrentButton", "current", _("Go to current value")),
-                ("ExportGraphButton", "export_graph", _("Export graph values to clipboard"))]:
-                button = wx.lib.buttons.GenBitmapButton(self, 
-                      bitmap=GetBitmap(bitmap), 
-                      size=wx.Size(28, 28), style=wx.NO_BORDER)
-                button.SetToolTipString(help)
-                setattr(self, name, button)
-                self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
-                graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
-            
-            self.CanvasPosition = wx.ScrollBar(self, 
-                  size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
-            self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK, 
-                  self.OnPositionChanging, self.CanvasPosition)
-            self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP, 
-                  self.OnPositionChanging, self.CanvasPosition)
-            self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN, 
-                  self.OnPositionChanging, self.CanvasPosition)
-            self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP, 
-                  self.OnPositionChanging, self.CanvasPosition)
-            self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN, 
-                  self.OnPositionChanging, self.CanvasPosition)
-            main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
-            
-            self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
-            main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL|wx.GROW)
-            
-            self.TickLabel = wx.StaticText(self)
-            self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
-            
-            self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY|wx.TE_CENTER|wx.NO_BORDER)
-            self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT|wx.GROW)
-            
-            self.TickTimeLabel = wx.StaticText(self)
-            self.TickSizer.AddWindow(self.TickTimeLabel)
-            
-            self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL|wx.VSCROLL)
-            self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
-            self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self))
-            self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
-            main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
-            
-            self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
-            self.GraphicsWindow.SetSizer(self.GraphicsSizer)
-            
-            self.RefreshCanvasRange()
-            
-        else:
-            main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
-            main_sizer.AddGrowableCol(0)
-            main_sizer.AddGrowableRow(1)
-            
-            button_sizer = wx.BoxSizer(wx.HORIZONTAL)
-            main_sizer.AddSizer(button_sizer, border=5, 
-                  flag=wx.ALIGN_RIGHT|wx.ALL)
-            
-            for name, bitmap, help in [
-                    ("DeleteButton", "remove_element", _("Remove debug variable")),
-                    ("UpButton", "up", _("Move debug variable up")),
-                    ("DownButton", "down", _("Move debug variable down"))]:
-                button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), 
-                      size=wx.Size(28, 28), style=wx.NO_BORDER)
-                button.SetToolTipString(help)
-                setattr(self, name, button)
-                button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
-            
-            self.VariablesGrid = CustomGrid(self, size=wx.Size(-1, 150), style=wx.VSCROLL)
-            self.VariablesGrid.SetDropTarget(DebugVariableDropTarget(self, self.VariablesGrid))
-            self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, 
-                  self.OnVariablesGridCellRightClick)
-            self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
-                  self.OnVariablesGridCellLeftClick)
-            main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
-        
-            self.Table = DebugVariableTable(self, [], GetDebugVariablesTableColnames())
-            self.VariablesGrid.SetTable(self.Table)
-            self.VariablesGrid.SetButtons({"Delete": self.DeleteButton,
-                                           "Up": self.UpButton,
-                                           "Down": self.DownButton})
-        
-            def _AddVariable(new_row):
-                return self.VariablesGrid.GetGridCursorRow()
-            setattr(self.VariablesGrid, "_AddRow", _AddVariable)
-        
-            def _DeleteVariable(row):
-                item = self.Table.GetItem(row)
-                self.RemoveDataConsumer(item)
-                self.Table.RemoveItem(row)
-                self.RefreshView()
-            setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
-            
-            def _MoveVariable(row, move):
-                new_row = max(0, min(row + move, self.Table.GetNumberRows() - 1))
-                if new_row != row:
-                    self.Table.MoveItem(row, new_row)
-                    self.RefreshView()
-                return new_row
-            setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
-            
-            self.VariablesGrid.SetRowLabelSize(0)
-            
-            self.GridColSizes = [200, 100]
-            
-            for col in range(self.Table.GetNumberCols()):
-                attr = wx.grid.GridCellAttr()
-                attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
-                self.VariablesGrid.SetColAttr(col, attr)
-                self.VariablesGrid.SetColSize(col, self.GridColSizes[col])
-            
-            self.Table.ResetView(self.VariablesGrid)
-            self.VariablesGrid.RefreshButtons()
-        
-        self.SetSizer(main_sizer)
-        
-    def SetDataProducer(self, producer):
-        DebugViewer.SetDataProducer(self, producer)
-        
-        if USE_MPL:
-            if self.DataProducer is not None:
-                self.Ticktime = self.DataProducer.GetTicktime()
-                self.RefreshCanvasRange()
-            else:
-                self.Ticktime = 0
-    
-    def RefreshNewData(self, *args, **kwargs):
-        if self.HasNewData or self.Force:
-            self.HasNewData = False
-            self.RefreshView(only_values=True)
-        DebugViewer.RefreshNewData(self, *args, **kwargs)
-    
-    def NewDataAvailable(self, tick, *args, **kwargs):
-        if USE_MPL and tick is not None:
-            if len(self.Ticks) == 0:
-                self.StartTick = tick 
-            self.Ticks = numpy.append(self.Ticks, [tick])
-            if not self.Fixed or tick < self.StartTick + self.CurrentRange:
-                self.StartTick = max(self.StartTick, tick - self.CurrentRange)
-            if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
-                self.Force = True
-        DebugViewer.NewDataAvailable(self, tick, *args, **kwargs)
-    
-    def ForceRefresh(self):
-        self.Force = True
-        wx.CallAfter(self.NewDataAvailable, None, True)
-    
-    def RefreshGraphicsSizer(self):
-        self.GraphicsSizer.Clear()
-        
-        for panel in self.GraphicPanels:
-            self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
-            
-        self.GraphicsSizer.Layout()
-        self.RefreshGraphicsWindowScrollbars()
-    
-    def SetCanvasPosition(self, tick):
-        tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
-        self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
-        self.Fixed = True
-        self.RefreshCanvasPosition()
-        self.ForceRefresh()
-    
-    def SetCursorTick(self, cursor_tick):
-        self.CursorTick = cursor_tick
-        self.Fixed = True
-        self.ResetCursorTick() 
-    
-    def ResetCursorTick(self):
-        self.CursorTick = None
-        self.ResetCursorTick()
-    
-    def ResetCursorTick(self):
-        for panel in self.GraphicPanels:
-            if isinstance(panel, DebugVariableGraphic):
-                panel.SetCursorTick(self.CursorTick)
-        self.ForceRefresh()
-    
-    def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
-        if len(panel.GetItems()) > 1:
-            self.DraggingAxesPanel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
-            self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
-            width, height = panel.GetSize()
-            self.DraggingAxesPanel.SetSize(wx.Size(width, height))
-            self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
-        else:
-            self.DraggingAxesPanel = panel
-        self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(absolute=True)
-        self.DraggingAxesMousePos = wx.Point(
-            x_mouse_start - self.DraggingAxesBoundingBox.x, 
-            y_mouse_start - self.DraggingAxesBoundingBox.y)
-        self.MoveDragNDrop(x_mouse, y_mouse)
-        
-    def MoveDragNDrop(self, x_mouse, y_mouse):
-        self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
-        self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
-        self.RefreshHighlight(x_mouse, y_mouse)
-    
-    def RefreshHighlight(self, x_mouse, y_mouse):
-        for idx, panel in enumerate(self.GraphicPanels):
-            x, y = panel.GetPosition()
-            width, height = panel.GetSize()
-            rect = wx.Rect(x, y, width, height)
-            if (rect.InsideXY(x_mouse, y_mouse) or 
-                idx == 0 and y_mouse < 0 or
-                idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
-                panel.OnDragging(x_mouse - x, y_mouse - y)
-            else:
-                panel.SetHighlight(HIGHLIGHT_NONE)
-        if wx.Platform == "__WXMSW__":
-            self.RefreshView()
-        else:
-            self.ForceRefresh()
-    
-    def ResetHighlight(self):
-        for panel in self.GraphicPanels:
-            panel.SetHighlight(HIGHLIGHT_NONE)
-        if wx.Platform == "__WXMSW__":
-            self.RefreshView()
-        else:
-            self.ForceRefresh()
-    
-    def IsDragging(self):
-        return self.DraggingAxesPanel is not None
-    
-    def GetDraggingAxesClippingRegion(self, panel):
-        x, y = panel.GetPosition()
-        width, height = panel.GetSize()
-        bbox = wx.Rect(x, y, width, height)
-        bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
-        bbox.x -= x
-        bbox.y -= y
-        return bbox
-    
-    def GetDraggingAxesPosition(self, panel):
-        x, y = panel.GetPosition()
-        return wx.Point(self.DraggingAxesBoundingBox.x - x,
-                        self.DraggingAxesBoundingBox.y - y)
-    
-    def StopDragNDrop(self, variable, x_mouse, y_mouse):
-        if self.DraggingAxesPanel not in self.GraphicPanels:
-            self.DraggingAxesPanel.Destroy()
-        self.DraggingAxesPanel = None
-        self.DraggingAxesBoundingBox = None
-        self.DraggingAxesMousePos = None
-        for idx, panel in enumerate(self.GraphicPanels):
-            panel.SetHighlight(HIGHLIGHT_NONE)
-            xw, yw = panel.GetPosition()
-            width, height = panel.GetSize()
-            bbox = wx.Rect(xw, yw, width, height)
-            if bbox.InsideXY(x_mouse, y_mouse):
-                panel.ShowButtons(True)
-                merge_type = GRAPH_PARALLEL
-                if isinstance(panel, DebugVariableText) or panel.Is3DCanvas():
-                    if y_mouse > yw + height / 2:
-                        idx += 1
-                    wx.CallAfter(self.MoveValue, variable, idx)
-                else:
-                    rect = panel.GetAxesBoundingBox(True)
-                    if rect.InsideXY(x_mouse, y_mouse):
-                        merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
-                        if merge_rect.InsideXY(x_mouse, y_mouse):
-                            merge_type = GRAPH_ORTHOGONAL
-                        wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
-                    else:
-                        if y_mouse > yw + height / 2:
-                            idx += 1
-                        wx.CallAfter(self.MoveValue, variable, idx)
-                self.ForceRefresh()
-                return 
-        width, height = self.GraphicsWindow.GetVirtualSize()
-        rect = wx.Rect(0, 0, width, height)
-        if rect.InsideXY(x_mouse, y_mouse):
-            wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels))
-        self.ForceRefresh()
-    
-    def RefreshView(self, only_values=False):
-        if USE_MPL:
-            self.RefreshCanvasPosition()
-            
-            width, height = self.GraphicsWindow.GetVirtualSize()
-            bitmap = wx.EmptyBitmap(width, height)
-            dc = wx.BufferedDC(wx.ClientDC(self.GraphicsWindow), bitmap)
-            dc.Clear()
-            dc.BeginDrawing()
-            if self.DraggingAxesPanel is not None:
-                destBBox = self.DraggingAxesBoundingBox
-                srcBBox = self.DraggingAxesPanel.GetAxesBoundingBox()
-                
-                srcBmp = _convert_agg_to_wx_bitmap(self.DraggingAxesPanel.get_renderer(), None)
-                srcDC = wx.MemoryDC()
-                srcDC.SelectObject(srcBmp)
-                    
-                dc.Blit(destBBox.x, destBBox.y, 
-                        int(destBBox.width), int(destBBox.height), 
-                        srcDC, srcBBox.x, srcBBox.y)
-            dc.EndDrawing()
-            
-            if not self.Fixed or self.Force:
-                self.Force = False
-                refresh_graphics = True
-            else:
-                refresh_graphics = False
-            
-            if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
-                self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
-            for panel in self.GraphicPanels:
-                if isinstance(panel, DebugVariableGraphic):
-                    panel.RefreshViewer(refresh_graphics)
-                else:
-                    panel.RefreshViewer()
-            
-            if self.CursorTick is not None:
-                tick = self.CursorTick
-            elif len(self.Ticks) > 0:
-                tick = self.Ticks[-1]
-            else:
-                tick = None
-            if tick is not None:
-                self.TickLabel.SetLabel("Tick: %d" % tick)
-                if self.Ticktime > 0:
-                    tick_duration = int(tick * self.Ticktime)
-                    not_null = False
-                    duration = ""
-                    for value, format in [(tick_duration / DAY, "%dd"),
-                                          ((tick_duration % DAY) / HOUR, "%dh"),
-                                          ((tick_duration % HOUR) / MINUTE, "%dm"),
-                                          ((tick_duration % MINUTE) / SECOND, "%ds")]:
-                        
-                        if value > 0 or not_null:
-                            duration += format % value
-                            not_null = True
-                    
-                    duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND) 
-                    self.TickTimeLabel.SetLabel("t: %s" % duration)
-                else:
-                    self.TickTimeLabel.SetLabel("")
-            else:
-                self.TickLabel.SetLabel("")
-                self.TickTimeLabel.SetLabel("")
-            self.TickSizer.Layout()
-        else:
-            self.Freeze()
-            
-            if only_values:
-                for col in xrange(self.Table.GetNumberCols()):
-                    if self.Table.GetColLabelValue(col, False) == "Value":
-                        for row in xrange(self.Table.GetNumberRows()):
-                            self.VariablesGrid.SetCellValue(row, col, str(self.Table.GetValueByName(row, "Value")))
-            else:
-                self.Table.ResetView(self.VariablesGrid)
-            self.VariablesGrid.RefreshButtons()
-            
-            self.Thaw()
-        
-    def UnregisterObsoleteData(self):
-        if USE_MPL:
-            if self.DataProducer is not None:
-                self.Ticktime = self.DataProducer.GetTicktime()
-                self.RefreshCanvasRange()
-            
-            for panel in self.GraphicPanels:
-                panel.UnregisterObsoleteData()
-                if panel.IsEmpty():
-                    if panel.HasCapture():
-                        panel.ReleaseMouse()
-                    self.GraphicPanels.remove(panel)
-                    panel.Destroy()
-            
-            self.ResetVariableNameMask()
-            self.RefreshGraphicsSizer()
-            self.ForceRefresh()
-            
-        else:
-            items = [(idx, item) for idx, item in enumerate(self.Table.GetData())]
-            items.reverse()
-            for idx, item in items:
-                iec_path = item.GetVariable().upper()
-                if self.GetDataType(iec_path) is None:
-                    self.RemoveDataConsumer(item)
-                    self.Table.RemoveItem(idx)
-                else:
-                    self.AddDataConsumer(iec_path, item)
-                    item.RefreshVariableType()
-            self.Freeze()
-            self.Table.ResetView(self.VariablesGrid)
-            self.VariablesGrid.RefreshButtons()
-            self.Thaw()
-    
-    def ResetView(self):
-        self.DeleteDataConsumers()
-        if USE_MPL:
-            self.Fixed = False
-            for panel in self.GraphicPanels:
-                panel.Destroy()
-            self.GraphicPanels = []
-            self.ResetVariableNameMask()
-            self.RefreshGraphicsSizer()
-        else:
-            self.Table.Empty()
-            self.Freeze()
-            self.Table.ResetView(self.VariablesGrid)
-            self.VariablesGrid.RefreshButtons()
-            self.Thaw()
-    
-    def RefreshCanvasRange(self):
-        if self.Ticktime == 0 and self.RangeValues != RANGE_VALUES:
-            self.RangeValues = RANGE_VALUES
-            self.CanvasRange.Clear()
-            for text, value in RANGE_VALUES:
-                self.CanvasRange.Append(text)
-            self.CanvasRange.SetStringSelection(RANGE_VALUES[0][0])
-            self.CurrentRange = RANGE_VALUES[0][1]
-            self.RefreshView(True)
-        elif self.Ticktime != 0 and self.RangeValues != TIME_RANGE_VALUES:
-            self.RangeValues = TIME_RANGE_VALUES
-            self.CanvasRange.Clear()
-            for text, value in TIME_RANGE_VALUES:
-                self.CanvasRange.Append(text)
-            self.CanvasRange.SetStringSelection(TIME_RANGE_VALUES[0][0])
-            self.CurrentRange = TIME_RANGE_VALUES[0][1] / self.Ticktime
-            self.RefreshView(True)
-    
-    def RefreshCanvasPosition(self):
-        if len(self.Ticks) > 0:
-            pos = int(self.StartTick - self.Ticks[0])
-            range = int(self.Ticks[-1] - self.Ticks[0])
-        else:
-            pos = 0
-            range = 0
-        self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
-    
-    def GetForceVariableMenuFunction(self, iec_path, item):
-        iec_type = self.GetDataType(iec_path)
-        def ForceVariableFunction(event):
-            if iec_type is not None:
-                dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
-                if dialog.ShowModal() == wx.ID_OK:
-                    self.ForceDataValue(iec_path, dialog.GetValue())
-        return ForceVariableFunction
-
-    def GetReleaseVariableMenuFunction(self, iec_path):
-        def ReleaseVariableFunction(event):
-            self.ReleaseDataValue(iec_path)
-        return ReleaseVariableFunction
-    
-    def OnVariablesGridCellLeftClick(self, event):
-        if event.GetCol() == 0:
-            row = event.GetRow()
-            data = wx.TextDataObject(str((self.Table.GetValueByName(row, "Variable"), "debug")))
-            dragSource = wx.DropSource(self.VariablesGrid)
-            dragSource.SetData(data)
-            dragSource.DoDragDrop()
-        event.Skip()
-    
-    def OnVariablesGridCellRightClick(self, event):
-        row, col = event.GetRow(), event.GetCol()
-        if self.Table.GetColLabelValue(col, False) == "Value":
-            iec_path = self.Table.GetValueByName(row, "Variable").upper()
-
-            menu = wx.Menu(title='')
-            
-            new_id = wx.NewId()
-            AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Force value"))
-            self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(iec_path.upper(), self.Table.GetItem(row)), id=new_id)
-            
-            new_id = wx.NewId()
-            AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Release value"))
-            self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(iec_path.upper()), id=new_id)
-            
-            if self.Table.IsForced(row):
-                menu.Enable(new_id, True)
-            else:
-                menu.Enable(new_id, False)
-            
-            self.PopupMenu(menu)
-            
-            menu.Destroy()
-        event.Skip()
-    
-    def ChangeRange(self, dir, tick=None):
-        current_range = self.CurrentRange
-        current_range_idx = self.CanvasRange.GetSelection()
-        new_range_idx = max(0, min(current_range_idx + dir, len(self.RangeValues) - 1))
-        if new_range_idx != current_range_idx:
-            self.CanvasRange.SetSelection(new_range_idx)
-            if self.Ticktime == 0:
-                self.CurrentRange = self.RangeValues[new_range_idx][1]
-            else:
-                self.CurrentRange = self.RangeValues[new_range_idx][1] / self.Ticktime
-            if len(self.Ticks) > 0:
-                if tick is None:
-                    tick = self.StartTick + self.CurrentRange / 2.
-                new_start_tick = tick - (tick - self.StartTick) * self.CurrentRange / current_range 
-                self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
-                self.Fixed = self.StartTick < self.Ticks[-1] - self.CurrentRange
-            self.ForceRefresh()
-    
-    def RefreshRange(self):
-        if len(self.Ticks) > 0:
-            if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
-                self.Fixed = False
-            if self.Fixed:
-                self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
-            else:
-                self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
-        self.ForceRefresh()
-    
-    def OnRangeChanged(self, event):
-        try:
-            if self.Ticktime == 0:
-                self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1]
-            else:
-                self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1] / self.Ticktime
-        except ValueError, e:
-            self.CanvasRange.SetValue(str(self.CurrentRange))
-        wx.CallAfter(self.RefreshRange)
-        event.Skip()
-    
-    def OnCurrentButton(self, event):
-        if len(self.Ticks) > 0:
-            self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
-            self.Fixed = False
-            self.CursorTick = None
-            self.ResetCursorTick()
-        event.Skip()
-    
-    def CopyDataToClipboard(self, variables):
-        text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
-        next_tick = NextTick(variables)
-        while next_tick is not None:
-            values = []
-            for item, data in variables:
-                if len(data) > 0:
-                    if next_tick == data[0][0]:
-                        var_type = item.GetVariableType()
-                        if var_type in ["STRING", "WSTRING"]:
-                            value = item.GetRawValue(int(data.pop(0)[2]))
-                            if var_type == "STRING":
-                                values.append("'%s'" % value)
-                            else:
-                                values.append('"%s"' % value)
-                        else:
-                            values.append("%.3f" % data.pop(0)[1])
-                    else:
-                        values.append("")
-                else:
-                    values.append("")
-            text += "%d;%s;\n" % (next_tick, ";".join(values))
-            next_tick = NextTick(variables)
-        self.ParentWindow.SetCopyBuffer(text)
-    
-    def OnExportGraphButton(self, event):
-        variables = []
-        if USE_MPL:
-            items = []
-            for panel in self.GraphicPanels:
-                items.extend(panel.GetItems())
-        else:
-            items = self.Table.GetData()
-        for item in items:
-            if item.IsNumVariable():
-                variables.append((item, [entry for entry in item.GetData()]))
-        wx.CallAfter(self.CopyDataToClipboard, variables)
-        event.Skip()
-    
-    def OnPositionChanging(self, event):
-        if len(self.Ticks) > 0:
-            self.StartTick = self.Ticks[0] + event.GetPosition()
-            self.Fixed = True
-            self.ForceRefresh()
-        event.Skip()
-    
-    def GetRange(self):
-        return self.StartTick, self.StartTick + self.CurrentRange
-    
-    def GetViewerIndex(self, viewer):
-        if viewer in self.GraphicPanels:
-            return self.GraphicPanels.index(viewer)
-        return None
-    
-    def IsViewerFirst(self, viewer):
-        return viewer == self.GraphicPanels[0]
-    
-    def IsViewerLast(self, viewer):
-        return viewer == self.GraphicPanels[-1]
-    
-    def HighlightPreviousViewer(self, viewer):
-        if self.IsViewerFirst(viewer):
-            return
-        idx = self.GetViewerIndex(viewer)
-        if idx is None:
-            return
-        self.GraphicPanels[idx-1].SetHighlight(HIGHLIGHT_AFTER)
-    
-    def ResetVariableNameMask(self):
-        items = []
-        for panel in self.GraphicPanels:
-            items.extend(panel.GetItems())
-        if len(items) > 1:
-            self.VariableNameMask = reduce(compute_mask,
-                [item.GetVariable().split('.') for item in items])
-        elif len(items) > 0:
-            self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*']
-        else:
-            self.VariableNameMask = []
-        self.MaskLabel.ChangeValue(".".join(self.VariableNameMask))
-        self.MaskLabel.SetInsertionPoint(self.MaskLabel.GetLastPosition())
-            
-    def GetVariableNameMask(self):
-        return self.VariableNameMask
-    
-    def InsertValue(self, iec_path, idx = None, force=False):
-        if USE_MPL:
-            for panel in self.GraphicPanels:
-                if panel.GetItem(iec_path) is not None:
-                    return
-            if idx is None:
-                idx = len(self.GraphicPanels)
-        else:
-            for item in self.Table.GetData():
-                if iec_path == item.GetVariable():
-                    return
-            if idx is None:
-                idx = self.Table.GetNumberRows()
-        item = VariableTableItem(self, iec_path)
-        result = self.AddDataConsumer(iec_path.upper(), item)
-        if result is not None or force:
-            
-            if USE_MPL:
-                if item.IsNumVariable():
-                    panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
-                    if self.CursorTick is not None:
-                        panel.SetCursorTick(self.CursorTick)
-                else:
-                    panel = DebugVariableText(self.GraphicsWindow, self, [item])
-                if idx is not None:
-                    self.GraphicPanels.insert(idx, panel)
-                else:
-                    self.GraphicPanels.append(panel)
-                self.ResetVariableNameMask()
-                self.RefreshGraphicsSizer()
-            else:
-                self.Table.InsertItem(idx, item)
-            
-            self.ForceRefresh()
-    
-    def MoveValue(self, iec_path, idx = None):
-        if idx is None:
-            idx = len(self.GraphicPanels)
-        source_panel = None
-        item = None
-        for panel in self.GraphicPanels:
-            item = panel.GetItem(iec_path)
-            if item is not None:
-                source_panel = panel
-                break
-        if source_panel is not None:
-            source_panel.RemoveItem(item)
-            if source_panel.IsEmpty():
-                if source_panel.HasCapture():
-                    source_panel.ReleaseMouse()
-                self.GraphicPanels.remove(source_panel)
-                source_panel.Destroy()
-            
-            if item.IsNumVariable():
-                panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
-                if self.CursorTick is not None:
-                    panel.SetCursorTick(self.CursorTick)
-            else:
-                panel = DebugVariableText(self.GraphicsWindow, self, [item])
-            self.GraphicPanels.insert(idx, panel)
-            self.ResetVariableNameMask()
-            self.RefreshGraphicsSizer()
-            self.ForceRefresh()
-    
-    def MergeGraphs(self, source, target_idx, merge_type, force=False):
-        source_item = None
-        source_panel = None
-        for panel in self.GraphicPanels:
-            source_item = panel.GetItem(source)
-            if source_item is not None:
-                source_panel = panel
-                break
-        if source_item is None:
-            item = VariableTableItem(self, source)
-            if item.IsNumVariable():
-                result = self.AddDataConsumer(source.upper(), item)
-                if result is not None or force:
-                    source_item = item
-        if source_item is not None and source_item.IsNumVariable():
-            target_panel = self.GraphicPanels[target_idx]
-            graph_type = target_panel.GraphType
-            if target_panel != source_panel:
-                if (merge_type == GRAPH_PARALLEL and graph_type != merge_type or
-                    merge_type == GRAPH_ORTHOGONAL and 
-                    (graph_type == GRAPH_PARALLEL and len(target_panel.Items) > 1 or
-                     graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
-                    return
-                
-                if source_panel is not None:
-                    source_panel.RemoveItem(source_item)
-                    if source_panel.IsEmpty():
-                        if source_panel.HasCapture():
-                            source_panel.ReleaseMouse()
-                        self.GraphicPanels.remove(source_panel)
-                        source_panel.Destroy()
-            elif (merge_type != graph_type and len(target_panel.Items) == 2):
-                target_panel.RemoveItem(source_item)
-            else:
-                target_panel = None
-                
-            if target_panel is not None:
-                target_panel.AddItem(source_item)
-                target_panel.GraphType = merge_type
-                target_panel.ResetGraphics()
-                
-                self.ResetVariableNameMask()
-                self.RefreshGraphicsSizer()
-                self.ForceRefresh()
-    
-    def DeleteValue(self, source_panel, item=None):
-        source_idx = self.GetViewerIndex(source_panel)
-        if source_idx is not None:
-            
-            if item is None:
-                source_panel.Clear()
-                source_panel.Destroy()
-                self.GraphicPanels.remove(source_panel)
-                self.ResetVariableNameMask()
-                self.RefreshGraphicsSizer()
-            else:
-                source_panel.RemoveItem(item)
-                if source_panel.IsEmpty():
-                    source_panel.Destroy()
-                    self.GraphicPanels.remove(source_panel)
-                    self.ResetVariableNameMask()
-                    self.RefreshGraphicsSizer()
-            self.ForceRefresh()
-    
-    def GetDebugVariables(self):
-        if USE_MPL:
-            return [panel.GetVariables() for panel in self.GraphicPanels]
-        else:
-            return [item.GetVariable() for item in self.Table.GetData()]
-    
-    def SetDebugVariables(self, variables):
-        if USE_MPL:
-            for variable in variables:
-                if isinstance(variable, (TupleType, ListType)):
-                    items = []
-                    for iec_path in variable:
-                        item = VariableTableItem(self, iec_path)
-                        if not item.IsNumVariable():
-                            continue
-                        self.AddDataConsumer(iec_path.upper(), item)
-                        items.append(item)
-                    if isinstance(variable, ListType):
-                        panel = DebugVariableGraphic(self.GraphicsWindow, self, items, GRAPH_PARALLEL)
-                    elif isinstance(variable, TupleType) and len(items) <= 3:
-                        panel = DebugVariableGraphic(self.GraphicsWindow, self, items, GRAPH_ORTHOGONAL)
-                    else:
-                        continue
-                    self.GraphicPanels.append(panel)
-                    self.ResetVariableNameMask()
-                    self.RefreshGraphicsSizer()
-                else:
-                    self.InsertValue(variable, force=True)
-            self.ForceRefresh()
-        else:
-            for variable in variables:
-                if isinstance(variable, (ListType, TupleType)):
-                    for iec_path in variable:
-                        self.InsertValue(iec_path, force=True)
-                else:
-                    self.InsertValue(variable, force=True)
-    
-    def ResetGraphicsValues(self):
-        if USE_MPL:
-            self.Ticks = numpy.array([])
-            self.StartTick = 0
-            self.Fixed = False
-            for panel in self.GraphicPanels:
-                panel.ResetData()
-
-    def RefreshGraphicsWindowScrollbars(self):
-        xstart, ystart = self.GraphicsWindow.GetViewStart()
-        window_size = self.GraphicsWindow.GetClientSize()
-        vwidth, vheight = self.GraphicsSizer.GetMinSize()
-        posx = max(0, min(xstart, (vwidth - window_size[0]) / SCROLLBAR_UNIT))
-        posy = max(0, min(ystart, (vheight - window_size[1]) / SCROLLBAR_UNIT))
-        self.GraphicsWindow.Scroll(posx, posy)
-        self.GraphicsWindow.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
-                vwidth / SCROLLBAR_UNIT, vheight / SCROLLBAR_UNIT, posx, posy)
-    
-    def OnGraphicsWindowResize(self, event):
-        self.RefreshGraphicsWindowScrollbars()
-        event.Skip()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableGraphicPanel.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,955 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from types import TupleType
+import math
+import numpy
+
+import wx
+import wx.lib.buttons
+
+import matplotlib
+matplotlib.use('WX')
+import matplotlib.pyplot
+from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
+
+from editors.DebugViewer import DebugViewer
+from util.BitmapLibrary import GetBitmap
+
+from DebugVariableItem import DebugVariableItem
+from DebugVariableTextViewer import DebugVariableTextViewer
+from DebugVariableGraphicViewer import *
+
+MILLISECOND = 1000000       # Number of nanosecond in a millisecond
+SECOND = 1000 * MILLISECOND # Number of nanosecond in a second
+MINUTE = 60 * SECOND        # Number of nanosecond in a minute
+HOUR = 60 * MINUTE          # Number of nanosecond in a hour
+DAY = 24 * HOUR             # Number of nanosecond in a day
+
+# List of values possible for graph range
+# Format is [(time_in_plain_text, value_in_nanosecond),...]
+RANGE_VALUES = \
+    [("%dms" % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \
+    [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
+    [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
+    [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
+
+# Scrollbar increment in pixel
+SCROLLBAR_UNIT = 10
+
+def compute_mask(x, y):
+    return [(xp if xp == yp else "*")
+            for xp, yp in zip(x, y)]
+
+def NextTick(variables):
+    next_tick = None
+    for item, data in variables:
+        if len(data) == 0:
+            continue
+        
+        next_tick = (data[0][0]
+                     if next_tick is None
+                     else min(next_tick, data[0][0]))
+    
+    return next_tick
+
+#-------------------------------------------------------------------------------
+#                    Debug Variable Graphic Panel Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Graphic
+Panel
+"""
+
+class DebugVariableDropTarget(wx.TextDropTarget):
+    
+    def __init__(self, window):
+        """
+        Constructor
+        @param window: Reference to the Debug Variable Panel
+        """
+        wx.TextDropTarget.__init__(self)
+        self.ParentWindow = window
+    
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to Debug Variable Panel
+        self.ParentWindow = None
+    
+    def OnDragOver(self, x, y, d):
+        """
+        Function called when mouse is dragged over Drop Target
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        @param d: Suggested default for return value
+        """
+       # Signal Debug Variable Panel to refresh highlight giving mouse position
+        self.ParentWindow.RefreshHighlight(x, y)
+        return wx.TextDropTarget.OnDragOver(self, x, y, d)
+        
+    def OnDropText(self, x, y, data):
+        """
+        Function called when mouse is released in Drop Target
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        @param data: Text associated to drag'n drop
+        """
+        # Signal Debug Variable Panel to reset highlight
+        self.ParentWindow.ResetHighlight()
+        
+        message = None
+        
+        # Check that data is valid regarding DebugVariablePanel
+        try:
+            values = eval(data)
+            if not isinstance(values, TupleType):
+                raise ValueError
+        except:
+            message = _("Invalid value \"%s\" for debug variable")%data
+            values = None
+            
+        # Display message if data is invalid
+        if message is not None:
+            wx.CallAfter(self.ShowMessage, message)
+        
+        # Data contain a reference to a variable to debug
+        elif values[1] == "debug":
+            
+            # Drag'n Drop is an internal is an internal move inside Debug
+            # Variable Panel 
+            if len(values) > 2 and values[2] == "move":
+                self.ParentWindow.MoveValue(values[0])
+            
+            # Drag'n Drop was initiated by another control of Beremiz
+            else:
+                self.ParentWindow.InsertValue(values[0], force=True)
+    
+    def OnLeave(self):
+        """
+        Function called when mouse is leave Drop Target
+        """
+        # Signal Debug Variable Panel to reset highlight
+        self.ParentWindow.ResetHighlight()
+        return wx.TextDropTarget.OnLeave(self)
+    
+    def ShowMessage(self, message):
+        """
+        Show error message in Error Dialog
+        @param message: Error message to display
+        """
+        dialog = wx.MessageDialog(self.ParentWindow, 
+                                  message, 
+                                  _("Error"), 
+                                  wx.OK|wx.ICON_ERROR)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+#                      Debug Variable Graphic Panel Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a Viewer that display variable values as a graphs
+"""
+
+class DebugVariableGraphicPanel(wx.Panel, DebugViewer):
+    
+    def __init__(self, parent, producer, window):
+        """
+        Constructor
+        @param parent: Reference to the parent wx.Window
+        @param producer: Object receiving debug value and dispatching them to
+        consumers
+        @param window: Reference to Beremiz frame
+        """
+        wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
+        
+        # Save Reference to Beremiz frame
+        self.ParentWindow = window
+        
+        # Variable storing flag indicating that variable displayed in table
+        # received new value and then table need to be refreshed
+        self.HasNewData = False
+        
+        # Variable storing flag indicating that refresh has been forced, and
+        # that next time refresh is possible, it will be done even if no new
+        # data is available
+        self.Force = False
+        
+        self.SetBackgroundColour(wx.WHITE)
+        
+        main_sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        self.Ticks = numpy.array([]) # List of tick received
+        self.StartTick = 0           # Tick starting range of data displayed
+        self.Fixed = False           # Flag that range of data is fixed
+        self.CursorTick = None       # Tick of cursor for displaying values
+        
+        self.DraggingAxesPanel = None
+        self.DraggingAxesBoundingBox = None
+        self.DraggingAxesMousePos = None
+        self.VetoScrollEvent = False
+        
+        self.VariableNameMask = []
+        
+        self.GraphicPanels = []
+        
+        graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
+        
+        range_label = wx.StaticText(self, label=_('Range:'))
+        graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
+        
+        self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
+        self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
+        graphics_button_sizer.AddWindow(self.CanvasRange, 1, 
+              border=5, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
+        
+        self.CanvasRange.Clear()
+        default_range_idx = 0
+        for idx, (text, value) in enumerate(RANGE_VALUES):
+            self.CanvasRange.Append(text)
+            if text == "1s":
+                default_range_idx = idx
+        self.CanvasRange.SetSelection(default_range_idx)
+        
+        for name, bitmap, help in [
+            ("CurrentButton", "current", _("Go to current value")),
+            ("ExportGraphButton", "export_graph", _("Export graph values to clipboard"))]:
+            button = wx.lib.buttons.GenBitmapButton(self, 
+                  bitmap=GetBitmap(bitmap), 
+                  size=wx.Size(28, 28), style=wx.NO_BORDER)
+            button.SetToolTipString(help)
+            setattr(self, name, button)
+            self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
+            graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
+        
+        self.CanvasPosition = wx.ScrollBar(self, 
+              size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
+        self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK, 
+              self.OnPositionChanging, self.CanvasPosition)
+        self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP, 
+              self.OnPositionChanging, self.CanvasPosition)
+        self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN, 
+              self.OnPositionChanging, self.CanvasPosition)
+        self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP, 
+              self.OnPositionChanging, self.CanvasPosition)
+        self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN, 
+              self.OnPositionChanging, self.CanvasPosition)
+        main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
+        
+        self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
+        main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL|wx.GROW)
+        
+        self.TickLabel = wx.StaticText(self)
+        self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
+        
+        self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY|wx.TE_CENTER|wx.NO_BORDER)
+        self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT|wx.GROW)
+        
+        self.TickTimeLabel = wx.StaticText(self)
+        self.TickSizer.AddWindow(self.TickTimeLabel)
+        
+        self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL|wx.VSCROLL)
+        self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
+        self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self))
+        self.GraphicsWindow.Bind(wx.EVT_ERASE_BACKGROUND, self.OnGraphicsWindowEraseBackground)
+        self.GraphicsWindow.Bind(wx.EVT_PAINT, self.OnGraphicsWindowPaint)
+        self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
+        self.GraphicsWindow.Bind(wx.EVT_MOUSEWHEEL, self.OnGraphicsWindowMouseWheel)
+        
+        main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
+        
+        self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
+        self.GraphicsWindow.SetSizer(self.GraphicsSizer)
+    
+        DebugViewer.__init__(self, producer, True)
+        
+        self.SetSizer(main_sizer)
+    
+    def SetTickTime(self, ticktime=0):
+        """
+        Set Ticktime for calculate data range according to time range selected
+        @param ticktime: Ticktime to apply to range (default: 0)
+        """
+        # Save ticktime
+        self.Ticktime = ticktime
+        
+        # Set ticktime to millisecond if undefined
+        if self.Ticktime == 0:
+            self.Ticktime = MILLISECOND
+        
+        # Calculate range to apply to data
+        self.CurrentRange = RANGE_VALUES[
+            self.CanvasRange.GetSelection()][1] / self.Ticktime
+    
+    def SetDataProducer(self, producer):
+        """
+        Set Data Producer
+        @param producer: Data Producer
+        """
+        DebugViewer.SetDataProducer(self, producer)
+        
+        # Set ticktime if data producer is available
+        if self.DataProducer is not None:
+            self.SetTickTime(self.DataProducer.GetTicktime())
+    
+    def RefreshNewData(self, *args, **kwargs):
+        """
+        Called to refresh Panel according to values received by variables
+        Can receive any parameters (not used here)
+        """
+        # Refresh graphs if new data is available or refresh is forced
+        if self.HasNewData or self.Force:
+            self.HasNewData = False
+            self.RefreshView()
+        
+        DebugViewer.RefreshNewData(self, *args, **kwargs)
+    
+    def NewDataAvailable(self, tick, *args, **kwargs):
+        """
+        Called by DataProducer for each tick captured or by panel to refresh
+        graphs
+        @param tick: PLC tick captured
+        All other parameters are passed to refresh function 
+        """
+        # If tick given
+        if tick is not None:
+            self.HasNewData = True
+            
+            # Save tick as start tick for range if data is still empty
+            if len(self.Ticks) == 0:
+                self.StartTick = tick 
+            
+            # Add tick to list of ticks received
+            self.Ticks = numpy.append(self.Ticks, [tick])
+            
+            # Update start tick for range if range follow ticks received
+            if not self.Fixed or tick < self.StartTick + self.CurrentRange:
+                self.StartTick = max(self.StartTick, tick - self.CurrentRange)
+            
+            # Force refresh if graph is fixed because range of data received
+            # is too small to fill data range selected
+            if self.Fixed and \
+               self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
+                self.Force = True
+        
+        DebugViewer.NewDataAvailable(self, tick, *args, **kwargs)
+    
+    def ForceRefresh(self):
+        """
+        Called to force refresh of graphs
+        """
+        self.Force = True
+        wx.CallAfter(self.NewDataAvailable, None, True)
+    
+    def SetCursorTick(self, cursor_tick):
+        """
+        Set Cursor for displaying values of items at a tick given
+        @param cursor_tick: Tick of cursor
+        """
+        # Save cursor tick
+        self.CursorTick = cursor_tick
+        self.Fixed = cursor_tick is not None
+        self.UpdateCursorTick() 
+    
+    def MoveCursorTick(self, move):
+        if self.CursorTick is not None:
+            cursor_tick = max(self.Ticks[0], 
+                          min(self.CursorTick + move, 
+                              self.Ticks[-1]))
+            cursor_tick_idx = numpy.argmin(numpy.abs(self.Ticks - cursor_tick))
+            if self.Ticks[cursor_tick_idx] == self.CursorTick:
+                cursor_tick_idx = max(0, 
+                                  min(cursor_tick_idx + abs(move) / move, 
+                                      len(self.Ticks) - 1))
+            self.CursorTick = self.Ticks[cursor_tick_idx]
+            self.StartTick = max(self.Ticks[
+                                numpy.argmin(numpy.abs(self.Ticks - 
+                                        self.CursorTick + self.CurrentRange))],
+                             min(self.StartTick, self.CursorTick))
+            self.RefreshCanvasPosition()
+            self.UpdateCursorTick() 
+            
+    def ResetCursorTick(self):
+        self.CursorTick = None
+        self.Fixed = False
+        self.UpdateCursorTick()
+    
+    def UpdateCursorTick(self):
+        for panel in self.GraphicPanels:
+            if isinstance(panel, DebugVariableGraphicViewer):
+                panel.SetCursorTick(self.CursorTick)
+        self.ForceRefresh()
+    
+    def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
+        if len(panel.GetItems()) > 1:
+            self.DraggingAxesPanel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
+            self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
+            width, height = panel.GetSize()
+            self.DraggingAxesPanel.SetSize(wx.Size(width, height))
+            self.DraggingAxesPanel.ResetGraphics()
+            self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
+        else:
+            self.DraggingAxesPanel = panel
+        self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(parent_coordinate=True)
+        self.DraggingAxesMousePos = wx.Point(
+            x_mouse_start - self.DraggingAxesBoundingBox.x, 
+            y_mouse_start - self.DraggingAxesBoundingBox.y)
+        self.MoveDragNDrop(x_mouse, y_mouse)
+        
+    def MoveDragNDrop(self, x_mouse, y_mouse):
+        self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
+        self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
+        self.RefreshHighlight(x_mouse, y_mouse)
+    
+    def RefreshHighlight(self, x_mouse, y_mouse):
+        for idx, panel in enumerate(self.GraphicPanels):
+            x, y = panel.GetPosition()
+            width, height = panel.GetSize()
+            rect = wx.Rect(x, y, width, height)
+            if (rect.InsideXY(x_mouse, y_mouse) or 
+                idx == 0 and y_mouse < 0 or
+                idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
+                panel.RefreshHighlight(x_mouse - x, y_mouse - y)
+            else:
+                panel.SetHighlight(HIGHLIGHT_NONE)
+        if wx.Platform == "__WXMSW__":
+            self.RefreshView()
+        else:
+            self.ForceRefresh()
+    
+    def ResetHighlight(self):
+        for panel in self.GraphicPanels:
+            panel.SetHighlight(HIGHLIGHT_NONE)
+        if wx.Platform == "__WXMSW__":
+            self.RefreshView()
+        else:
+            self.ForceRefresh()
+    
+    def IsDragging(self):
+        return self.DraggingAxesPanel is not None
+    
+    def GetDraggingAxesClippingRegion(self, panel):
+        x, y = panel.GetPosition()
+        width, height = panel.GetSize()
+        bbox = wx.Rect(x, y, width, height)
+        bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
+        bbox.x -= x
+        bbox.y -= y
+        return bbox
+    
+    def GetDraggingAxesPosition(self, panel):
+        x, y = panel.GetPosition()
+        return wx.Point(self.DraggingAxesBoundingBox.x - x,
+                        self.DraggingAxesBoundingBox.y - y)
+    
+    def StopDragNDrop(self, variable, x_mouse, y_mouse):
+        if self.DraggingAxesPanel not in self.GraphicPanels:
+            self.DraggingAxesPanel.Destroy()
+        self.DraggingAxesPanel = None
+        self.DraggingAxesBoundingBox = None
+        self.DraggingAxesMousePos = None
+        for idx, panel in enumerate(self.GraphicPanels):
+            panel.SetHighlight(HIGHLIGHT_NONE)
+            xw, yw = panel.GetPosition()
+            width, height = panel.GetSize()
+            bbox = wx.Rect(xw, yw, width, height)
+            if bbox.InsideXY(x_mouse, y_mouse):
+                panel.ShowButtons(True)
+                merge_type = GRAPH_PARALLEL
+                if isinstance(panel, DebugVariableTextViewer) or panel.Is3DCanvas():
+                    if y_mouse > yw + height / 2:
+                        idx += 1
+                    wx.CallAfter(self.MoveValue, variable, idx, True)
+                else:
+                    rect = panel.GetAxesBoundingBox(True)
+                    if rect.InsideXY(x_mouse, y_mouse):
+                        merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
+                        if merge_rect.InsideXY(x_mouse, y_mouse):
+                            merge_type = GRAPH_ORTHOGONAL
+                        wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
+                    else:
+                        if y_mouse > yw + height / 2:
+                            idx += 1
+                        wx.CallAfter(self.MoveValue, variable, idx, True)
+                self.ForceRefresh()
+                return 
+        width, height = self.GraphicsWindow.GetVirtualSize()
+        rect = wx.Rect(0, 0, width, height)
+        if rect.InsideXY(x_mouse, y_mouse):
+            wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels), True)
+        self.ForceRefresh()
+    
+    def RefreshGraphicsSizer(self):
+        self.GraphicsSizer.Clear()
+        
+        for panel in self.GraphicPanels:
+            self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
+            
+        self.GraphicsSizer.Layout()
+        self.RefreshGraphicsWindowScrollbars()
+    
+    def RefreshView(self):
+        self.RefreshCanvasPosition()
+        
+        width, height = self.GraphicsWindow.GetVirtualSize()
+        bitmap = wx.EmptyBitmap(width, height)
+        dc = wx.BufferedDC(wx.ClientDC(self.GraphicsWindow), bitmap)
+        dc.Clear()
+        dc.BeginDrawing()
+        if self.DraggingAxesPanel is not None:
+            destBBox = self.DraggingAxesBoundingBox
+            srcBBox = self.DraggingAxesPanel.GetAxesBoundingBox()
+            
+            srcBmp = _convert_agg_to_wx_bitmap(self.DraggingAxesPanel.get_renderer(), None)
+            srcDC = wx.MemoryDC()
+            srcDC.SelectObject(srcBmp)
+                
+            dc.Blit(destBBox.x, destBBox.y, 
+                    int(destBBox.width), int(destBBox.height), 
+                    srcDC, srcBBox.x, srcBBox.y)
+        dc.EndDrawing()
+        
+        if not self.Fixed or self.Force:
+            self.Force = False
+            refresh_graphics = True
+        else:
+            refresh_graphics = False
+        
+        if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
+            self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
+        for panel in self.GraphicPanels:
+            if isinstance(panel, DebugVariableGraphicViewer):
+                panel.RefreshViewer(refresh_graphics)
+            else:
+                panel.RefreshViewer()
+        
+        if self.CursorTick is not None:
+            tick = self.CursorTick
+        elif len(self.Ticks) > 0:
+            tick = self.Ticks[-1]
+        else:
+            tick = None
+        if tick is not None:
+            self.TickLabel.SetLabel("Tick: %d" % tick)
+            tick_duration = int(tick * self.Ticktime)
+            not_null = False
+            duration = ""
+            for value, format in [(tick_duration / DAY, "%dd"),
+                                  ((tick_duration % DAY) / HOUR, "%dh"),
+                                  ((tick_duration % HOUR) / MINUTE, "%dm"),
+                                  ((tick_duration % MINUTE) / SECOND, "%ds")]:
+                
+                if value > 0 or not_null:
+                    duration += format % value
+                    not_null = True
+            
+            duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND) 
+            self.TickTimeLabel.SetLabel("t: %s" % duration)
+        else:
+            self.TickLabel.SetLabel("")
+            self.TickTimeLabel.SetLabel("")
+        self.TickSizer.Layout()
+    
+    def SubscribeAllDataConsumers(self):
+        DebugViewer.SubscribeAllDataConsumers(self)
+        
+        if self.DataProducer is not None:
+            if self.DataProducer is not None:
+                self.SetTickTime(self.DataProducer.GetTicktime())
+        
+        self.ResetCursorTick()
+        
+        for panel in self.GraphicPanels[:]:
+            panel.SubscribeAllDataConsumers()
+            if panel.ItemsIsEmpty():
+                if panel.HasCapture():
+                    panel.ReleaseMouse()
+                self.GraphicPanels.remove(panel)
+                panel.Destroy()
+        
+        self.ResetVariableNameMask()
+        self.RefreshGraphicsSizer()
+        self.ForceRefresh()
+    
+    def ResetView(self):
+        self.UnsubscribeAllDataConsumers()
+        
+        self.Fixed = False
+        for panel in self.GraphicPanels:
+            panel.Destroy()
+        self.GraphicPanels = []
+        self.ResetVariableNameMask()
+        self.RefreshGraphicsSizer()
+    
+    def SetCanvasPosition(self, tick):
+        tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
+        self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
+        self.Fixed = True
+        self.RefreshCanvasPosition()
+        self.ForceRefresh()
+    
+    def RefreshCanvasPosition(self):
+        if len(self.Ticks) > 0:
+            pos = int(self.StartTick - self.Ticks[0])
+            range = int(self.Ticks[-1] - self.Ticks[0])
+        else:
+            pos = 0
+            range = 0
+        self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
+    
+    def ChangeRange(self, dir, tick=None):
+        current_range = self.CurrentRange
+        current_range_idx = self.CanvasRange.GetSelection()
+        new_range_idx = max(0, min(current_range_idx + dir, len(RANGE_VALUES) - 1))
+        if new_range_idx != current_range_idx:
+            self.CanvasRange.SetSelection(new_range_idx)
+            self.CurrentRange = RANGE_VALUES[new_range_idx][1] / self.Ticktime
+            if len(self.Ticks) > 0:
+                if tick is None:
+                    tick = self.StartTick + self.CurrentRange / 2.
+                new_start_tick = min(tick - (tick - self.StartTick) * self.CurrentRange / current_range,
+                                     self.Ticks[-1] - self.CurrentRange)
+                self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
+                self.Fixed = new_start_tick < self.Ticks[-1] - self.CurrentRange
+            self.ForceRefresh()
+    
+    def RefreshRange(self):
+        if len(self.Ticks) > 0:
+            if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
+                self.Fixed = False
+            if self.Fixed:
+                self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
+            else:
+                self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
+        self.ForceRefresh()
+    
+    def OnRangeChanged(self, event):
+        try:
+            self.CurrentRange = RANGE_VALUES[self.CanvasRange.GetSelection()][1] / self.Ticktime
+        except ValueError, e:
+            self.CanvasRange.SetValue(str(self.CurrentRange))
+        wx.CallAfter(self.RefreshRange)
+        event.Skip()
+    
+    def OnCurrentButton(self, event):
+        if len(self.Ticks) > 0:
+            self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
+            self.ResetCursorTick()
+        event.Skip()
+    
+    def CopyDataToClipboard(self, variables):
+        text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
+        next_tick = NextTick(variables)
+        while next_tick is not None:
+            values = []
+            for item, data in variables:
+                if len(data) > 0:
+                    if next_tick == data[0][0]:
+                        var_type = item.GetVariableType()
+                        if var_type in ["STRING", "WSTRING"]:
+                            value = item.GetRawValue(int(data.pop(0)[2]))
+                            if var_type == "STRING":
+                                values.append("'%s'" % value)
+                            else:
+                                values.append('"%s"' % value)
+                        else:
+                            values.append("%.3f" % data.pop(0)[1])
+                    else:
+                        values.append("")
+                else:
+                    values.append("")
+            text += "%d;%s;\n" % (next_tick, ";".join(values))
+            next_tick = NextTick(variables)
+        self.ParentWindow.SetCopyBuffer(text)
+    
+    def OnExportGraphButton(self, event):
+        items = reduce(lambda x, y: x + y,
+                       [panel.GetItems() for panel in self.GraphicPanels],
+                       [])
+        variables = [(item, [entry for entry in item.GetData()])
+                     for item in items
+                     if item.IsNumVariable()]
+        wx.CallAfter(self.CopyDataToClipboard, variables)
+        event.Skip()
+    
+    def OnPositionChanging(self, event):
+        if len(self.Ticks) > 0:
+            self.StartTick = self.Ticks[0] + event.GetPosition()
+            self.Fixed = True
+            self.ForceRefresh()
+        event.Skip()
+    
+    def GetRange(self):
+        return self.StartTick, self.StartTick + self.CurrentRange
+    
+    def GetViewerIndex(self, viewer):
+        if viewer in self.GraphicPanels:
+            return self.GraphicPanels.index(viewer)
+        return None
+    
+    def IsViewerFirst(self, viewer):
+        return viewer == self.GraphicPanels[0]
+    
+    def HighlightPreviousViewer(self, viewer):
+        if self.IsViewerFirst(viewer):
+            return
+        idx = self.GetViewerIndex(viewer)
+        if idx is None:
+            return
+        self.GraphicPanels[idx-1].SetHighlight(HIGHLIGHT_AFTER)
+    
+    def ResetVariableNameMask(self):
+        items = []
+        for panel in self.GraphicPanels:
+            items.extend(panel.GetItems())
+        if len(items) > 1:
+            self.VariableNameMask = reduce(compute_mask,
+                [item.GetVariable().split('.') for item in items])
+        elif len(items) > 0:
+            self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*']
+        else:
+            self.VariableNameMask = []
+        self.MaskLabel.ChangeValue(".".join(self.VariableNameMask))
+        self.MaskLabel.SetInsertionPoint(self.MaskLabel.GetLastPosition())
+            
+    def GetVariableNameMask(self):
+        return self.VariableNameMask
+    
+    def InsertValue(self, iec_path, idx = None, force=False, graph=False):
+        for panel in self.GraphicPanels:
+            if panel.GetItem(iec_path) is not None:
+                if graph and isinstance(panel, DebugVariableTextViewer):
+                    self.ToggleViewerType(panel)
+                return
+        if idx is None:
+            idx = len(self.GraphicPanels)
+        item = DebugVariableItem(self, iec_path, True)
+        result = self.AddDataConsumer(iec_path.upper(), item)
+        if result is not None or force:
+            
+            self.Freeze()
+            if item.IsNumVariable() and graph:
+                panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
+                if self.CursorTick is not None:
+                    panel.SetCursorTick(self.CursorTick)
+            else:
+                panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
+            if idx is not None:
+                self.GraphicPanels.insert(idx, panel)
+            else:
+                self.GraphicPanels.append(panel)
+            self.ResetVariableNameMask()
+            self.RefreshGraphicsSizer()
+            self.Thaw()
+            self.ForceRefresh()
+    
+    def MoveValue(self, iec_path, idx = None, graph=False):
+        if idx is None:
+            idx = len(self.GraphicPanels)
+        source_panel = None
+        item = None
+        for panel in self.GraphicPanels:
+            item = panel.GetItem(iec_path)
+            if item is not None:
+                source_panel = panel
+                break
+        if source_panel is not None:
+            source_panel_idx = self.GraphicPanels.index(source_panel)
+            
+            if (len(source_panel.GetItems()) == 1):
+                
+                if source_panel_idx < idx:
+                    self.GraphicPanels.insert(idx, source_panel)
+                    self.GraphicPanels.pop(source_panel_idx)
+                elif source_panel_idx > idx:
+                    self.GraphicPanels.pop(source_panel_idx)
+                    self.GraphicPanels.insert(idx, source_panel)
+                else:
+                    return
+                
+            else:
+                source_panel.RemoveItem(item)
+                source_size = source_panel.GetSize()
+                if item.IsNumVariable() and graph:
+                    panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
+                    panel.SetCanvasHeight(source_size.height)
+                    if self.CursorTick is not None:
+                        panel.SetCursorTick(self.CursorTick)
+                
+                else:
+                    panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
+                
+                self.GraphicPanels.insert(idx, panel)
+                
+                if source_panel.ItemsIsEmpty():
+                    if source_panel.HasCapture():
+                        source_panel.ReleaseMouse()
+                    source_panel.Destroy()
+                    self.GraphicPanels.remove(source_panel)
+                
+            self.ResetVariableNameMask()
+            self.RefreshGraphicsSizer()
+            self.ForceRefresh()
+    
+    def MergeGraphs(self, source, target_idx, merge_type, force=False):
+        source_item = None
+        source_panel = None
+        for panel in self.GraphicPanels:
+            source_item = panel.GetItem(source)
+            if source_item is not None:
+                source_panel = panel
+                break
+        if source_item is None:
+            item = DebugVariableItem(self, source, True)
+            if item.IsNumVariable():
+                result = self.AddDataConsumer(source.upper(), item)
+                if result is not None or force:
+                    source_item = item
+        if source_item is not None and source_item.IsNumVariable():
+            if source_panel is not None:
+                source_size = source_panel.GetSize()
+            else:
+                source_size = None
+            target_panel = self.GraphicPanels[target_idx]
+            graph_type = target_panel.GraphType
+            if target_panel != source_panel:
+                if (merge_type == GRAPH_PARALLEL and graph_type != merge_type or
+                    merge_type == GRAPH_ORTHOGONAL and 
+                    (graph_type == GRAPH_PARALLEL and len(target_panel.Items) > 1 or
+                     graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
+                    return
+                
+                if source_panel is not None:
+                    source_panel.RemoveItem(source_item)
+                    if source_panel.ItemsIsEmpty():
+                        if source_panel.HasCapture():
+                            source_panel.ReleaseMouse()
+                        source_panel.Destroy()
+                        self.GraphicPanels.remove(source_panel)
+            elif (merge_type != graph_type and len(target_panel.Items) == 2):
+                target_panel.RemoveItem(source_item)
+            else:
+                target_panel = None
+                
+            if target_panel is not None:
+                target_panel.AddItem(source_item)
+                target_panel.GraphType = merge_type
+                size = target_panel.GetSize()
+                if merge_type == GRAPH_ORTHOGONAL:
+                    target_panel.SetCanvasHeight(size.width)
+                elif source_size is not None and source_panel != target_panel:
+                    target_panel.SetCanvasHeight(size.height + source_size.height)
+                else:
+                    target_panel.SetCanvasHeight(size.height)
+                target_panel.ResetGraphics()
+                
+                self.ResetVariableNameMask()
+                self.RefreshGraphicsSizer()
+                self.ForceRefresh()
+    
+    def DeleteValue(self, source_panel, item=None):
+        source_idx = self.GetViewerIndex(source_panel)
+        if source_idx is not None:
+            
+            if item is None:
+                source_panel.ClearItems()
+                source_panel.Destroy()
+                self.GraphicPanels.remove(source_panel)
+                self.ResetVariableNameMask()
+                self.RefreshGraphicsSizer()
+            else:
+                source_panel.RemoveItem(item)
+                if source_panel.ItemsIsEmpty():
+                    source_panel.Destroy()
+                    self.GraphicPanels.remove(source_panel)
+                    self.ResetVariableNameMask()
+                    self.RefreshGraphicsSizer()
+            if len(self.GraphicPanels) == 0:
+                self.Fixed = False
+                self.ResetCursorTick()
+            self.ForceRefresh()
+    
+    def ToggleViewerType(self, panel):
+        panel_idx = self.GetViewerIndex(panel)
+        if panel_idx is not None:
+            self.GraphicPanels.remove(panel)
+            items = panel.GetItems()
+            if isinstance(panel, DebugVariableGraphicViewer):
+                for idx, item in enumerate(items):
+                    new_panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
+                    self.GraphicPanels.insert(panel_idx + idx, new_panel)
+            else:
+                new_panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, items, GRAPH_PARALLEL)
+                self.GraphicPanels.insert(panel_idx, new_panel)
+            panel.Destroy()
+        self.RefreshGraphicsSizer()
+        self.ForceRefresh()
+    
+    def ResetGraphicsValues(self):
+        self.Ticks = numpy.array([])
+        self.StartTick = 0
+        for panel in self.GraphicPanels:
+            panel.ResetItemsData()
+        self.ResetCursorTick()
+
+    def RefreshGraphicsWindowScrollbars(self):
+        xstart, ystart = self.GraphicsWindow.GetViewStart()
+        window_size = self.GraphicsWindow.GetClientSize()
+        vwidth, vheight = self.GraphicsSizer.GetMinSize()
+        posx = max(0, min(xstart, (vwidth - window_size[0]) / SCROLLBAR_UNIT))
+        posy = max(0, min(ystart, (vheight - window_size[1]) / SCROLLBAR_UNIT))
+        self.GraphicsWindow.Scroll(posx, posy)
+        self.GraphicsWindow.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
+                vwidth / SCROLLBAR_UNIT, vheight / SCROLLBAR_UNIT, posx, posy)
+    
+    def OnGraphicsWindowEraseBackground(self, event):
+        pass
+    
+    def OnGraphicsWindowPaint(self, event):
+        self.RefreshView()
+        event.Skip()
+    
+    def OnGraphicsWindowResize(self, event):
+        size = self.GetSize()
+        for panel in self.GraphicPanels:
+            panel_size = panel.GetSize()
+            if (isinstance(panel, DebugVariableGraphicViewer) and 
+                panel.GraphType == GRAPH_ORTHOGONAL and 
+                panel_size.width == panel_size.height):
+                panel.SetCanvasHeight(size.width)
+        self.RefreshGraphicsWindowScrollbars()
+        self.GraphicsSizer.Layout()
+        event.Skip()
+
+    def OnGraphicsWindowMouseWheel(self, event):
+        if self.VetoScrollEvent:
+            self.VetoScrollEvent = False
+        else:
+            event.Skip()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,1410 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from types import TupleType
+from time import time as gettime
+import numpy
+
+import wx
+
+import matplotlib
+matplotlib.use('WX')
+import matplotlib.pyplot
+from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
+from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
+from matplotlib.backends.backend_agg import FigureCanvasAgg
+from mpl_toolkits.mplot3d import Axes3D
+
+from editors.DebugViewer import REFRESH_PERIOD
+
+from DebugVariableItem import DebugVariableItem
+from DebugVariableViewer import *
+from GraphButton import GraphButton
+
+# Graph variable display type
+GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
+
+# Canvas height
+[SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
+
+CANVAS_BORDER = (20., 10.) # Border height on at bottom and top of graph
+CANVAS_PADDING = 8.5       # Border inside graph where no label is drawn
+VALUE_LABEL_HEIGHT = 17.   # Height of variable label in graph
+AXES_LABEL_HEIGHT = 12.75  # Height of variable value in graph
+
+# Colors used cyclically for graph curves
+COLOR_CYCLE = ['r', 'b', 'g', 'm', 'y', 'k']
+# Color for graph cursor
+CURSOR_COLOR = '#800080'
+
+#-------------------------------------------------------------------------------
+#                      Debug Variable Graphic Viewer Helpers
+#-------------------------------------------------------------------------------
+
+def merge_ranges(ranges):
+    """
+    Merge variables data range in a list to return a range of minimal min range
+    value and maximal max range value extended of 10% for keeping a padding
+    around graph in canvas
+    @param ranges: [(range_min_value, range_max_value),...]
+    @return: merged_range_min_value, merged_range_max_value
+    """
+    # Get minimal and maximal range value
+    min_value = max_value = None
+    for range_min, range_max in ranges:
+        # Update minimal range value
+        if min_value is None:
+            min_value = range_min
+        elif range_min is not None:
+            min_value = min(min_value, range_min)
+        
+        # Update maximal range value
+        if max_value is None:
+            max_value = range_max
+        elif range_min is not None:
+            max_value = max(max_value, range_max)
+    
+    # Calculate range center and width if at least one valid range is defined
+    if min_value is not None and max_value is not None:
+        center = (min_value + max_value) / 2.
+        range_size = max(1.0, max_value - min_value)
+    
+    # Set default center and with if no valid range is defined
+    else:
+        center = 0.5
+        range_size = 1.0
+    
+    # Return range expended from 10 %
+    return center - range_size * 0.55, center + range_size * 0.55
+
+#-------------------------------------------------------------------------------
+#                   Debug Variable Graphic Viewer Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Graphic
+Viewer
+"""
+
+class DebugVariableGraphicDropTarget(wx.TextDropTarget):
+    
+    def __init__(self, parent, window):
+        """
+        Constructor
+        @param parent: Reference to Debug Variable Graphic Viewer
+        @param window: Reference to the Debug Variable Panel
+        """
+        wx.TextDropTarget.__init__(self)
+        self.ParentControl = parent
+        self.ParentWindow = window
+        
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to Debug Variable Graphic Viewer and Debug Variable
+        # Panel
+        self.ParentControl = None
+        self.ParentWindow = None
+        
+    def OnDragOver(self, x, y, d):
+        """
+        Function called when mouse is dragged over Drop Target
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        @param d: Suggested default for return value
+        """
+        # Signal parent that mouse is dragged over
+        self.ParentControl.OnMouseDragging(x, y)
+        
+        return wx.TextDropTarget.OnDragOver(self, x, y, d)
+        
+    def OnDropText(self, x, y, data):
+        """
+        Function called when mouse is released in Drop Target
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        @param data: Text associated to drag'n drop
+        """
+        # Signal Debug Variable Panel to reset highlight
+        self.ParentWindow.ResetHighlight()
+        
+        message = None
+        
+        # Check that data is valid regarding DebugVariablePanel
+        try:
+            values = eval(data)
+            if not isinstance(values, TupleType):
+                raise ValueError
+        except:
+            message = _("Invalid value \"%s\" for debug variable")%data
+            values = None
+        
+        # Display message if data is invalid
+        if message is not None:
+            wx.CallAfter(self.ShowMessage, message)
+        
+        # Data contain a reference to a variable to debug
+        elif values[1] == "debug":
+            target_idx = self.ParentControl.GetIndex()
+            
+            # If mouse is dropped in graph canvas bounding box and graph is
+            # not 3D canvas, graphs will be merged
+            rect = self.ParentControl.GetAxesBoundingBox()
+            if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
+                # Default merge type is parallel
+                merge_type = GRAPH_PARALLEL
+                
+                # If mouse is dropped in left part of graph canvas, graph
+                # wall be merged orthogonally
+                merge_rect = wx.Rect(rect.x, rect.y, 
+                                     rect.width / 2., rect.height)
+                if merge_rect.InsideXY(x, y):
+                    merge_type = GRAPH_ORTHOGONAL
+                
+                # Merge graphs
+                wx.CallAfter(self.ParentWindow.MergeGraphs, 
+                             values[0], target_idx, 
+                             merge_type, force=True)
+                
+            else:
+                width, height = self.ParentControl.GetSize()
+                
+                # Get Before which Viewer the variable has to be moved or added
+                # according to the position of mouse in Viewer.
+                if y > height / 2:
+                    target_idx += 1
+                
+                # Drag'n Drop is an internal is an internal move inside Debug
+                # Variable Panel 
+                if len(values) > 2 and values[2] == "move":
+                    self.ParentWindow.MoveValue(values[0], 
+                                                target_idx)
+                
+                # Drag'n Drop was initiated by another control of Beremiz
+                else:
+                    self.ParentWindow.InsertValue(values[0], 
+                                                  target_idx, 
+                                                  force=True)
+    
+    def OnLeave(self):
+        """
+        Function called when mouse is leave Drop Target
+        """
+        # Signal Debug Variable Panel to reset highlight
+        self.ParentWindow.ResetHighlight()
+        return wx.TextDropTarget.OnLeave(self)
+    
+    def ShowMessage(self, message):
+        """
+        Show error message in Error Dialog
+        @param message: Error message to display
+        """
+        dialog = wx.MessageDialog(self.ParentWindow, 
+                                  message, 
+                                  _("Error"), 
+                                  wx.OK|wx.ICON_ERROR)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+#                      Debug Variable Graphic Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a Viewer that display variable values as a graphs
+"""
+
+class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
+    
+    def __init__(self, parent, window, items, graph_type):
+        """
+        Constructor
+        @param parent: Parent wx.Window of DebugVariableText
+        @param window: Reference to the Debug Variable Panel
+        @param items: List of DebugVariableItem displayed by Viewer
+        @param graph_type: Graph display type (Parallel or orthogonal)
+        """
+        DebugVariableViewer.__init__(self, window, items)
+        
+        self.GraphType = graph_type        # Graph type display
+        self.CursorTick = None             # Tick of the graph cursor
+        
+        # Mouse position when start dragging
+        self.MouseStartPos = None
+        # Tick when moving tick start
+        self.StartCursorTick = None
+        # Canvas size when starting to resize canvas
+        self.CanvasStartSize = None        
+        
+        # List of current displayed contextual buttons
+        self.ContextualButtons = []
+        # Reference to item for which contextual buttons was displayed
+        self.ContextualButtonsItem = None
+        
+        # Flag indicating that zoom fit current displayed data range or whole
+        # data range if False
+        self.ZoomFit = False
+        
+        # Create figure for drawing graphs
+        self.Figure = matplotlib.figure.Figure(facecolor='w')
+        # Defined border around figure in canvas
+        self.Figure.subplotpars.update(top=0.95, left=0.1, 
+                                       bottom=0.1, right=0.95)
+        
+        FigureCanvas.__init__(self, parent, -1, self.Figure)
+        self.SetWindowStyle(wx.WANTS_CHARS)
+        self.SetBackgroundColour(wx.WHITE)
+        
+        # Bind wx events
+        self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
+        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+        self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
+        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+        self.Bind(wx.EVT_SIZE, self.OnResize)
+        
+        # Set canvas min size
+        canvas_size = self.GetCanvasMinSize()
+        self.SetMinSize(canvas_size)
+        
+        # Define Viewer drop target
+        self.SetDropTarget(DebugVariableGraphicDropTarget(self, window))
+        
+        # Connect matplotlib events
+        self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
+        self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
+        self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
+        self.mpl_connect('scroll_event', self.OnCanvasScroll)
+        
+        # Add buttons for zooming on current displayed data range
+        self.Buttons.append(
+                GraphButton(0, 0, "fit_graph", self.OnZoomFitButton))
+        
+        # Add buttons for changing canvas size with predefined height
+        for size, bitmap in zip(
+                [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
+                ["minimize_graph", "middle_graph", "maximize_graph"]):
+            self.Buttons.append(
+                    GraphButton(0, 0, bitmap, 
+                                self.GetOnChangeSizeButton(size)))
+        
+        # Add buttons for exporting graph values to clipboard and close graph
+        for bitmap, callback in [
+                ("export_graph_mini", self.OnExportGraphButton),
+                ("delete_graph", self.OnCloseButton)]:
+            self.Buttons.append(GraphButton(0, 0, bitmap, callback))
+        
+        # Update graphs elements
+        self.ResetGraphics()
+        self.RefreshLabelsPosition(canvas_size.height)
+    
+    def AddItem(self, item):
+        """
+        Add an item to the list of items displayed by Viewer
+        @param item: Item to add to the list
+        """
+        DebugVariableViewer.AddItem(self, item)
+        self.ResetGraphics()
+        
+    def RemoveItem(self, item):
+        """
+        Remove an item from the list of items displayed by Viewer
+        @param item: Item to remove from the list
+        """
+        DebugVariableViewer.RemoveItem(self, item)
+        
+        # If list of items is not empty
+        if not self.ItemsIsEmpty():
+            # Return to parallel graph if there is only one item
+            # especially if it's actually orthogonal
+            if len(self.Items) == 1:
+                self.GraphType = GRAPH_PARALLEL
+            self.ResetGraphics()
+    
+    def SetCursorTick(self, cursor_tick):
+        """
+        Set cursor tick
+        @param cursor_tick: Cursor tick
+        """
+        self.CursorTick = cursor_tick
+    
+    def SetZoomFit(self, zoom_fit):
+        """
+        Set flag indicating that zoom fit current displayed data range
+        @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range)
+        """
+        # Flag is different from the actual one 
+        if zoom_fit != self.ZoomFit:
+            # Save new flag value
+            self.ZoomFit = zoom_fit
+            
+            # Update button for zoom fit bitmap
+            self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph")
+            
+            # Refresh canvas
+            self.RefreshViewer()
+        
+    def SubscribeAllDataConsumers(self):
+        """
+        Function that unsubscribe and remove every item that store values of
+        a variable that doesn't exist in PLC anymore
+        """
+        DebugVariableViewer.SubscribeAllDataConsumers(self)
+        
+        # Graph still have data to display
+        if not self.ItemsIsEmpty():
+            # Reset flag indicating that zoom fit current displayed data range
+            self.SetZoomFit(False)
+            
+            self.ResetGraphics()
+    
+    def Is3DCanvas(self):
+        """
+        Return if Viewer is a 3D canvas
+        @return: True if Viewer is a 3D canvas
+        """
+        return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
+    
+    def GetButtons(self):
+        """
+        Return list of buttons defined in Viewer
+        @return: List of buttons
+        """
+        # Add contextual buttons to default buttons
+        return self.Buttons + self.ContextualButtons
+    
+    def PopupContextualButtons(self, item, rect, direction=wx.RIGHT):
+        """
+        Show contextual menu for item aside a label of this item defined
+        by the bounding box of label in figure
+        @param item: Item for which contextual is shown
+        @param rect: Bounding box of label aside which drawing menu
+        @param direction: Direction in which buttons must be drawn
+        """
+        # Return immediately if contextual menu for item is already shown
+        if self.ContextualButtonsItem == item:
+            return
+        
+        # Close already shown contextual menu
+        self.DismissContextualButtons()
+        
+        # Save item for which contextual menu is shown
+        self.ContextualButtonsItem = item
+        
+        # If item variable is forced, add button for release variable to
+        # contextual menu
+        if self.ContextualButtonsItem.IsForced():
+            self.ContextualButtons.append(
+                GraphButton(0, 0, "release", self.OnReleaseItemButton))
+        
+        # Add other buttons to contextual menu
+        for bitmap, callback in [
+                ("force", self.OnForceItemButton),
+                ("export_graph_mini", self.OnExportItemGraphButton),
+                ("delete_graph", self.OnRemoveItemButton)]:
+            self.ContextualButtons.append(
+                    GraphButton(0, 0, bitmap, callback))
+        
+        # If buttons are shown at left side or upper side of rect, positions
+        # will be set in reverse order
+        buttons = self.ContextualButtons[:]
+        if direction in [wx.TOP, wx.LEFT]:
+             buttons.reverse()
+             
+        # Set contextual menu buttons position aside rect depending on
+        # direction given
+        offset = 0
+        for button in buttons:
+            w, h = button.GetSize()
+            if direction in [wx.LEFT, wx.RIGHT]:
+                x = rect.x + (- w - offset
+                            if direction == wx.LEFT
+                            else rect.width + offset)
+                y = rect.y + (rect.height - h) / 2
+                offset += w
+            else:
+                x = rect.x + (rect.width - w ) / 2
+                y = rect.y + (- h - offset
+                              if direction == wx.TOP
+                              else rect.height + offset)
+                offset += h
+            button.SetPosition(x, y)
+            button.Show()
+        
+        # Refresh canvas
+        self.ParentWindow.ForceRefresh()
+    
+    def DismissContextualButtons(self):
+        """
+        Close current shown contextual menu
+        """
+        # Return immediately if no contextual menu is shown
+        if self.ContextualButtonsItem is None:
+            return
+        
+        # Reset variables corresponding to contextual menu
+        self.ContextualButtonsItem = None
+        self.ContextualButtons = []
+        
+        # Refresh canvas
+        self.ParentWindow.ForceRefresh()
+    
+    def IsOverContextualButton(self, x, y):
+        """
+        Return if point is over one contextual button of Viewer
+        @param x: X coordinate of point
+        @param y: Y coordinate of point
+        @return: contextual button where point is over
+        """
+        for button in self.ContextualButtons:
+            if button.HitTest(x, y):
+                return button
+        return None
+    
+    def ExportGraph(self, item=None):
+        """
+        Export item(s) data to clipboard in CSV format
+        @param item: Item from which data to export, all items if None
+        (default None)
+        """
+        self.ParentWindow.CopyDataToClipboard(
+            [(item, [entry for entry in item.GetData()])
+             for item in (self.Items 
+                          if item is None 
+                          else [item])])
+    
+    def OnZoomFitButton(self):
+        """
+        Function called when Viewer Zoom Fit button is pressed
+        """
+        # Toggle zoom fit flag value
+        self.SetZoomFit(not self.ZoomFit)
+        
+    def GetOnChangeSizeButton(self, height):
+        """
+        Function that generate callback function for change Viewer height to
+        pre-defined height button
+        @param height: Height that change Viewer to
+        @return: callback function
+        """
+        def OnChangeSizeButton():
+            self.SetCanvasHeight(height)
+        return OnChangeSizeButton
+    
+    def OnExportGraphButton(self):
+        """
+        Function called when Viewer Export button is pressed
+        """
+        # Export data of every item in Viewer
+        self.ExportGraph()
+    
+    def OnForceItemButton(self):
+        """
+        Function called when contextual menu Force button is pressed
+        """
+        # Open dialog for forcing item variable value 
+        self.ForceValue(self.ContextualButtonsItem)
+        # Close contextual menu
+        self.DismissContextualButtons()
+        
+    def OnReleaseItemButton(self):
+        """
+        Function called when contextual menu Release button is pressed
+        """
+        # Release item variable value 
+        self.ReleaseValue(self.ContextualButtonsItem)
+        # Close contextual menu
+        self.DismissContextualButtons()
+    
+    def OnExportItemGraphButton(self):
+        """
+        Function called when contextual menu Export button is pressed
+        """
+        # Export data of item variable
+        self.ExportGraph(self.ContextualButtonsItem)
+        # Close contextual menu
+        self.DismissContextualButtons()
+        
+    def OnRemoveItemButton(self):            
+        """
+        Function called when contextual menu Remove button is pressed
+        """
+        # Remove item from Viewer
+        wx.CallAfter(self.ParentWindow.DeleteValue, self, 
+                     self.ContextualButtonsItem)
+        # Close contextual menu
+        self.DismissContextualButtons()
+    
+    def HandleCursorMove(self, event):
+        """
+        Update Cursor position according to mouse position and graph type
+        @param event: Mouse event
+        """
+        start_tick, end_tick = self.ParentWindow.GetRange()
+        cursor_tick = None
+        items = self.ItemsDict.values()
+        
+        # Graph is orthogonal
+        if self.GraphType == GRAPH_ORTHOGONAL:
+            # Extract items data displayed in canvas figure
+            start_tick = max(start_tick, self.GetItemsMinCommonTick())
+            end_tick = max(end_tick, start_tick)
+            x_data = items[0].GetData(start_tick, end_tick)
+            y_data = items[1].GetData(start_tick, end_tick)
+            
+            # Search for the nearest point from mouse position
+            if len(x_data) > 0 and len(y_data) > 0:
+                length = min(len(x_data), len(y_data)) 
+                d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + \
+                               (y_data[:length,1]-event.ydata) ** 2)
+                
+                # Set cursor tick to the tick of this point
+                cursor_tick = x_data[numpy.argmin(d), 0]
+        
+        # Graph is parallel
+        else:
+            # Extract items tick
+            data = items[0].GetData(start_tick, end_tick)
+            
+            # Search for point that tick is the nearest from mouse X position
+            # and set cursor tick to the tick of this point
+            if len(data) > 0:
+                cursor_tick = data[numpy.argmin(
+                        numpy.abs(data[:,0] - event.xdata)), 0]
+        
+        # Update cursor tick
+        if cursor_tick is not None:
+            self.ParentWindow.SetCursorTick(cursor_tick)
+    
+    def OnCanvasButtonPressed(self, event):
+        """
+        Function called when a button of mouse is pressed
+        @param event: Mouse event
+        """
+        # Get mouse position, graph Y coordinate is inverted in matplotlib
+        # comparing to wx
+        width, height = self.GetSize()
+        x, y = event.x, height - event.y
+        
+        # Return immediately if mouse is over a button
+        if self.IsOverButton(x, y):
+            return 
+        
+        # Mouse was clicked inside graph figure
+        if event.inaxes == self.Axes:
+            
+            # Find if it was on an item label
+            item_idx = None
+            # Check every label paired with corresponding item
+            for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
+                         [pair for pair in enumerate(self.Labels)]):
+                # Get label bounding box
+                (x0, y0), (x1, y1) = t.get_window_extent().get_points()
+                rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
+                # Check if mouse was over label
+                if rect.InsideXY(x, y):
+                    item_idx = i
+                    break
+            
+            # If an item label have been clicked
+            if item_idx is not None:
+                # Hide buttons and contextual buttons
+                self.ShowButtons(False)
+                self.DismissContextualButtons()
+                
+                # Start a drag'n drop from mouse position in wx coordinate of
+                # parent
+                xw, yw = self.GetPosition()
+                self.ParentWindow.StartDragNDrop(self, 
+                    self.ItemsDict.values()[item_idx], 
+                    x + xw, y + yw, # Current mouse position
+                    x + xw, y + yw) # Mouse position when button was clicked
+            
+            # Don't handle mouse button if canvas is 3D and let matplotlib do
+            # the default behavior (rotate 3D axes)
+            elif not self.Is3DCanvas():
+                # Save mouse position when clicked
+                self.MouseStartPos = wx.Point(x, y)
+                
+                # Mouse button was left button, start moving cursor
+                if event.button == 1:
+                    # Save current tick in case a drag'n drop is initiate to
+                    # restore it
+                    self.StartCursorTick = self.CursorTick
+                    
+                    self.HandleCursorMove(event)
+                    
+                # Mouse button is middle button and graph is parallel, start
+                # moving graph along X coordinate (tick)
+                elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
+                    self.StartCursorTick = self.ParentWindow.GetRange()[0]
+        
+        # Mouse was clicked outside graph figure and over resize highlight with
+        # left button, start resizing Viewer
+        elif event.button == 1 and event.y <= 5:
+            self.MouseStartPos = wx.Point(x, y)
+            self.CanvasStartSize = height
+    
+    def OnCanvasButtonReleased(self, event):
+        """
+        Function called when a button of mouse is released
+        @param event: Mouse event
+        """
+        # If a drag'n drop is in progress, stop it
+        if self.ParentWindow.IsDragging():
+            width, height = self.GetSize()
+            xw, yw = self.GetPosition()
+            item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0]
+            # Give mouse position in wx coordinate of parent
+            self.ParentWindow.StopDragNDrop(item.GetVariable(),
+                xw + event.x, yw + height - event.y)
+        
+        else:
+            # Reset any move in progress
+            self.MouseStartPos = None
+            self.CanvasStartSize = None
+            
+            # Handle button under mouse if it exist
+            width, height = self.GetSize()
+            self.HandleButton(event.x, height - event.y)
+    
+    def OnCanvasMotion(self, event):
+        """
+        Function called when a button of mouse is moved over Viewer
+        @param event: Mouse event
+        """
+        width, height = self.GetSize()
+        
+        # If a drag'n drop is in progress, move canvas dragged
+        if self.ParentWindow.IsDragging():
+            xw, yw = self.GetPosition()
+            # Give mouse position in wx coordinate of parent
+            self.ParentWindow.MoveDragNDrop(
+                xw + event.x, 
+                yw + height - event.y)
+        
+        # If a Viewer resize is in progress, change Viewer size 
+        elif event.button == 1 and self.CanvasStartSize is not None:
+            width, height = self.GetSize()
+            self.SetCanvasHeight(
+                self.CanvasStartSize + height - event.y - self.MouseStartPos.y)
+        
+        # If no button is pressed, show or hide contextual buttons or resize
+        # highlight
+        elif event.button is None:
+            # Compute direction for items label according graph type
+            if self.GraphType == GRAPH_PARALLEL: # Graph is parallel
+                directions = [wx.RIGHT] * len(self.AxesLabels) + \
+                             [wx.LEFT] * len(self.Labels)
+            elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D
+                directions = [wx.RIGHT, wx.TOP,  # Directions for AxesLabels
+                             wx.LEFT, wx.BOTTOM] # Directions for Labels
+            else: # Graph is orthogonal in 3D
+                directions = [wx.LEFT] * len(self.Labels)
+            
+            # Find if mouse is over an item label
+            item_idx = None
+            menu_direction = None
+            for (i, t), dir in zip(
+                    [pair for pair in enumerate(self.AxesLabels)] + 
+                    [pair for pair in enumerate(self.Labels)], 
+                    directions):
+                # Check every label paired with corresponding item
+                (x0, y0), (x1, y1) = t.get_window_extent().get_points()
+                rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
+                # Check if mouse was over label
+                if rect.InsideXY(event.x, height - event.y):
+                    item_idx = i
+                    menu_direction = dir
+                    break
+            
+            # If mouse is over an item label, 
+            if item_idx is not None:
+                self.PopupContextualButtons(
+                    self.ItemsDict.values()[item_idx], 
+                    rect, menu_direction)
+                return
+            
+            # If mouse isn't over a contextual menu, hide the current shown one
+            # if it exists 
+            if self.IsOverContextualButton(event.x, height - event.y) is None:
+                self.DismissContextualButtons()
+            
+            # Update resize highlight
+            if event.y <= 5:
+                if self.SetHighlight(HIGHLIGHT_RESIZE):
+                    self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
+                    self.ParentWindow.ForceRefresh()
+            else:
+                if self.SetHighlight(HIGHLIGHT_NONE):
+                    self.SetCursor(wx.NullCursor)
+                    self.ParentWindow.ForceRefresh()
+        
+        # Handle buttons if canvas is not 3D 
+        elif not self.Is3DCanvas():
+            
+            # If left button is pressed
+            if event.button == 1:
+                
+                # Mouse is inside graph figure
+                if event.inaxes == self.Axes:
+                    
+                    # If a cursor move is in progress, update cursor position
+                    if self.MouseStartPos is not None:
+                        self.HandleCursorMove(event)
+                
+                # Mouse is outside graph figure, cursor move is in progress and
+                # there is only one item in Viewer, start a drag'n drop
+                elif self.MouseStartPos is not None and len(self.Items) == 1:
+                    xw, yw = self.GetPosition()
+                    self.ParentWindow.SetCursorTick(self.StartCursorTick)
+                    self.ParentWindow.StartDragNDrop(self, 
+                        self.ItemsDict.values()[0],
+                        # Current mouse position
+                        event.x + xw, height - event.y + yw,
+                        # Mouse position when button was clicked
+                        self.MouseStartPos.x + xw,
+                        self.MouseStartPos.y + yw)
+            
+            # If middle button is pressed and moving graph along X coordinate
+            # is in progress
+            elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \
+                 self.MouseStartPos is not None:
+                start_tick, end_tick = self.ParentWindow.GetRange()
+                rect = self.GetAxesBoundingBox()
+                
+                # Move graph along X coordinate
+                self.ParentWindow.SetCanvasPosition(
+                    self.StartCursorTick + 
+                    (self.MouseStartPos.x - event.x) *
+                    (end_tick - start_tick) / rect.width)
+    
+    def OnCanvasScroll(self, event):
+        """
+        Function called when a wheel mouse is use in Viewer
+        @param event: Mouse event
+        """
+        # Change X range of graphs if mouse is in canvas figure and ctrl is
+        # pressed
+        if event.inaxes is not None and event.guiEvent.ControlDown():
+            
+            # Calculate position of fixed tick point according to graph type
+            # and mouse position
+            if self.GraphType == GRAPH_ORTHOGONAL:
+                start_tick, end_tick = self.ParentWindow.GetRange()
+                tick = (start_tick + end_tick) / 2.
+            else:
+                tick = event.xdata
+            self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
+            
+            # Vetoing event to prevent parent panel to be scrolled
+            self.ParentWindow.VetoScrollEvent = True
+    
+    def OnLeftDClick(self, event):
+        """
+        Function called when a left mouse button is double clicked
+        @param event: Mouse event
+        """
+        # Check that double click was done inside figure
+        pos = event.GetPosition()
+        rect = self.GetAxesBoundingBox()
+        if rect.InsideXY(pos.x, pos.y):
+            # Reset Cursor tick to value before starting clicking
+            self.ParentWindow.SetCursorTick(self.StartCursorTick)
+            # Toggle to text Viewer(s)
+            self.ParentWindow.ToggleViewerType(self)
+        
+        else:
+            event.Skip()
+    
+    # Cursor tick move for each arrow key
+    KEY_CURSOR_INCREMENT = {
+        wx.WXK_LEFT: -1,
+        wx.WXK_RIGHT: 1,
+        wx.WXK_UP: 10,
+        wx.WXK_DOWN: -10}
+    
+    def OnKeyDown(self, event):
+        """
+        Function called when key is pressed
+        @param event: wx.KeyEvent
+        """
+        # If cursor is shown and arrow key is pressed, move cursor tick
+        if self.CursorTick is not None:
+            move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
+            if move is not None:
+                self.ParentWindow.MoveCursorTick(move)
+        event.Skip()
+    
+    def OnLeave(self, event):
+        """
+        Function called when mouse leave Viewer
+        @param event: wx.MouseEvent
+        """
+        # If Viewer is not resizing, reset resize highlight
+        if self.CanvasStartSize is None:
+            self.SetHighlight(HIGHLIGHT_NONE)
+            self.SetCursor(wx.NullCursor)
+            DebugVariableViewer.OnLeave(self, event)
+        else:
+            event.Skip()
+    
+    def GetCanvasMinSize(self):
+        """
+        Return the minimum size of Viewer so that all items label can be
+        displayed
+        @return: wx.Size containing Viewer minimum size
+        """
+        # The minimum height take in account the height of all items, padding
+        # inside figure and border around figure
+        return wx.Size(200, 
+            CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
+            2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
+    
+    def SetCanvasHeight(self, height):
+        """
+        Set Viewer size checking that it respects Viewer minimum size
+        @param height: Viewer height
+        """
+        min_width, min_height = self.GetCanvasMinSize()
+        height = max(height, min_height)
+        self.SetMinSize(wx.Size(min_width, height))
+        self.RefreshLabelsPosition(height)
+        self.ParentWindow.RefreshGraphicsSizer()
+        
+    def GetAxesBoundingBox(self, parent_coordinate=False):
+        """
+        Return figure bounding box in wx coordinate
+        @param parent_coordinate: True if use parent coordinate (default False)
+        """
+        # Calculate figure bounding box. Y coordinate is inverted in matplotlib
+        # figure comparing to wx panel
+        width, height = self.GetSize()
+        ax, ay, aw, ah = self.figure.gca().get_position().bounds
+        bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
+                       aw * width + 2, ah * height + 1)
+        
+        # If parent_coordinate, add Viewer position in parent
+        if parent_coordinate:
+            xw, yw = self.GetPosition()
+            bbox.x += xw
+            bbox.y += yw
+        
+        return bbox
+    
+    def RefreshHighlight(self, x, y):
+        """
+        Refresh Viewer highlight according to mouse position
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        """
+        width, height = self.GetSize()
+        
+        # Mouse is over Viewer figure and graph is not 3D
+        bbox = self.GetAxesBoundingBox()
+        if bbox.InsideXY(x, y) and not self.Is3DCanvas():
+            rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
+            # Mouse is over Viewer left part of figure
+            if rect.InsideXY(x, y):
+                self.SetHighlight(HIGHLIGHT_LEFT)
+            
+            # Mouse is over Viewer right part of figure
+            else:
+                self.SetHighlight(HIGHLIGHT_RIGHT)
+        
+        # Mouse is over upper part of Viewer
+        elif y < height / 2:
+            # Viewer is upper one in Debug Variable Panel, show highlight
+            if self.ParentWindow.IsViewerFirst(self):
+                self.SetHighlight(HIGHLIGHT_BEFORE)
+            
+            # Viewer is not the upper one, show highlight in previous one
+            # It prevents highlight to move when mouse leave one Viewer to
+            # another
+            else:
+                self.SetHighlight(HIGHLIGHT_NONE)
+                self.ParentWindow.HighlightPreviousViewer(self)
+        
+        # Mouse is over lower part of Viewer
+        else:
+            self.SetHighlight(HIGHLIGHT_AFTER)
+    
+    def OnAxesMotion(self, event):
+        """
+        Function overriding default function called when mouse is dragged for
+        rotating graph preventing refresh to be called too quickly
+        @param event: Mouse event
+        """
+        if self.Is3DCanvas():
+            # Call default function at most 10 times per second
+            current_time = gettime()
+            if current_time - self.LastMotionTime > REFRESH_PERIOD:
+                self.LastMotionTime = current_time
+                Axes3D._on_move(self.Axes, event)
+    
+    def GetAddTextFunction(self):
+        """
+        Return function for adding text in figure according to graph type
+        @return: Function adding text to figure
+        """
+        text_func = (self.Axes.text2D if self.Is3DCanvas() else self.Axes.text)
+        def AddText(*args, **kwargs):
+            args = [0, 0, ""]
+            kwargs["transform"] = self.Axes.transAxes
+            return text_func(*args, **kwargs)
+        return AddText
+    
+    def ResetGraphics(self):
+        """
+        Reset figure and graphical elements displayed in it
+        Called any time list of items or graph type change 
+        """
+        # Clear figure from any axes defined
+        self.Figure.clear()
+        
+        # Add 3D projection if graph is in 3D
+        if self.Is3DCanvas():
+            self.Axes = self.Figure.gca(projection='3d')
+            self.Axes.set_color_cycle(['b'])
+            
+            # Override function to prevent too much refresh when graph is 
+            # rotated
+            self.LastMotionTime = gettime()
+            setattr(self.Axes, "_on_move", self.OnAxesMotion)
+            
+            # Init graph mouse event so that graph can be rotated
+            self.Axes.mouse_init()
+            
+            # Set size of Z axis labels
+            self.Axes.tick_params(axis='z', labelsize='small')
+        
+        else:
+            self.Axes = self.Figure.gca()
+            self.Axes.set_color_cycle(COLOR_CYCLE)
+        
+        # Set size of X and Y axis labels
+        self.Axes.tick_params(axis='x', labelsize='small')
+        self.Axes.tick_params(axis='y', labelsize='small')
+        
+        # Init variables storing graphical elements added to figure
+        self.Plots = []      # List of curves
+        self.VLine = None    # Vertical line for cursor
+        self.HLine = None    # Horizontal line for cursor (only orthogonal 2D)
+        self.AxesLabels = [] # List of items variable path text label
+        self.Labels = []     # List of items text label
+        
+        # Get function to add a text in figure according to graph type 
+        add_text_func = self.GetAddTextFunction()
+        
+        # Graph type is parallel or orthogonal in 3D
+        if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
+            num_item = len(self.Items)
+            for idx in xrange(num_item):
+                
+                # Get color from color cycle (black if only one item)
+                color = ('k' if num_item == 1
+                             else COLOR_CYCLE[idx % len(COLOR_CYCLE)])
+                
+                # In 3D graph items variable label are not displayed as text
+                # in figure, but as axis title
+                if not self.Is3DCanvas():
+                    # Items variable labels are in figure upper left corner
+                    self.AxesLabels.append(
+                        add_text_func(size='small', color=color,
+                                      verticalalignment='top'))
+                
+                # Items variable labels are in figure lower right corner
+                self.Labels.append(
+                    add_text_func(size='large', color=color, 
+                                  horizontalalignment='right'))
+        
+        # Graph type is orthogonal in 2D
+        else:
+            # X coordinate labels are in figure lower side
+            self.AxesLabels.append(add_text_func(size='small'))
+            self.Labels.append(
+                add_text_func(size='large',
+                              horizontalalignment='right'))
+            
+            # Y coordinate labels are vertical and in figure left side
+            self.AxesLabels.append(
+                add_text_func(size='small', rotation='vertical',
+                              verticalalignment='bottom'))
+            self.Labels.append(
+                add_text_func(size='large', rotation='vertical',
+                              verticalalignment='top'))
+       
+        # Refresh position of labels according to Viewer size
+        width, height = self.GetSize()
+        self.RefreshLabelsPosition(height)
+    
+    def RefreshLabelsPosition(self, height):
+        """
+        Function called when mouse leave Viewer
+        @param event: wx.MouseEvent
+        """
+        # Figure position like text position in figure are expressed is ratio
+        # canvas size and figure size. As we want that border around figure and
+        # text position in figure don't change when canvas size change, we
+        # expressed border and text position in pixel on screen and apply the
+        # ratio calculated hereafter to get border and text position in
+        # matplotlib coordinate
+        canvas_ratio = 1. / height # Divide by canvas height in pixel
+        graph_ratio = 1. / (
+            (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio)
+             * height)             # Divide by figure height in pixel
+        
+        # Update position of figure (keeping up and bottom border the same
+        # size)
+        self.Figure.subplotpars.update(
+            top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
+            bottom= CANVAS_BORDER[0] * canvas_ratio)
+        
+        # Update position of items labels
+        if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
+            num_item = len(self.Items)
+            for idx in xrange(num_item):
+                
+                # In 3D graph items variable label are not displayed
+                if not self.Is3DCanvas():
+                    # Items variable labels are in figure upper left corner
+                    self.AxesLabels[idx].set_position(
+                        (0.05, 
+                         1.0 - (CANVAS_PADDING + 
+                                AXES_LABEL_HEIGHT * idx) * graph_ratio))
+                
+                # Items variable labels are in figure lower right corner
+                self.Labels[idx].set_position(
+                    (0.95, 
+                     CANVAS_PADDING * graph_ratio + 
+                     (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
+        else:
+            # X coordinate labels are in figure lower side
+            self.AxesLabels[0].set_position(
+                    (0.1, CANVAS_PADDING * graph_ratio))
+            self.Labels[0].set_position(
+                    (0.95, CANVAS_PADDING * graph_ratio))
+            
+            # Y coordinate labels are vertical and in figure left side
+            self.AxesLabels[1].set_position(
+                    (0.05, 2 * CANVAS_PADDING * graph_ratio))
+            self.Labels[1].set_position(
+                    (0.05, 1.0 - CANVAS_PADDING * graph_ratio))
+        
+        # Update subplots
+        self.Figure.subplots_adjust()
+    
+    def RefreshViewer(self, refresh_graphics=True):
+        """
+        Function called to refresh displayed by matplotlib canvas
+        @param refresh_graphics: Flag indicating that graphs have to be
+        refreshed (False: only label values have to be refreshed)
+        """
+        # Refresh graphs if needed
+        if refresh_graphics:
+            # Get tick range of values to display
+            start_tick, end_tick = self.ParentWindow.GetRange()
+            
+            # Graph is parallel
+            if self.GraphType == GRAPH_PARALLEL:    
+                # Init list of data range for each variable displayed
+                ranges = []
+                
+                # Get data and range for each variable displayed
+                for idx, item in enumerate(self.Items):
+                    data, min_value, max_value = item.GetDataAndValueRange(
+                                start_tick, end_tick, not self.ZoomFit)
+                    
+                    # Check that data is not empty
+                    if data is not None:
+                        # Add variable range to list of variable data range
+                        ranges.append((min_value, max_value))
+                        
+                        # Add plot to canvas if not yet created
+                        if len(self.Plots) <= idx:
+                            self.Plots.append(
+                                self.Axes.plot(data[:, 0], data[:, 1])[0])
+                        
+                        # Set data to already created plot in canvas
+                        else:
+                            self.Plots[idx].set_data(data[:, 0], data[:, 1])
+                
+                # Get X and Y axis ranges
+                x_min, x_max = start_tick, end_tick
+                y_min, y_max = merge_ranges(ranges)
+                
+                # Display cursor in canvas if a cursor tick is defined and it is
+                # include in values tick range
+                if (self.CursorTick is not None and 
+                    start_tick <= self.CursorTick <= end_tick):
+                    
+                    # Define a vertical line to display cursor position if no
+                    # line is already defined
+                    if self.VLine is None:
+                        self.VLine = self.Axes.axvline(self.CursorTick, 
+                                                       color=CURSOR_COLOR)
+                    
+                    # Set value of vertical line if already defined
+                    else:
+                        self.VLine.set_xdata((self.CursorTick, self.CursorTick))
+                    self.VLine.set_visible(True)
+                
+                # Hide vertical line if cursor tick is not defined or reset
+                elif self.VLine is not None:
+                    self.VLine.set_visible(False)
+            
+            # Graph is orthogonal
+            else:
+                # Update tick range, removing ticks that don't have a value for
+                # each variable
+                start_tick = max(start_tick, self.GetItemsMinCommonTick())
+                end_tick = max(end_tick, start_tick)
+                items = self.ItemsDict.values()
+                
+                # Get data and range for first variable (X coordinate)
+                x_data, x_min, x_max = items[0].GetDataAndValueRange(
+                                        start_tick, end_tick, not self.ZoomFit)
+                # Get data and range for second variable (Y coordinate)
+                y_data, y_min, y_max = items[1].GetDataAndValueRange(
+                                        start_tick, end_tick, not self.ZoomFit)
+                
+                # Normalize X and Y coordinates value range
+                x_min, x_max = merge_ranges([(x_min, x_max)])
+                y_min, y_max = merge_ranges([(y_min, y_max)])
+                
+                # Get X and Y coordinates for cursor if cursor tick is defined 
+                if self.CursorTick is not None:
+                    x_cursor, x_forced = items[0].GetValue(
+                                            self.CursorTick, raw=True)
+                    y_cursor, y_forced = items[1].GetValue(
+                                            self.CursorTick, raw=True)
+                
+                # Get common data length so that each value has an x and y
+                # coordinate
+                length = (min(len(x_data), len(y_data))
+                          if x_data is not None and y_data is not None
+                          else 0)
+                
+                # Graph is orthogonal 2D 
+                if len(self.Items) < 3:
+                    
+                    # Check that x and y data are not empty
+                    if x_data is not None and y_data is not None:
+                        
+                        # Add plot to canvas if not yet created
+                        if len(self.Plots) == 0:
+                            self.Plots.append(
+                                self.Axes.plot(x_data[:, 1][:length], 
+                                               y_data[:, 1][:length])[0])
+                        
+                        # Set data to already created plot in canvas
+                        else:
+                            self.Plots[0].set_data(
+                                x_data[:, 1][:length], 
+                                y_data[:, 1][:length])
+                    
+                    # Display cursor in canvas if a cursor tick is defined and it is
+                    # include in values tick range
+                    if (self.CursorTick is not None and 
+                        start_tick <= self.CursorTick <= end_tick):
+                        
+                        # Define a vertical line to display cursor x coordinate
+                        # if no line is already defined
+                        if self.VLine is None:
+                            self.VLine = self.Axes.axvline(x_cursor, 
+                                                           color=CURSOR_COLOR)
+                        # Set value of vertical line if already defined
+                        else:
+                            self.VLine.set_xdata((x_cursor, x_cursor))
+                        
+                        
+                        # Define a horizontal line to display cursor y
+                        # coordinate if no line is already defined
+                        if self.HLine is None:
+                            self.HLine = self.Axes.axhline(y_cursor, 
+                                                           color=CURSOR_COLOR)
+                        # Set value of horizontal line if already defined
+                        else:
+                            self.HLine.set_ydata((y_cursor, y_cursor))
+                        
+                        self.VLine.set_visible(True)
+                        self.HLine.set_visible(True)
+                    
+                    # Hide vertical and horizontal line if cursor tick is not
+                    # defined or reset
+                    else:
+                        if self.VLine is not None:
+                            self.VLine.set_visible(False)
+                        if self.HLine is not None:
+                            self.HLine.set_visible(False)
+                
+                # Graph is orthogonal 3D
+                else:
+                    # Remove all plots already defined in 3D canvas
+                    while len(self.Axes.lines) > 0:
+                        self.Axes.lines.pop()
+                    
+                    # Get data and range for third variable (Z coordinate)
+                    z_data, z_min, z_max = items[2].GetDataAndValueRange(
+                                    start_tick, end_tick, not self.ZoomFit)
+                    
+                    # Normalize Z coordinate value range
+                    z_min, z_max = merge_ranges([(z_min, z_max)])
+                    
+                    # Check that x, y and z data are not empty
+                    if (x_data is not None and y_data is not None and 
+                        z_data is not None):
+                        
+                        # Get common data length so that each value has an x, y
+                        # and z coordinate
+                        length = min(length, len(z_data))
+                        
+                        # Add plot to canvas
+                        self.Axes.plot(x_data[:, 1][:length],
+                                       y_data[:, 1][:length],
+                                       zs = z_data[:, 1][:length])
+                    
+                    # Display cursor in canvas if a cursor tick is defined and
+                    # it is include in values tick range
+                    if (self.CursorTick is not None and 
+                        start_tick <= self.CursorTick <= end_tick):
+                        
+                        # Get Z coordinate for cursor
+                        z_cursor, z_forced = items[2].GetValue(
+                                                self.CursorTick, raw=True)
+                        
+                        # Add 3 lines parallel to x, y and z axis to display
+                        # cursor position in 3D
+                        for kwargs in [{"xs": numpy.array([x_min, x_max])},
+                                       {"ys": numpy.array([y_min, y_max])},
+                                       {"zs": numpy.array([z_min, z_max])}]:
+                            for param, value in [
+                                    ("xs", numpy.array([x_cursor, x_cursor])),
+                                    ("ys", numpy.array([y_cursor, y_cursor])),
+                                    ("zs", numpy.array([z_cursor, z_cursor]))]:
+                                kwargs.setdefault(param, value)
+                            kwargs["color"] = CURSOR_COLOR
+                            self.Axes.plot(**kwargs)
+                    
+                    # Set Z axis limits
+                    self.Axes.set_zlim(z_min, z_max)
+            
+            # Set X and Y axis limits
+            self.Axes.set_xlim(x_min, x_max)
+            self.Axes.set_ylim(y_min, y_max)
+        
+        # Get value and forced flag for each variable displayed in graph
+        # If cursor tick is not defined get value and flag of last received
+        # or get value and flag of variable at cursor tick
+        values, forced = apply(zip, [
+                (item.GetValue(self.CursorTick)
+                 if self.CursorTick is not None
+                 else (item.GetValue(), item.IsForced()))
+                for item in self.Items])
+        
+        # Get path of each variable displayed simplified using panel variable
+        # name mask
+        labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask()) 
+                  for item in self.Items]
+        
+        # Get style for each variable according to 
+        styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
+        
+        # Graph is orthogonal 3D, set variables path as 3D axis label
+        if self.Is3DCanvas():
+            for idx, label_func in enumerate([self.Axes.set_xlabel, 
+                                              self.Axes.set_ylabel,
+                                              self.Axes.set_zlabel]):
+                label_func(labels[idx], fontdict={'size': 'small',
+                                                  'color': COLOR_CYCLE[idx]})
+        
+        # Graph is not orthogonal 3D, set variables path in axes labels
+        else:
+            for label, text in zip(self.AxesLabels, labels):
+                label.set_text(text)
+        
+        # Set value label text and style according to value and forced flag for
+        # each variable displayed
+        for label, value, style in zip(self.Labels, values, styles):
+            label.set_text(value)
+            label.set_style(style)
+        
+        # Refresh figure
+        self.draw()
+
+    def draw(self, drawDC=None):
+        """
+        Render the figure.
+        """
+        # Render figure using agg
+        FigureCanvasAgg.draw(self)
+        
+        # Get bitmap of figure rendered
+        self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
+        self.bitmap.UseAlpha()
+        
+        # Create DC for rendering graphics in bitmap
+        destDC = wx.MemoryDC()
+        destDC.SelectObject(self.bitmap)
+        
+        # Get Graphics Context for DC, for anti-aliased and transparent
+        # rendering
+        destGC = wx.GCDC(destDC)
+        
+        destGC.BeginDrawing()
+        
+        # Get canvas size and figure bounding box in canvas
+        width, height = self.GetSize()
+        bbox = self.GetAxesBoundingBox()
+        
+        # If highlight to display is resize, draw thick grey line at bottom
+        # side of canvas 
+        if self.Highlight == HIGHLIGHT_RESIZE:
+            destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
+            destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
+            destGC.DrawRectangle(0, height - 5, width, 5)
+        
+        # If highlight to display is merging graph, draw 50% transparent blue
+        # rectangle on left or right part of figure depending on highlight type
+        elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]:
+            destGC.SetPen(HIGHLIGHT_DROP_PEN)
+            destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
+            
+            x_offset = (bbox.width / 2 
+                        if self.Highlight == HIGHLIGHT_RIGHT
+                        else 0)
+            destGC.DrawRectangle(bbox.x + x_offset, bbox.y, 
+                                 bbox.width / 2, bbox.height)
+        
+        # Draw other Viewer common elements
+        self.DrawCommonElements(destGC, self.GetButtons())
+        
+        destGC.EndDrawing()
+        
+        self._isDrawn = True
+        self.gui_repaint(drawDC=drawDC)
+    
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableItem.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,373 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import numpy
+import binascii
+
+from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR
+
+#-------------------------------------------------------------------------------
+#                 Constant for calculate CRC for string variables
+#-------------------------------------------------------------------------------
+
+STRING_CRC_SIZE = 8
+STRING_CRC_MASK = 2 ** STRING_CRC_SIZE - 1
+
+#-------------------------------------------------------------------------------
+#                          Debug Variable Item Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements an element that consumes debug values for PLC variable and
+stores received values for displaying them in graphic panel or table
+"""
+
+class DebugVariableItem(DebugDataConsumer):
+    
+    def __init__(self, parent, variable, store_data=False):
+        """
+        Constructor
+        @param parent: Reference to debug variable panel
+        @param variable: Path of variable to debug
+        """
+        DebugDataConsumer.__init__(self)
+        
+        self.Parent = parent
+        self.Variable = variable
+        self.StoreData = store_data
+        
+        # Get Variable data type
+        self.RefreshVariableType()
+        
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Reset reference to debug variable panel
+        self.Parent = None
+    
+    def SetVariable(self, variable):
+        """
+        Set path of variable 
+        @param variable: Path of variable to debug
+        """
+        if self.Parent is not None and self.Variable != variable:
+            # Store variable path
+            self.Variable = variable
+            # Get Variable data type
+            self.RefreshVariableType()
+            
+            # Refresh debug variable panel
+            self.Parent.RefreshView()
+    
+    def GetVariable(self, mask=None):
+        """
+        Return path of variable 
+        @param mask: Mask to apply to variable path [var_name, '*',...]
+        @return: String containing masked variable path
+        """
+        # Apply mask to variable name
+        if mask is not None:
+            # '#' correspond to parts that are different between all items
+            
+            # Extract variable path parts
+            parts = self.Variable.split('.')
+            # Adjust mask size to size of variable path
+            mask = mask + ['*'] * max(0, len(parts) - len(mask))
+            
+            # Store previous mask
+            last = None
+            # Init masked variable path
+            variable = ""
+            
+            for m, p in zip(mask, parts):
+                # Part is not masked, add part prefixed with '.' is previous
+                # wasn't masked
+                if m == '*':
+                    variable += ('.' if last == '*' else '') + p
+                
+                # Part is mask, add '..' if first or previous wasn't masked
+                elif last is None or last == '*':
+                    variable += '..'
+                
+                last = m
+            
+            return variable
+        
+        return self.Variable
+    
+    def RefreshVariableType(self):
+        """
+        Get and store variable data type
+        """
+        self.VariableType = self.Parent.GetDataType(self.Variable)
+        # Reset data stored
+        self.ResetData()
+    
+    def GetVariableType(self):
+        """
+        Return variable data type
+        @return: Variable data type
+        """
+        return self.VariableType
+    
+    def GetData(self, start_tick=None, end_tick=None):
+        """
+        Return data stored contained in given range
+        @param start_tick: Start tick of given range (default None, first data)
+        @param end_tick: end tick of given range (default None, last data)
+        @return: Data as numpy.array([(tick, value, forced),...])
+        """
+        # Return immediately if data empty or none
+        if self.Data is None or len(self.Data) == 0:
+            return self.Data
+        
+        # Find nearest data outside given range indexes
+        start_idx = (self.GetNearestData(start_tick, -1)
+                     if start_tick is not None
+                     else 0)
+        end_idx = (self.GetNearestData(end_tick, 1)
+                   if end_tick is not None
+                   else len(self.Data))
+        
+        # Return data between indexes
+        return self.Data[start_idx:end_idx]
+    
+    def GetRawValue(self, index):
+        """
+        Return raw value at given index for string variables
+        @param index: Variable value index
+        @return: Variable data type
+        """
+        if (self.VariableType in ["STRING", "WSTRING"] and
+            index < len(self.RawData)):
+            return self.RawData[index][0]
+        return ""
+    
+    def GetValueRange(self):
+        """
+        Return variable value range
+        @return: (minimum_value, maximum_value)
+        """
+        return self.MinValue, self.MaxValue
+    
+    def GetDataAndValueRange(self, start_tick, end_tick, full_range=True):
+        """
+        Return variable data and value range for a given tick range
+        @param start_tick: Start tick of given range (default None, first data)
+        @param end_tick: end tick of given range (default None, last data)
+        @param full_range: Value range is calculated on whole data (False: only
+        calculated on data in given range)
+        @return: (numpy.array([(tick, value, forced),...]), 
+                  min_value, max_value)
+        """
+        # Get data in given tick range
+        data = self.GetData(start_tick, end_tick)
+        
+        # Value range is calculated on whole data
+        if full_range:
+            return data, self.MinValue, self.MaxValue
+        
+        # Check that data in given range is not empty
+        values = data[:, 1]
+        if len(values) > 0:
+            # Return value range for data in given tick range
+            return (data,
+                    data[numpy.argmin(values), 1],
+                    data[numpy.argmax(values), 1])
+        
+        # Return default values
+        return data, None, None
+    
+    def ResetData(self):
+        """
+        Reset data stored when store data option enabled
+        """
+        if self.StoreData and self.IsNumVariable():
+            # Init table storing data
+            self.Data = numpy.array([]).reshape(0, 3)
+            
+            # Init table storing raw data if variable is strin
+            self.RawData = ([]
+                            if self.VariableType in ["STRING", "WSTRING"]
+                            else None)
+                
+            # Init Value range variables
+            self.MinValue = None
+            self.MaxValue = None
+        
+        else:
+            self.Data = None
+        
+        # Init variable value
+        self.Value = ""
+    
+    def IsNumVariable(self):
+        """
+        Return if variable data type is numeric. String variables are
+        considered as numeric (string CRC)
+        @return: True if data type is numeric
+        """
+        return (self.Parent.IsNumType(self.VariableType) or 
+                self.VariableType in ["STRING", "WSTRING"])
+    
+    def NewValue(self, tick, value, forced=False):
+        """
+        Function called by debug thread when a new debug value is available
+        @param tick: PLC tick when value was captured
+        @param value: Value captured
+        @param forced: Forced flag, True if value is forced (default: False)
+        """
+        DebugDataConsumer.NewValue(self, tick, value, forced, raw=None)
+        
+        if self.Data is not None:
+            # String data value is CRC
+            num_value = (binascii.crc32(value) & STRING_CRC_MASK
+                         if self.VariableType in ["STRING", "WSTRING"]
+                         else float(value))
+            
+            # Update variable range values
+            self.MinValue = (min(self.MinValue, num_value)
+                             if self.MinValue is not None
+                             else num_value)
+            self.MaxValue = (max(self.MaxValue, num_value)
+                             if self.MaxValue is not None
+                             else num_value)
+            
+            # Translate forced flag to float for storing in Data table
+            forced_value = float(forced)
+            
+            # In the case of string variables, we store raw string value and
+            # forced flag in raw data table. Only changes in this two values
+            # are stored. Index to the corresponding raw value is stored in 
+            # data third column
+            if self.VariableType in ["STRING", "WSTRING"]:
+                raw_data = (value, forced_value)
+                if len(self.RawData) == 0 or self.RawData[-1] != raw_data:
+                    extra_value = len(self.RawData)
+                    self.RawData.append(raw_data)
+                else:
+                    extra_value = len(self.RawData) - 1
+            
+            # In other case, data third column is forced flag
+            else:
+                extra_value = forced_value
+            
+            # Add New data to stored data table
+            self.Data = numpy.append(self.Data, 
+                    [[float(tick), num_value, extra_value]], axis=0)
+        
+            # Signal to debug variable panel to refresh
+            self.Parent.HasNewData = True
+        
+    def SetForced(self, forced):
+        """
+        Update Forced flag
+        @param forced: New forced flag
+        """
+        # Store forced flag
+        if self.Forced != forced:
+            self.Forced = forced
+            
+            # Signal to debug variable panel to refresh
+            self.Parent.HasNewData = True
+    
+    def SetValue(self, value):
+        """
+        Update value.
+        @param value: New value
+        """
+        # Remove quote and double quote surrounding string value to get raw value
+        if (self.VariableType == "STRING" and
+            value.startswith("'") and value.endswith("'") or
+            self.VariableType == "WSTRING" and
+            value.startswith('"') and value.endswith('"')):
+            value = value[1:-1]
+        
+        # Store variable value
+        if self.Value != value:
+            self.Value = value
+            
+            # Signal to debug variable panel to refresh
+            self.Parent.HasNewData = True
+    
+    def GetValue(self, tick=None, raw=False):
+        """
+        Return current value or value and forced flag for tick given
+        @return: Current value or value and forced flag
+        """
+        # If tick given and stored data option enabled
+        if tick is not None and self.Data is not None:
+            
+            # Return current value and forced flag if data empty
+            if len(self.Data) == 0:
+                return self.Value, self.IsForced()
+            
+            # Get index of nearest data from tick given
+            idx = self.GetNearestData(tick, 0)
+            
+            # Get value and forced flag at given index
+            value, forced = self.RawData[int(self.Data[idx, 2])] \
+                            if self.VariableType in ["STRING", "WSTRING"] \
+                            else self.Data[idx, 1:3]
+            
+            # Get raw value if asked
+            if not raw:
+                value = TYPE_TRANSLATOR.get(
+                        self.VariableType, str)(value)
+            
+            return value, forced
+            
+        # Return raw value if asked
+        if not raw and self.VariableType in ["STRING", "WSTRING"]:
+            return TYPE_TRANSLATOR.get(
+                    self.VariableType, str)(self.Value)
+        return self.Value
+
+    def GetNearestData(self, tick, adjust):
+        """
+        Return index of nearest data from tick given
+        @param tick: Tick where find nearest data
+        @param adjust: Constraint for data position from tick
+                       -1: older than tick
+                       1:  newer than tick
+                       0:  doesn't matter
+        @return: Index of nearest data
+        """
+        # Return immediately if data is empty
+        if self.Data is None:
+            return None
+        
+        # Extract data ticks
+        ticks = self.Data[:, 0]
+        
+        # Get nearest data from tick
+        idx = numpy.argmin(abs(ticks - tick))
+        
+        # Adjust data index according to constraint
+        if (adjust < 0 and ticks[idx] > tick and idx > 0 or
+            adjust > 0 and ticks[idx] < tick and idx < len(ticks)):
+            idx += adjust
+        
+        return idx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableTablePanel.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,509 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from types import TupleType
+
+import wx
+import wx.lib.buttons
+
+from editors.DebugViewer import DebugViewer
+from controls import CustomGrid, CustomTable
+from dialogs.ForceVariableDialog import ForceVariableDialog
+from util.BitmapLibrary import GetBitmap
+
+from DebugVariableItem import DebugVariableItem
+
+def GetDebugVariablesTableColnames():
+    """
+    Function returning table column header labels
+    @return: List of labels [col_label,...]
+    """
+    _ = lambda x : x
+    return [_("Variable"), _("Value")]
+
+#-------------------------------------------------------------------------------
+#                        Debug Variable Table Panel
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom table storing value to display in Debug Variable
+Table Panel grid
+"""
+
+class DebugVariableTable(CustomTable):
+    
+    def GetValue(self, row, col):
+        if row < self.GetNumberRows():
+            return self.GetValueByName(row, self.GetColLabelValue(col, False))
+        return ""
+    
+    def SetValue(self, row, col, value):
+        if col < len(self.colnames):
+            self.SetValueByName(row, self.GetColLabelValue(col, False), value)
+            
+    def GetValueByName(self, row, colname):
+        if row < self.GetNumberRows():
+            if colname == "Variable":
+                return self.data[row].GetVariable()
+            elif colname == "Value":
+                return self.data[row].GetValue()
+        return ""
+
+    def SetValueByName(self, row, colname, value):
+        if row < self.GetNumberRows():
+            if colname == "Variable":
+                self.data[row].SetVariable(value)
+            elif colname == "Value":
+                self.data[row].SetValue(value)
+    
+    def IsForced(self, row):
+        if row < self.GetNumberRows():
+            return self.data[row].IsForced()
+        return False
+    
+    def _updateColAttrs(self, grid):
+        """
+        wx.grid.Grid -> update the column attributes to add the
+        appropriate renderer given the column name.
+
+        Otherwise default to the default renderer.
+        """
+        
+        for row in range(self.GetNumberRows()):
+            for col in range(self.GetNumberCols()):
+                colname = self.GetColLabelValue(col, False)
+                if colname == "Value":
+                    if self.IsForced(row):
+                        grid.SetCellTextColour(row, col, wx.BLUE)
+                    else:
+                        grid.SetCellTextColour(row, col, wx.BLACK)
+                grid.SetReadOnly(row, col, True)
+            self.ResizeRow(grid, row)
+    
+    def RefreshValues(self, grid):
+        for col in xrange(self.GetNumberCols()):
+            colname = self.GetColLabelValue(col, False)
+            if colname == "Value":
+                for row in xrange(self.GetNumberRows()):
+                    grid.SetCellValue(row, col, str(self.data[row].GetValue()))
+                    if self.IsForced(row):
+                        grid.SetCellTextColour(row, col, wx.BLUE)
+                    else:
+                        grid.SetCellTextColour(row, col, wx.BLACK)
+      
+    def AppendItem(self, item):
+        self.data.append(item)
+    
+    def InsertItem(self, idx, item):
+        self.data.insert(idx, item)
+    
+    def RemoveItem(self, item):
+        self.data.remove(item)
+    
+    def MoveItem(self, idx, new_idx):
+        self.data.insert(new_idx, self.data.pop(idx))
+        
+    def GetItem(self, idx):
+        return self.data[idx]
+
+
+#-------------------------------------------------------------------------------
+#                  Debug Variable Table Panel Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Table Panel
+"""
+
+class DebugVariableTableDropTarget(wx.TextDropTarget):
+    
+    def __init__(self, parent):
+        """
+        Constructor
+        @param window: Reference to the Debug Variable Panel
+        """
+        wx.TextDropTarget.__init__(self)
+        self.ParentWindow = parent
+        
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to Debug Variable Panel
+        self.ParentWindow = None
+        
+    def OnDropText(self, x, y, data):
+        """
+        Function called when mouse is dragged over Drop Target
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        @param data: Text associated to drag'n drop
+        """
+        message = None
+        
+        # Check that data is valid regarding DebugVariablePanel
+        try:
+            values = eval(data)
+            if not isinstance(values, TupleType):
+                raise ValueError
+        except:
+            message = _("Invalid value \"%s\" for debug variable") % data
+            values = None
+        
+        # Display message if data is invalid
+        if message is not None:
+            wx.CallAfter(self.ShowMessage, message)
+        
+        # Data contain a reference to a variable to debug
+        elif values[1] == "debug":
+            grid = self.ParentWindow.VariablesGrid
+            
+            # Get row where variable was dropped
+            x, y = grid.CalcUnscrolledPosition(x, y)
+            row = grid.YToRow(y - grid.GetColLabelSize())
+            
+            # If no row found add variable at table end
+            if row == wx.NOT_FOUND:
+                row = self.ParentWindow.Table.GetNumberRows()
+            
+            # Add variable to table
+            self.ParentWindow.InsertValue(values[0], row, force=True)
+            
+    def ShowMessage(self, message):
+        """
+        Show error message in Error Dialog
+        @param message: Error message to display
+        """
+        dialog = wx.MessageDialog(self.ParentWindow, 
+                                  message, 
+                                  _("Error"), 
+                                  wx.OK|wx.ICON_ERROR)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+#                       Debug Variable Table Panel
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a panel displaying debug variable values in a table
+"""
+
+class DebugVariableTablePanel(wx.Panel, DebugViewer):
+    
+    def __init__(self, parent, producer, window):
+        """
+        Constructor
+        @param parent: Reference to the parent wx.Window
+        @param producer: Object receiving debug value and dispatching them to
+        consumers
+        @param window: Reference to Beremiz frame
+        """
+        wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
+        
+        # Save Reference to Beremiz frame
+        self.ParentWindow = window
+        
+        # Variable storing flag indicating that variable displayed in table
+        # received new value and then table need to be refreshed
+        self.HasNewData = False
+        
+        DebugViewer.__init__(self, producer, True)
+        
+        # Construction of window layout by creating controls and sizers
+        
+        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
+        main_sizer.AddGrowableCol(0)
+        main_sizer.AddGrowableRow(1)
+        
+        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        main_sizer.AddSizer(button_sizer, border=5, 
+              flag=wx.ALIGN_RIGHT|wx.ALL)
+        
+        # Creation of buttons for navigating in table
+        
+        for name, bitmap, help in [
+                ("DeleteButton", "remove_element", _("Remove debug variable")),
+                ("UpButton", "up", _("Move debug variable up")),
+                ("DownButton", "down", _("Move debug variable down"))]:
+            button = wx.lib.buttons.GenBitmapButton(self, 
+                  bitmap=GetBitmap(bitmap), 
+                  size=wx.Size(28, 28), style=wx.NO_BORDER)
+            button.SetToolTipString(help)
+            setattr(self, name, button)
+            button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
+        
+        # Creation of grid and associated table
+        
+        self.VariablesGrid = CustomGrid(self, 
+                size=wx.Size(-1, 150), style=wx.VSCROLL)
+        # Define grid drop target
+        self.VariablesGrid.SetDropTarget(DebugVariableTableDropTarget(self))
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, 
+              self.OnVariablesGridCellRightClick)
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
+              self.OnVariablesGridCellLeftClick)
+        main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+    
+        self.Table = DebugVariableTable(self, [], 
+                GetDebugVariablesTableColnames())
+        self.VariablesGrid.SetTable(self.Table)
+        self.VariablesGrid.SetButtons({"Delete": self.DeleteButton,
+                                       "Up": self.UpButton,
+                                       "Down": self.DownButton})
+        
+        # Definition of function associated to navigation buttons
+        
+        def _AddVariable(new_row):
+            return self.VariablesGrid.GetGridCursorRow()
+        setattr(self.VariablesGrid, "_AddRow", _AddVariable)
+    
+        def _DeleteVariable(row):
+            item = self.Table.GetItem(row)
+            self.RemoveDataConsumer(item)
+            self.Table.RemoveItem(item)
+            self.RefreshView()
+        setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
+        
+        def _MoveVariable(row, move):
+            new_row = max(0, min(row + move, self.Table.GetNumberRows() - 1))
+            if new_row != row:
+                self.Table.MoveItem(row, new_row)
+                self.RefreshView()
+            return new_row
+        setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
+        
+        # Initialization of grid layout
+        
+        self.VariablesGrid.SetRowLabelSize(0)
+        
+        self.GridColSizes = [200, 100]
+        
+        for col in range(self.Table.GetNumberCols()):
+            attr = wx.grid.GridCellAttr()
+            attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
+            self.VariablesGrid.SetColAttr(col, attr)
+            self.VariablesGrid.SetColSize(col, self.GridColSizes[col])
+        
+        self.Table.ResetView(self.VariablesGrid)
+        self.VariablesGrid.RefreshButtons()
+        
+        self.SetSizer(main_sizer)
+    
+    def RefreshNewData(self, *args, **kwargs):
+        """
+        Called to refresh Table according to values received by variables
+        Can receive any parameters (not used here)
+        """
+        # Refresh 'Value' column of table if new data have been received since
+        # last refresh
+        if self.HasNewData:
+            self.HasNewData = False
+            self.RefreshView(only_values=True)
+        DebugViewer.RefreshNewData(self, *args, **kwargs)
+    
+    def RefreshView(self, only_values=False):
+        """
+        Function refreshing table layout and values
+        @param only_values: True if only 'Value' column need to be updated
+        """
+        # Block refresh until table layout and values are completely updated
+        self.Freeze()
+        
+        # Update only 'value' column from table 
+        if only_values:
+            self.Table.RefreshValues(self.VariablesGrid)
+        
+        # Update complete table layout refreshing table navigation buttons
+        # state according to 
+        else:
+            self.Table.ResetView(self.VariablesGrid)
+            self.VariablesGrid.RefreshButtons()
+        
+        self.Thaw()
+        
+    def ResetView(self):
+        """
+        Function removing all variables denugged from table
+        @param only_values: True if only 'Value' column need to be updated
+        """
+        # Unsubscribe all variables debugged
+        self.UnsubscribeAllDataConsumers()
+        
+        # Clear table content
+        self.Table.Empty()
+        
+        # Update table layout
+        self.Freeze()
+        self.Table.ResetView(self.VariablesGrid)
+        self.VariablesGrid.RefreshButtons()
+        self.Thaw()
+    
+    def SubscribeAllDataConsumers(self):
+        """
+        Function refreshing table layout and values
+        @param only_values: True if only 'Value' column need to be updated
+        """
+        DebugViewer.SubscribeAllDataConsumers(self)
+        
+        # Navigate through variable displayed in table, removing those that
+        # doesn't exist anymore in PLC
+        for item in self.Table.GetData()[:]:
+            iec_path = item.GetVariable()
+            if self.GetDataType(iec_path) is None:
+                self.RemoveDataConsumer(item)
+                self.Table.RemoveItem(idx)
+            else:
+                self.AddDataConsumer(iec_path.upper(), item)
+                item.RefreshVariableType()
+        
+        # Update table layout
+        self.Freeze()
+        self.Table.ResetView(self.VariablesGrid)
+        self.VariablesGrid.RefreshButtons()
+        self.Thaw()
+    
+    def GetForceVariableMenuFunction(self, item):
+        """
+        Function returning callback function for contextual menu 'Force' item
+        @param item: Debug Variable item where contextual menu was opened 
+        @return: Callback function
+        """
+        def ForceVariableFunction(event):
+            # Get variable path and data type
+            iec_path = item.GetVariable()
+            iec_type = self.GetDataType(iec_path)
+            
+            # Return immediately if not data type found
+            if iec_type is None:
+                return
+            
+            # Open dialog for entering value to force variable
+            dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
+            
+            # If valid value entered, force variable
+            if dialog.ShowModal() == wx.ID_OK:
+                self.ForceDataValue(iec_path.upper(), dialog.GetValue())
+        
+        return ForceVariableFunction
+
+    def GetReleaseVariableMenuFunction(self, iec_path):
+        """
+        Function returning callback function for contextual menu 'Release' item
+        @param iec_path: Debug Variable path where contextual menu was opened
+        @return: Callback function
+        """
+        def ReleaseVariableFunction(event):
+            # Release variable
+            self.ReleaseDataValue(iec_path)
+        return ReleaseVariableFunction
+    
+    def OnVariablesGridCellLeftClick(self, event):
+        """
+        Called when left mouse button is pressed on a table cell
+        @param event: wx.grid.GridEvent
+        """
+        # Initiate a drag and drop if the cell clicked was in 'Variable' column
+        if self.Table.GetColLabelValue(event.GetCol(), False) == "Variable":
+            item = self.Table.GetItem(event.GetRow())
+            data = wx.TextDataObject(str((item.GetVariable(), "debug")))
+            dragSource = wx.DropSource(self.VariablesGrid)
+            dragSource.SetData(data)
+            dragSource.DoDragDrop()
+        
+        event.Skip()
+    
+    def OnVariablesGridCellRightClick(self, event):
+        """
+        Called when right mouse button is pressed on a table cell
+        @param event: wx.grid.GridEvent
+        """
+        # Open a contextual menu if the cell clicked was in 'Value' column
+        if self.Table.GetColLabelValue(event.GetCol(), False) == "Value":
+            row = event.GetRow()
+            
+            # Get variable path
+            item = self.Table.GetItem(row)
+            iec_path = item.GetVariable().upper()
+            
+            # Create contextual menu
+            menu = wx.Menu(title='')
+            
+            # Add menu items
+            for text, enable, callback in [
+                (_("Force value"), True,
+                 self.GetForceVariableMenuFunction(item)),
+                # Release menu item is enabled only if variable is forced 
+                (_("Release value"), self.Table.IsForced(row),
+                 self.GetReleaseVariableMenuFunction(iec_path))]:
+                
+                new_id = wx.NewId()
+                menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=text)
+                menu.Enable(new_id, enable)
+                self.Bind(wx.EVT_MENU, callback, id=new_id)
+            
+            # Popup contextual menu
+            self.PopupMenu(menu)
+            
+            menu.Destroy()
+        event.Skip()
+    
+    def InsertValue(self, iec_path, index=None, force=False, graph=False):
+        """
+        Insert a new variable to debug in table
+        @param iec_path: Variable path to debug
+        @param index: Row where insert the variable in table (default None,
+        insert at last position)
+        @param force: Force insertion of variable even if not defined in
+        producer side
+        @param graph: Values must be displayed in graph canvas (Do nothing,
+        here for compatibility with Debug Variable Graphic Panel)
+        """
+        # Return immediately if variable is already debugged
+        for item in self.Table.GetData():
+            if iec_path == item.GetVariable():
+                return
+            
+        # Insert at last position if index not defined
+        if index is None:
+            index = self.Table.GetNumberRows()
+        
+        # Subscribe variable to producer
+        item = DebugVariableItem(self, iec_path)
+        result = self.AddDataConsumer(iec_path.upper(), item)
+        
+        # Insert variable in table if subscription done or insertion forced
+        if result is not None or force:
+            self.Table.InsertItem(index, item)
+            self.RefreshView()
+    
+    def ResetGraphicsValues(self):
+        """
+        Called to reset graphics values when PLC is started
+        (Nothing to do because no graphic values here. Defined for
+        compatibility with Debug Variable Graphic Panel)
+        """
+        pass
+    
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableTextViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from types import TupleType
+
+import wx
+
+from DebugVariableItem import DebugVariableItem
+from DebugVariableViewer import DebugVariableViewer
+from GraphButton import GraphButton
+
+#-------------------------------------------------------------------------------
+#                     Debug Variable Text Viewer Drop Target
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom drop target class for Debug Variable Text Viewer
+"""
+
+class DebugVariableTextDropTarget(wx.TextDropTarget):
+    
+    def __init__(self, parent, window):
+        """
+        Constructor
+        @param parent: Reference to Debug Variable Text Viewer
+        @param window: Reference to the Debug Variable Panel
+        """
+        wx.TextDropTarget.__init__(self)
+        self.ParentControl = parent
+        self.ParentWindow = window
+    
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to Debug Variable Text Viewer and Debug Variable
+        # Panel
+        self.ParentControl = None
+        self.ParentWindow = None
+        
+    def OnDragOver(self, x, y, d):
+        """
+        Function called when mouse is dragged over Drop Target
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        @param d: Suggested default for return value
+        """
+        # Signal parent that mouse is dragged over
+        self.ParentControl.OnMouseDragging(x, y)
+        
+        return wx.TextDropTarget.OnDragOver(self, x, y, d)
+        
+    def OnDropText(self, x, y, data):
+        """
+        Function called when mouse is released in Drop Target
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        @param data: Text associated to drag'n drop
+        """
+        # Signal Debug Variable Panel to reset highlight
+        self.ParentWindow.ResetHighlight()
+        
+        message = None
+        
+        # Check that data is valid regarding DebugVariablePanel
+        try:
+            values = eval(data)
+            if not isinstance(values, TupleType):
+                raise ValueError
+        except:
+            message = _("Invalid value \"%s\" for debug variable") % data
+            values = None
+        
+        # Display message if data is invalid
+        if message is not None:
+            wx.CallAfter(self.ShowMessage, message)
+        
+        # Data contain a reference to a variable to debug
+        elif values[1] == "debug":
+            
+            # Get Before which Viewer the variable has to be moved or added
+            # according to the position of mouse in Viewer.
+            width, height = self.ParentControl.GetSize()
+            target_idx = self.ParentControl.GetIndex()
+            if y > height / 2:
+                target_idx += 1
+            
+            # Drag'n Drop is an internal is an internal move inside Debug
+            # Variable Panel 
+            if len(values) > 2 and values[2] == "move":
+                self.ParentWindow.MoveValue(values[0], 
+                                            target_idx)
+            
+            # Drag'n Drop was initiated by another control of Beremiz
+            else:
+                self.ParentWindow.InsertValue(values[0], 
+                                              target_idx, 
+                                              force=True)
+    
+    def OnLeave(self):
+        """
+        Function called when mouse is leave Drop Target
+        """
+        # Signal Debug Variable Panel to reset highlight
+        self.ParentWindow.ResetHighlight()
+        
+        return wx.TextDropTarget.OnLeave(self)
+    
+    def ShowMessage(self, message):
+        """
+        Show error message in Error Dialog
+        @param message: Error message to display
+        """
+        dialog = wx.MessageDialog(self.ParentWindow, 
+                                  message, 
+                                  _("Error"), 
+                                  wx.OK|wx.ICON_ERROR)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+
+#-------------------------------------------------------------------------------
+#                      Debug Variable Text Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a Viewer that display variable values as a text
+"""
+
+class DebugVariableTextViewer(DebugVariableViewer, wx.Panel):
+    
+    def __init__(self, parent, window, items=[]):
+        """
+        Constructor
+        @param parent: Parent wx.Window of DebugVariableText
+        @param window: Reference to the Debug Variable Panel
+        @param items: List of DebugVariableItem displayed by Viewer
+        """
+        DebugVariableViewer.__init__(self, window, items)
+        
+        wx.Panel.__init__(self, parent)
+        # Set panel background colour
+        self.SetBackgroundColour(wx.WHITE)
+        # Define panel drop target
+        self.SetDropTarget(DebugVariableTextDropTarget(self, window))
+        
+        # Bind events
+        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
+        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
+        self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
+        self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
+        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
+        self.Bind(wx.EVT_SIZE, self.OnResize)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        
+        # Define panel min size for parent sizer layout
+        self.SetMinSize(wx.Size(0, 25))
+        
+        # Add buttons to Viewer
+        for bitmap, callback in [("force", self.OnForceButton),
+                                 ("release", self.OnReleaseButton),
+                                 ("delete_graph", self.OnCloseButton)]:
+            self.Buttons.append(GraphButton(0, 0, bitmap, callback))
+    
+    def RefreshViewer(self):
+        """
+        Method that refresh the content displayed by Viewer
+        """
+        # Create buffered DC for drawing in panel
+        width, height = self.GetSize()
+        bitmap = wx.EmptyBitmap(width, height)
+        dc = wx.BufferedDC(wx.ClientDC(self), bitmap)
+        dc.Clear()
+        
+        # Get Graphics Context for DC, for anti-aliased and transparent
+        # rendering
+        gc = wx.GCDC(dc)
+        
+        gc.BeginDrawing()
+        
+        # Get first item
+        item = self.ItemsDict.values()[0]
+        
+        # Get item variable path masked according Debug Variable Panel mask
+        item_path = item.GetVariable(
+                self.ParentWindow.GetVariableNameMask())
+        
+        # Draw item variable path at Viewer left side
+        w, h = gc.GetTextExtent(item_path)
+        gc.DrawText(item_path, 20, (height - h) / 2)
+        
+        # Update 'Release' button state and text color according to item forced
+        # flag value
+        item_forced = item.IsForced()
+        self.Buttons[1].Enable(item_forced)
+        self.RefreshButtonsPosition()
+        if item_forced:
+            gc.SetTextForeground(wx.BLUE)
+        
+        # Draw item current value at right side of Viewer
+        item_value = item.GetValue()
+        w, h = gc.GetTextExtent(item_value)
+        gc.DrawText(item_value, width - 40 - w, (height - h) / 2)
+        
+        # Draw other Viewer common elements
+        self.DrawCommonElements(gc)
+        
+        gc.EndDrawing()
+    
+    def OnLeftDown(self, event):
+        """
+        Function called when mouse left button is pressed
+        @param event: wx.MouseEvent
+        """
+        # Get first item
+        item = self.ItemsDict.values()[0]
+        
+        # Calculate item path bounding box
+        width, height = self.GetSize()
+        item_path = item.GetVariable(
+                self.ParentWindow.GetVariableNameMask())
+        w, h = self.GetTextExtent(item_path)
+        
+        # Test if mouse has been pressed in this bounding box. In that case
+        # start a move drag'n drop of item variable 
+        x, y = event.GetPosition()
+        item_path_bbox = wx.Rect(20, (height - h) / 2, w, h)
+        if item_path_bbox.InsideXY(x, y):
+            self.ShowButtons(False)
+            data = wx.TextDataObject(str((item.GetVariable(), "debug", "move")))
+            dragSource = wx.DropSource(self)
+            dragSource.SetData(data)
+            dragSource.DoDragDrop()
+        
+        # In other case handle event normally
+        else:
+            event.Skip()
+    
+    def OnLeftUp(self, event):
+        """
+        Function called when mouse left button is released
+        @param event: wx.MouseEvent
+        """
+        # Execute callback on button under mouse pointer if it exists
+        x, y = event.GetPosition()
+        wx.CallAfter(self.HandleButton, x, y)
+        event.Skip()
+    
+    def OnLeftDClick(self, event):
+        """
+        Function called when mouse left button is double clicked
+        @param event: wx.MouseEvent
+        """
+        # Only numeric variables can be toggled to graph canvas
+        if self.ItemsDict.values()[0].IsNumVariable():
+            self.ParentWindow.ToggleViewerType(self)
+    
+    def OnPaint(self, event):
+        """
+        Function called when redrawing Viewer content is needed
+        @param event: wx.PaintEvent
+        """
+        self.RefreshViewer()
+        event.Skip()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,426 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from collections import OrderedDict
+
+import wx
+
+import matplotlib
+matplotlib.use('WX')
+import matplotlib.pyplot
+from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
+
+from dialogs.ForceVariableDialog import ForceVariableDialog
+
+# Viewer highlight types
+[HIGHLIGHT_NONE,
+ HIGHLIGHT_BEFORE,
+ HIGHLIGHT_AFTER,
+ HIGHLIGHT_LEFT,
+ HIGHLIGHT_RIGHT,
+ HIGHLIGHT_RESIZE] = range(6)
+
+# Viewer highlight styles
+HIGHLIGHT_DROP_PEN = wx.Pen(wx.Colour(0, 128, 255))
+HIGHLIGHT_DROP_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128))
+HIGHLIGHT_RESIZE_PEN = wx.Pen(wx.Colour(200, 200, 200))
+HIGHLIGHT_RESIZE_BRUSH = wx.Brush(wx.Colour(200, 200, 200))
+
+#-------------------------------------------------------------------------------
+#                        Base Debug Variable Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a generic viewer that display a list of variable values
+This class has to be inherited to effectively display variable values
+"""
+
+class DebugVariableViewer:
+    
+    def __init__(self, window, items=[]):
+        """
+        Constructor
+        @param window: Reference to the Debug Variable Panel
+        @param items: List of DebugVariableItem displayed by Viewer
+        """
+        self.ParentWindow = window
+        self.ItemsDict = OrderedDict([(item.GetVariable(), item)
+                                      for item in items])
+        self.Items = self.ItemsDict.viewvalues()
+        
+        # Variable storing current highlight displayed in Viewer
+        self.Highlight = HIGHLIGHT_NONE
+        # List of buttons
+        self.Buttons = []
+    
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to Debug Variable Panel
+        self.ParentWindow = None
+    
+    def GetIndex(self):
+        """
+        Return position of Viewer in Debug Variable Panel
+        @return: Position of Viewer
+        """
+        return self.ParentWindow.GetViewerIndex(self)
+    
+    def GetItem(self, variable):
+        """
+        Return item storing values of a variable
+        @param variable: Variable path
+        @return: Item storing values of this variable
+        """
+        return self.ItemsDict.get(variable, None)
+    
+    def GetItems(self):
+        """
+        Return items displayed by Viewer
+        @return: List of items displayed in Viewer
+        """
+        return self.ItemsDict.values()
+    
+    def AddItem(self, item):
+        """
+        Add an item to the list of items displayed by Viewer
+        @param item: Item to add to the list
+        """
+        self.ItemsDict[item.GetVariable()] = item
+        
+    def RemoveItem(self, item):
+        """
+        Remove an item from the list of items displayed by Viewer
+        @param item: Item to remove from the list
+        """
+        self.ItemsDict.pop(item.GetVariable(), None)
+    
+    def ClearItems(self):
+        """
+        Clear list of items displayed by Viewer
+        """
+        # Unsubscribe every items of the list
+        for item in self.Items:
+            self.ParentWindow.RemoveDataConsumer(item)
+        
+        # Clear list
+        self.ItemsDict.clear()
+        
+    def ItemsIsEmpty(self):
+        """
+        Return if list of items displayed by Viewer is empty
+        @return: True if list is empty
+        """
+        return len(self.Items) == 0
+    
+    def SubscribeAllDataConsumers(self):
+        """
+        Function that unsubscribe and remove every item that store values of
+        a variable that doesn't exist in PLC anymore
+        """
+        for item in self.ItemsDict.values()[:]:
+            iec_path = item.GetVariable()
+            
+            # Check that variablepath exist in PLC
+            if self.ParentWindow.GetDataType(iec_path) is None:
+                # If not, unsubscribe and remove it
+                self.ParentWindow.RemoveDataConsumer(item)
+                self.RemoveItem(item)
+            else:
+                # If it exist, resubscribe and refresh data type
+                self.ParentWindow.AddDataConsumer(iec_path.upper(), item)
+                item.RefreshVariableType()
+    
+    def ResetItemsData(self):
+        """
+        Reset data stored in every items displayed in Viewer
+        """
+        for item in self.Items:
+            item.ResetData()
+    
+    def GetItemsMinCommonTick(self):
+        """
+        Return the minimum tick common to all iems displayed in Viewer
+        @return: Minimum common tick between items
+        """
+        return reduce(max, [item.GetData()[0, 0] 
+                            for item in self.Items
+                            if len(item.GetData()) > 0], 0)
+    
+    def RefreshViewer(self):
+        """
+        Method that refresh the content displayed by Viewer
+        Need to be overridden by inherited classes
+        """
+        pass
+    
+    def SetHighlight(self, highlight):
+        """
+        Set Highlight type displayed in Viewer
+        @return: True if highlight has changed
+        """
+        # Return immediately if highlight don't change
+        if self.Highlight == highlight:
+            return False
+        
+        self.Highlight = highlight
+        return True
+    
+    def GetButtons(self):
+        """
+        Return list of buttons defined in Viewer
+        @return: List of buttons
+        """
+        return self.Buttons
+    
+    def IsOverButton(self, x, y):
+        """
+        Return if point is over one button of Viewer
+        @param x: X coordinate of point
+        @param y: Y coordinate of point
+        @return: button where point is over
+        """
+        for button in self.GetButtons():
+            if button.HitTest(x, y):
+                return button
+        return None
+    
+    def HandleButton(self, x, y):
+        """
+        Search for the button under point and if found execute associated
+        callback
+        @param x: X coordinate of point
+        @param y: Y coordinate of point
+        @return: True if a button was found and callback executed
+        """
+        button = self.IsOverButton(x, y)
+        if button is None:
+            return False
+        
+        button.ProcessCallback()
+        return True
+    
+    def ShowButtons(self, show):
+        """
+        Set display state of buttons in Viewer
+        @param show: Display state (True if buttons must be displayed)
+        """
+        # Change display of every buttons
+        for button in self.Buttons:
+            button.Show(show)
+        
+        # Refresh button positions
+        self.RefreshButtonsPosition()
+        self.RefreshViewer()
+    
+    def RefreshButtonsPosition(self):
+        """
+        Function that refresh buttons position in Viewer
+        """
+        # Get Viewer size
+        width, height = self.GetSize()
+        
+        # Buttons are align right so we calculate buttons positions in
+        # reverse order
+        buttons = self.Buttons[:]
+        buttons.reverse()
+        
+        # Position offset on x coordinate
+        x_offset = 0
+        for button in buttons:
+            # Buttons are stacked right, removing those that are not active 
+            if button.IsEnabled():
+                # Update button position according to button width and offset
+                # on x coordinate
+                w, h = button.GetSize()
+                button.SetPosition(width - 5 - w - x_offset, 5)
+                # Update offset on x coordinate
+                x_offset += w + 2
+    
+    def DrawCommonElements(self, dc, buttons=None):
+        """
+        Function that draw common graphics for every Viewers
+        @param dc: wx.DC object corresponding to Device context where drawing
+        common graphics
+        @param buttons: List of buttons to draw if different from default
+        (default None)
+        """
+        # Get Viewer size
+        width, height = self.GetSize()
+        
+        # Set dc styling for drop before or drop after highlight
+        dc.SetPen(HIGHLIGHT_DROP_PEN)
+        dc.SetBrush(HIGHLIGHT_DROP_BRUSH)
+        
+        # Draw line at upper side of Viewer if highlight is drop before
+        if self.Highlight == HIGHLIGHT_BEFORE:
+            dc.DrawLine(0, 1, width - 1, 1)
+        
+        # Draw line at lower side of Viewer if highlight is drop before
+        elif self.Highlight == HIGHLIGHT_AFTER:
+            dc.DrawLine(0, height - 1, width - 1, height - 1)
+        
+        # If no specific buttons are defined, get default buttons
+        if buttons is None:
+            buttons = self.Buttons
+        # Draw buttons
+        for button in buttons:
+            button.Draw(dc)
+        
+        # If graph dragging is processing
+        if self.ParentWindow.IsDragging():
+            destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
+            srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
+            if destBBox.width > 0 and destBBox.height > 0:
+                srcPanel = self.ParentWindow.DraggingAxesPanel
+                srcBBox = srcPanel.GetAxesBoundingBox()
+                
+                srcX = srcBBox.x - (srcPos.x if destBBox.x == 0 else 0)
+                srcY = srcBBox.y - (srcPos.y if destBBox.y == 0 else 0)
+                
+                srcBmp = _convert_agg_to_wx_bitmap(
+                            srcPanel.get_renderer(), None)
+                srcDC = wx.MemoryDC()
+                srcDC.SelectObject(srcBmp)
+                
+                dc.Blit(destBBox.x, destBBox.y, 
+                        int(destBBox.width), int(destBBox.height), 
+                        srcDC, srcX, srcY)
+    
+    def OnEnter(self, event):
+        """
+        Function called when entering Viewer
+        @param event: wx.MouseEvent 
+        """
+        # Display buttons
+        self.ShowButtons(True)
+        event.Skip()
+        
+    def OnLeave(self, event):
+        """
+        Function called when leaving Viewer
+        @param event: wx.MouseEvent 
+        """
+        # Hide buttons
+        self.ShowButtons(False)
+        event.Skip()
+    
+    def OnCloseButton(self):
+        """
+        Function called when Close button is pressed
+        """
+        wx.CallAfter(self.ParentWindow.DeleteValue, self)
+    
+    def OnForceButton(self):
+        """
+        Function called when Force button is pressed
+        """
+        self.ForceValue(self.ItemsDict.values()[0])
+        
+    def OnReleaseButton(self):
+        """
+        Function called when Release button is pressed
+        """
+        self.ReleaseValue(self.ItemsDict.values()[0])
+    
+    def OnMouseDragging(self, x, y):
+        """
+        Function called when mouse is dragged over Viewer
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        """
+        xw, yw = self.GetPosition()
+        # Refresh highlight in Debug Variable Panel (highlight can be displayed
+        # in another Viewer
+        self.ParentWindow.RefreshHighlight(x + xw, y + yw)
+    
+    def RefreshHighlight(self, x, y):
+        """
+        Function called by Debug Variable Panel asking Viewer to refresh
+        highlight according to mouse position
+        @param x: X coordinate of mouse pointer
+        @param y: Y coordinate of mouse pointer
+        """
+        # Get Viewer size
+        width, height = self.GetSize()
+        
+        # Mouse is in the first half of Viewer
+        if y < height / 2:
+            # If Viewer is the upper one, draw drop before highlight
+            if self.ParentWindow.IsViewerFirst(self):
+                self.SetHighlight(HIGHLIGHT_BEFORE)
+            
+            # Else draw drop after highlight in previous Viewer
+            else:
+                self.SetHighlight(HIGHLIGHT_NONE)
+                self.ParentWindow.HighlightPreviousViewer(self)
+        
+        # Mouse is in the second half of Viewer, draw drop after highlight 
+        else:
+            self.SetHighlight(HIGHLIGHT_AFTER)
+    
+    def OnEraseBackground(self, event):
+        """
+        Function called when Viewer background is going to be erase
+        @param event: wx.EraseEvent
+        """
+        # Prevent flicker on Windows
+        pass
+    
+    def OnResize(self, event):
+        """
+        Function called when Viewer size changed
+        @param event: wx.ResizeEvent
+        """
+        # Refresh button positions
+        self.RefreshButtonsPosition()
+        self.ParentWindow.ForceRefresh()
+        event.Skip()
+    
+    def ForceValue(self, item):
+        """
+        Force value of item given
+        @param item: Item to force value
+        """
+        # Check variable data type
+        iec_path = item.GetVariable()
+        iec_type = self.ParentWindow.GetDataType(iec_path)
+        # Return immediately if not found
+        if iec_type is None:
+            return
+        
+        # Open a dialog to enter varaible forced value
+        dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
+        if dialog.ShowModal() == wx.ID_OK:
+            self.ParentWindow.ForceDataValue(iec_path.upper(),
+                                             dialog.GetValue())
+    
+    def ReleaseValue(self, item):
+        """
+        Release value of item given
+        @param item: Item to release value
+        """
+        self.ParentWindow.ReleaseDataValue(
+                item.GetVariable().upper())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/GraphButton.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,163 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import wx
+
+from util.BitmapLibrary import GetBitmap
+
+#-------------------------------------------------------------------------------
+#                        Custom button for Graphic Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a custom button for graphic Viewer
+"""
+
+class GraphButton():
+    
+    def __init__(self, x, y, bitmap, callback):
+        """
+        Constructor
+        @param x: X coordinate of Button in Graphic Viewer
+        @param y: Y coordinate of Button in Graphic Viewer
+        @param bitmap: Name of bitmap to use for button
+        @param callback: Reference to function to call when button is pressed
+        """
+        # Save button position
+        self.SetPosition(x, y)
+        # Set button bitmap
+        self.SetBitmap(bitmap)
+        
+        # By default button is hide and enabled
+        self.Shown = False
+        self.Enabled = True
+        
+        # Save reference to callback function
+        self.Callback = callback
+    
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to callback function
+        self.callback = None
+    
+    def SetBitmap(self, bitmap):
+        """
+        Set bitmap to use for button
+        @param bitmap: Name of bitmap to use for button
+        """
+        # Get wx.Bitmap object corresponding to bitmap
+        self.Bitmap = GetBitmap(bitmap)
+    
+    def GetSize(self):
+        """
+        Return size of button
+        @return: wx.Size object containing button size
+        """
+        # Button size is size of bitmap
+        return self.Bitmap.GetSize()
+    
+    def SetPosition(self, x, y):
+        """
+        Set button position
+        @param x: X coordinate of Button in Graphic Viewer
+        @param y: Y coordinate of Button in Graphic Viewer
+        """
+        self.Position = wx.Point(x, y)
+    
+    def Show(self, show=True):
+        """
+        Mark if button to be displayed in Graphic Viewer
+        @param show: True if button to be displayed in Graphic Viewer
+        (default True)
+        """
+        self.Shown = show
+        
+    def Hide(self):
+        """
+        Hide button from Graphic Viewer
+        """
+        self.Show(False)
+    
+    def IsShown(self):
+        """
+        Return if button is displayed in Graphic Viewer
+        @return: True if button is displayed in Graphic Viewer
+        """
+        return self.Shown
+    
+    def Enable(self, enable=True):
+        """
+        Mark if button is active in Graphic Viewer
+        @param enable: True if button is active in Graphic Viewer
+        (default True)
+        """
+        self.Enabled = enable
+    
+    def Disable(self):
+        """
+        Deactivate button in Graphic Viewer
+        """
+        self.Enabled = False
+    
+    def IsEnabled(self):
+        """
+        Return if button is active in Graphic Viewer
+        @return: True if button is active in Graphic Viewer
+        """
+        return self.Enabled
+    
+    def HitTest(self, x, y):
+        """
+        Test if point is inside button
+        @param x: X coordinate of point
+        @param y: Y coordinate of point
+        @return: True if button is active and displayed and point is inside
+        button 
+        """
+        # Return immediately if button is hidden or inactive
+        if not (self.IsShown() and self.IsEnabled()):
+            return False
+        
+        # Test if point is inside button
+        w, h = self.Bitmap.GetSize()
+        rect = wx.Rect(self.Position.x, self.Position.y, w, h)
+        return rect.InsideXY(x, y)
+    
+    def ProcessCallback(self):
+        """
+        Call callback function if defined
+        """
+        if self.Callback is not None:
+            self.Callback()
+    
+    def Draw(self, dc):
+        """
+        Draw button in Graphic Viewer
+        @param dc: wx.DC object corresponding to Graphic Viewer device context
+        """
+        # Only draw button if button is active and displayed
+        if self.Shown and self.Enabled:
+            dc.DrawBitmap(self.Bitmap, self.Position.x, self.Position.y, True)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,4 @@
+try:
+    from DebugVariableGraphicPanel import DebugVariableGraphicPanel as DebugVariablePanel
+except:
+    from DebugVariableTablePanel import DebugVariableTablePanel as DebugVariablePanel
\ No newline at end of file
--- a/controls/DurationCellEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/DurationCellEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -98,10 +98,11 @@
     '''
     Grid cell editor that uses DurationCellControl to display an edit button.
     '''
-    def __init__(self, table):
+    def __init__(self, table, colname):
         wx.grid.PyGridCellEditor.__init__(self)
         
         self.Table = table
+        self.Colname = colname
     
     def __del__(self):
         self.CellControl = None
@@ -114,15 +115,15 @@
 
     def BeginEdit(self, row, col, grid):
         self.CellControl.Enable()
-        self.CellControl.SetValue(self.Table.GetValueByName(row, 'Interval'))
+        self.CellControl.SetValue(self.Table.GetValueByName(row, self.Colname))
         self.CellControl.SetFocus()
 
     def EndEdit(self, row, col, grid):
         duration = self.CellControl.GetValue()
-        old_duration = self.Table.GetValueByName(row, 'Interval')
+        old_duration = self.Table.GetValueByName(row, self.Colname)
         changed = duration != old_duration
         if changed:
-            self.Table.SetValueByName(row, 'Interval', duration)
+            self.Table.SetValueByName(row, self.Colname, duration)
         self.CellControl.Disable()
         return changed
 
--- a/controls/FolderTree.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/FolderTree.py	Wed Jul 31 10:45:07 2013 +0900
@@ -203,19 +203,23 @@
             event.Veto()
     
     def OnTreeEndLabelEdit(self, event):
-        old_filepath = self.GetPath(event.GetItem())
-        new_filepath = os.path.join(os.path.split(old_filepath)[0], event.GetLabel())
-        if new_filepath != old_filepath:
-            if not os.path.exists(new_filepath):
-                os.rename(old_filepath, new_filepath)
-                event.Skip()
-            else:
-                message =  wx.MessageDialog(self, 
-                    _("File '%s' already exists!") % event.GetLabel(), 
-                    _("Error"), wx.OK|wx.ICON_ERROR)
-                message.ShowModal()
-                message.Destroy()
-                event.Veto()
+        new_name = event.GetLabel()
+        if new_name != "":
+            old_filepath = self.GetPath(event.GetItem())
+            new_filepath = os.path.join(os.path.split(old_filepath)[0], new_name)
+            if new_filepath != old_filepath:
+                if not os.path.exists(new_filepath):
+                    os.rename(old_filepath, new_filepath)
+                    event.Skip()
+                else:
+                    message =  wx.MessageDialog(self, 
+                        _("File '%s' already exists!") % new_name, 
+                        _("Error"), wx.OK|wx.ICON_ERROR)
+                    message.ShowModal()
+                    message.Destroy()
+                    event.Veto()
+        else:
+            event.Skip()
     
     def OnFilterChanged(self, event):
         self.CurrentFilter = self.Filters[self.Filter.GetStringSelection()]
--- a/controls/LibraryPanel.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/LibraryPanel.py	Wed Jul 31 10:45:07 2013 +0900
@@ -34,28 +34,50 @@
 #                              Library Panel
 #-------------------------------------------------------------------------------
 
+"""
+Class that implements a panel displaying a tree containing an hierarchical list
+of functions and function blocks available in project an a search control for
+quickly find one functions or function blocks in this list and a text control
+displaying informations about selected functions or function blocks
+"""
+
 class LibraryPanel(wx.Panel):
     
     def __init__(self, parent, enable_drag=False):
+        """
+        Constructor
+        @param parent: Parent wx.Window of LibraryPanel
+        @param enable_drag: Flag indicating that function or function block can
+        be drag'n drop from LibraryPanel (default: False)
+        """
         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
         
+        # Define LibraryPanel main sizer
         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
         main_sizer.AddGrowableCol(0)
         main_sizer.AddGrowableRow(1)
         
+        # Add SearchCtrl to main sizer
         self.SearchCtrl = wx.SearchCtrl(self)
+        # Add a button with a magnifying glass, essentially to show that this
+        # control is for searching in tree
         self.SearchCtrl.ShowSearchButton(True)
         self.Bind(wx.EVT_TEXT, self.OnSearchCtrlChanged, self.SearchCtrl)
         self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
-              self.OnSearchButtonClick, self.SearchCtrl)
+                  self.OnSearchButtonClick, self.SearchCtrl)
+        # Bind keyboard event on SearchCtrl text control to catch UP and DOWN
+        # for search previous and next occurrence
         search_textctrl = self.SearchCtrl.GetChildren()[0]
         search_textctrl.Bind(wx.EVT_CHAR, self.OnKeyDown)
         main_sizer.AddWindow(self.SearchCtrl, flag=wx.GROW)
         
+        # Add Splitter window for tree and block comment to main sizer
         splitter_window = wx.SplitterWindow(self)
         splitter_window.SetSashGravity(1.0)
         main_sizer.AddWindow(splitter_window, flag=wx.GROW)
         
+        # Add TreeCtrl for functions and function blocks library in splitter
+        # window
         self.Tree = wx.TreeCtrl(splitter_window,
               size=wx.Size(0, 0),  
               style=wx.TR_HAS_BUTTONS|
@@ -65,166 +87,336 @@
                     wx.TR_LINES_AT_ROOT)
         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemSelected, self.Tree)
         self.Tree.Bind(wx.EVT_CHAR, self.OnKeyDown)
+        # If drag'n drop is enabled, bind event generated when a drag begins on
+        # tree to start a drag'n drop
         if enable_drag:
             self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, self.Tree)
         
+        # Add TextCtrl for function and function block informations
         self.Comment = wx.TextCtrl(splitter_window, size=wx.Size(0, 80), 
               style=wx.TE_READONLY|wx.TE_MULTILINE)
         
         splitter_window.SplitHorizontally(self.Tree, self.Comment, -80)
         
         self.SetSizer(main_sizer)
-            
+        
+        # Reference to the project controller
         self.Controller = None
-    
+        
+        # Variable storing functions and function blocks library to display
         self.BlockList = None
     
     def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to project controller
         self.Controller = None
     
     def SetController(self, controller):
+        """
+        Set reference to project controller
+        @param controller: Reference to project controller
+        """
         self.Controller = controller
     
     def SetBlockList(self, blocklist):
+        """
+        Set function and function block library to display in TreeCtrl
+        @param blocklist: Function and function block library
+        """
+        # Save functions and function blocks library
         self.BlockList = blocklist
+        # Refresh TreeCtrl values
         self.RefreshTree()
     
     def SetFocus(self):
+        """
+        Called to give focus to LibraryPanel
+        Override wx.Window SetFocus method 
+        """
+        # Give focus to SearchCtrl
         self.SearchCtrl.SetFocus()
     
     def ResetTree(self):
+        """
+        Reset LibraryPanel values displayed in controls
+        """
+        # Clear SearchCtrl, TreeCtrl and TextCtrl
         self.SearchCtrl.SetValue("")
         self.Tree.DeleteAllItems()
         self.Comment.SetValue("")
     
     def RefreshTree(self):
-        if self.Controller is not None:
-            to_delete = []
-            selected_name = None
-            selected = self.Tree.GetSelection()
-            if selected.IsOk():
-                selected_pydata = self.Tree.GetPyData(selected)
-                if selected_pydata is not None and selected_pydata["type"] != CATEGORY:
-                    selected_name = self.Tree.GetItemText(selected)
-            if self.BlockList is not None:
-                blocktypes = self.BlockList
-            else:
-                blocktypes = self.Controller.GetBlockTypes()
+        """
+        Refresh LibraryPanel values displayed in controls
+        """
+        # Get function and function blocks library
+        blocktypes = self.BlockList
+        if blocktypes is None and self.Controller is not None:
+            # Get library from project controller if not defined
+            blocktypes = self.Controller.GetBlockTypes()
+        
+        # Refresh TreeCtrl values if a library is defined
+        if blocktypes is not None:
+            # List that will contain tree items to be deleted when TreeCtrl
+            # will be refreshed
+            items_to_delete = []
+            
+            # Get current selected item for selected it when values refreshed
+            selected_item = self.Tree.GetSelection()
+            selected_pydata = (self.Tree.GetPyData(selected_item)
+                               if selected_item.IsOk() and
+                                  selected_item != self.Tree.GetRootItem()
+                               else None)
+            # Don't save selected item if it is a category
+            selected_infos = ((self.Tree.GetItemText(selected_item),
+                               selected_pydata["inputs"])
+                             if selected_pydata is not None and
+                                selected_pydata["type"] == BLOCK
+                             else (None, None))
+            
+            # Get TreeCtrl root item (hidden)
             root = self.Tree.GetRootItem()
             if not root.IsOk():
+                # Create root if not present
                 root = self.Tree.AddRoot("")
+            
+            # Iterate over functions and function blocks library categories and
+            # add a tree item to root item for each of them
+            
+            # Get first child under root item
             category_item, root_cookie = self.Tree.GetFirstChild(root)
             for category in blocktypes:
+                # Store category name in a local variable to prevent script
+                # extracting translated strings for gettext to consider "name"
+                # to be translated
                 category_name = category["name"]
-                if not category_item.IsOk():
+                
+                # Tree item already exists, set item label
+                if category_item.IsOk():
+                    self.Tree.SetItemText(category_item, _(category_name))
+                
+                # Tree item doesn't exist, add new one to root
+                else:
                     category_item = self.Tree.AppendItem(root, _(category_name))
+                    # On Windows, needs to get next child of root to have a
+                    # reference to the newly added tree item
                     if wx.Platform != '__WXMSW__':
-                        category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
-                else:
-                    self.Tree.SetItemText(category_item, _(category_name))
+                        category_item, root_cookie = \
+                            self.Tree.GetNextChild(root, root_cookie)
+                
+                # Set data associated to tree item (only save that item is a 
+                # category)
                 self.Tree.SetPyData(category_item, {"type" : CATEGORY})
-                blocktype_item, category_cookie = self.Tree.GetFirstChild(category_item)
+                
+                # Iterate over functions and function blocks defined in library
+                # category add a tree item to category tree item for each of 
+                # them
+                
+                # Get first child under category tree item
+                blocktype_item, category_cookie = \
+                    self.Tree.GetFirstChild(category_item)
                 for blocktype in category["list"]:
-                    if not blocktype_item.IsOk():
-                        blocktype_item = self.Tree.AppendItem(category_item, blocktype["name"])
+                    
+                    # Tree item already exists, set item label
+                    if blocktype_item.IsOk():
+                        self.Tree.SetItemText(blocktype_item, blocktype["name"])
+                    
+                    # Tree item doesn't exist, add new one to category item
+                    else:
+                        blocktype_item = self.Tree.AppendItem(
+                                            category_item, blocktype["name"])
+                        # See comment when adding category
                         if wx.Platform != '__WXMSW__':
-                            blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
-                    else:
-                        self.Tree.SetItemText(blocktype_item, blocktype["name"])
+                            blocktype_item, category_cookie = \
+                                self.Tree.GetNextChild(category_item, 
+                                                       category_cookie)
+                    
+                    # Define data to associate to block tree item
+                    comment = blocktype["comment"]
                     block_data = {"type" : BLOCK, 
                                   "block_type" : blocktype["type"], 
-                                  "inputs" : tuple([type for name, type, modifier in blocktype["inputs"]]), 
-                                  "extension" : None}
-                    if blocktype["extensible"]:
-                        block_data["extension"] = len(blocktype["inputs"])
+                                  "inputs" : tuple([type 
+                                                    for name, type, modifier
+                                                    in blocktype["inputs"]]), 
+                                  "extension" : (len(blocktype["inputs"])
+                                                 if blocktype["extensible"]
+                                                 else None),
+                                  "comment": _(comment) + 
+                                             blocktype.get("usage", "")}
                     self.Tree.SetPyData(blocktype_item, block_data)
-                    if selected_name == blocktype["name"]:
+                    
+                    # Select block tree item in tree if it corresponds to
+                    # previously selected one
+                    if selected_infos == (blocktype["name"], 
+                                          blocktype["inputs"]):
                         self.Tree.SelectItem(blocktype_item)
-                        comment = blocktype["comment"]
-                        self.Comment.SetValue(_(comment) + blocktype.get("usage", ""))
-                    blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
+                        
+                        # Update TextCtrl value
+                        self.Comment.SetValue(block_data["comment"])
+                
+                    # Get next block tree item under category tree item
+                    blocktype_item, category_cookie = \
+                        self.Tree.GetNextChild(category_item, category_cookie)
+                
+                # Add every remaining tree item under category tree item after
+                # updating all block items to the list of items to delete
                 while blocktype_item.IsOk():
-                    to_delete.append(blocktype_item)
-                    blocktype_item, category_cookie = self.Tree.GetNextChild(category_item, category_cookie)
-                category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+                    items_to_delete.append(blocktype_item)
+                    blocktype_item, category_cookie = \
+                        self.Tree.GetNextChild(category_item, category_cookie)
+                
+                # Get next category tree item under root item
+                category_item, root_cookie = \
+                    self.Tree.GetNextChild(root, root_cookie)
+            
+            # Add every remaining tree item under root item after updating all 
+            # category items to the list of items to delete
             while category_item.IsOk():
-                to_delete.append(category_item)
-                category_item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
-            for item in to_delete:
+                items_to_delete.append(category_item)
+                category_item, root_cookie = \
+                    self.Tree.GetNextChild(root, root_cookie)
+            
+            # Remove all items in list of items to delete from TreeCtrl
+            for item in items_to_delete:
                 self.Tree.Delete(item)
     
     def GetSelectedBlock(self):
-        selected = self.Tree.GetSelection()
-        if (selected.IsOk() and 
-            self.Tree.GetItemParent(selected) != self.Tree.GetRootItem() and 
-            selected != self.Tree.GetRootItem()):
-            selected_data = self.Tree.GetPyData(selected)
-            return {"type": self.Tree.GetItemText(selected), 
-                    "inputs": selected_data["inputs"]}
-        return None
+        """
+        Get selected block informations
+        @return: {"type": block_type_name, "inputs": [input_type,...]} or None
+        if no block selected
+        """
+        # Get selected item associated data in tree
+        selected_item = self.Tree.GetSelection()
+        selected_pydata = (self.Tree.GetPyData(selected_item)
+                           if selected_item.IsOk() and
+                              selected_item != self.Tree.GetRootItem()
+                           else None)
+        
+        # Return value is None if selected tree item is root or a category
+        return ({"type": self.Tree.GetItemText(selected_item), 
+                 "inputs": selected_pydata["inputs"]}
+                if selected_pydata is not None and 
+                   selected_pydata["type"] == BLOCK
+                else None)
     
     def SelectTreeItem(self, name, inputs):
+        """
+        Select Tree item corresponding to block informations given 
+        @param name: Block type name
+        @param inputs: List of block inputs type [input_type,...]
+        """
+        # Find tree item corresponding to block informations
         item = self.FindTreeItem(self.Tree.GetRootItem(), name, inputs)
         if item is not None and item.IsOk():
+            # Select tree item found
             self.Tree.SelectItem(item)
             self.Tree.EnsureVisible(item)
     
-    def FindTreeItem(self, root, name, inputs = None):
-        if root.IsOk():
-            pydata = self.Tree.GetPyData(root)
-            if pydata is not None:
-                type_inputs = pydata.get("inputs", None)
-                type_extension = pydata.get("extension", None)
-                if inputs is not None and type_inputs is not None:
-                    if type_extension is not None:
-                        same_inputs = type_inputs == inputs[:type_extension]
-                    else:
-                        same_inputs = type_inputs == inputs
-                else:
-                    same_inputs = True
-            if pydata is not None and self.Tree.GetItemText(root) == name and same_inputs:
-                return root
+    def FindTreeItem(self, item, name, inputs = None):
+        """
+        Find Tree item corresponding to block informations given
+        Function is recursive
+        @param item: Item to test
+        @param name: Block type name
+        @param inputs: List of block inputs type [input_type,...]
+        """
+        # Return immediately if item isn't valid
+        if not item.IsOk():
+            return None
+        
+        # Get data associated to item to test
+        item_pydata = self.Tree.GetPyData(item)
+        if item_pydata is not None and item_pydata["type"] == BLOCK:
+            # Only test item corresponding to block
+            
+            # Test if block inputs type are the same than those given
+            type_inputs = item_pydata.get("inputs", None)
+            type_extension = item_pydata.get("extension", None)
+            if inputs is not None and type_inputs is not None:
+                same_inputs = reduce(
+                    lambda x, y: x and y,
+                    map(
+                        lambda x: x[0]==x[1] or x[0]=='ANY' or x[1]=='ANY',
+                        zip(type_inputs,
+                            (inputs[:type_extension]
+                             if type_extension is not None
+                             else inputs))),
+                    True)
             else:
-                if wx.VERSION < (2, 6, 0):
-                    item, root_cookie = self.Tree.GetFirstChild(root, 0)
-                else:
-                    item, root_cookie = self.Tree.GetFirstChild(root)
-                while item.IsOk():
-                    result = self.FindTreeItem(item, name, inputs)
-                    if result:
-                        return result
-                    item, root_cookie = self.Tree.GetNextChild(root, root_cookie)
+                same_inputs = True
+            
+            # Return item if  block data corresponds to informations given
+            if self.Tree.GetItemText(item) == name and same_inputs:
+                return item
+        
+        # Test item children if item doesn't correspond
+        child, child_cookie = self.Tree.GetFirstChild(item)
+        while child.IsOk():
+            result = self.FindTreeItem(child, name, inputs)
+            if result:
+                return result
+            child, child_cookie = self.Tree.GetNextChild(item, child_cookie)
+        
         return None
     
     def SearchInTree(self, value, mode="first"):
+        """
+        Search in Tree and select item that name contains string given
+        @param value: String contained in block name to find
+        @param mode: Search mode ('first', 'previous' or 'next')
+        (default: 'first')
+        @return: True if an item was found
+        """
+        # Return immediately if root isn't valid
         root = self.Tree.GetRootItem()
         if not root.IsOk():
             return False
         
-        if mode == "first":
+        # Set function to navigate in Tree item sibling according to search
+        # mode defined 
+        sibling_function = (self.Tree.GetPrevSibling
+                            if mode == "previous"
+                            else self.Tree.GetNextSibling)
+        
+        # Get current selected item (for next and previous mode)
+        item = self.Tree.GetSelection()
+        if not item.IsOk() or mode == "first":
             item, item_cookie = self.Tree.GetFirstChild(root)
             selected = None
         else:
-            item = self.Tree.GetSelection()
             selected = item
-            if not item.IsOk():
-                item, item_cookie = self.Tree.GetFirstChild(root)
+        
+        # Navigate through tree items until one matching found or reach tree
+        # starting or ending
         while item.IsOk():
+            
+            # Get item data to get item type
             item_pydata = self.Tree.GetPyData(item)
+            
+            # Item is a block category
             if item_pydata["type"] == CATEGORY:
-                if mode == "previous":
-                    child = self.Tree.GetLastChild(item)
-                else:
-                    child, child_cookie = self.Tree.GetFirstChild(item)
-                if child.IsOk():
-                    item = child
-                elif mode == "previous":
-                    item = self.Tree.GetPrevSibling(item)
-                else:
-                    item = self.Tree.GetNextSibling(item)
+                
+                # Get category first or last child according to search mode 
+                # defined
+                child = (self.Tree.GetLastChild(item)
+                         if mode == "previous"
+                         else self.Tree.GetFirstChild(item)[0])
+                
+                # If category has no child, go to sibling category
+                item = (child if child.IsOk() else sibling_function(item))
+            
+            # Item is a block
             else:
+                
+                # Extract item block name
                 name = self.Tree.GetItemText(item)
-                if name.upper().startswith(value.upper()) and item != selected:
+                # Test if block name contains string given
+                if name.upper().find(value.upper()) != -1 and item != selected:
+                    # Select block and collapse all categories other than block
+                    # category
                     child, child_cookie = self.Tree.GetFirstChild(root)
                     while child.IsOk():
                         self.Tree.CollapseAllChildren(child)
@@ -233,63 +425,91 @@
                     self.Tree.EnsureVisible(item)
                     return True
                 
-                elif mode == "previous":
-                    previous = self.Tree.GetPrevSibling(item)
-                    if previous.IsOk():
-                        item = previous
-                    else:
-                        parent = self.Tree.GetItemParent(item)
-                        item = self.Tree.GetPrevSibling(parent)
-                
-                else:
-                    next = self.Tree.GetNextSibling(item)
-                    if next.IsOk():
-                        item = next
-                    else:
-                        parent = self.Tree.GetItemParent(item)
-                        item = self.Tree.GetNextSibling(parent)
+                # Go to next item sibling if block not found
+                next = sibling_function(item)
+                
+                # If category has no other child, go to next category sibling
+                item = (next
+                        if next.IsOk()
+                        else sibling_function(self.Tree.GetItemParent(item)))
+        
         return False
     
     def OnSearchCtrlChanged(self, event):
+        """
+        Called when SearchCtrl text control value changed
+        @param event: TextCtrl change event
+        """
+        # Search for block containing SearchCtrl value in 'first' mode
         self.SearchInTree(self.SearchCtrl.GetValue())
         event.Skip()
     
     def OnSearchButtonClick(self, event):
+        """
+        Called when SearchCtrl search button was clicked
+        @param event: Button clicked event
+        """
+        # Search for block containing SearchCtrl value in 'next' mode
         self.SearchInTree(self.SearchCtrl.GetValue(), "next")
         event.Skip()
     
     def OnTreeItemSelected(self, event):
-        selected = event.GetItem()
-        pydata = self.Tree.GetPyData(selected)
-        if pydata is not None and pydata["type"] != CATEGORY:
-            blocktype = self.Controller.GetBlockType(self.Tree.GetItemText(selected), pydata["inputs"])
-            if blocktype:
-                comment = blocktype["comment"]
-                self.Comment.SetValue(_(comment) + blocktype.get("usage", ""))
-            else:
-                self.Comment.SetValue("")
-        else:
-            self.Comment.SetValue("")
+        """
+        Called when tree item is selected
+        @param event: wx.TreeEvent
+        """
+        # Update TextCtrl value with block selected usage
+        item_pydata = self.Tree.GetPyData(event.GetItem())
+        self.Comment.SetValue(
+            item_pydata["comment"]
+            if item_pydata is not None and item_pydata["type"] == BLOCK
+            else "")
+        
+        # Call extra function defined when tree item is selected
         if getattr(self, "_OnTreeItemSelected", None) is not None:
             self._OnTreeItemSelected(event)
+        
         event.Skip()
     
     def OnTreeBeginDrag(self, event):
-        selected = event.GetItem()
-        pydata = self.Tree.GetPyData(selected)
-        if pydata is not None and pydata["type"] == BLOCK:
-            data = wx.TextDataObject(str((self.Tree.GetItemText(selected), 
-                pydata["block_type"], "", pydata["inputs"])))
+        """
+        Called when a drag is started in tree
+        @param event: wx.TreeEvent
+        """
+        selected_item = event.GetItem()
+        item_pydata = self.Tree.GetPyData(selected_item)
+        
+        # Item dragged is a block
+        if item_pydata is not None and item_pydata["type"] == BLOCK:
+            # Start a drag'n drop
+            data = wx.TextDataObject(str(
+                (self.Tree.GetItemText(selected_item), 
+                 item_pydata["block_type"], 
+                 "", 
+                 item_pydata["inputs"])))
             dragSource = wx.DropSource(self.Tree)
             dragSource.SetData(data)
             dragSource.DoDragDrop()
     
     def OnKeyDown(self, event):
+        """
+        Called when key is pressed in SearchCtrl text control
+        @param event: wx.KeyEvent
+        """
+        # Get event keycode and value in SearchCtrl
         keycode = event.GetKeyCode()
         search_value = self.SearchCtrl.GetValue()
+        
+        # Up key was pressed and SearchCtrl isn't empty, search for block in
+        # 'previous' mode 
         if keycode == wx.WXK_UP and search_value != "":
             self.SearchInTree(search_value, "previous")
+        
+        # Down key was pressed and SearchCtrl isn't empty, search for block in
+        # 'next' mode 
         elif keycode == wx.WXK_DOWN and search_value != "":
             self.SearchInTree(search_value, "next")
+        
+        # Handle key normally
         else:
             event.Skip()
--- a/controls/LocationCellEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/LocationCellEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -85,13 +85,36 @@
         dialog = BrowseLocationsDialog(self, self.VarType, self.Controller)
         if dialog.ShowModal() == wx.ID_OK:
             infos = dialog.GetValues()
-
+        else:
+            infos = None
+        dialog.Destroy()
+        
+        if infos is not None:
+            location = infos["location"]
             # set the location
-            self.Location.SetValue(infos["location"])
+            if not infos["location"].startswith("%"):
+                dialog = wx.SingleChoiceDialog(self, 
+                      _("Select a variable class:"), _("Variable class"), 
+                      ["Input", "Output", "Memory"], 
+                      wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+                if dialog.ShowModal() == wx.ID_OK:
+                    selected = dialog.GetSelection()
+                else:
+                    selected = None
+                dialog.Destroy()
+                if selected is None:
+                    self.Location.SetFocus()
+                    return
+                if selected == 0:
+                    location = "%I" + location
+                elif selected == 1:
+                    location = "%Q" + location
+                else:
+                    location = "%M" + location
+            
+            self.Location.SetValue(location)
             self.VarType = infos["IEC_type"]
 
-        dialog.Destroy()
-
         self.Location.SetFocus()
 
     def OnLocationChar(self, event):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/LogViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,826 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from datetime import datetime
+from time import time as gettime
+import numpy
+
+import wx
+
+from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
+from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
+from targets.typemapping import LogLevelsCount, LogLevels
+from util.BitmapLibrary import GetBitmap
+
+THUMB_SIZE_RATIO = 1. / 8.
+
+def ArrowPoints(direction, width, height, xoffset, yoffset):
+    if direction == wx.TOP:
+        return [wx.Point(xoffset + 1, yoffset + height - 2),
+                wx.Point(xoffset + width / 2, yoffset + 1),
+                wx.Point(xoffset + width - 1, yoffset + height - 2)]
+    else:
+        return [wx.Point(xoffset + 1, yoffset - height + 1),
+                wx.Point(xoffset + width / 2, yoffset - 2),
+                wx.Point(xoffset + width - 1, yoffset - height + 1)]
+
+class LogScrollBar(wx.Panel):
+    
+    def __init__(self, parent, size):
+        wx.Panel.__init__(self, parent, size=size)
+        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
+        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
+        self.Bind(wx.EVT_MOTION, self.OnMotion)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_SIZE, self.OnResize)
+        
+        self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
+        self.ThumbScrollingStartPos = None
+    
+    def GetRangeRect(self):
+        width, height = self.GetClientSize()
+        return wx.Rect(0, width, width, height - 2 * width)
+    
+    def GetThumbRect(self):
+        width, height = self.GetClientSize()
+        range_rect = self.GetRangeRect()
+        thumb_size = range_rect.height * THUMB_SIZE_RATIO
+        thumb_range = range_rect.height - thumb_size
+        thumb_center_position = (thumb_size + (self.ThumbPosition + 1) * thumb_range) / 2.
+        thumb_start = int(thumb_center_position - thumb_size / 2.)
+        thumb_end = int(thumb_center_position + thumb_size / 2.)
+        return wx.Rect(0, range_rect.y + thumb_start, width, thumb_end - thumb_start)
+    
+    def RefreshThumbPosition(self, thumb_position=None):
+        if thumb_position is None:
+            thumb_position = self.ThumbPosition
+        if self.Parent.IsMessagePanelTop():
+            thumb_position = max(0., thumb_position)
+        if self.Parent.IsMessagePanelBottom():
+            thumb_position = min(0., thumb_position)
+        if thumb_position != self.ThumbPosition:
+            self.ThumbPosition = thumb_position
+            self.Parent.SetScrollSpeed(self.ThumbPosition)
+        self.Refresh()
+    
+    def OnLeftDown(self, event):
+        self.CaptureMouse()
+        posx, posy = event.GetPosition()
+        width, height = self.GetClientSize()
+        range_rect = self.GetRangeRect()
+        thumb_rect = self.GetThumbRect()
+        if range_rect.InsideXY(posx, posy):
+            if thumb_rect.InsideXY(posx, posy):
+                self.ThumbScrollingStartPos = wx.Point(posx, posy)
+            elif posy < thumb_rect.y:
+                self.Parent.ScrollToLast()
+            elif posy > thumb_rect.y + thumb_rect.height:
+                self.Parent.ScrollToFirst()
+        elif posy < width:
+            self.Parent.ScrollMessagePanelByPage(1)
+        elif posy > height - width:
+            self.Parent.ScrollMessagePanelByPage(-1)
+        event.Skip()
+        
+    def OnLeftUp(self, event):
+        self.ThumbScrollingStartPos = None
+        self.RefreshThumbPosition(0.)
+        if self.HasCapture():
+            self.ReleaseMouse()
+        event.Skip()
+        
+    def OnMotion(self, event):
+        if event.Dragging() and self.ThumbScrollingStartPos is not None:
+            posx, posy = event.GetPosition()
+            width, height = self.GetClientSize()
+            range_rect = self.GetRangeRect()
+            thumb_size = range_rect.height * THUMB_SIZE_RATIO
+            thumb_range = range_rect.height - thumb_size
+            self.RefreshThumbPosition(
+                max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. / thumb_range, 1.)))
+        event.Skip()
+    
+    def OnResize(self, event):
+        self.Refresh()
+        event.Skip()
+    
+    def OnEraseBackground(self, event):
+        pass
+    
+    def OnPaint(self, event):
+        dc = wx.BufferedPaintDC(self)
+        dc.Clear()
+        dc.BeginDrawing()
+        
+        gc = wx.GCDC(dc)
+        
+        width, height = self.GetClientSize()
+        
+        gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
+        gc.SetBrush(wx.GREY_BRUSH)
+        
+        gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
+        gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
+        
+        gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
+        gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
+        
+        thumb_rect = self.GetThumbRect()
+        exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
+                                 thumb_rect.width, thumb_rect.height)
+        if self.Parent.IsMessagePanelTop():
+            exclusion_rect.y, exclusion_rect.height = width, exclusion_rect.y + exclusion_rect.height - width
+        if self.Parent.IsMessagePanelBottom():
+            exclusion_rect.height = height - width - exclusion_rect.y
+        if exclusion_rect != thumb_rect:
+            colour = wx.NamedColour("LIGHT GREY")
+            gc.SetPen(wx.Pen(colour))
+            gc.SetBrush(wx.Brush(colour))
+        
+            gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y, 
+                             exclusion_rect.width, exclusion_rect.height)
+        
+        gc.SetPen(wx.GREY_PEN)
+        gc.SetBrush(wx.GREY_BRUSH)
+        
+        gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
+        
+        gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
+            
+        gc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
+                         thumb_rect.width, thumb_rect.height)
+        
+        dc.EndDrawing()
+        event.Skip()
+
+BUTTON_SIZE = (30, 15)
+
+class LogButton():
+    
+    def __init__(self, label, callback):
+        self.Position = wx.Point(0, 0)
+        self.Size = wx.Size(*BUTTON_SIZE)
+        self.Label = label
+        self.Shown = True
+        self.Callback = callback
+    
+    def __del__(self):
+        self.callback = None
+    
+    def GetSize(self):
+        return self.Size
+    
+    def SetPosition(self, x, y):
+        self.Position = wx.Point(x, y)
+    
+    def HitTest(self, x, y):
+        rect = wx.Rect(self.Position.x, self.Position.y, 
+                       self.Size.width, self.Size.height)
+        if rect.InsideXY(x, y):
+            return True
+        return False
+    
+    def ProcessCallback(self):
+        if self.Callback is not None:
+            wx.CallAfter(self.Callback)
+            
+    def Draw(self, dc):
+        dc.SetPen(wx.TRANSPARENT_PEN)
+        dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
+        
+        dc.DrawRectangle(self.Position.x, self.Position.y, 
+                         self.Size.width, self.Size.height)
+        
+        w, h = dc.GetTextExtent(self.Label)
+        dc.DrawText(self.Label, 
+            self.Position.x + (self.Size.width - w) / 2, 
+            self.Position.y + (self.Size.height - h) / 2)
+
+DATE_INFO_SIZE = 10
+MESSAGE_INFO_SIZE = 18
+
+class LogMessage:
+    
+    def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
+        self.Date = datetime.utcfromtimestamp(tv_sec)
+        self.Seconds = self.Date.second + tv_nsec * 1e-9
+        self.Date = self.Date.replace(second=0)
+        self.Timestamp = tv_sec + tv_nsec * 1e-9
+        self.Level = level
+        self.LevelBitmap = level_bitmap
+        self.Message = msg
+        self.DrawDate = True
+    
+    def __cmp__(self, other):
+        if self.Date == other.Date:
+            return cmp(self.Seconds, other.Seconds)
+        return cmp(self.Date, other.Date)
+    
+    def GetFullText(self):
+        date = self.Date.replace(second=int(self.Seconds))
+        nsec = (self.Seconds % 1.) * 1e9
+        return "%s at %s.%9.9d:\n%s" % (
+            LogLevels[self.Level],
+            str(date), nsec,
+            self.Message)
+    
+    def Draw(self, dc, offset, width, draw_date):
+        if draw_date:
+            datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
+            dw, dh = dc.GetTextExtent(datetime_text)
+            dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
+            offset += DATE_INFO_SIZE
+        
+        seconds_text = "%12.9f" % self.Seconds
+        sw, sh = dc.GetTextExtent(seconds_text)
+        dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
+        
+        bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
+        dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
+        
+        text = self.Message.replace("\n", " ")
+        mw, mh = dc.GetTextExtent(text)
+        dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
+        
+    def GetHeight(self, draw_date):
+        if draw_date:
+            return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
+        return MESSAGE_INFO_SIZE
+
+SECOND = 1
+MINUTE = 60 * SECOND
+HOUR = 60 * MINUTE
+DAY = 24 * HOUR
+
+CHANGE_TIMESTAMP_BUTTONS = [(_("1d"), DAY),
+                            (_("1h"), HOUR),
+                            (_("1m"), MINUTE),
+                            (_("1s"), SECOND)]
+
+class LogViewer(DebugViewer, wx.Panel):
+    
+    def __init__(self, parent, window):
+        wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
+        DebugViewer.__init__(self, None, False, False)
+        
+        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
+        main_sizer.AddGrowableCol(0)
+        main_sizer.AddGrowableRow(1)
+        
+        filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
+        
+        self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
+        self.MessageFilter.Append(_("All"))
+        levels = LogLevels[:3]
+        levels.reverse()
+        for level in levels:
+            self.MessageFilter.Append(_(level))
+        self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
+        filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+        
+        self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
+        self.SearchMessage.ShowSearchButton(True)
+        self.SearchMessage.ShowCancelButton(True)
+        self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
+        self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
+              self.OnSearchMessageSearchButtonClick, self.SearchMessage)
+        self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, 
+              self.OnSearchMessageCancelButtonClick, self.SearchMessage)
+        filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
+        
+        self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"), 
+              size=wx.Size(28, 28), style=wx.NO_BORDER)
+        self.CleanButton.SetToolTipString(_("Clean log messages"))
+        self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
+        filter_sizer.AddWindow(self.CleanButton)
+        
+        message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
+        message_panel_sizer.AddGrowableCol(0)
+        message_panel_sizer.AddGrowableRow(0)
+        main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
+        
+        self.MessagePanel = wx.Panel(self)
+        if wx.Platform == '__WXMSW__':
+            self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
+        else:
+            self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
+        self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp)
+        self.MessagePanel.Bind(wx.EVT_RIGHT_UP, self.OnMessagePanelRightUp)
+        self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick)
+        self.MessagePanel.Bind(wx.EVT_MOTION, self.OnMessagePanelMotion)
+        self.MessagePanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnMessagePanelLeaveWindow)
+        self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
+        self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
+        self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
+        self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
+        message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
+        
+        self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
+        message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
+        
+        self.SetSizer(main_sizer)
+        
+        self.LeftButtons = []
+        for label, callback in [("+" + text, self.GenerateOnDurationButton(duration)) 
+                                for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
+            self.LeftButtons.append(LogButton(label, callback))
+        
+        self.RightButtons = []
+        for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration)) 
+                                for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
+            self.RightButtons.append(LogButton(label, callback))
+        
+        self.MessageFilter.SetSelection(0)
+        self.LogSource = None
+        self.ResetLogMessages()
+        self.ParentWindow = window
+    
+        self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
+        self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
+        self.CurrentFilter = self.LevelFilters[0]
+        self.CurrentSearchValue = ""
+        
+        self.ScrollSpeed = 0.
+        self.LastStartTime = None
+        self.ScrollTimer = wx.Timer(self, -1)
+        self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
+        
+        self.LastMousePos = None
+        self.MessageToolTip = None
+        self.MessageToolTipTimer = wx.Timer(self, -1)
+        self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
+    
+    def __del__(self):
+        self.ScrollTimer.Stop()
+    
+    def ResetLogMessages(self):
+        self.previous_log_count = [None]*LogLevelsCount
+        self.OldestMessages = []
+        self.LogMessages = []
+        self.LogMessagesTimestamp = numpy.array([])
+        self.CurrentMessage = None
+        self.HasNewData = False
+    
+    def SetLogSource(self, log_source):
+        self.LogSource = log_source
+        self.CleanButton.Enable(self.LogSource is not None)
+        if log_source is not None:
+            self.ResetLogMessages()
+            self.RefreshView()
+    
+    def GetLogMessageFromSource(self, msgidx, level):
+        if self.LogSource is not None:
+            answer = self.LogSource.GetLogMessage(level, msgidx)
+            if answer is not None:
+                msg, tick, tv_sec, tv_nsec = answer
+                return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
+        return None
+    
+    def SetLogCounters(self, log_count):
+        new_messages = []
+        for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
+            if count is not None and prev != count:
+                if prev is None:
+                    dump_end = max(-1, count - 10)
+                    oldest_message = (-1, None)
+                else:
+                    dump_end = prev - 1
+                for msgidx in xrange(count-1, dump_end,-1):
+                    new_message = self.GetLogMessageFromSource(msgidx, level)
+                    if new_message is None:
+                        if prev is None:
+                            oldest_message = (-1, None)
+                        break
+                    if prev is None:
+                        oldest_message = (msgidx, new_message)
+                        if len(new_messages) == 0:
+                            new_messages = [new_message]
+                        else:
+                            new_messages.insert(0, new_message)
+                    else:
+                        new_messages.insert(0, new_message)
+                if prev is None and len(self.OldestMessages) <= level:
+                    self.OldestMessages.append(oldest_message)
+                self.previous_log_count[level] = count
+        new_messages.sort()
+        if len(new_messages) > 0:
+            self.HasNewData = True
+            if self.CurrentMessage is not None:
+                current_is_last = self.GetNextMessage(self.CurrentMessage)[0] is None
+            else:
+                current_is_last = True
+            for new_message in new_messages:
+                self.LogMessages.append(new_message)
+                self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp])
+            if current_is_last:
+                self.ScrollToLast(False)
+                self.ResetMessageToolTip()
+                self.MessageToolTipTimer.Stop()
+                self.ParentWindow.SelectTab(self)
+            self.NewDataAvailable(None)
+    
+    def FilterLogMessage(self, message, timestamp=None):
+        return (message.Level in self.CurrentFilter and 
+                message.Message.find(self.CurrentSearchValue) != -1 and
+                (timestamp is None or message.Timestamp < timestamp))
+    
+    def GetMessageByTimestamp(self, timestamp):
+        if self.CurrentMessage is not None:
+            msgidx = numpy.argmin(abs(self.LogMessagesTimestamp - timestamp))
+            message = self.LogMessages[msgidx]
+            if self.FilterLogMessage(message) and message.Timestamp > timestamp:
+                return self.GetPreviousMessage(msgidx, timestamp)
+            return message, msgidx
+        return None, None
+    
+    def GetNextMessage(self, msgidx):
+        while msgidx < len(self.LogMessages) - 1:
+            message = self.LogMessages[msgidx + 1]
+            if self.FilterLogMessage(message):
+                return message, msgidx + 1
+            msgidx += 1
+        return None, None
+    
+    def GetPreviousMessage(self, msgidx, timestamp=None):
+        message = None
+        while 0 < msgidx < len(self.LogMessages):
+            message = self.LogMessages[msgidx - 1]
+            if self.FilterLogMessage(message, timestamp):
+                return message, msgidx - 1
+            msgidx -= 1
+        if len(self.LogMessages) > 0:
+            message = self.LogMessages[0]
+            for idx, msg in self.OldestMessages:
+                if msg is not None and msg > message:
+                    message = msg
+            while message is not None:
+                level = message.Level
+                oldest_msgidx, oldest_message = self.OldestMessages[level]
+                if oldest_msgidx > 0:
+                    message = self.GetLogMessageFromSource(oldest_msgidx - 1, level)
+                    if message is not None:
+                        self.OldestMessages[level] = (oldest_msgidx - 1, message)
+                    else:
+                        self.OldestMessages[level] = (-1, None)
+                else:
+                    message = None
+                    self.OldestMessages[level] = (-1, None)
+                if message is not None:
+                    message_idx = 0
+                    while (message_idx < len(self.LogMessages) and 
+                           self.LogMessages[message_idx] < message):
+                        message_idx += 1
+                    if len(self.LogMessages) > 0:
+                        current_message = self.LogMessages[self.CurrentMessage]
+                    else:
+                        current_message = message
+                    self.LogMessages.insert(message_idx, message)
+                    self.LogMessagesTimestamp = numpy.insert(
+                            self.LogMessagesTimestamp, 
+                            [message_idx], 
+                            [message.Timestamp])
+                    self.CurrentMessage = self.LogMessages.index(current_message)
+                    if message_idx == 0 and self.FilterLogMessage(message, timestamp):
+                        return message, 0
+                for idx, msg in self.OldestMessages:
+                    if msg is not None and (message is None or msg > message):
+                        message = msg
+        return None, None
+    
+    def RefreshNewData(self, *args, **kwargs):
+        if self.HasNewData:
+            self.HasNewData = False
+            self.RefreshView()
+        DebugViewer.RefreshNewData(self, *args, **kwargs)
+    
+    def RefreshView(self):
+        width, height = self.MessagePanel.GetClientSize()
+        bitmap = wx.EmptyBitmap(width, height)
+        dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
+        dc.Clear()
+        dc.BeginDrawing()
+        
+        if self.CurrentMessage is not None:
+            
+            dc.SetFont(self.Font)
+            
+            for button in self.LeftButtons + self.RightButtons:
+                button.Draw(dc)
+            
+            message_idx = self.CurrentMessage
+            message = self.LogMessages[message_idx]
+            draw_date = True
+            offset = 5
+            while offset < height and message is not None:
+                message.Draw(dc, offset, width, draw_date)
+                offset += message.GetHeight(draw_date)
+                
+                previous_message, message_idx = self.GetPreviousMessage(message_idx)
+                if previous_message is not None:
+                    draw_date = message.Date != previous_message.Date
+                message = previous_message
+        
+        dc.EndDrawing()
+        
+        self.MessageScrollBar.RefreshThumbPosition()
+    
+    def IsMessagePanelTop(self, message_idx=None):
+        if message_idx is None:
+            message_idx = self.CurrentMessage
+        if message_idx is not None:
+            return self.GetNextMessage(message_idx)[0] is None
+        return True
+    
+    def IsMessagePanelBottom(self, message_idx=None):
+        if message_idx is None:
+            message_idx = self.CurrentMessage
+        if message_idx is not None:
+            width, height = self.MessagePanel.GetClientSize()
+            offset = 5
+            message = self.LogMessages[message_idx]
+            draw_date = True
+            while message is not None and offset < height:
+                offset += message.GetHeight(draw_date)
+                previous_message, message_idx = self.GetPreviousMessage(message_idx)
+                if previous_message is not None:
+                    draw_date = message.Date != previous_message.Date
+                message = previous_message
+            return offset < height
+        return True
+    
+    def ScrollMessagePanel(self, scroll):
+        if self.CurrentMessage is not None:
+            message = self.LogMessages[self.CurrentMessage]
+            while scroll > 0 and message is not None:
+                message, msgidx = self.GetNextMessage(self.CurrentMessage)
+                if message is not None:
+                    self.CurrentMessage = msgidx
+                    scroll -= 1
+            while scroll < 0 and message is not None and not self.IsMessagePanelBottom():
+                message, msgidx = self.GetPreviousMessage(self.CurrentMessage)
+                if message is not None:
+                    self.CurrentMessage = msgidx
+                    scroll += 1
+            self.RefreshView()
+    
+    def ScrollMessagePanelByPage(self, page):
+        if self.CurrentMessage is not None:
+            width, height = self.MessagePanel.GetClientSize()
+            message_per_page = max(1, (height - DATE_INFO_SIZE) / MESSAGE_INFO_SIZE - 1)
+            self.ScrollMessagePanel(page * message_per_page)
+    
+    def ScrollMessagePanelByTimestamp(self, seconds):
+        if self.CurrentMessage is not None:
+            current_message = self.LogMessages[self.CurrentMessage]
+            message, msgidx = self.GetMessageByTimestamp(current_message.Timestamp + seconds)
+            if message is None or self.IsMessagePanelBottom(msgidx):
+                self.ScrollToFirst()
+            else:
+                if seconds > 0 and self.CurrentMessage == msgidx and msgidx < len(self.LogMessages) - 1:
+                    msgidx += 1
+                self.CurrentMessage = msgidx
+                self.RefreshView()
+            
+    def ResetMessagePanel(self):
+        if len(self.LogMessages) > 0:
+            self.CurrentMessage = len(self.LogMessages) - 1
+            message = self.LogMessages[self.CurrentMessage]
+            while message is not None and not self.FilterLogMessage(message):
+                message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
+            self.RefreshView()
+    
+    def OnMessageFilterChanged(self, event):
+        self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
+        self.ResetMessagePanel()
+        event.Skip()
+    
+    def OnSearchMessageChanged(self, event):
+        self.CurrentSearchValue = self.SearchMessage.GetValue()
+        self.ResetMessagePanel()
+        event.Skip()
+        
+    def OnSearchMessageSearchButtonClick(self, event):
+        self.CurrentSearchValue = self.SearchMessage.GetValue()
+        self.ResetMessagePanel()
+        event.Skip()
+    
+    def OnSearchMessageCancelButtonClick(self, event):
+        self.CurrentSearchValue = ""
+        self.SearchMessage.SetValue("")
+        self.ResetMessagePanel()
+        event.Skip()
+    
+    def OnCleanButton(self, event):
+        if self.LogSource is not None:
+            self.LogSource.ResetLogCount()
+        self.ResetLogMessages()
+        self.RefreshView()
+        event.Skip()
+    
+    def GenerateOnDurationButton(self, duration):
+        def OnDurationButton():
+            self.ScrollMessagePanelByTimestamp(duration)
+        return OnDurationButton
+    
+    def GetCopyMessageToClipboardFunction(self, message):
+        def CopyMessageToClipboardFunction(event):
+            self.ParentWindow.SetCopyBuffer(message.GetFullText())
+        return CopyMessageToClipboardFunction
+    
+    def GetMessageByScreenPos(self, posx, posy):
+        if self.CurrentMessage is not None:
+            width, height = self.MessagePanel.GetClientSize()
+            message_idx = self.CurrentMessage
+            message = self.LogMessages[message_idx]
+            draw_date = True
+            offset = 5
+            
+            while offset < height and message is not None:
+                if draw_date:
+                    offset += DATE_INFO_SIZE
+    
+                if offset <= posy < offset + MESSAGE_INFO_SIZE:
+                    return message
+        
+                offset += MESSAGE_INFO_SIZE
+                
+                previous_message, message_idx = self.GetPreviousMessage(message_idx)
+                if previous_message is not None:
+                    draw_date = message.Date != previous_message.Date
+                message = previous_message
+        return None
+    
+    def OnMessagePanelLeftUp(self, event):
+        if self.CurrentMessage is not None:
+            posx, posy = event.GetPosition()
+            for button in self.LeftButtons + self.RightButtons:
+                if button.HitTest(posx, posy):
+                    button.ProcessCallback()
+                    break
+        event.Skip()
+    
+    def OnMessagePanelRightUp(self, event):
+        message = self.GetMessageByScreenPos(*event.GetPosition())
+        if message is not None:
+            menu = wx.Menu(title='')
+            
+            new_id = wx.NewId()
+            menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
+            self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
+    
+            self.MessagePanel.PopupMenu(menu)
+            menu.Destroy()
+        event.Skip()
+    
+    def OnMessagePanelLeftDCLick(self, event):
+        message = self.GetMessageByScreenPos(*event.GetPosition())
+        if message is not None:
+            self.SearchMessage.SetFocus()
+            self.SearchMessage.SetValue(message.Message)
+        event.Skip()
+    
+    def ResetMessageToolTip(self):
+        if self.MessageToolTip is not None:
+            self.MessageToolTip.Destroy()
+            self.MessageToolTip = None
+    
+    def OnMessageToolTipTimer(self, event):
+        if self.LastMousePos is not None:
+            message = self.GetMessageByScreenPos(*self.LastMousePos)
+            if message is not None:
+                tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos)
+                tooltip_pos.x += 10
+                tooltip_pos.y += 10
+                self.MessageToolTip = CustomToolTip(self.MessagePanel, message.GetFullText(), False)
+                self.MessageToolTip.SetFont(self.Font)
+                self.MessageToolTip.SetToolTipPosition(tooltip_pos)
+                self.MessageToolTip.Show()
+        event.Skip()
+    
+    def OnMessagePanelMotion(self, event):
+        if not event.Dragging():
+            self.ResetMessageToolTip()
+            self.LastMousePos = event.GetPosition()
+            self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
+        event.Skip()
+        
+    def OnMessagePanelLeaveWindow(self, event):
+        self.ResetMessageToolTip()
+        self.LastMousePos = None
+        self.MessageToolTipTimer.Stop()
+        event.Skip()
+    
+    def OnMessagePanelMouseWheel(self, event):
+        self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
+        event.Skip()
+    
+    def OnMessagePanelEraseBackground(self, event):
+        pass
+    
+    def OnMessagePanelPaint(self, event):
+        self.RefreshView()
+        event.Skip()
+    
+    def OnMessagePanelResize(self, event):
+        width, height = self.MessagePanel.GetClientSize()
+        offset = 2
+        for button in self.LeftButtons:
+            button.SetPosition(offset, 2)
+            w, h = button.GetSize()
+            offset += w + 2
+        offset = width - 2
+        for button in self.RightButtons:
+            w, h = button.GetSize()
+            button.SetPosition(offset - w, 2)
+            offset -= w + 2
+        if self.IsMessagePanelBottom():
+            self.ScrollToFirst()
+        else:
+            self.RefreshView()
+        event.Skip()
+    
+    def OnScrollTimer(self, event):
+        if self.ScrollSpeed != 0.:
+            speed_norm = abs(self.ScrollSpeed)
+            period = REFRESH_PERIOD / speed_norm
+            self.ScrollMessagePanel(-speed_norm / self.ScrollSpeed)
+            self.LastStartTime = gettime()
+            self.ScrollTimer.Start(int(period * 1000), True)
+        event.Skip()
+    
+    def SetScrollSpeed(self, speed):
+        if speed == 0.:
+            self.ScrollTimer.Stop()
+        else:
+            speed_norm = abs(speed)
+            period = REFRESH_PERIOD / speed_norm
+            current_time = gettime()
+            if self.LastStartTime is not None:
+                elapsed_time = current_time - self.LastStartTime
+                if elapsed_time > period:
+                    self.ScrollMessagePanel(-speed_norm / speed)
+                    self.LastStartTime = current_time
+                else:
+                    period -= elapsed_time
+            else:
+                self.LastStartTime = current_time
+            self.ScrollTimer.Start(int(period * 1000), True)
+        self.ScrollSpeed = speed    
+    
+    def ScrollToLast(self, refresh=True):
+        if len(self.LogMessages) > 0:
+            self.CurrentMessage = len(self.LogMessages) - 1
+            message = self.LogMessages[self.CurrentMessage]
+            if not self.FilterLogMessage(message):
+                message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
+            if refresh:
+                self.RefreshView()
+
+    def ScrollToFirst(self):
+        if len(self.LogMessages) > 0:
+            message_idx = 0
+            message = self.LogMessages[message_idx]
+            if not self.FilterLogMessage(message):
+                next_message, msgidx = self.GetNextMessage(message_idx)
+                if next_message is not None:
+                    message_idx = msgidx
+                    message = next_message
+            while message is not None:
+                message, msgidx = self.GetPreviousMessage(message_idx)
+                if message is not None:
+                    message_idx = msgidx
+            message = self.LogMessages[message_idx]
+            if self.FilterLogMessage(message):
+                while message is not None:
+                    message, msgidx = self.GetNextMessage(message_idx)
+                    if message is not None:
+                        if not self.IsMessagePanelBottom(msgidx):
+                            break
+                        message_idx = msgidx
+                self.CurrentMessage = message_idx
+            else:
+                self.CurrentMessage = None
+            self.RefreshView()
--- a/controls/PouInstanceVariablesPanel.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/PouInstanceVariablesPanel.py	Wed Jul 31 10:45:07 2013 +0900
@@ -52,11 +52,10 @@
         self.InstanceChoice = wx.ComboBox(self, size=wx.Size(0, 0), style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnInstanceChoiceChanged,
                 self.InstanceChoice)
-        self.InstanceChoice.Bind(wx.EVT_LEFT_DOWN, self.OnInstanceChoiceLeftDown)
         
         self.DebugButton = wx.lib.buttons.GenBitmapButton(self, 
               bitmap=GetBitmap("debug_instance"), size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.ParentButton.SetToolTipString(_("Debug instance"))
+        self.DebugButton.SetToolTipString(_("Debug instance"))
         self.Bind(wx.EVT_BUTTON, self.OnDebugButtonClick, 
                 self.DebugButton)
         
@@ -74,6 +73,7 @@
         self.VariablesList.Bind(CT.EVT_TREE_ITEM_ACTIVATED,
                 self.OnVariablesListItemActivated)
         self.VariablesList.Bind(wx.EVT_LEFT_DOWN, self.OnVariablesListLeftDown)
+        self.VariablesList.Bind(wx.EVT_KEY_DOWN, self.OnVariablesListKeyDown)
         
         buttons_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
         buttons_sizer.AddWindow(self.ParentButton)
@@ -112,16 +112,21 @@
         self.RefreshView()
     
     def SetPouType(self, tagname, pou_instance=None):
-        if  self.Controller is not None:
-            self.PouTagName = tagname
-            if self.PouTagName == "Project":
+        if self.Controller is not None:
+            if tagname == "Project":
                 config_name = self.Controller.GetProjectMainConfigurationName()
                 if config_name is not None:
-                    self.PouTagName = self.Controller.ComputeConfigurationName(config_name)
+                    tagname = self.Controller.ComputeConfigurationName(config_name)
             if pou_instance is not None:
                 self.PouInstance = pou_instance
-        
-        self.RefreshView()
+            
+            if self.PouTagName != tagname:
+                self.PouTagName = tagname
+                self.RefreshView()
+            else:
+                self.RefreshInstanceChoice()
+        else:
+            self.RefreshView()
     
     def ResetView(self):
         self.Controller = None
@@ -133,9 +138,8 @@
         self.RefreshView()
     
     def RefreshView(self):
+        self.Freeze()
         self.VariablesList.DeleteAllItems()
-        self.InstanceChoice.Clear()
-        self.InstanceChoice.SetValue("")
         
         if self.Controller is not None and self.PouTagName is not None:
             self.PouInfos = self.Controller.GetPouVariables(self.PouTagName, self.Debug)
@@ -173,6 +177,7 @@
                           bitmap=GetBitmap("debug_instance"), 
                           size=wx.Size(28, 28), style=wx.NO_BORDER)
                     self.Bind(wx.EVT_BUTTON, self.GenDebugButtonCallback(var_infos), debug_button)
+                    debug_button.Bind(wx.EVT_LEFT_DCLICK, self.GenDebugButtonDClickCallback(var_infos))
                     buttons.append(debug_button)
                 
                 button_num = len(buttons)
@@ -194,6 +199,15 @@
                 self.VariablesList.SetItemImage(item, self.ParentWindow.GetTreeImage(var_infos["class"]))
                 self.VariablesList.SetPyData(item, var_infos)
             
+        self.RefreshInstanceChoice()
+        self.RefreshButtons()
+        
+        self.Thaw()
+    
+    def RefreshInstanceChoice(self):
+        self.InstanceChoice.Clear()
+        self.InstanceChoice.SetValue("")
+        if self.Controller is not None and self.PouInfos is not None:
             instances = self.Controller.SearchPouInstances(self.PouTagName, self.Debug)
             for instance in instances:
                 self.InstanceChoice.Append(instance)
@@ -207,9 +221,7 @@
             else:
                 self.PouInstance = None
                 self.InstanceChoice.SetValue(_("Select an instance"))
-        
-        self.RefreshButtons()
-        
+    
     def RefreshButtons(self):
         enabled = self.InstanceChoice.GetSelection() != -1
         self.ParentButton.Enable(enabled and self.PouInfos["class"] != ITEM_CONFIGURATION)
@@ -278,6 +290,18 @@
             event.Skip()
         return DebugButtonCallback
     
+    def GenDebugButtonDClickCallback(self, infos):
+        def DebugButtonDClickCallback(event):
+            if self.InstanceChoice.GetSelection() != -1:
+                if infos["class"] in ITEMS_VARIABLE:
+                    self.ParentWindow.AddDebugVariable(
+                        "%s.%s" % (self.InstanceChoice.GetStringSelection(), 
+                                   infos["name"]), 
+                        force=True,
+                        graph=True)
+            event.Skip()
+        return DebugButtonDClickCallback
+    
     def GenGraphButtonCallback(self, infos):
         def GraphButtonCallback(event):
             if self.InstanceChoice.GetSelection() != -1:
@@ -321,21 +345,27 @@
         event.Skip()
         
     def OnVariablesListItemActivated(self, event):
-        if self.InstanceChoice.GetSelection() != -1:
-            instance_path = self.InstanceChoice.GetStringSelection()
-            selected_item = event.GetItem()
-            if selected_item is not None and selected_item.IsOk():
-                item_infos = self.VariablesList.GetPyData(selected_item)
-                if item_infos is not None and item_infos["class"] not in ITEMS_VARIABLE:
-                    if item_infos["class"] == ITEM_RESOURCE:
+        selected_item = event.GetItem()
+        if selected_item is not None and selected_item.IsOk():
+            item_infos = self.VariablesList.GetPyData(selected_item)
+            if item_infos is not None and item_infos["class"] not in ITEMS_VARIABLE:
+                instance_path = self.InstanceChoice.GetStringSelection()
+                if item_infos["class"] == ITEM_RESOURCE:
+                    if instance_path != "":
                         tagname = self.Controller.ComputeConfigurationResourceName(
                                        instance_path, 
                                        item_infos["name"])
                     else:
-                        tagname = self.Controller.ComputePouName(item_infos["type"])
-                    item_path = "%s.%s" % (instance_path, item_infos["name"])
-                    wx.CallAfter(self.SetPouType, tagname, item_path)
-                    wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, tagname)
+                        tagname = None
+                else:
+                    tagname = self.Controller.ComputePouName(item_infos["type"])
+                if tagname is not None:
+                    if instance_path != "":
+                        item_path = "%s.%s" % (instance_path, item_infos["name"])
+                    else:
+                        item_path = None
+                    self.SetPouType(tagname, item_path)
+                    self.ParentWindow.SelectProjectTreeItem(tagname)
         event.Skip()
     
     def OnVariablesListLeftDown(self, event):
@@ -347,12 +377,17 @@
             if item is not None and flags & CT.TREE_HITTEST_ONITEMLABEL:
                 item_infos = self.VariablesList.GetPyData(item)
                 if item_infos is not None and item_infos["class"] in ITEMS_VARIABLE:
+                    self.ParentWindow.EnsureTabVisible(
+                        self.ParentWindow.DebugVariablePanel)
                     item_path = "%s.%s" % (instance_path, item_infos["name"])
                     data = wx.TextDataObject(str((item_path, "debug")))
                     dragSource = wx.DropSource(self.VariablesList)
                     dragSource.SetData(data)
                     dragSource.DoDragDrop()
         event.Skip()
-        
-    def OnInstanceChoiceLeftDown(self, event):
-        event.Skip()
+
+    def OnVariablesListKeyDown(self, event):
+        keycode = event.GetKeyCode()
+        if keycode != wx.WXK_LEFT:
+            event.Skip()
+
--- a/controls/TextCtrlAutoComplete.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/TextCtrlAutoComplete.py	Wed Jul 31 10:45:07 2013 +0900
@@ -28,28 +28,23 @@
 MAX_ITEM_COUNT = 10
 MAX_ITEM_SHOWN = 6
 if wx.Platform == '__WXMSW__':
-    ITEM_INTERVAL_HEIGHT = 3
+    LISTBOX_BORDER_HEIGHT = 2
+    LISTBOX_INTERVAL_HEIGHT = 0
 else:
-    ITEM_INTERVAL_HEIGHT = 6
-
-if wx.Platform == '__WXMSW__':
-    popupclass = wx.PopupTransientWindow
-else:
-    popupclass = wx.PopupWindow
-
-class PopupWithListbox(popupclass):
+    LISTBOX_BORDER_HEIGHT = 4
+    LISTBOX_INTERVAL_HEIGHT = 6
+
+class PopupWithListbox(wx.PopupWindow):
     
     def __init__(self, parent, choices=[]):
-        popupclass.__init__(self, parent, wx.SIMPLE_BORDER)
+        wx.PopupWindow.__init__(self, parent, wx.BORDER_SIMPLE)
         
         self.ListBox = wx.ListBox(self, -1, style=wx.LB_HSCROLL|wx.LB_SINGLE|wx.LB_SORT)
-        if not wx.Platform == '__WXMSW__':
-            self.ListBox.Bind(wx.EVT_LISTBOX, self.OnListBoxClick)
-            self.ListBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxClick)
-            
+        
         self.SetChoices(choices)
         
-        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+        self.ListBox.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
+        self.ListBox.Bind(wx.EVT_MOTION, self.OnMotion)
     
     def SetChoices(self, choices):
         max_text_width = 0
@@ -64,10 +59,14 @@
         
         itemcount = min(len(choices), MAX_ITEM_SHOWN)
         width = self.Parent.GetSize()[0]
-        height = max_text_height * itemcount + ITEM_INTERVAL_HEIGHT * (itemcount + 1)
+        height = max_text_height * itemcount + \
+                 LISTBOX_INTERVAL_HEIGHT * max(0, itemcount - 1) + \
+                 2 * LISTBOX_BORDER_HEIGHT
         if max_text_width + 10 > width:
             height += 15
         size = wx.Size(width, height)
+        if wx.Platform == '__WXMSW__':
+            size.width -= 2
         self.ListBox.SetSize(size)
         self.SetClientSize(size)
     
@@ -87,28 +86,28 @@
     def GetSelection(self):
         return self.ListBox.GetStringSelection()
     
-    def ProcessLeftDown(self, event):
+    def OnLeftDown(self, event):
         selected = self.ListBox.HitTest(wx.Point(event.m_x, event.m_y))
+        parent_size = self.Parent.GetSize()
+        parent_rect = wx.Rect(0, -parent_size[1], parent_size[0], parent_size[1])
         if selected != wx.NOT_FOUND:
             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
-        return False
-    
-    def OnListBoxClick(self, event):
-        selected = event.GetSelection()
-        if selected != wx.NOT_FOUND:
-            wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
-        event.Skip()
-    
-    def OnKeyDown(self, event):
-        self.Parent.ProcessEvent(event)
-
-    def OnDismiss(self):
-        self.Parent.listbox = None
-        wx.CallAfter(self.Parent.DismissListBox)
+        elif parent_rect.InsideXY(event.m_x, event.m_y):
+            result, x, y = self.Parent.HitTest(wx.Point(event.m_x, event.m_y + parent_size[1]))
+            if result != wx.TE_HT_UNKNOWN:
+                self.Parent.SetInsertionPoint(self.Parent.XYToPosition(x, y))
+        else:
+            wx.CallAfter(self.Parent.DismissListBox)
+        event.Skip()
+    
+    def OnMotion(self, event):
+        self.ListBox.SetSelection(
+            self.ListBox.HitTest(wx.Point(event.m_x, event.m_y)))
+        event.Skip()
     
 class TextCtrlAutoComplete(wx.TextCtrl):
 
-    def __init__ (self, parent, appframe, choices=None, dropDownClick=True,
+    def __init__ (self, parent, choices=None, dropDownClick=True,
                   element_path=None, **therest):
         """
         Constructor works just like wx.TextCtrl except you can pass in a
@@ -119,11 +118,11 @@
         therest['style'] = wx.TE_PROCESS_ENTER | therest.get('style', 0)
 
         wx.TextCtrl.__init__(self, parent, **therest)
-        self.AppFrame = appframe
         
         #Some variables
         self._dropDownClick = dropDownClick
         self._lastinsertionpoint = None
+        self._hasfocus = False
         
         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
         self.element_path = element_path
@@ -148,9 +147,6 @@
             self.Bind(wx.EVT_LEFT_DOWN, self.OnClickToggleDown)
             self.Bind(wx.EVT_LEFT_UP, self.OnClickToggleUp)
 
-    def __del__(self):
-        self.AppFrame = None
-
     def ChangeValue(self, value):
         wx.TextCtrl.ChangeValue(self, value)
         self.RefreshListBoxChoices()
@@ -171,7 +167,11 @@
             else:
                 self.listbox.MoveSelection(-1)
         elif keycode in [wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_RETURN] and self.listbox is not None:
-            self.SetValueFromSelected(self.listbox.GetSelection())
+            selected = self.listbox.GetSelection()
+            if selected != "":
+                self.SetValueFromSelected(selected)
+            else:
+                event.Skip()
         elif event.GetKeyCode() == wx.WXK_ESCAPE:
             self.DismissListBox()
         else:
@@ -182,7 +182,9 @@
         event.Skip()
 
     def OnClickToggleUp(self, event):
-        if self.GetInsertionPoint() == self._lastinsertionpoint:
+        if not self._hasfocus:
+            self._hasfocus = True
+        elif self.GetInsertionPoint() == self._lastinsertionpoint:
             wx.CallAfter(self.PopupListBox)
         self._lastinsertionpoint = None
         event.Skip()
@@ -197,6 +199,7 @@
             config.Flush()
             self.SetChoices(listentries)
         self.DismissListBox()
+        self._hasfocus = False
         event.Skip()
     
     def SetChoices(self, choices):
@@ -207,13 +210,13 @@
         return self._choices
     
     def SetValueFromSelected(self, selected):
-         """
-         Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
-         Will do nothing if no item is selected in the wx.ListCtrl.
-         """
-         if selected != "":
+        """
+        Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
+        Will do nothing if no item is selected in the wx.ListCtrl.
+        """
+        if selected != "":
             self.SetValue(selected)
-         self.DismissListBox()
+        self.DismissListBox()
     
     def RefreshListBoxChoices(self):
         if self.listbox is not None:
@@ -229,22 +232,18 @@
             # depending on available screen space...
             pos = self.ClientToScreen((0, 0))
             sz = self.GetSize()
+            if wx.Platform == '__WXMSW__':
+                pos.x -= 2
+                pos.y -= 2
             self.listbox.Position(pos, (0, sz[1]))
             
             self.RefreshListBoxChoices()
             
-            if wx.Platform == '__WXMSW__':
-                self.listbox.Popup()
-            else:
-                self.listbox.Show()
-            self.AppFrame.EnableScrolling(False)
+            self.listbox.Show()
 
     def DismissListBox(self):
         if self.listbox is not None:
-            if wx.Platform == '__WXMSW__':
-                self.listbox.Dismiss()
-            else:
-                self.listbox.Destroy()
+            if self.listbox.ListBox.HasCapture():
+                self.listbox.ListBox.ReleaseMouse()
+            self.listbox.Destroy()
             self.listbox = None
-        self.AppFrame.EnableScrolling(True)
-
--- a/controls/VariablePanel.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/VariablePanel.py	Wed Jul 31 10:45:07 2013 +0900
@@ -42,15 +42,15 @@
 #                                 Helpers
 #-------------------------------------------------------------------------------
 
-def AppendMenu(parent, help, id, kind, text):
-    if wx.VERSION >= (2, 6, 0):
-        parent.Append(help=help, id=id, kind=kind, text=text)
-    else:
-        parent.Append(helpString=help, id=id, kind=kind, item=text)
-
+def AppendMenu(parent, help, id, kind, text):
+    if wx.VERSION >= (2, 6, 0):
+        parent.Append(help=help, id=id, kind=kind, text=text)
+    else:
+        parent.Append(helpString=help, id=id, kind=kind, item=text)
+
 [TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, 
  POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
-] = range(10)
+] = range(10)
 
 def GetVariableTableColnames(location):
     _ = lambda x : x
@@ -89,6 +89,7 @@
                       }
 
 LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$")
+VARIABLE_NAME_SUFFIX_MODEL = re.compile("([0-9]*)$")
 
 #-------------------------------------------------------------------------------
 #                            Variables Panel Table
@@ -166,11 +167,8 @@
                     elif col != 0 and self.GetValueByName(row, "Edit"):
                         grid.SetReadOnly(row, col, False)
                         if colname == "Name":
-                            if self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
-                                grid.SetReadOnly(row, col, True)
-                            else:
-                                editor = wx.grid.GridCellTextEditor()
-                                renderer = wx.grid.GridCellStringRenderer()
+                            editor = wx.grid.GridCellTextEditor()
+                            renderer = wx.grid.GridCellStringRenderer()
                         elif colname == "Initial Value":
                             if var_class not in ["External", "InOut"]:
                                 if self.Parent.Controler.IsEnumeratedType(var_type):
@@ -188,13 +186,11 @@
                             else:
                                 grid.SetReadOnly(row, col, True)
                         elif colname == "Class":
-                            if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
+                            if len(self.Parent.ClassList) == 1:
                                 grid.SetReadOnly(row, col, True)
                             else:
                                 editor = wx.grid.GridCellChoiceEditor()
                                 excluded = []
-                                if self.Parent.PouIsUsed:
-                                    excluded.extend(["Input","Output","InOut"])
                                 if self.Parent.IsFunctionBlockType(var_type):
                                     excluded.extend(["Local","Temp"])
                                 editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
@@ -302,22 +298,61 @@
                         self.ParentWindow.Table.SetValue(row, col, values[0])
                         self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
                         self.ParentWindow.SaveValues()
-            elif (element_type not in ["config", "resource"] and values[1] == "Global" and self.ParentWindow.Filter in ["All", "Interface", "External"] or
-                  element_type in ["config", "resource"] and values[1] == "location"):
+            elif (element_type not in ["config", "resource", "function"] and values[1] == "Global" and 
+                  self.ParentWindow.Filter in ["All", "Interface", "External"] or
+                  element_type != "function" and values[1] == "location"):
                 if values[1] == "location":
                     var_name = values[3]
                 else:
                     var_name = values[0]
                 tagname = self.ParentWindow.GetTagName()
-                if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
+                if var_name.upper() in [name.upper() 
+                        for name in self.ParentWindow.Controler.\
+                            GetProjectPouNames(self.ParentWindow.Debug)]:
                     message = _("\"%s\" pou already exists!")%var_name
-                elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
+                elif not var_name.upper() in [name.upper() 
+                        for name in self.ParentWindow.Controler.\
+                            GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
                     var_infos = self.ParentWindow.DefaultValue.copy()
                     var_infos["Name"] = var_name
                     var_infos["Type"] = values[2]
                     if values[1] == "location":
-                        var_infos["Class"] = "Global"
-                        var_infos["Location"] = values[0]
+                        location = values[0]
+                        if not location.startswith("%"):
+                            dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow, 
+                                  _("Select a variable class:"), _("Variable class"), 
+                                  ["Input", "Output", "Memory"], 
+                                  wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
+                            if dialog.ShowModal() == wx.ID_OK:
+                                selected = dialog.GetSelection()
+                            else:
+                                selected = None
+                            dialog.Destroy()
+                            if selected is None:
+                                return
+                            if selected == 0:
+                                location = "%I" + location
+                            elif selected == 1:
+                                location = "%Q" + location
+                            else:
+                                location = "%M" + location
+                        if element_type == "functionBlock":
+                            configs = self.ParentWindow.Controler.GetProjectConfigNames(
+                                                                self.ParentWindow.Debug)
+                            if len(configs) == 0:
+                                return
+                            if not var_name.upper() in [name.upper() 
+                                for name in self.ParentWindow.Controler.\
+                                    GetConfigurationVariableNames(configs[0])]:
+                                self.ParentWindow.Controler.AddConfigurationGlobalVar(
+                                    configs[0], values[2], var_name, location, "")
+                            var_infos["Class"] = "External"
+                        else:
+                            if element_type == "program":
+                                var_infos["Class"] = "Local"
+                            else:
+                                var_infos["Class"] = "Global"
+                            var_infos["Location"] = location
                     else:
                         var_infos["Class"] = "External"
                     var_infos["Number"] = len(self.ParentWindow.Values)
@@ -486,34 +521,56 @@
         self.VariablesGrid.SetEditable(not self.Debug)
         
         def _AddVariable(new_row):
-            if not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]:
+            if new_row > 0:
+                row_content = self.Values[new_row - 1].copy()
+                
+                result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content["Name"])
+                if result is not None:
+                    name = row_content["Name"][:result.start(1)]
+                    suffix = result.group(1)
+                    if suffix != "":
+                        start_idx = int(suffix)
+                    else:
+                        start_idx = 0
+                else:
+                    name = row_content["Name"]
+                    start_idx = 0
+            else:
+                row_content = None
+                start_idx = 0
+                name = "LocalVar"
+                
+            if row_content is not None and row_content["Edit"]: 
+                row_content = self.Values[new_row - 1].copy()
+            else:
                 row_content = self.DefaultValue.copy()
                 if self.Filter in self.DefaultTypes:
                     row_content["Class"] = self.DefaultTypes[self.Filter]
                 else:
                     row_content["Class"] = self.Filter
-                if self.Filter == "All" and len(self.Values) > 0:
-                    self.Values.insert(new_row, row_content)
-                else:
-                    self.Values.append(row_content)
-                    new_row = self.Table.GetNumberRows()
-                self.SaveValues()
-                self.RefreshValues()
-                return new_row
-            return self.VariablesGrid.GetGridCursorRow()
+            
+            row_content["Name"] = self.Controler.GenerateNewName(
+                    self.TagName, None, name + "%d", start_idx)
+            
+            if self.Filter == "All" and len(self.Values) > 0:
+                self.Values.insert(new_row, row_content)
+            else:
+                self.Values.append(row_content)
+                new_row = self.Table.GetNumberRows()
+            self.SaveValues()
+            self.RefreshValues()
+            return new_row
         setattr(self.VariablesGrid, "_AddRow", _AddVariable)
         
         def _DeleteVariable(row):
-            if (self.Table.GetValueByName(row, "Edit") and 
-                (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
+            if self.Table.GetValueByName(row, "Edit"):
                 self.Values.remove(self.Table.GetRow(row))
                 self.SaveValues()
                 self.RefreshValues()
         setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
             
         def _MoveVariable(row, move):
-            if (self.Filter == "All" and 
-                (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
+            if self.Filter == "All":
                 new_row = max(0, min(row + move, len(self.Values) - 1))
                 if new_row != row:
                     self.Values.insert(new_row, self.Values.pop(row))
@@ -532,12 +589,10 @@
                 if table_length > 0:
                     row = self.VariablesGrid.GetGridCursorRow()
                     row_edit = self.Table.GetValueByName(row, "Edit")
-                    if self.PouIsUsed:
-                        row_class = self.Table.GetValueByName(row, "Class")
-                self.AddButton.Enable(not self.Debug and (not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]))
-                self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"]))
-                self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
-                self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
+                self.AddButton.Enable(not self.Debug)
+                self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit))
+                self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All"))
+                self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All"))
         setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons)
         
         self.VariablesGrid.SetRowLabelSize(0)
@@ -572,10 +627,8 @@
         
         words = self.TagName.split("::")
         if self.ElementType == "config":
-            self.PouIsUsed = False
             self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
         elif self.ElementType == "resource":
-            self.PouIsUsed = False
             self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
         else:
             if self.ElementType == "function":
@@ -584,7 +637,6 @@
                     self.ReturnType.Append(data_type)
                 returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
             description = self.Controler.GetPouDescription(words[1])
-            self.PouIsUsed = self.Controler.PouIsUsed(words[1])
             self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
         
         if returnType is not None:
@@ -625,7 +677,7 @@
         new_description = self.Description.GetValue()
         if new_description != old_description:
             self.Controler.SetPouDescription(words[1], new_description)
-            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
         event.Skip()
     
     def OnClassFilter(self, event):
@@ -666,13 +718,12 @@
                 if old_value != "":
                     self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
                 self.Controler.BufferProject()
-                self.ParentWindow.RefreshView(variablepanel = False)
-                self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
-                event.Skip()
+                wx.CallAfter(self.ParentWindow.RefreshView, False)
+                self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
         else:
             self.SaveValues()
             if colname == "Class":
-                self.ParentWindow.RefreshView(variablepanel = False)
+                wx.CallAfter(self.ParentWindow.RefreshView, False)
             elif colname == "Location":
                 wx.CallAfter(self.ParentWindow.RefreshView)
             
@@ -761,7 +812,7 @@
             self.SaveValues(False)
             self.ParentWindow.RefreshView(variablepanel = False)
             self.Controler.BufferProject()
-            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
         return VariableTypeFunction
     
     def VariableArrayTypeFunction(self, event):
@@ -775,7 +826,7 @@
             self.SaveValues(False)
             self.ParentWindow.RefreshView(variablepanel = False)
             self.Controler.BufferProject()
-            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
+            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
         dialog.Destroy()
     
     def OnVariablesGridCellLeftClick(self, event):
@@ -811,7 +862,7 @@
             self.Controler.SetPouInterfaceVars(words[1], self.Values)
         if buffer:
             self.Controler.BufferProject()
-            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)            
+            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)            
 
 #-------------------------------------------------------------------------------
 #                        Highlights showing functions
--- a/controls/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/controls/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -38,3 +38,6 @@
 from SearchResultPanel import SearchResultPanel
 from TextCtrlAutoComplete import TextCtrlAutoComplete
 from FolderTree import FolderTree
+from LogViewer import LogViewer
+from CustomStyledTextCtrl import CustomStyledTextCtrl
+from CustomToolTip import CustomToolTip
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/BlockPreviewDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,303 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import wx
+
+from plcopen.structures import TestIdentifier, IEC_KEYWORDS
+from graphics.GraphicCommons import FREEDRAWING_MODE
+
+#-------------------------------------------------------------------------------
+#                    Dialog with preview for graphic block
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a generic dialog containing a preview panel for displaying
+graphic created by dialog
+"""
+
+class BlockPreviewDialog(wx.Dialog):
+    
+    def __init__(self, parent, controller, tagname, size, title):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        @param size: wx.Size object containing size of dialog
+        @param title: Title of dialog frame
+        """
+        wx.Dialog.__init__(self, parent, size=size, title=title)
+        
+        # Save reference to
+        self.Controller = controller
+        self.TagName = tagname
+        
+        # Label for preview
+        self.PreviewLabel = wx.StaticText(self, label=_('Preview:'))
+        
+        # Create Preview panel
+        self.Preview = wx.Panel(self, style=wx.SIMPLE_BORDER)
+        self.Preview.SetBackgroundColour(wx.WHITE)
+        
+        # Add function to preview panel so that it answers to graphic elements
+        # like Viewer
+        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
+        setattr(self.Preview, "GetScaling", lambda:None)
+        setattr(self.Preview, "GetBlockType", controller.GetBlockType)
+        setattr(self.Preview, "IsOfType", controller.IsOfType)
+        
+        # Bind paint event on Preview panel
+        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
+        
+        # Add default dialog buttons sizer
+        self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, 
+                  self.ButtonSizer.GetAffirmativeButton())
+        
+        self.Element = None            # Graphic element to display in preview
+        self.MinElementSize = None     # Graphic element minimal size
+        
+        # Variable containing the graphic element name when dialog is opened
+        self.DefaultElementName = None
+        
+        # List of variables defined in POU {var_name: (var_class, var_type),...}
+        self.VariableList = {}
+        
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Remove reference to project controller
+        self.Controller = None
+    
+    def _init_sizers(self, main_rows, main_growable_row,
+                            left_rows, left_growable_row,
+                            right_rows, right_growable_row):
+        """
+        Initialize common sizers
+        @param main_rows: Number of rows in main sizer
+        @param main_growable_row: Row that is growable in main sizer, None if no
+        row is growable
+        @param left_rows: Number of rows in left grid sizer
+        @param left_growable_row: Row that is growable in left grid sizer, None
+        if no row is growable
+        @param right_rows: Number of rows in right grid sizer
+        @param right_growable_row: Row that is growable in right grid sizer,
+        None if no row is growable
+        """
+        # Create dialog main sizer
+        self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, 
+                                          rows=main_rows, vgap=10)
+        self.MainSizer.AddGrowableCol(0)
+        if main_growable_row is not None:
+            self.MainSizer.AddGrowableRow(main_growable_row)
+        
+        # Create a sizer for dividing parameters in two columns
+        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.MainSizer.AddSizer(column_sizer, border=20, 
+              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+        
+        # Create a sizer for left column
+        self.LeftGridSizer = wx.FlexGridSizer(cols=1, hgap=0, 
+                                              rows=left_rows, vgap=5)
+        self.LeftGridSizer.AddGrowableCol(0)
+        if left_growable_row is not None:
+            self.LeftGridSizer.AddGrowableRow(left_growable_row)
+        column_sizer.AddSizer(self.LeftGridSizer, 1, border=5, 
+              flag=wx.GROW|wx.RIGHT)
+        
+        # Create a sizer for right column
+        self.RightGridSizer = wx.FlexGridSizer(cols=1, hgap=0, 
+                                               rows=right_rows, vgap=0)
+        self.RightGridSizer.AddGrowableCol(0)
+        if right_growable_row is not None:
+            self.RightGridSizer.AddGrowableRow(right_growable_row)
+        column_sizer.AddSizer(self.RightGridSizer, 1, border=5, 
+              flag=wx.GROW|wx.LEFT)
+        
+        self.SetSizer(self.MainSizer)
+    
+    def SetMinElementSize(self, size):
+        """
+        Define minimal graphic element size
+        @param size: Tuple containing minimal size (width, height)
+        """
+        self.MinElementSize = size
+    
+    def GetMinElementSize(self):
+        """
+        Get minimal graphic element size
+        @return: Tuple containing minimal size (width, height) or None if no
+        element defined
+        May be overridden by inherited classes
+        """
+        if self.Element is None:
+            return None
+        
+        return self.Element.GetMinSize()
+    
+    def SetPreviewFont(self, font):
+        """
+        Set font of Preview panel
+        @param font: wx.Font object containing font style
+        """
+        self.Preview.SetFont(font)
+    
+    def RefreshVariableList(self):
+        """
+        Extract list of variables defined in POU
+        """
+        # Get list of variables defined in POU
+        self.VariableList = {
+            var["Name"]: (var["Class"], var["Type"])
+            for var in self.Controller.GetEditedElementInterfaceVars(
+                                                        self.TagName)
+            if var["Edit"]}
+        
+        # Add POU name to variable list if POU is a function 
+        returntype = self.Controller.GetEditedElementInterfaceReturnType(
+                                                            self.TagName)
+        if returntype is not None:
+            self.VariableList[
+                self.Controller.GetEditedElementName(self.TagName)] = \
+                 ("Output", returntype)
+        
+        # Add POU name if POU is a transition
+        words = self.TagName.split("::")
+        if words[0] == "T":
+            self.VariableList[words[2]] = ("Output", "BOOL")
+    
+    def TestElementName(self, element_name):
+        """
+        Test displayed graphic element name
+        @param element_name: Graphic element name
+        """
+        # Variable containing error message format
+        message_format = None
+        # Get graphic element name in upper case
+        uppercase_element_name = element_name.upper()
+        
+        # Test if graphic element name is a valid identifier
+        if not TestIdentifier(element_name):
+            message_format = _("\"%s\" is not a valid identifier!")
+        
+        # Test that graphic element name isn't a keyword
+        elif uppercase_element_name in IEC_KEYWORDS:
+            message_format = _("\"%s\" is a keyword. It can't be used!")
+        
+        # Test that graphic element name isn't a POU name
+        elif uppercase_element_name in self.Controller.GetProjectPouNames():
+            message_format = _("\"%s\" pou already exists!")
+        
+        # Test that graphic element name isn't already used in POU by a variable
+        # or another graphic element
+        elif ((self.DefaultElementName is None or 
+               self.DefaultElementName.upper() != uppercase_element_name) and 
+              uppercase_element_name in self.Controller.\
+                    GetEditedElementVariables(self.TagName)):
+            message_format = _("\"%s\" element for this pou already exists!")
+        
+        # If an error have been identify, show error message dialog
+        if message_format is not None:
+            self.ShowErrorMessage(message_format % element_name)
+            # Test failed
+            return False
+        
+        # Test succeed
+        return True
+    
+    def ShowErrorMessage(self, message):
+        """
+        Show an error message dialog over this dialog
+        @param message: Error message to display
+        """
+        dialog = wx.MessageDialog(self, message, 
+                                  _("Error"), 
+                                  wx.OK|wx.ICON_ERROR)
+        dialog.ShowModal()
+        dialog.Destroy()
+    
+    def OnOK(self, event):
+        """
+        Called when dialog OK button is pressed
+        Need to be overridden by inherited classes to check that dialog values
+        are valid
+        @param event: wx.Event from OK button
+        """
+        # Close dialog
+        self.EndModal(wx.ID_OK)
+    
+    def RefreshPreview(self):
+        """
+        Refresh preview panel of graphic element
+        May be overridden by inherited classes
+        """
+        # Init preview panel paint device context
+        dc = wx.ClientDC(self.Preview)
+        dc.SetFont(self.Preview.GetFont())
+        dc.Clear()
+        
+        # Return immediately if no graphic element defined
+        if self.Element is None:
+            return
+        
+        # Calculate block size according to graphic element min size due to its
+        # parameters and graphic element min size defined
+        min_width, min_height = self.GetMinElementSize()
+        width = max(self.MinElementSize[0], min_width)
+        height = max(self.MinElementSize[1], min_height)
+        self.Element.SetSize(width, height)
+        
+        # Get element position and bounding box to center in preview
+        posx, posy = self.Element.GetPosition()
+        bbox = self.Element.GetBoundingBox()
+        
+        # Get Preview panel size
+        client_size = self.Preview.GetClientSize()
+        
+        # If graphic element is too big to be displayed in preview panel,
+        # calculate preview panel scale so that graphic element fit inside
+        scale = (max(float(bbox.width) / client_size.width, 
+                     float(bbox.height) / client_size.height) * 1.1
+                 if bbox.width * 1.1 > client_size.width or 
+                    bbox.height * 1.1 > client_size.height
+                 else 1.0)
+        dc.SetUserScale(1.0 / scale, 1.0 / scale)
+        
+        # Center graphic element in preview panel
+        x = int(client_size.width * scale - bbox.width) / 2 + posx - bbox.x
+        y = int(client_size.height * scale - bbox.height) / 2 + posy - bbox.y
+        self.Element.SetPosition(x, y)
+        
+        # Draw graphic element
+        self.Element.Draw(dc)
+    
+    def OnPaint(self, event):
+        """
+        Called when Preview panel need to be redraw
+        @param event: wx.PaintEvent
+        """
+        self.RefreshPreview()
+        event.Skip()
+        
\ No newline at end of file
--- a/dialogs/ConnectionDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/ConnectionDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,178 +24,196 @@
 
 import wx
 
-from graphics import *
+from graphics.GraphicCommons import CONNECTOR, CONTINUATION
+from graphics.FBD_Objects import FBD_Connector
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
-#                          Create New Connection Dialog
+#                       Set Connection Parameters Dialog
 #-------------------------------------------------------------------------------
 
-class ConnectionDialog(wx.Dialog):
-    
-    def __init__(self, parent, controller, apply_button=False):
-        wx.Dialog.__init__(self, parent,
+"""
+Class that implements a dialog for defining parameters of a connection graphic
+element
+"""
+
+class ConnectionDialog(BlockPreviewDialog):
+    
+    def __init__(self, parent, controller, tagname, apply_button=False):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        @param apply_button: Enable button for applying connector modification
+        to all connector having the same name in POU (default: False)
+        """
+        BlockPreviewDialog.__init__(self, parent, controller, tagname, 
               size=wx.Size(350, 220), title=_('Connection Properties'))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
-        
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-        
-        left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5)
-        left_gridsizer.AddGrowableCol(0)
-        column_sizer.AddSizer(left_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.RIGHT)
-        
+        # Init common sizers
+        self._init_sizers(2, 0, 5, None, 2, 1)
+        
+        # Create label for connection type
         type_label = wx.StaticText(self, label=_('Type:'))
-        left_gridsizer.AddWindow(type_label, flag=wx.GROW)
-        
-        self.ConnectorRadioButton = wx.RadioButton(self, 
-              label=_('Connector'), style=wx.RB_GROUP)
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectorRadioButton)
-        self.ConnectorRadioButton.SetValue(True)
-        left_gridsizer.AddWindow(self.ConnectorRadioButton, flag=wx.GROW)
-        
-        self.ConnectionRadioButton = wx.RadioButton(self, label=_('Continuation'))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectionRadioButton)
-        left_gridsizer.AddWindow(self.ConnectionRadioButton, flag=wx.GROW)
-        
+        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        
+        # Create radio buttons for selecting connection type
+        self.TypeRadioButtons = {}
+        first = True
+        for type, label in [(CONNECTOR, _('Connector')),
+                            (CONTINUATION, _('Continuation'))]:
+            radio_button = wx.RadioButton(self, label=label, 
+                  style=(wx.RB_GROUP if first else 0))
+            radio_button.SetValue(first)
+            self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.TypeRadioButtons[type] = radio_button
+            first = False
+        
+        # Create label for connection name
         name_label = wx.StaticText(self, label=_('Name:'))
-        left_gridsizer.AddWindow(name_label, flag=wx.GROW)
-        
+        self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
+        
+        # Create text control for defining connection name
         self.ConnectionName = wx.TextCtrl(self)
         self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.ConnectionName)
-        left_gridsizer.AddWindow(self.ConnectionName, flag=wx.GROW)
-        
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(1)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
-        
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-        
-        self.Preview = wx.Panel(self, 
-              style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "GetScaling", lambda:None)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-        
-        button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20, 
+        self.LeftGridSizer.AddWindow(self.ConnectionName, flag=wx.GROW)
+        
+        # Add preview panel and associated label to sizers
+        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
+        # Add button for applying connection name modification to all connection
+        # of POU
         if apply_button:
             self.ApplyToAllButton = wx.Button(self, label=_("Propagate Name"))
             self.ApplyToAllButton.SetToolTipString(
                 _("Apply name modification to all continuations with the same name"))
             self.Bind(wx.EVT_BUTTON, self.OnApplyToAll, self.ApplyToAllButton)
-            button_sizer.AddWindow(self.ApplyToAllButton)
-        
-        self.SetSizer(main_sizer)
-        
-        self.Connection = None
-        self.MinConnectionSize = None
-        
-        self.PouNames = []
-        self.PouElementNames = []
-        
-        self.ConnectorRadioButton.SetFocus()
-    
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
-    
-    def SetMinConnectionSize(self, size):
-        self.MinConnectionSize = size
+            self.ButtonSizer.AddWindow(self.ApplyToAllButton, 
+                    border=(3 if wx.Platform == '__WXMSW__' else 10),
+                    flag=wx.LEFT)
+        else:
+            self.ConnectionName.ChangeValue(
+                controller.GenerateNewName(
+                        tagname, None, "Connection%d", 0))
+        
+        # Connector radio button is default control having keyboard focus
+        self.TypeRadioButtons[CONNECTOR].SetFocus()
+    
+    def GetConnectionType(self):
+        """
+        Return type selected for connection
+        @return: Type selected (CONNECTOR or CONTINUATION)
+        """
+        return (CONNECTOR
+                if self.TypeRadioButtons[CONNECTOR].GetValue()
+                else CONTINUATION)
     
     def SetValues(self, values):
+        """
+        Set default connection parameters
+        @param values: Connection parameters values
+        """
+        # For each parameters defined, set corresponding control value
         for name, value in values.items():
+            
+            # Parameter is connection type
             if name == "type":
-                if value == CONNECTOR:
-                    self.ConnectorRadioButton.SetValue(True)
-                elif value == CONTINUATION:
-                    self.ConnectionRadioButton.SetValue(True)
+                self.TypeRadioButtons[value].SetValue(True)
+            
+            # Parameter is connection name
             elif name == "name":
                 self.ConnectionName.SetValue(value)
+        
+        # Refresh preview panel
         self.RefreshPreview()
     
     def GetValues(self):
-        values = {}
-        if self.ConnectorRadioButton.GetValue():
-            values["type"] = CONNECTOR
-        else:
-            values["type"] = CONTINUATION
-        values["name"] = self.ConnectionName.GetValue()
-        values["width"], values["height"] = self.Connection.GetSize()
+        """
+        Return connection parameters defined in dialog
+        @return: {parameter_name: parameter_value,...}
+        """
+        values = {
+            "type": self.GetConnectionType(),
+            "name": self.ConnectionName.GetValue()}
+        values["width"], values["height"] = self.Element.GetSize()
         return values
 
-    def SetPouNames(self, pou_names):
-        self.PouNames = [pou_name.upper() for pou_name in pou_names]
-        
-    def SetPouElementNames(self, element_names):
-        self.PouElementNames = [element_name.upper() for element_name in element_names]
-    
-    def TestName(self):
+    def TestConnectionName(self):
+        """
+        Test that connection name is valid
+        @return: True if connection name is valid
+        """
         message = None
+        
+        # Get connection name typed by user
         connection_name = self.ConnectionName.GetValue()
+        
+        # Test that a name have been defined
         if connection_name == "":
             message = _("Form isn't complete. Name must be filled!")
-        elif not TestIdentifier(connection_name):
-            message = _("\"%s\" is not a valid identifier!") % connection_name
-        elif connection_name.upper() in IEC_KEYWORDS:
-            message = _("\"%s\" is a keyword. It can't be used!") % connection_name
-        elif connection_name.upper() in self.PouNames:
-            message = _("\"%s\" pou already exists!") % connection_name
-        elif connection_name.upper() in self.PouElementNames:
-            message = _("\"%s\" element for this pou already exists!") % connection_name
+        
+        # If an error have been identify, show error message dialog
         if message is not None:
-            dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
-            dialog.ShowModal()
-            dialog.Destroy()
+            self.ShowErrorMessage(message)
+            # Test failed
             return False
-        return True
+        
+        # Return result of element name test
+        return self.TestElementName(connection_name)
         
     def OnOK(self, event):
-        if self.TestName():
+        """
+        Called when dialog OK button is pressed
+        Test if connection name is valid
+        @param event: wx.Event from OK button
+        """
+        # Close dialog if connection name is valid
+        if self.TestConnectionName():
             self.EndModal(wx.ID_OK)
 
     def OnApplyToAll(self, event):
-        if self.TestName():
+        """
+        Called when Apply To All button is pressed
+        Test if connection name is valid
+        @param event: wx.Event from OK button
+        """
+        # Close dialog if connection name is valid
+        if self.TestConnectionName():
             self.EndModal(wx.ID_YESTOALL)
 
     def OnTypeChanged(self, event):
+        """
+        Called when connection type changed
+        @param event: wx.RadioButtonEvent
+        """
         self.RefreshPreview()
         event.Skip()
 
     def OnNameChanged(self, event):
+        """
+        Called when connection name value changed
+        @param event: wx.TextEvent
+        """
         self.RefreshPreview()
         event.Skip()
         
     def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
-        if self.ConnectorRadioButton.GetValue():
-            self.Connection = FBD_Connector(self.Preview, CONNECTOR, self.ConnectionName.GetValue())
-        else:
-            self.Connection = FBD_Connector(self.Preview, CONTINUATION, self.ConnectionName.GetValue())
-        width, height = self.MinConnectionSize
-        min_width, min_height = self.Connection.GetMinSize()
-        width, height = max(min_width, width), max(min_height, height)
-        self.Connection.SetSize(width, height)
-        clientsize = self.Preview.GetClientSize()
-        x = (clientsize.width - width) / 2
-        y = (clientsize.height - height) / 2
-        self.Connection.SetPosition(x, y)
-        self.Connection.Draw(dc)
-
-    def OnPaint(self, event):
-        self.RefreshPreview()
-        event.Skip()
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        # Set graphic element displayed, creating a FBD connection element
+        self.Element = FBD_Connector(self.Preview, 
+                self.GetConnectionType(),
+                self.ConnectionName.GetValue())
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
+        
\ No newline at end of file
--- a/dialogs/FBDBlockDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/FBDBlockDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -22,254 +22,331 @@
 #License along with this library; if not, write to the Free Software
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+import re
+
 import wx
 
-from graphics import *
+from graphics.FBD_Objects import FBD_Block
 from controls.LibraryPanel import LibraryPanel
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
-#                          Create New Block Dialog
+#                                    Helpers
 #-------------------------------------------------------------------------------
 
-class FBDBlockDialog(wx.Dialog):
-    
-    def __init__(self, parent, controller):
-        wx.Dialog.__init__(self, parent,
+def GetBlockTypeDefaultNameModel(blocktype):
+    return re.compile("%s[0-9]+" % blocktype if blocktype is not None else ".*")
+
+#-------------------------------------------------------------------------------
+#                         Set Block Parameters Dialog
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a dialog for defining parameters of a FBD block graphic
+element
+"""
+
+class FBDBlockDialog(BlockPreviewDialog):
+    
+    def __init__(self, parent, controller, tagname):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        """
+        BlockPreviewDialog.__init__(self, parent, controller, tagname,
               size=wx.Size(600, 450), title=_('Block Properties'))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
-        
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-        
+        # Init common sizers
+        self._init_sizers(2, 0, 1, 0, 3, 2)
+        
+        # Create static box around library panel
         type_staticbox = wx.StaticBox(self, label=_('Type:'))
         left_staticboxsizer = wx.StaticBoxSizer(type_staticbox, wx.VERTICAL)
-        column_sizer.AddSizer(left_staticboxsizer, 1, border=5, 
-              flag=wx.GROW|wx.RIGHT)
-        
+        self.LeftGridSizer.AddSizer(left_staticboxsizer, border=5, flag=wx.GROW)
+        
+        # Create Library panel and add it to static box
         self.LibraryPanel = LibraryPanel(self)
-        self.LibraryPanel.SetController(controller)
+        # Set function to call when selection in Library panel changed
         setattr(self.LibraryPanel, "_OnTreeItemSelected", 
               self.OnLibraryTreeItemSelected)
         left_staticboxsizer.AddWindow(self.LibraryPanel, 1, border=5, 
               flag=wx.GROW|wx.TOP)
         
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(2)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
-        
+        # Create sizer for other block parameters
         top_right_gridsizer = wx.FlexGridSizer(cols=2, hgap=0, rows=4, vgap=5)
         top_right_gridsizer.AddGrowableCol(1)
-        right_gridsizer.AddSizer(top_right_gridsizer, flag=wx.GROW)
-        
+        self.RightGridSizer.AddSizer(top_right_gridsizer, flag=wx.GROW)
+        
+        # Create label for block name
         name_label = wx.StaticText(self, label=_('Name:'))
         top_right_gridsizer.AddWindow(name_label, 
               flag=wx.ALIGN_CENTER_VERTICAL)
         
+        # Create text control for defining block name
         self.BlockName = wx.TextCtrl(self)
         self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.BlockName)
         top_right_gridsizer.AddWindow(self.BlockName, flag=wx.GROW)
         
+        # Create label for extended block input number
         inputs_label = wx.StaticText(self, label=_('Inputs:'))
         top_right_gridsizer.AddWindow(inputs_label, 
               flag=wx.ALIGN_CENTER_VERTICAL)
         
+        # Create spin control for defining extended block input number
         self.Inputs = wx.SpinCtrl(self, min=2, max=20,
               style=wx.SP_ARROW_KEYS)
         self.Bind(wx.EVT_SPINCTRL, self.OnInputsChanged, self.Inputs)
         top_right_gridsizer.AddWindow(self.Inputs, flag=wx.GROW)
         
-        execution_order_label = wx.StaticText(self, label=_('Execution Order:'))
+        # Create label for block execution order
+        execution_order_label = wx.StaticText(self, 
+              label=_('Execution Order:'))
         top_right_gridsizer.AddWindow(execution_order_label, 
               flag=wx.ALIGN_CENTER_VERTICAL)
         
+        # Create spin control for defining block execution order
         self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
-        self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, self.ExecutionOrder)
+        self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, 
+                  self.ExecutionOrder)
         top_right_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
                 
-        execution_control_label = wx.StaticText(self, label=_('Execution Control:'))
+        # Create label for block execution control
+        execution_control_label = wx.StaticText(self, 
+              label=_('Execution Control:'))
         top_right_gridsizer.AddWindow(execution_control_label, 
               flag=wx.ALIGN_CENTER_VERTICAL)
         
+        # Create check box to enable block execution control
         self.ExecutionControl = wx.CheckBox(self)
-        self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged, self.ExecutionControl)
+        self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged, 
+                  self.ExecutionControl)
         top_right_gridsizer.AddWindow(self.ExecutionControl, flag=wx.GROW)
         
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
-        self.Preview = wx.Panel(self,
-              style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "GetScaling", lambda:None)
-        setattr(self.Preview, "GetBlockType", controller.GetBlockType)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-        
-        button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20, 
+        # Add preview panel and associated label to sizers
+        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
-        self.SetSizer(main_sizer)
-        
-        self.Controller = controller
-        
+        # Dictionary containing correspondence between parameter exchanged and
+        # control to fill with parameter value
+        self.ParamsControl = {
+            "extension": self.Inputs,
+            "executionOrder": self.ExecutionOrder,
+            "executionControl": self.ExecutionControl
+        }
+        
+        # Init controls value and sensibility
         self.BlockName.SetValue("")
         self.BlockName.Enable(False)
         self.Inputs.Enable(False)
-        self.Block = None
-        self.MinBlockSize = None
-        self.First = True
-        
-        self.PouNames = []
-        self.PouElementNames = []
-        
+        
+        # Variable containing last name typed
+        self.CurrentBlockName = None
+        
+        # Refresh Library panel values
+        self.LibraryPanel.SetBlockList(controller.GetBlockTypes(tagname))
         self.LibraryPanel.SetFocus()
     
-    def __del__(self):
-        self.Controller = None
-    
-    def SetBlockList(self, blocklist):
-        self.LibraryPanel.SetBlockList(blocklist)
-    
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
-    
-    def OnOK(self, event):
-        message = None
-        selected = self.LibraryPanel.GetSelectedBlock()
-        block_name = self.BlockName.GetValue()
-        name_enabled = self.BlockName.IsEnabled()
-        if selected is None:
-            message = _("Form isn't complete. Valid block type must be selected!")
-        elif name_enabled and block_name == "":
-            message = _("Form isn't complete. Name must be filled!")
-        elif name_enabled and not TestIdentifier(block_name):
-            message = _("\"%s\" is not a valid identifier!") % block_name
-        elif name_enabled and block_name.upper() in IEC_KEYWORDS:
-            message = _("\"%s\" is a keyword. It can't be used!") % block_name
-        elif name_enabled and block_name.upper() in self.PouNames:
-            message = _("\"%s\" pou already exists!") % block_name
-        elif name_enabled and block_name.upper() in self.PouElementNames:
-            message = _("\"%s\" element for this pou already exists!") % block_name
-        if message is not None:
-            dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
-            dialog.ShowModal()
-            dialog.Destroy()
-        else:
-            self.EndModal(wx.ID_OK)
-
-    def SetMinBlockSize(self, size):
-        self.MinBlockSize = size
-
-    def SetPouNames(self, pou_names):
-        self.PouNames = [pou_name.upper() for pou_name in pou_names]
-        
-    def SetPouElementNames(self, element_names):
-        self.PouElementNames = [element_name.upper() for element_name in element_names]
-        
     def SetValues(self, values):
+        """
+        Set default block parameters
+        @param values: Block parameters values
+        """
+        # Extract block type defined in parameters
         blocktype = values.get("type", None)
+        
+        # Define regular expression for determine if block name is block
+        # default name
+        default_name_model = GetBlockTypeDefaultNameModel(blocktype)
+        
+        # For each parameters defined, set corresponding control value
+        for name, value in values.items():
+            
+            # Parameter is block name
+            if name == "name":
+                if value != "":
+                    # Set default graphic element name for testing
+                    self.DefaultElementName = value
+                    
+                    # Test if block name is type default block name and save
+                    # block name if not (name have been typed by user)
+                    if default_name_model.match(value) is None:
+                        self.CurrentBlockName = value
+            
+                self.BlockName.ChangeValue(value)
+            
+            # Set value of other controls
+            else:
+                control = self.ParamsControl.get(name, None)
+                if control is not None:
+                    control.SetValue(value)
+        
+        # Select block type in library panel    
         if blocktype is not None:
-            self.LibraryPanel.SelectTreeItem(blocktype, values.get("inputs", None))
-        for name, value in values.items():
-            if name == "name":
-                self.BlockName.SetValue(value)
-            elif name == "extension":
-                self.Inputs.SetValue(value)
-            elif name == "executionOrder":
-                self.ExecutionOrder.SetValue(value)
-            elif name == "executionControl":
-                   self.ExecutionControl.SetValue(value)
+            self.LibraryPanel.SelectTreeItem(blocktype, 
+                                             values.get("inputs", None))
+        
+        # Refresh preview panel
         self.RefreshPreview()
 
     def GetValues(self):
+        """
+        Return block parameters defined in dialog
+        @return: {parameter_name: parameter_value,...}
+        """
         values = self.LibraryPanel.GetSelectedBlock()
         if self.BlockName.IsEnabled() and self.BlockName.GetValue() != "":
             values["name"] = self.BlockName.GetValue()
-        values["width"], values["height"] = self.Block.GetSize()
-        values["extension"] = self.Inputs.GetValue()
-        values["executionOrder"] = self.ExecutionOrder.GetValue()
-        values["executionControl"] = self.ExecutionControl.GetValue()
+        values["width"], values["height"] = self.Element.GetSize()
+        values.update({
+            name: control.GetValue()
+            for name, control in self.ParamsControl.iteritems()})
         return values
-
+    
+    def OnOK(self, event):
+        """
+        Called when dialog OK button is pressed
+        Test if parameters defined are valid
+        @param event: wx.Event from OK button
+        """
+        message = None
+        
+        # Get block type selected
+        selected = self.LibraryPanel.GetSelectedBlock()
+        
+        # Get block type name and if block is a function block
+        block_name = self.BlockName.GetValue()
+        name_enabled = self.BlockName.IsEnabled()
+        
+        # Test that a type has been selected for block
+        if selected is None:
+            message = _("Form isn't complete. Valid block type must be selected!")
+        
+        # Test, if block is a function block, that a name have been defined
+        elif name_enabled and block_name == "":
+            message = _("Form isn't complete. Name must be filled!")
+        
+        # Show error message if an error is detected
+        if message is not None:
+            self.ShowErrorMessage(message)
+        
+        # Test block name validity if necessary
+        elif not name_enabled or self.TestElementName(block_name):
+            # Call BlockPreviewDialog function
+            BlockPreviewDialog.OnOK(self, event)
+    
     def OnLibraryTreeItemSelected(self, event):
+        """
+        Called when block type selected in library panel
+        @param event: wx.TreeEvent
+        """
+        # Get type selected in library panel
         values = self.LibraryPanel.GetSelectedBlock()
-        if values is not None:
-            blocktype = self.Controller.GetBlockType(values["type"], values["inputs"])
-        else:
-            blocktype = None
+        
+        # Get block type informations
+        blocktype = (self.Controller.GetBlockType(values["type"], 
+                                                  values["inputs"])
+                     if values is not None else None)
+        
+        # Set input number spin control according to block type informations
         if blocktype is not None:
             self.Inputs.SetValue(len(blocktype["inputs"]))
             self.Inputs.Enable(blocktype["extensible"])
-            self.BlockName.Enable(blocktype["type"] != "function")
-            wx.CallAfter(self.RefreshPreview)
+        else:
+            self.Inputs.SetValue(2)
+            self.Inputs.Enable(False)
+        
+        # Update block name with default value if block type is a function and
+        # current block name wasn't typed by user
+        if blocktype is not None and blocktype["type"] != "function":
+            self.BlockName.Enable(True)
+            
+            if self.CurrentBlockName is None:
+                # Generate new block name according to block type, taking
+                # default element name if it was already a default name for this
+                # block type
+                default_name_model = GetBlockTypeDefaultNameModel(values["type"])
+                block_name = (
+                    self.DefaultElementName
+                    if (self.DefaultElementName is not None and 
+                        default_name_model.match(self.DefaultElementName))
+                    else self.Controller.GenerateNewName(
+                        self.TagName, None, values["type"]+"%d", 0))
+            else:
+                block_name = self.CurrentBlockName
+                
+            self.BlockName.ChangeValue(block_name)
         else:
             self.BlockName.Enable(False)
-            self.Inputs.Enable(False)
-            self.Inputs.SetValue(2)
-            wx.CallAfter(self.ErasePreview)
+            self.BlockName.ChangeValue("")
+        
+        # Refresh preview panel
+        self.RefreshPreview()
     
     def OnNameChanged(self, event):
+        """
+        Called when block name value changed
+        @param event: wx.TextEvent
+        """
         if self.BlockName.IsEnabled():
+            # Save block name typed by user
+            self.CurrentBlockName = self.BlockName.GetValue()
             self.RefreshPreview()
         event.Skip()
     
     def OnInputsChanged(self, event):
+        """
+        Called when block inputs number changed
+        @param event: wx.SpinEvent
+        """
         if self.Inputs.IsEnabled():
             self.RefreshPreview()
         event.Skip()
     
     def OnExecutionOrderChanged(self, event):
+        """
+        Called when block execution order value changed
+        @param event: wx.SpinEvent
+        """
         self.RefreshPreview()
         event.Skip()
     
     def OnExecutionControlChanged(self, event):
+        """
+        Called when block execution control value changed
+        @param event: wx.SpinEvent
+        """
         self.RefreshPreview()
         event.Skip()
     
-    def ErasePreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.Clear()
-        self.Block = None
-        
     def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        # Get type selected in library panel
         values = self.LibraryPanel.GetSelectedBlock()
+        
+        # If a block type is selected in library panel
         if values is not None:
-            if self.BlockName.IsEnabled():
-                blockname = self.BlockName.GetValue()
-            else:
-                blockname = ""
-            self.Block = FBD_Block(self.Preview, values["type"], 
-                    blockname, 
+            # Set graphic element displayed, creating a FBD block element
+            self.Element = FBD_Block(self.Preview, values["type"], 
+                    (self.BlockName.GetValue()
+                     if self.BlockName.IsEnabled()
+                     else ""), 
                     extension = self.Inputs.GetValue(), 
                     inputs = values["inputs"], 
                     executionControl = self.ExecutionControl.GetValue(), 
                     executionOrder = self.ExecutionOrder.GetValue())
-            width, height = self.MinBlockSize
-            min_width, min_height = self.Block.GetMinSize()
-            width, height = max(min_width, width), max(min_height, height)
-            self.Block.SetSize(width, height)
-            clientsize = self.Preview.GetClientSize()
-            x = (clientsize.width - width) / 2
-            y = (clientsize.height - height) / 2
-            self.Block.SetPosition(x, y)
-            self.Block.Draw(dc)
+        
+        # Reset graphic element displayed
         else:
-            self.Block = None        
-
-    def OnPaint(self, event):
-        if self.Block is not None:
-            self.RefreshPreview()
-        event.Skip()
+            self.Element = None 
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/FBDVariableDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/FBDVariableDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,12 +24,16 @@
 
 import wx
 
-from graphics import *
+from graphics.GraphicCommons import INPUT, INOUT, OUTPUT
+from graphics.FBD_Objects import FBD_Variable
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
 #                                    Helpers
 #-------------------------------------------------------------------------------
 
+# Dictionaries containing correspondence between variable block class and string
+# to be shown in Class combo box in both sense
 VARIABLE_CLASSES_DICT = {INPUT : _("Input"),
                          INOUT : _("InOut"),
                          OUTPUT : _("Output")}
@@ -37,223 +41,250 @@
     [(value, key) for key, value in VARIABLE_CLASSES_DICT.iteritems()])
 
 #-------------------------------------------------------------------------------
-#                          Create New Variable Dialog
-#-------------------------------------------------------------------------------
-
-class FBDVariableDialog(wx.Dialog):
-
-    def __init__(self, parent, controller, transition = ""):
-        wx.Dialog.__init__(self, parent,
+#                        Set Variable Parameters Dialog
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a dialog for defining parameters of a FBD variable graphic
+element
+"""
+
+class FBDVariableDialog(BlockPreviewDialog):
+
+    def __init__(self, parent, controller, tagname, exclude_input=False):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        @param exclude_input: Exclude input from variable class selection
+        """
+        BlockPreviewDialog.__init__(self, parent, controller, tagname,
               size=wx.Size(400, 380), title=_('Variable Properties'))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(2)
-        
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-        
-        left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=5)
-        left_gridsizer.AddGrowableCol(0)
-        column_sizer.AddSizer(left_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.RIGHT)
-        
+        # Init common sizers
+        self._init_sizers(4, 2, 4, None, 3, 2)
+        
+        # Create label for variable class
         class_label = wx.StaticText(self, label=_('Class:'))
-        left_gridsizer.AddWindow(class_label, flag=wx.GROW)
-        
+        self.LeftGridSizer.AddWindow(class_label, flag=wx.GROW)
+        
+        # Create a combo box for defining variable class
         self.Class = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnClassChanged, self.Class)
-        left_gridsizer.AddWindow(self.Class, flag=wx.GROW)
-        
-        expression_label = wx.StaticText(self, label=_('Expression:'))
-        left_gridsizer.AddWindow(expression_label, flag=wx.GROW)
-        
+        self.LeftGridSizer.AddWindow(self.Class, flag=wx.GROW)
+        
+        # Create label for variable execution order
+        execution_order_label = wx.StaticText(self, 
+              label=_('Execution Order:'))
+        self.LeftGridSizer.AddWindow(execution_order_label, flag=wx.GROW)
+        
+        # Create spin control for defining variable execution order
+        self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
+        self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, 
+                  self.ExecutionOrder)
+        self.LeftGridSizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
+        
+        # Create label for variable expression
+        name_label = wx.StaticText(self, label=_('Expression:'))
+        self.RightGridSizer.AddWindow(name_label, border=5, 
+              flag=wx.GROW|wx.BOTTOM)
+        
+        # Create text control for defining variable expression
         self.Expression = wx.TextCtrl(self)
         self.Bind(wx.EVT_TEXT, self.OnExpressionChanged, self.Expression)
-        left_gridsizer.AddWindow(self.Expression, flag=wx.GROW)
-        
-        execution_order_label = wx.StaticText(self, label=_('Execution Order:'))
-        left_gridsizer.AddWindow(execution_order_label, flag=wx.GROW)
-        
-        self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
-        self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, self.ExecutionOrder)
-        left_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
-        
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(1)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
-        
-        name_label = wx.StaticText(self, label=_('Name:'))
-        right_gridsizer.AddWindow(name_label, flag=wx.GROW)
-        
-        self.VariableName = wx.ListBox(self, size=wx.Size(0, 0), 
+        self.RightGridSizer.AddWindow(self.Expression, flag=wx.GROW)
+        
+        # Create a list box to selected variable expression in the list of
+        # variables defined in POU
+        self.VariableName = wx.ListBox(self, size=wx.Size(0, 120), 
               style=wx.LB_SINGLE|wx.LB_SORT)
         self.Bind(wx.EVT_LISTBOX, self.OnNameChanged, self.VariableName)
-        right_gridsizer.AddWindow(self.VariableName, flag=wx.GROW)
-        
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        main_sizer.AddWindow(preview_label, border=20,
+        self.RightGridSizer.AddWindow(self.VariableName, flag=wx.GROW)
+        
+        # Add preview panel and associated label to sizers
+        self.MainSizer.AddWindow(self.PreviewLabel, border=20,
               flag=wx.GROW|wx.LEFT|wx.RIGHT)
-        
-        self.Preview = wx.Panel(self, 
-              style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "GetScaling", lambda:None)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        main_sizer.AddWindow(self.Preview, border=20,
+        self.MainSizer.AddWindow(self.Preview, border=20,
               flag=wx.GROW|wx.LEFT|wx.RIGHT)
         
-        button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20, 
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
-        self.SetSizer(main_sizer)
-        
-        self.Transition = transition
-        self.Variable = None
-        self.VarList = []
-        self.MinVariableSize = None
-        
-        for choice in VARIABLE_CLASSES_DICT.itervalues():
-            self.Class.Append(choice)
-        self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[INPUT])
-
+        # Set options that can be selected in class combo box
+        for var_class, choice in VARIABLE_CLASSES_DICT.iteritems():
+            if not exclude_input or var_class != INPUT:
+                self.Class.Append(choice)
+        self.Class.SetSelection(0)
+        
+        # Extract list of variables defined in POU
+        self.RefreshVariableList()
+        
+        # Refresh values in name list box
         self.RefreshNameList()
+        
+        # Class combo box is default control having keyboard focus
         self.Class.SetFocus()
 
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
-
     def RefreshNameList(self):
-        selected = self.VariableName.GetStringSelection()
-        var_class = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
+        """
+        Called to refresh names in name list box
+        """
+        # Get variable class to select POU variable applicable
+        var_class = VARIABLE_CLASSES_DICT_REVERSE[
+                            self.Class.GetStringSelection()]
+        
+        # Refresh names in name list box by selecting variables in POU variables
+        # list that can be applied to variable class
         self.VariableName.Clear()
-        self.VariableName.Append("")
-        for name, var_type, value_type in self.VarList:
+        for name, (var_type, value_type) in self.VariableList.iteritems():
             if var_type != "Input" or var_class == INPUT:
                 self.VariableName.Append(name)
-        if selected != "" and self.VariableName.FindString(selected) != wx.NOT_FOUND:
+        
+        # Get variable expression and select corresponding value in name list
+        # box if it exists
+        selected = self.Expression.GetValue()
+        if (selected != "" and 
+            self.VariableName.FindString(selected) != wx.NOT_FOUND):
             self.VariableName.SetStringSelection(selected)
-            self.Expression.Enable(False)
         else:
-            self.VariableName.SetStringSelection("")
-            #self.Expression.Enable(var_class == INPUT)
+            self.VariableName.SetSelection(wx.NOT_FOUND)
+        
+        # Disable name list box if no name present inside
         self.VariableName.Enable(self.VariableName.GetCount() > 0)
             
-    def SetMinVariableSize(self, size):
-        self.MinVariableSize = size
-
-    def SetVariables(self, vars):
-        self.VarList = vars
-        self.RefreshNameList()
-
     def SetValues(self, values):
-        value_type = values.get("type", None)
-        value_name = values.get("name", None)
-        if value_type:
-            self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[value_type])
+        """
+        Set default variable parameters
+        @param values: Variable parameters values
+        """
+        
+        # Get class parameter value
+        var_class = values.get("class", None)
+        if var_class is not None:
+            # Set class selected in class combo box
+            self.Class.SetStringSelection(VARIABLE_CLASSES_DICT[var_class])
+            # Refresh names in name list box according to var class
             self.RefreshNameList()
-        if value_name:
-            if self.VariableName.FindString(value_name) != wx.NOT_FOUND:
-                self.VariableName.SetStringSelection(value_name)
-                self.Expression.Enable(False)
-            else:
-                self.Expression.SetValue(value_name)
-                self.VariableName.Enable(False)
-        if "executionOrder" in values:
-            self.ExecutionOrder.SetValue(values["executionOrder"])
+        
+        # For each parameters defined, set corresponding control value
+        for name, value in values.items():
+            
+            # Parameter is variable expression
+            if name == "expression":
+                # Set expression text control value
+                self.Expression.ChangeValue(value)
+                # Select corresponding text in name list box if it exists
+                if self.VariableName.FindString(value) != wx.NOT_FOUND:
+                    self.VariableName.SetStringSelection(value)
+                else:
+                    self.VariableName.SetSelection(wx.NOT_FOUND)
+            
+            # Parameter is variable execution order
+            elif name == "executionOrder":
+                self.ExecutionOrder.SetValue(value)
+        
+        # Refresh preview panel
         self.RefreshPreview()
         
     def GetValues(self):
-        values = {}
-        values["type"] = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
+        """
+        Return block parameters defined in dialog
+        @return: {parameter_name: parameter_value,...}
+        """
         expression = self.Expression.GetValue()
-        if self.Expression.IsEnabled() and expression != "":
-            values["name"] = expression
-        else:
-            values["name"] = self.VariableName.GetStringSelection()
-        values["value_type"] = None
-        for var_name, var_type, value_type in self.VarList:
-            if var_name == values["name"]:
-                values["value_type"] = value_type
-        values["width"], values["height"] = self.Variable.GetSize()
-        values["executionOrder"] = self.ExecutionOrder.GetValue()
+        values = {
+            "class": VARIABLE_CLASSES_DICT_REVERSE[
+                        self.Class.GetStringSelection()],
+            "expression": expression,
+            "var_type": self.VariableList.get(expression, (None, None))[1],
+            "executionOrder": self.ExecutionOrder.GetValue()}
+        values["width"], values["height"] = self.Element.GetSize()
         return values
 
     def OnOK(self, event):
+        """
+        Called when dialog OK button is pressed
+        Test if parameters defined are valid
+        @param event: wx.Event from OK button
+        """
         message = None
-        expression = self.Expression.GetValue()
-        if self.Expression.IsEnabled():
-            value = expression
-        else:
-            value = self.VariableName.GetStringSelection()
+        
+        # Test that an expression have been selected or typed by user
+        value = self.Expression.GetValue()
         if value == "":
             message = _("At least a variable or an expression must be selected!")
-        elif value.upper() in IEC_KEYWORDS:
-            message = _("\"%s\" is a keyword. It can't be used!") % value
+        
+        # Show error message if an error is detected
         if message is not None:
-            message = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
-            message.ShowModal()
-            message.Destroy()
+            self.ShowErrorMessage(message)
+        
         else:
-            self.EndModal(wx.ID_OK)
+            # Call BlockPreviewDialog function
+            BlockPreviewDialog.OnOK(self, event)
 
     def OnClassChanged(self, event):
+        """
+        Called when variable class value changed
+        @param event: wx.ComboBoxEvent
+        """
+        # Refresh name list box values
         self.RefreshNameList()
+        
         self.RefreshPreview()
         event.Skip()
 
     def OnNameChanged(self, event):
-        if self.VariableName.GetStringSelection() != "":
-            self.Expression.Enable(False)
-        elif VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()] == INPUT:
-            self.Expression.Enable(True)
+        """
+        Called when name selected in name list box changed
+        @param event: wx.ListBoxEvent
+        """
+        # Change expression test control value to the value selected in name
+        # list box if value selected is valid
+        if self.VariableName.GetSelection() != wx.NOT_FOUND:
+            self.Expression.ChangeValue(self.VariableName.GetStringSelection())
+        
         self.RefreshPreview()
         event.Skip()
     
     def OnExpressionChanged(self, event):
-        if self.Expression.GetValue() != "":
-            self.VariableName.Enable(False)
-        else:
-            self.VariableName.Enable(True)
+        """
+        Called when expression text control is changed by user
+        @param event: wx.ListBoxEvent
+        """
+        # Select the corresponding value in name list box if it exists
+        self.VariableName.SetSelection(
+            self.VariableName.FindString(self.Expression.GetValue()))
+        
         self.RefreshPreview()
         event.Skip()
     
     def OnExecutionOrderChanged(self, event):
+        """
+        Called when block execution control value changed
+        @param event: wx.SpinEvent
+        """
         self.RefreshPreview()
         event.Skip()
     
     def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
-        expression = self.Expression.GetValue()
-        if self.Expression.IsEnabled() and expression != "":
-            name = expression
-        else:
-            name = self.VariableName.GetStringSelection()
-        type = ""
-        for var_name, var_type, value_type in self.VarList:
-            if var_name == name:
-                type = value_type
-        classtype = VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()]
-        self.Variable = FBD_Variable(self.Preview, classtype, name, type, executionOrder = self.ExecutionOrder.GetValue())
-        width, height = self.MinVariableSize
-        min_width, min_height = self.Variable.GetMinSize()
-        width, height = max(min_width, width), max(min_height, height)
-        self.Variable.SetSize(width, height)
-        clientsize = self.Preview.GetClientSize()
-        x = (clientsize.width - width) / 2
-        y = (clientsize.height - height) / 2
-        self.Variable.SetPosition(x, y)
-        self.Variable.Draw(dc)
-
-    def OnPaint(self, event):
-        self.RefreshPreview()
-        event.Skip()
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        # Get expression value to put in FBD variable element
+        name = self.Expression.GetValue()
+        
+        # Set graphic element displayed, creating a FBD variable element
+        self.Element = FBD_Variable(self.Preview, 
+                    VARIABLE_CLASSES_DICT_REVERSE[
+                        self.Class.GetStringSelection()], 
+                    name, 
+                    self.VariableList.get(name, ("", ""))[1], 
+                    executionOrder = self.ExecutionOrder.GetValue())
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
+        
+        
\ No newline at end of file
--- a/dialogs/FindInPouDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/FindInPouDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -97,6 +97,7 @@
               flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT)
         
         self.FindButton = wx.Button(panel, label=_("Find"))
+        self.FindButton.SetDefault()
         self.Bind(wx.EVT_BUTTON, self.OnFindButton, self.FindButton)
         buttons_sizer.AddWindow(self.FindButton, border=5, flag=wx.RIGHT)
         
@@ -109,7 +110,8 @@
         self.ParentWindow = parent
         
         self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
-    
+        
+        self.FindPattern.SetFocus()
         self.RefreshButtonsState()
     
     def RefreshButtonsState(self):
--- a/dialogs/LDElementDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/LDElementDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,198 +24,172 @@
 
 import wx
 
-from graphics import *
+from graphics.GraphicCommons import CONTACT_NORMAL, CONTACT_REVERSE, \
+    CONTACT_RISING, CONTACT_FALLING, COIL_NORMAL, COIL_REVERSE, COIL_SET, \
+    COIL_RESET, COIL_RISING, COIL_FALLING
+from graphics.LD_Objects import LD_Contact, LD_Coil
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
-#                        Edit Ladder Element Properties Dialog
+#                       Set Ladder Element Parmeters Dialog
 #-------------------------------------------------------------------------------
 
-class LDElementDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters of a LD contact or coil
+graphic element
+"""
+
+class LDElementDialog(BlockPreviewDialog):
     
-    def __init__(self, parent, controller, type):
-        if type == "contact":
-            wx.Dialog.__init__(self, parent, size=wx.Size(350, 260),
-                  title=_("Edit Contact Values"))
-        else:
-            wx.Dialog.__init__(self, parent, size=wx.Size(350, 310),
-                  title=_("Edit Coil Values"))
+    def __init__(self, parent, controller, tagname, type):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        @param type: Type of LD element ('contact or 'coil')
+        """
+        BlockPreviewDialog.__init__(self, parent, controller, tagname, 
+              size=wx.Size(350, 280 if type == "contact" else 330),
+              title=(_("Edit Contact Values")
+                     if type == "contact"
+                     else _("Edit Coil Values")))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
+        # Init common sizers
+        self._init_sizers(2, 0, 
+              (7 if type == "contact" else 9), None, 2, 1)
         
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+        # Create label for LD element modifier
+        modifier_label = wx.StaticText(self, label=_('Modifier:'))
+        self.LeftGridSizer.AddWindow(modifier_label, border=5, 
+              flag=wx.GROW|wx.BOTTOM)
         
-        if type == "contact":
-            left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=7, vgap=0)
-        else:
-            left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=9, vgap=0)
-        left_gridsizer.AddGrowableCol(0)
-        column_sizer.AddSizer(left_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.RIGHT)
+        # Create radio buttons for selecting LD element modifier
+        self.ModifierRadioButtons = {}
+        first = True
+        element_modifiers = ([CONTACT_NORMAL, CONTACT_REVERSE, 
+                              CONTACT_RISING, CONTACT_FALLING]
+                             if type == "contact"
+                             else [COIL_NORMAL, COIL_REVERSE, COIL_SET,
+                                   COIL_RESET, COIL_RISING, COIL_FALLING])
+        modifiers_label = [_("Normal"), _("Negated")] + \
+                          ([_("Set"), _("Reset")] if type == "coil" else []) + \
+                          [_("Rising Edge"), _("Falling Edge")]
         
-        modifier_label = wx.StaticText(self, label=_('Modifier:'))
-        left_gridsizer.AddWindow(modifier_label, border=5, flag=wx.GROW|wx.BOTTOM)
+        for modifier, label in zip(element_modifiers, modifiers_label):
+            radio_button = wx.RadioButton(self, label=label, 
+                  style=(wx.RB_GROUP if first else 0))
+            radio_button.SetValue(first)
+            self.Bind(wx.EVT_RADIOBUTTON, self.OnModifierChanged, radio_button)
+            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.ModifierRadioButtons[modifier] = radio_button
+            first = False
         
-        self.Normal = wx.RadioButton(self, label=_("Normal"), style=wx.RB_GROUP)
-        self.Normal.SetValue(True)
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Normal)
-        left_gridsizer.AddWindow(self.Normal, flag=wx.GROW)
+        # Create label for LD element variable
+        element_variable_label = wx.StaticText(self, label=_('Variable:'))
+        self.LeftGridSizer.AddWindow(element_variable_label, border=5,
+              flag=wx.GROW|wx.TOP)
         
-        self.Negated = wx.RadioButton(self, label=_("Negated"))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Negated)
-        left_gridsizer.AddWindow(self.Negated, flag=wx.GROW)
+        # Create a combo box for defining LD element variable
+        self.ElementVariable = wx.ComboBox(self, style=wx.CB_READONLY|wx.CB_SORT)
+        self.Bind(wx.EVT_COMBOBOX, self.OnVariableChanged, 
+                  self.ElementVariable)
+        self.LeftGridSizer.AddWindow(self.ElementVariable, border=5,
+             flag=wx.GROW|wx.TOP)
         
-        if type != "contact":
-            self.Set = wx.RadioButton(self, label=_("Set"))
-            self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Set)
-            left_gridsizer.AddWindow(self.Set, flag=wx.GROW)
-            
-            self.Reset = wx.RadioButton(self, label=_("Reset"))
-            self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.Reset)
-            left_gridsizer.AddWindow(self.Reset, flag=wx.GROW)
-            
-        self.RisingEdge = wx.RadioButton(self, label=_("Rising Edge"))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.RisingEdge)
-        left_gridsizer.AddWindow(self.RisingEdge, flag=wx.GROW)
+        # Add preview panel and associated label to sizers
+        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
         
-        self.FallingEdge = wx.RadioButton(self, label=_("Falling Edge"))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.FallingEdge)
-        left_gridsizer.AddWindow(self.FallingEdge, flag=wx.GROW)
-        
-        element_name_label = wx.StaticText(self, label=_('Name:'))
-        left_gridsizer.AddWindow(element_name_label, border=5, flag=wx.GROW|wx.TOP)
-        
-        self.ElementName = wx.ComboBox(self, style=wx.CB_READONLY)
-        self.Bind(wx.EVT_COMBOBOX, self.OnNameChanged, self.ElementName)
-        left_gridsizer.AddWindow(self.ElementName, border=5, flag=wx.GROW|wx.TOP)
-        
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(1)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
-        
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-
-        self.Preview = wx.Panel(self,
-              style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "GetScaling", lambda:None)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-        
-        button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-        main_sizer.AddSizer(button_sizer, border=20, 
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
-        self.SetSizer(main_sizer)
+        # Save LD element class
+        self.ElementClass = (LD_Contact if type == "contact" else LD_Coil)
         
-        if type == "contact":
-            self.Element = LD_Contact(self.Preview, CONTACT_NORMAL, "")
-        else:
-            self.Element = LD_Coil(self.Preview, COIL_NORMAL, "")
+        # Extract list of variables defined in POU
+        self.RefreshVariableList()
         
-        self.Type = type
+        # Set values in ElementVariable
+        for name, (var_type, value_type) in self.VariableList.iteritems():
+            # Only select BOOL variable and avoid input for coil
+            if (type == "contact" or var_type != "Input") and \
+               value_type == "BOOL":
+                self.ElementVariable.Append(name)
+        self.ElementVariable.Enable(self.ElementVariable.GetCount() > 0)
         
-        self.Normal.SetFocus()
+        # Normal radio button is default control having keyboard focus
+        self.ModifierRadioButtons[element_modifiers[0]].SetFocus()
     
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
-    
-    def SetElementSize(self, size):
-        min_width, min_height = self.Element.GetMinSize()
-        width, height = max(min_width, size[0]), max(min_height, size[1])
-        self.Element.SetSize(width, height)
-        
-    def SetVariables(self, vars):
-        self.ElementName.Clear()
-        for name in vars:
-            self.ElementName.Append(name)
-        self.ElementName.Enable(self.ElementName.GetCount() > 0)
+    def GetElementModifier(self):
+        """
+        Return modifier selected for LD element
+        @return: Modifier selected (None if not found)
+        """
+        # Go through radio buttons and return modifier associated to the one
+        # that is selected
+        for modifier, control in self.ModifierRadioButtons.iteritems():
+            if control.GetValue():
+                return modifier
+        return None
 
     def SetValues(self, values):
+        """
+        Set default LD element parameters
+        @param values: LD element parameters values
+        """
+        # For each parameters defined, set corresponding control value
         for name, value in values.items():
-            if name == "name":
-                self.Element.SetName(value)
-                self.ElementName.SetStringSelection(value)
-            elif name == "type":
-                self.Element.SetType(value)
-                if self.Type == "contact":
-                    if value == CONTACT_NORMAL:
-                        self.Normal.SetValue(True)
-                    elif value == CONTACT_REVERSE:
-                        self.Negated.SetValue(True)
-                    elif value == CONTACT_RISING:
-                        self.RisingEdge.SetValue(True)
-                    elif value == CONTACT_FALLING:
-                        self.FallingEdge.SetValue(True)
-                elif self.Type == "coil":
-                    if value == COIL_NORMAL:
-                        self.Normal.SetValue(True)
-                    elif value == COIL_REVERSE:
-                        self.Negated.SetValue(True)
-                    elif value == COIL_SET:
-                        self.Set.SetValue(True)
-                    elif value == COIL_RESET:
-                        self.Reset.SetValue(True)
-                    elif value == COIL_RISING:
-                        self.RisingEdge.SetValue(True)
-                    elif value == COIL_FALLING:
-                        self.FallingEdge.SetValue(True)
+            
+            # Parameter is LD element variable
+            if name == "variable":
+                self.ElementVariable.SetStringSelection(value)
+            
+            # Set value of other controls
+            elif name == "modifier":
+                self.ModifierRadioButtons[value].SetValue(True)
+        
+        # Refresh preview panel
+        self.RefreshPreview()
 
     def GetValues(self):
-        values = {}
-        values["name"] = self.Element.GetName()
-        values["type"] = self.Element.GetType()
+        """
+        Return LD element parameters defined in dialog
+        @return: {parameter_name: parameter_value,...}
+        """
+        values = {
+            "variable": self.ElementVariable.GetValue(),
+            "modifier": self.GetElementModifier()}
         values["width"], values["height"] = self.Element.GetSize()
         return values
 
-    def OnTypeChanged(self, event):
-        if self.Type == "contact":
-            if self.Normal.GetValue():
-                self.Element.SetType(CONTACT_NORMAL)
-            elif self.Negated.GetValue():
-                self.Element.SetType(CONTACT_REVERSE)
-            elif self.RisingEdge.GetValue():
-                self.Element.SetType(CONTACT_RISING)
-            elif self.FallingEdge.GetValue():
-                self.Element.SetType(CONTACT_FALLING)
-        elif self.Type == "coil":
-            if self.Normal.GetValue():
-                self.Element.SetType(COIL_NORMAL)
-            elif self.Negated.GetValue():
-                self.Element.SetType(COIL_REVERSE)
-            elif self.Set.GetValue():
-                self.Element.SetType(COIL_SET)
-            elif self.Reset.GetValue():
-                self.Element.SetType(COIL_RESET)
-            elif self.RisingEdge.GetValue():
-                self.Element.SetType(COIL_RISING)
-            elif self.FallingEdge.GetValue():
-                self.Element.SetType(COIL_FALLING)
+    def OnModifierChanged(self, event):
+        """
+        Called when LD element modifier changed
+        @param event: wx.RadioButtonEvent
+        """
         self.RefreshPreview()
         event.Skip()
 
-    def OnNameChanged(self, event):
-        self.Element.SetName(self.ElementName.GetStringSelection())
+    def OnVariableChanged(self, event):
+        """
+        Called when LD element associated variable changed
+        @param event: wx.ComboBoxEvent
+        """
         self.RefreshPreview()
         event.Skip()
 
     def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
-        clientsize = self.Preview.GetClientSize()
-        width, height = self.Element.GetSize()
-        self.Element.SetPosition((clientsize.width - width) / 2, (clientsize.height - height) / 2)
-        self.Element.Draw(dc)
-
-    def OnPaint(self, event):
-        self.RefreshPreview()
-        event.Skip()
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        # Set graphic element displayed, creating a LD element
+        self.Element = self.ElementClass(
+                self.Preview, 
+                self.GetElementModifier(),
+                self.ElementVariable.GetStringSelection())
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/LDPowerRailDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/LDPowerRailDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -23,126 +23,142 @@
 
 import wx
 
-from graphics import *
+from graphics.GraphicCommons import LEFTRAIL, RIGHTRAIL, LD_LINE_SIZE
+from graphics.LD_Objects import LD_PowerRail
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
-#                      Edit Ladder Power Rail Properties Dialog
+#                    Set Ladder Power Rail Parameters Dialog
 #-------------------------------------------------------------------------------
 
-class LDPowerRailDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters of a power rail graphic
+element
+"""
+
+class LDPowerRailDialog(BlockPreviewDialog):
     
-    def __init__(self, parent, controller, type = LEFTRAIL, number = 1):
-        wx.Dialog.__init__(self, parent, size=wx.Size(350, 260),
-              title=_('Power Rail Properties'))
+    def __init__(self, parent, controller, tagname):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        """
+        BlockPreviewDialog.__init__(self, parent, controller, tagname,
+              size=wx.Size(350, 260), title=_('Power Rail Properties'))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
+        # Init common sizers
+        self._init_sizers(2, 0, 5, None, 2, 1)
         
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+        # Create label for connection type
+        type_label = wx.StaticText(self, label=_('Type:'))
+        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
         
-        left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=5)
-        left_gridsizer.AddGrowableCol(0)
-        column_sizer.AddSizer(left_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.RIGHT)
+        # Create radio buttons for selecting power rail type
+        self.TypeRadioButtons = {}
+        first = True
+        for type, label in [(LEFTRAIL, _('Left PowerRail')),
+                            (RIGHTRAIL, _('Right PowerRail'))]:
+            radio_button = wx.RadioButton(self, label=label, 
+                  style=(wx.RB_GROUP if first else 0))
+            radio_button.SetValue(first)
+            self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.TypeRadioButtons[type] = radio_button
+            first = False
         
-        type_label = wx.StaticText(self, label=_('Type:'))
-        left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+        # Create label for power rail pin number
+        pin_number_label = wx.StaticText(self, label=_('Pin number:'))
+        self.LeftGridSizer.AddWindow(pin_number_label, flag=wx.GROW)
         
-        self.LeftPowerRail = wx.RadioButton(self,
-              label=_('Left PowerRail'), style=wx.RB_GROUP)
-        self.LeftPowerRail.SetValue(True)
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.LeftPowerRail)
-        left_gridsizer.AddWindow(self.LeftPowerRail, flag=wx.GROW)
-        
-        self.RightPowerRail = wx.RadioButton(self, label=_('Right PowerRail'))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.RightPowerRail)
-        left_gridsizer.AddWindow(self.RightPowerRail, flag=wx.GROW)
-        
-        pin_number_label = wx.StaticText(self, label=_('Pin number:'))
-        left_gridsizer.AddWindow(pin_number_label, flag=wx.GROW)
-        
+        # Create spin control for defining power rail pin number
         self.PinNumber = wx.SpinCtrl(self, min=1, max=50,
               style=wx.SP_ARROW_KEYS)
         self.Bind(wx.EVT_SPINCTRL, self.OnPinNumberChanged, self.PinNumber)
-        left_gridsizer.AddWindow(self.PinNumber, flag=wx.GROW)
+        self.LeftGridSizer.AddWindow(self.PinNumber, flag=wx.GROW)
         
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(1)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
+        # Add preview panel and associated label to sizers
+        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
         
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-        
-        self.Preview = wx.Panel(self,
-              style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "GetScaling", lambda:None)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-        
-        button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-        main_sizer.AddSizer(button_sizer, border=20, 
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
-        self.SetSizer(main_sizer)
-        
-        self.Type = type
-        if type == LEFTRAIL:
-            self.LeftPowerRail.SetValue(True)
-        elif type == RIGHTRAIL:
-            self.RightPowerRail.SetValue(True)
-        self.PinNumber.SetValue(number)
-        
-        self.PowerRailMinSize = (0, 0)
-        self.PowerRail = None
-        
-        self.LeftPowerRail.SetFocus()
-
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
-
-    def SetMinSize(self, size):
-        self.PowerRailMinSize = size
-        self.RefreshPreview()    
+        # Left Power Rail radio button is default control having keyboard focus
+        self.TypeRadioButtons[LEFTRAIL].SetFocus()
+    
+    def GetMinElementSize(self):
+        """
+        Get minimal graphic element size
+        @return: Tuple containing minimal size (width, height) or None if no
+        element defined
+        """
+        return self.Element.GetMinSize(True)
+    
+    def GetPowerRailType(self):
+        """
+        Return type selected for power rail
+        @return: Type selected (LEFTRAIL or RIGHTRAIL)
+        """
+        return (LEFTRAIL
+                if self.TypeRadioButtons[LEFTRAIL].GetValue()
+                else RIGHTRAIL)
+    
+    def SetValues(self, values):
+        """
+        Set default power rail parameters
+        @param values: Power rail parameters values
+        """
+        # For each parameters defined, set corresponding control value
+        for name, value in values.items():
+            
+            # Parameter is power rail type
+            if name == "type":
+                self.TypeRadioButtons[value].SetValue(True)
+            
+            # Parameter is power rail pin number
+            elif name == "pin_number":
+                self.PinNumber.SetValue(value)
 
     def GetValues(self):
-        values = {}
-        values["type"] = self.Type
-        values["number"] = self.PinNumber.GetValue()
-        values["width"], values["height"] = self.PowerRail.GetSize()
+        """
+        Return power rail parameters defined in dialog
+        @return: {parameter_name: parameter_value,...}
+        """
+        values = {
+            "type": self.GetPowerRailType(),
+            "pin_number": self.PinNumber.GetValue()}
+        values["width"], values["height"] = self.Element.GetSize()
         return values
 
     def OnTypeChanged(self, event):
-        if self.LeftPowerRail.GetValue():
-            self.Type = LEFTRAIL
-        elif self.RightPowerRail.GetValue():
-            self.Type = RIGHTRAIL
+        """
+        Called when power rail type changed
+        @param event: wx.RadioButtonEvent
+        """
         self.RefreshPreview()
         event.Skip()
 
     def OnPinNumberChanged(self, event):
+        """
+        Called when power rail pin number value changed
+        @param event: wx.SpinEvent
+        """
         self.RefreshPreview()
         event.Skip()
 
     def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
-        self.PowerRail = LD_PowerRail(self.Preview, self.Type, connectors = self.PinNumber.GetValue())
-        min_width, min_height = 2, LD_LINE_SIZE * self.PinNumber.GetValue()
-        width, height = max(min_width, self.PowerRailMinSize[0]), max(min_height, self.PowerRailMinSize[1])
-        self.PowerRail.SetSize(width, height)
-        clientsize = self.Preview.GetClientSize()
-        self.PowerRail.SetPosition((clientsize.width - width) / 2, (clientsize.height - height) / 2)
-        self.PowerRail.Draw(dc)
-
-    def OnPaint(self, event):
-        self.RefreshPreview()
-        event.Skip()
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        
+        # Set graphic element displayed, creating a power rail element
+        self.Element = LD_PowerRail(self.Preview, 
+                self.GetPowerRailType(), 
+                connectors = self.PinNumber.GetValue())
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/SFCDivergenceDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/SFCDivergenceDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -23,141 +23,132 @@
 
 import wx
 
-from graphics import *
+from graphics.GraphicCommons import SELECTION_DIVERGENCE, \
+    SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE
+from graphics.SFC_Objects import SFC_Divergence
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
 #                         Create New Divergence Dialog
 #-------------------------------------------------------------------------------
 
-class SFCDivergenceDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters for creating a new
+divergence graphic element
+"""
+
+class SFCDivergenceDialog(BlockPreviewDialog):
     
-    def __init__(self, parent, controller):
-        wx.Dialog.__init__(self, parent, size=wx.Size(500, 300), 
+    def __init__(self, parent, controller, tagname):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        """
+        BlockPreviewDialog.__init__(self, parent, controller, tagname, 
+              size=wx.Size(500, 300), 
               title=_('Create a new divergence or convergence'))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
+        # Init common sizers
+        self._init_sizers(2, 0, 7, None, 2, 1)
         
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+        # Create label for divergence type
+        type_label = wx.StaticText(self, label=_('Type:'))
+        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
         
-        left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=7, vgap=5)
-        left_gridsizer.AddGrowableCol(0)
-        column_sizer.AddSizer(left_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.RIGHT)
-        
-        type_label = wx.StaticText(self, label=_('Type:'))
-        left_gridsizer.AddWindow(type_label, flag=wx.GROW)
+        # Create radio buttons for selecting divergence type
+        self.TypeRadioButtons = {}
+        first = True
+        for type, label in [
+                (SELECTION_DIVERGENCE, _('Selection Divergence')),
+                (SELECTION_CONVERGENCE, _('Selection Convergence')),
+                (SIMULTANEOUS_DIVERGENCE, _('Simultaneous Divergence')),
+                (SIMULTANEOUS_CONVERGENCE, _('Simultaneous Convergence'))]:
+            radio_button = wx.RadioButton(self, label=label, 
+                  style=(wx.RB_GROUP if first else 0))
+            radio_button.SetValue(first)
+            self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.TypeRadioButtons[type] = radio_button
+            first = False
 
-        self.SelectionDivergence = wx.RadioButton(self, 
-              label=_('Selection Divergence'), style=wx.RB_GROUP)
-        self.SelectionDivergence.SetValue(True)
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, 
-                  self.SelectionDivergence)
-        left_gridsizer.AddWindow(self.SelectionDivergence, flag=wx.GROW)
-        
-        self.SelectionConvergence = wx.RadioButton(self,
-              label=_('Selection Convergence'))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, 
-                  self.SelectionConvergence)
-        left_gridsizer.AddWindow(self.SelectionConvergence, flag=wx.GROW)
-        
-        self.SimultaneousDivergence = wx.RadioButton(self,
-              label=_('Simultaneous Divergence'))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, 
-                  self.SimultaneousDivergence)
-        left_gridsizer.AddWindow(self.SimultaneousDivergence, flag=wx.GROW)
-        
-        self.SimultaneousConvergence = wx.RadioButton(self,
-              label=_('Simultaneous Convergence'))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, 
-                  self.SimultaneousConvergence)
-        left_gridsizer.AddWindow(self.SimultaneousConvergence, flag=wx.GROW)
-        
+        # Create label for number of divergence sequences
         sequences_label = wx.StaticText(self, 
               label=_('Number of sequences:'))
-        left_gridsizer.AddWindow(sequences_label, flag=wx.GROW)
+        self.LeftGridSizer.AddWindow(sequences_label, flag=wx.GROW)
         
+        # Create spin control for defining number of divergence sequences
         self.Sequences = wx.SpinCtrl(self, min=2, max=20)
         self.Bind(wx.EVT_SPINCTRL, self.OnSequencesChanged, self.Sequences)
-        left_gridsizer.AddWindow(self.Sequences, flag=wx.GROW)
+        self.LeftGridSizer.AddWindow(self.Sequences, flag=wx.GROW)
         
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(1)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
+        # Add preview panel and associated label to sizers
+        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
         
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
+              flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
-        self.Preview = wx.Panel(self, style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-        
-        button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-        main_sizer.AddSizer(button_sizer, border=20, flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
-        
-        self.SetSizer(main_sizer)
-        
-        self.Divergence = None
-        self.MinSize = (0, 0)
-        
-        self.SelectionDivergence.SetFocus()
+        # Selection divergence radio button is default control having keyboard
+        # focus
+        self.TypeRadioButtons[SELECTION_DIVERGENCE].SetFocus()
     
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
+    def GetMinElementSize(self):
+        """
+        Get minimal graphic element size
+        @return: Tuple containing minimal size (width, height) or None if no
+        element defined
+        """
+        return self.Element.GetMinSize(True)
+    
+    def GetDivergenceType(self):
+        """
+        Return type selected for SFC divergence
+        @return: Type selected (None if not found)
+        """
+        # Go through radio buttons and return type associated to the one that
+        # is selected
+        for type, control in self.TypeRadioButtons.iteritems():
+            if control.GetValue():
+                return type
+        return None
     
     def GetValues(self):
-        values = {}
-        if self.SelectionDivergence.GetValue():
-            values["type"] = SELECTION_DIVERGENCE
-        elif self.SelectionConvergence.GetValue():
-            values["type"] = SELECTION_CONVERGENCE
-        elif self.SimultaneousDivergence.GetValue():
-            values["type"] = SIMULTANEOUS_DIVERGENCE
-        else:
-            values["type"] = SIMULTANEOUS_CONVERGENCE
-        values["number"] = self.Sequences.GetValue()
-        return values
-
-    def SetMinSize(self, size):
-        self.MinSize = size
+        """
+        Set default SFC divergence parameters
+        @param values: Divergence parameters values
+        """
+        return {"type": self.GetDivergenceType(),
+                "number": self.Sequences.GetValue()}
 
     def OnTypeChanged(self, event):
+        """
+        Called when SFC divergence type changed
+        @param event: wx.RadioButtonEvent
+        """
         self.RefreshPreview()
         event.Skip()
 
     def OnSequencesChanged(self, event):
+        """
+        Called when SFC divergence number of sequences changed
+        @param event: wx.SpinEvent
+        """
         self.RefreshPreview()
         event.Skip()
         
     def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
-        if self.SelectionDivergence.GetValue():
-            self.Divergence = SFC_Divergence(self.Preview, SELECTION_DIVERGENCE, self.Sequences.GetValue())
-        elif self.SelectionConvergence.GetValue():
-            self.Divergence = SFC_Divergence(self.Preview, SELECTION_CONVERGENCE, self.Sequences.GetValue())
-        elif self.SimultaneousDivergence.GetValue():
-            self.Divergence = SFC_Divergence(self.Preview, SIMULTANEOUS_DIVERGENCE, self.Sequences.GetValue())
-        else:
-            self.Divergence = SFC_Divergence(self.Preview, SIMULTANEOUS_CONVERGENCE, self.Sequences.GetValue())
-        width, height = self.Divergence.GetSize()
-        min_width, min_height = max(width, self.MinSize[0]), max(height, self.MinSize[1])
-        self.Divergence.SetSize(min_width, min_height)
-        clientsize = self.Preview.GetClientSize()
-        x = (clientsize.width - min_width) / 2
-        y = (clientsize.height - min_height) / 2
-        self.Divergence.SetPosition(x, y)
-        self.Divergence.Draw(dc)
-
-    def OnPaint(self, event):
-        self.RefreshPreview()
-        event.Skip()
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        # Set graphic element displayed, creating a SFC divergence
+        self.Element = SFC_Divergence(self.Preview, 
+                                      self.GetDivergenceType(), 
+                                      self.Sequences.GetValue())
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
+        
\ No newline at end of file
--- a/dialogs/SFCStepDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/SFCStepDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -23,183 +23,164 @@
 
 import wx
 
-from graphics import *
+from graphics.SFC_Objects import SFC_Step
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
-#                          Edit Step Content Dialog
+#                         Set SFC Step Parameters Dialog
 #-------------------------------------------------------------------------------
 
-class SFCStepDialog(wx.Dialog):
+"""
+Class that implements a dialog for defining parameters of a SFC step graphic
+element
+"""
+
+class SFCStepDialog(BlockPreviewDialog):
     
-    def __init__(self, parent, controller, initial = False):
-        wx.Dialog.__init__(self, parent, title=_('Edit Step'), 
-              size=wx.Size(400, 250))
+    def __init__(self, parent, controller, tagname, initial=False):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        @param initial: True if step is initial (default: False)
+        """
+        BlockPreviewDialog.__init__(self,parent, controller, tagname,  
+              size=wx.Size(400, 250), title=_('Edit Step'))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
+        # Init common sizers
+        self._init_sizers(2, 0, 6, None, 2, 1)
         
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
+        # Create label for SFC step name
+        name_label = wx.StaticText(self, label=_('Name:'))
+        self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
         
-        left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=5)
-        left_gridsizer.AddGrowableCol(0)
-        column_sizer.AddSizer(left_gridsizer, 1, border=5, 
-                              flag=wx.GROW|wx.RIGHT)
-        
-        name_label = wx.StaticText(self, label=_('Name:'))
-        left_gridsizer.AddWindow(name_label, flag=wx.GROW)
-        
+        # Create text control for defining SFC step name
         self.StepName = wx.TextCtrl(self)
         self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.StepName)
-        left_gridsizer.AddWindow(self.StepName, flag=wx.GROW)
+        self.LeftGridSizer.AddWindow(self.StepName, flag=wx.GROW)
         
+        # Create label for SFC step connectors
         connectors_label = wx.StaticText(self, label=_('Connectors:'))
-        left_gridsizer.AddWindow(connectors_label, flag=wx.GROW)
+        self.LeftGridSizer.AddWindow(connectors_label, flag=wx.GROW)
         
-        self.Input = wx.CheckBox(self, label=_("Input"))
-        self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Input)
-        left_gridsizer.AddWindow(self.Input, flag=wx.GROW)
+        # Create check boxes for defining connectors available on SFC step
+        self.ConnectorsCheckBox = {}
+        for name, label in [("input", _("Input")),
+                            ("output", _("Output")),
+                            ("action", _("Action"))]:
+            check_box = wx.CheckBox(self, label=label)
+            self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, check_box)
+            self.LeftGridSizer.AddWindow(check_box, flag=wx.GROW)
+            self.ConnectorsCheckBox[name] = check_box
         
-        self.Output = wx.CheckBox(self, label=_("Output"))
-        self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Output)
-        left_gridsizer.AddWindow(self.Output, flag=wx.GROW)
+        # Add preview panel and associated label to sizers
+        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
         
-        self.Action = wx.CheckBox(self, label=_("Action"))
-        self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, self.Action)
-        left_gridsizer.AddWindow(self.Action, flag=wx.GROW)
-        
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(1)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
-        
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-        
-        self.Preview = wx.Panel(self,
-              style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "RefreshStepModel", lambda x:None)
-        setattr(self.Preview, "GetScaling", lambda:None)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-        
-        button_sizer = self.CreateButtonSizer(
-                  wx.OK|wx.CANCEL|wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, 
-                  button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20, 
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
-        self.SetSizer(main_sizer)
+        # Save flag that indicates that step is initial
+        self.Initial = initial
         
-        self.Step = None
-        self.Initial = initial
-        self.MinStepSize = None
-    
-        self.PouNames = []
-        self.Variables = []
-        self.StepNames = []
+        # Set default name for step
+        self.StepName.ChangeValue(controller.GenerateNewName(
+               tagname, None, "Step%d", 0))
         
+        # Step name text control is default control having keyboard focus
         self.StepName.SetFocus()
     
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
-    
-    def OnOK(self, event):
-        message = None
-        step_name = self.StepName.GetValue()
-        if step_name == "":
-            message = _("You must type a name!")
-        elif not TestIdentifier(step_name):
-            message = _("\"%s\" is not a valid identifier!") % step_name
-        elif step_name.upper() in IEC_KEYWORDS:
-            message = _("\"%s\" is a keyword. It can't be used!") % step_name
-        elif step_name.upper() in self.PouNames:
-            message = _("A POU named \"%s\" already exists!") % step_name
-        elif step_name.upper() in self.Variables:
-            message = _("A variable with \"%s\" as name already exists in this pou!") % step_name
-        elif step_name.upper() in self.StepNames:
-            message = _("\"%s\" step already exists!") % step_name
-        if message is not None:
-            dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
-            dialog.ShowModal()
-            dialog.Destroy()    
-        else:
-            self.EndModal(wx.ID_OK)
-    
-    def SetMinStepSize(self, size):
-        self.MinStepSize = size
-
-    def SetPouNames(self, pou_names):
-        self.PouNames = [pou_name.upper() for pou_name in pou_names]
-
-    def SetVariables(self, variables):
-        self.Variables = [var["Name"].upper() for var in variables]
-
-    def SetStepNames(self, step_names):
-        self.StepNames = [step_name.upper() for step_name in step_names]
-
     def SetValues(self, values):
-        value_name = values.get("name", None)
-        if value_name:
-            self.StepName.SetValue(value_name)
-        else:
-            self.StepName.SetValue("")
-        self.Input.SetValue(values.get("input", False))
-        self.Output.SetValue(values.get("output", False))
-        self.Action.SetValue(values.get("action", False))
+        """
+        Set default block parameters
+        @param values: Block parameters values
+        """
+        # For each parameters defined, set corresponding control value
+        for name, value in values.items():
+            
+            # Parameter is step name
+            if name == "name":
+                self.StepName.ChangeValue(value)
+        
+            # Set value of other controls
+            else:
+                control = self.ConnectorsCheckBox.get(name, None)
+                if control is not None:
+                    control.SetValue(value)
+        
+        # Refresh preview panel
         self.RefreshPreview()
         
     def GetValues(self):
-        values = {}
-        values["name"] = self.StepName.GetValue()
-        values["input"] = self.Input.IsChecked()
-        values["output"] = self.Output.IsChecked()
-        values["action"] = self.Action.IsChecked()
-        values["width"], values["height"] = self.Step.GetSize()
+        """
+        Return step parameters defined in dialog
+        @return: {parameter_name: parameter_value,...}
+        """
+        values = {"name": self.StepName.GetValue()}
+        values.update({
+            name: control.IsChecked()
+            for name, control in self.ConnectorsCheckBox.iteritems()})
+        values["width"], values["height"] = self.Element.GetSize()
         return values
     
+    def OnOK(self, event):
+        """
+        Called when dialog OK button is pressed
+        Test if step name defined is valid
+        @param event: wx.Event from OK button
+        """
+        message = None
+        
+        # Get step name typed by user
+        step_name = self.StepName.GetValue()
+        
+        # Test that a name have been defined
+        if step_name == "":
+            message = _("Form isn't complete. Name must be filled!")
+        
+        # If an error have been identify, show error message dialog
+        if message is not None:
+            self.ShowErrorMessage(message)
+        
+        # Test step name validity
+        elif self.TestElementName(step_name):
+            # Call BlockPreviewDialog function
+            BlockPreviewDialog.OnOK(self, event)
+    
     def OnConnectorsChanged(self, event):
+        """
+        Called when a step connector value changed
+        @param event: wx.CheckBoxEvent
+        """
         self.RefreshPreview()
         event.Skip()
 
     def OnNameChanged(self, event):
+        """
+        Called when step name value changed
+        @param event: wx.TextEvent
+        """
         self.RefreshPreview()
         event.Skip()
     
     def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
-        self.Step = SFC_Step(self.Preview, self.StepName.GetValue(), self.Initial)
-        if self.Input.IsChecked():
-            self.Step.AddInput()
-        else:
-            self.Step.RemoveInput()
-        if self.Output.IsChecked():
-            self.Step.AddOutput()
-        else:
-            self.Step.RemoveOutput()
-        if self.Action.IsChecked():
-            self.Step.AddAction()    
-        else:
-            self.Step.RemoveAction()
-        width, height = self.MinStepSize
-        min_width, min_height = self.Step.GetMinSize()
-        width, height = max(min_width, width), max(min_height, height)
-        self.Step.SetSize(width, height)
-        clientsize = self.Preview.GetClientSize()
-        x = (clientsize.width - width) / 2
-        y = (clientsize.height - height) / 2
-        self.Step.SetPosition(x, y)
-        self.Step.Draw(dc)
-
-    def OnPaint(self, event):
-        self.RefreshPreview()
-        event.Skip()
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        # Set graphic element displayed, creating a SFC step element
+        self.Element = SFC_Step(self.Preview, 
+                                self.StepName.GetValue(), 
+                                self.Initial)
+        
+        # Update connectors of SFC step element according to check boxes value
+        for name, control in self.ConnectorsCheckBox.iteritems():
+            if control.IsChecked():
+                getattr(self.Element, "Add" + name.capitalize())()
+            else:
+                getattr(self.Element, "Remove" + name.capitalize())()
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
--- a/dialogs/SFCTransitionDialog.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/dialogs/SFCTransitionDialog.py	Wed Jul 31 10:45:07 2013 +0900
@@ -23,221 +23,215 @@
 
 import wx
 
-from graphics import *
+from graphics.SFC_Objects import SFC_Transition
+from BlockPreviewDialog import BlockPreviewDialog
 
 #-------------------------------------------------------------------------------
-#                          Edit Transition Content Dialog
+#                        Set Transition Parameters Dialog
 #-------------------------------------------------------------------------------
 
-class SFCTransitionDialog(wx.Dialog):
-    
-    def __init__(self, parent, controller, connection):
-        self.Connection = connection
-        
-        wx.Dialog.__init__(self, parent, 
+"""
+Class that implements a dialog for defining parameters of a transition graphic
+element
+"""
+
+class SFCTransitionDialog(BlockPreviewDialog):
+    
+    def __init__(self, parent, controller, tagname, connection=True):
+        """
+        Constructor
+        @param parent: Parent wx.Window of dialog for modal
+        @param controller: Reference to project controller
+        @param tagname: Tagname of project POU edited
+        @param connection: True if transition value can be defined by a
+        connection (default: True)
+        """
+        BlockPreviewDialog.__init__(self, parent, controller, tagname,
               size=wx.Size(350, 300), title=_('Edit transition'))
         
-        main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
-        main_sizer.AddGrowableCol(0)
-        main_sizer.AddGrowableRow(0)
-        
-        column_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(column_sizer, border=20, 
-              flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
-        
-        left_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=8, vgap=5)
-        left_gridsizer.AddGrowableCol(0)
-        column_sizer.AddSizer(left_gridsizer, 1, border=5, 
-                              flag=wx.GROW|wx.RIGHT)
-        
+        # Init common sizers
+        self._init_sizers(2, 0, 8, None, 2, 1)
+        
+        # Create label for transition type
         type_label = wx.StaticText(self, label=_('Type:'))
-        left_gridsizer.AddWindow(type_label, flag=wx.GROW)
-        
-        self.ReferenceRadioButton = wx.RadioButton(self,
-              label=_('Reference'), style=wx.RB_GROUP)
-        self.ReferenceRadioButton.SetValue(True)
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ReferenceRadioButton)
-        left_gridsizer.AddWindow(self.ReferenceRadioButton, flag=wx.GROW)
-        
-        self.Reference = wx.ComboBox(self, style=wx.CB_READONLY)
-        self.Bind(wx.EVT_COMBOBOX, self.OnReferenceChanged, self.Reference)
-        left_gridsizer.AddWindow(self.Reference, flag=wx.GROW)
-        
-        self.InlineRadioButton = wx.RadioButton(self, label=_('Inline'))
-        self.InlineRadioButton.SetValue(False)
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.InlineRadioButton)
-        left_gridsizer.AddWindow(self.InlineRadioButton, flag=wx.GROW)
-        
-        self.Inline = wx.TextCtrl(self)
-        self.Inline.Enable(False)
-        self.Bind(wx.EVT_TEXT, self.OnInlineChanged, self.Inline)
-        left_gridsizer.AddWindow(self.Inline, flag=wx.GROW)
-        
-        self.ConnectionRadioButton = wx.RadioButton(self, label=_('Connection'))
-        self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, self.ConnectionRadioButton)
-        self.ConnectionRadioButton.SetValue(False)
-        if not self.Connection:
-            self.ConnectionRadioButton.Hide()
-        left_gridsizer.AddWindow(self.ConnectionRadioButton, flag=wx.GROW)
-        
+        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        
+        # Create combo box for selecting reference value
+        reference = wx.ComboBox(self, style=wx.CB_READONLY)
+        reference.Append("")
+        for transition in controller.GetEditedElementTransitions(tagname):
+            reference.Append(transition)
+        self.Bind(wx.EVT_COMBOBOX, self.OnReferenceChanged, reference)
+        
+        # Create Text control for defining inline value
+        inline = wx.TextCtrl(self)
+        self.Bind(wx.EVT_TEXT, self.OnInlineChanged, inline)
+        
+        # Create radio buttons for selecting power rail type
+        self.TypeRadioButtons = {}
+        first = True
+        for type, label, control in [('reference', _('Reference'), reference),
+                                     ('inline', _('Inline'), inline),
+                                     ('connection', _('Connection'), None)]:
+            radio_button = wx.RadioButton(self, label=label, 
+                  style=(wx.RB_GROUP if first else 0))
+            radio_button.SetValue(first)
+            self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
+            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            if control is not None:
+                control.Enable(first)
+                self.LeftGridSizer.AddWindow(control, flag=wx.GROW)
+            self.TypeRadioButtons[type] = (radio_button, control)
+            first = False
+        
+        # Create label for transition priority
         priority_label = wx.StaticText(self, label=_('Priority:'))
-        left_gridsizer.AddWindow(priority_label, flag=wx.GROW)
-        
+        self.LeftGridSizer.AddWindow(priority_label, flag=wx.GROW)
+        
+        # Create spin control for defining priority value
         self.Priority = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
         self.Bind(wx.EVT_TEXT, self.OnPriorityChanged, self.Priority)
-        left_gridsizer.AddWindow(self.Priority, flag=wx.GROW)
-        
-        right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        right_gridsizer.AddGrowableCol(0)
-        right_gridsizer.AddGrowableRow(1)
-        column_sizer.AddSizer(right_gridsizer, 1, border=5, 
-              flag=wx.GROW|wx.LEFT)
-        
-        preview_label = wx.StaticText(self, label=_('Preview:'))
-        right_gridsizer.AddWindow(preview_label, flag=wx.GROW)
-        
-        self.Preview = wx.Panel(self, 
-              style=wx.TAB_TRAVERSAL|wx.SIMPLE_BORDER)
-        self.Preview.SetBackgroundColour(wx.Colour(255,255,255))
-        setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE)
-        setattr(self.Preview, "RefreshTransitionModel", lambda x:None)
-        setattr(self.Preview, "GetScaling", lambda:None)
-        setattr(self.Preview, "IsOfType", controller.IsOfType)
-        self.Preview.Bind(wx.EVT_PAINT, self.OnPaint)
-        right_gridsizer.AddWindow(self.Preview, flag=wx.GROW)
-        
-        button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, 
-              button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20, 
+        self.LeftGridSizer.AddWindow(self.Priority, flag=wx.GROW)
+        
+        # Add preview panel and associated label to sizers
+        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        
+        # Add buttons sizer to sizers
+        self.MainSizer.AddSizer(self.ButtonSizer, border=20, 
               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
         
-        self.SetSizer(main_sizer)
-        
-        self.Transition = None
-        self.MinTransitionSize = None
-        
+        # Reference radio button is default control having keyboard focus
+        self.TypeRadioButtons["reference"][0].SetFocus()
+    
+    def GetTransitionType(self):
+        """
+        Return type selected for SFC transition and associated value
+        @return: Type selected and associated value (None if no value)
+        """
+        # Go through radio buttons and return type and value associated to the
+        # one that is selected
+        for type, (radio, control) in self.TypeRadioButtons.iteritems():
+            if radio.GetValue():
+                if isinstance(control, wx.ComboBox):
+                    return type, control.GetStringSelection()
+                elif isinstance(control, wx.TextCtrl):
+                    return type, control.GetValue()
+                else:
+                    return type, None
+        return None, None
+    
+    def SetValues(self, values):
+        """
+        Set default SFC transition parameters
+        @param values: Transition parameters values
+        """
+        # Extract transition value according to type
+        type_value = values.get("value", None)
+        
+        # For each parameters defined, set corresponding control value
+        for name, value in values.items():
+            
+            # Parameter is SFC transition priority
+            if name == "priority":
+                self.Priority.SetValue(values["priority"])
+            
+            # Parameter is SFC transition type
+            elif name == "type":
+                for type, (radio, control) in self.TypeRadioButtons.iteritems():
+                    radio.SetValue(type == value)
+                    if control is not None:
+                        # Enable associated control to type and set value
+                        control.Enable(type == value)
+                        if type == value:
+                            if isinstance(control, wx.ComboBox):
+                                control.SetStringSelection(type_value)
+                            elif isinstance(control, wx.TextCtrl):
+                                control.ChangeValue(type_value)
+        
+        # Refresh preview panel
+        self.RefreshPreview()
+        
+    def GetValues(self):
+        """
+        Return SFC transition parameters defined in dialog
+        @return: {parameter_name: parameter_value,...}
+        """
+        values = {"priority" : self.Priority.GetValue()}
+        values["type"], values["value"] = self.GetTransitionType()
+        values["width"], values["height"] = self.Element.GetSize()
+        return values
+    
+    def OnOK(self, event):
+        """
+        Called when dialog OK button is pressed
+        Test if parameters defined are valid
+        @param event: wx.Event from OK button
+        """
+        message = None
+        
+        # Get transition type and value associated
+        type, value = self.GetTransitionType()
+        
+        # Test that value associated to type is defined
+        if type != "connection" and value == "":
+            message = _("Form isn't complete. %s must be filled!") % type
+        
+        # Show error message if an error is detected
+        if message is not None:
+            self.ShowErrorMessage(message)
+        
+        else:
+            # Call BlockPreviewDialog function
+            BlockPreviewDialog.OnOK(self, event)
+
+    def OnTypeChanged(self, event):
+        """
+        Called when transition type changed
+        @param event: wx.RadioButtonEvent
+        """
+        # Refresh sensibility of control associated to transition types
+        for type, (radio, control) in self.TypeRadioButtons.iteritems():
+            if control is not None:
+                control.Enable(radio.GetValue())
+        
+        # Refresh preview panel
+        self.RefreshPreview()
+        event.Skip()
+
+    def OnReferenceChanged(self, event):
+        """
+        Called when SFC transition reference value changed
+        @param event: wx.ComboBoxEvent
+        """
+        self.RefreshPreview()
+        event.Skip()
+
+    def OnInlineChanged(self, event):
+        """
+        Called when SFC transition inline value changed
+        @param event: wx.TextEvent
+        """
+        self.RefreshPreview()
+        event.Skip()
+
+    def OnPriorityChanged(self, event):
+        """
+        Called when block inputs number changed
+        @param event: wx.SpinEvent
+        """
+        self.RefreshPreview()
+        event.Skip()
+
+    def RefreshPreview(self):
+        """
+        Refresh preview panel of graphic element
+        Override BlockPreviewDialog function
+        """
+        # Set graphic element displayed, creating a SFC transition
         self.Element = SFC_Transition(self.Preview)
-        
-        self.ReferenceRadioButton.SetFocus()
-    
-    def SetPreviewFont(self, font):
-        self.Preview.SetFont(font)
-    
-    def SetElementSize(self, size):
-        min_width, min_height = self.Element.GetMinSize()
-        width, height = max(min_width, size[0]), max(min_height, size[1])
-        self.Element.SetSize(width, height)
-    
-    def OnOK(self, event):
-        error = []
-        if self.ReferenceRadioButton.GetValue() and self.Reference.GetStringSelection() == "":
-            error.append(_("Reference"))
-        if self.InlineRadioButton.GetValue() and self.Inline.GetValue() == "":
-            error.append(_("Inline"))
-        if len(error) > 0:
-            text = ""
-            for i, item in enumerate(error):
-                if i == 0:
-                    text += item
-                elif i == len(error) - 1:
-                    text += _(" and %s")%item
-                else:
-                    text += _(", %s")%item 
-            dialog = wx.MessageDialog(self, _("Form isn't complete. %s must be filled!")%text, _("Error"), wx.OK|wx.ICON_ERROR)
-            dialog.ShowModal()
-            dialog.Destroy()
-        else:
-            self.EndModal(wx.ID_OK)
-
-    def OnTypeChanged(self, event):
-        if self.ReferenceRadioButton.GetValue():
-            self.Element.SetType("reference", self.Reference.GetStringSelection())
-            self.Reference.Enable(True)
-            self.Inline.Enable(False)
-        elif self.InlineRadioButton.GetValue():
-            self.Element.SetType("inline", self.Inline.GetValue())
-            self.Reference.Enable(False)
-            self.Inline.Enable(True)
-        else:
-            self.Element.SetType("connection")
-            self.Reference.Enable(False)
-            self.Inline.Enable(False)
-        self.RefreshPreview()
-        event.Skip()
-
-    def OnReferenceChanged(self, event):
-        self.Element.SetType("reference", self.Reference.GetStringSelection())
-        self.RefreshPreview()
-        event.Skip()
-
-    def OnInlineChanged(self, event):
-        self.Element.SetType("inline", self.Inline.GetValue())
-        self.RefreshPreview()
-        event.Skip()
-
-    def OnPriorityChanged(self, event):
-        self.Element.SetPriority(int(self.Priority.GetValue()))
-        self.RefreshPreview()
-        event.Skip()
-
-    def SetTransitions(self, transitions):
-        self.Reference.Append("")
-        for transition in transitions:
-            self.Reference.Append(transition)
-
-    def SetValues(self, values):
-        if values["type"] == "reference":
-            self.ReferenceRadioButton.SetValue(True)
-            self.InlineRadioButton.SetValue(False)
-            self.ConnectionRadioButton.SetValue(False)
-            self.Reference.Enable(True)
-            self.Inline.Enable(False)
-            self.Reference.SetStringSelection(values["value"])
-            self.Element.SetType("reference", values["value"])
-        elif values["type"] == "inline":
-            self.ReferenceRadioButton.SetValue(False)
-            self.InlineRadioButton.SetValue(True)
-            self.ConnectionRadioButton.SetValue(False)
-            self.Reference.Enable(False)
-            self.Inline.Enable(True)
-            self.Inline.SetValue(values["value"])
-            self.Element.SetType("inline", values["value"])
-        elif values["type"] == "connection" and self.Connection:
-            self.ReferenceRadioButton.SetValue(False)
-            self.InlineRadioButton.SetValue(False)
-            self.ConnectionRadioButton.SetValue(True)
-            self.Reference.Enable(False)
-            self.Inline.Enable(False)
-            self.Element.SetType("connection")
-        self.Priority.SetValue(values["priority"])
-        self.Element.SetPriority(values["priority"])
-        self.RefreshPreview()
-        
-    def GetValues(self):
-        values = {"priority" : int(self.Priority.GetValue())}
-        if self.ReferenceRadioButton.GetValue():
-            values["type"] = "reference"
-            values["value"] = self.Reference.GetStringSelection()
-        elif self.InlineRadioButton.GetValue():
-            values["type"] = "inline"
-            values["value"] = self.Inline.GetValue()
-        else:
-            values["type"] = "connection"
-            values["value"] = None
-        return values
-
-    def RefreshPreview(self):
-        dc = wx.ClientDC(self.Preview)
-        dc.SetFont(self.Preview.GetFont())
-        dc.Clear()
-        clientsize = self.Preview.GetClientSize()
-        posx, posy = self.Element.GetPosition()
-        rect = self.Element.GetBoundingBox()
-        diffx, diffy = posx - rect.x, posy - rect.y
-        self.Element.SetPosition((clientsize.width - rect.width) / 2 + diffx, (clientsize.height - rect.height) / 2 + diffy)
-        self.Element.Draw(dc)
-
-    def OnPaint(self, event):
-        self.RefreshPreview()
-        event.Skip()
+        self.Element.SetType(*self.GetTransitionType())
+        self.Element.SetPriority(self.Priority.GetValue())
+        
+        # Call BlockPreviewDialog function
+        BlockPreviewDialog.RefreshPreview(self)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/CodeFileEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,866 @@
+import re
+
+import wx
+import wx.grid
+import wx.stc as stc
+import wx.lib.buttons
+
+from plcopen.plcopen import TestTextElement
+from plcopen.structures import TestIdentifier, IEC_KEYWORDS
+from controls import CustomGrid, CustomTable
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from util.BitmapLibrary import GetBitmap
+from controls.CustomStyledTextCtrl import CustomStyledTextCtrl, faces, GetCursorPos, NAVIGATION_KEYS
+from controls.VariablePanel import VARIABLE_NAME_SUFFIX_MODEL
+from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
+
+[STC_CODE_ERROR, STC_CODE_SEARCH_RESULT, 
+ STC_CODE_SECTION] = range(15, 18)
+
+HIGHLIGHT_TYPES = {
+    ERROR_HIGHLIGHT: STC_CODE_ERROR,
+    SEARCH_RESULT_HIGHLIGHT: STC_CODE_SEARCH_RESULT,
+}
+
+EDGE_COLUMN = 80
+
+class CodeEditor(CustomStyledTextCtrl):
+    
+    KEYWORDS = []
+    COMMENT_HEADER = ""
+
+    def __init__(self, parent, window, controler):
+        CustomStyledTextCtrl.__init__(self, parent, -1, wx.DefaultPosition, 
+                 wx.Size(-1, 300), 0)
+        
+        self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
+        self.SetMarginWidth(1, 25)
+
+        self.SetProperty("fold", "1")
+        self.SetProperty("tab.timmy.whinge.level", "1")
+        self.SetMargins(0,0)
+
+        self.SetViewWhiteSpace(False)
+        
+        self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
+        self.SetEdgeColumn(EDGE_COLUMN)
+
+        # Setup a margin to hold fold markers
+        self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
+        self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
+        self.SetMarginSensitive(2, True)
+        self.SetMarginWidth(2, 12)
+
+        # Like a flattened tree control using square headers
+        self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_BOXMINUS,          "white", "#808080")
+        self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_BOXPLUS,           "white", "#808080")
+        self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,             "white", "#808080")
+        self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNER,           "white", "#808080")
+        self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_BOXPLUSCONNECTED,  "white", "#808080")
+        self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
+        self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,           "white", "#808080")
+        
+        self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
+        self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
+        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
+
+        # Make some styles,  The lexer defines what each style is used for, we
+        # just have to define what each style looks like.  This set is adapted from
+        # Scintilla sample property files.
+
+        # Global default styles for all languages
+        self.StyleSetSpec(stc.STC_STYLE_DEFAULT,     "face:%(mono)s,size:%(size)d" % faces)
+        self.StyleClearAll()  # Reset all to be like the default
+
+        # Global default styles for all languages
+        self.StyleSetSpec(stc.STC_STYLE_DEFAULT,     "face:%(mono)s,size:%(size)d" % faces)
+        self.StyleSetSpec(stc.STC_STYLE_LINENUMBER,  "back:#C0C0C0,face:%(helv)s,size:%(size)d" % faces)
+        self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces)
+        self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,  "fore:#FFFFFF,back:#0000FF,bold")
+        self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,    "fore:#000000,back:#FF0000,bold")
+        
+        # Highlighting styles
+        self.StyleSetSpec(STC_CODE_ERROR, 'fore:#FF0000,back:#FFFF00,size:%(size)d' % faces)
+        self.StyleSetSpec(STC_CODE_SEARCH_RESULT, 'fore:#FFFFFF,back:#FFA500,size:%(size)d' % faces)
+        
+        # Section style
+        self.StyleSetSpec(STC_CODE_SECTION, 'fore:#808080,size:%(size)d')
+        self.StyleSetChangeable(STC_CODE_SECTION, False)
+        
+        # Indentation size
+        self.SetTabWidth(4)
+        self.SetUseTabs(0)
+        
+        self.SetCodeLexer()
+        self.SetKeyWords(0, " ".join(self.KEYWORDS))
+        
+        self.Controler = controler
+        self.ParentWindow = window
+        
+        self.DisableEvents = True
+        self.CurrentAction = None
+        
+        self.ResetSearchResults()
+        
+        self.RefreshHighlightsTimer = wx.Timer(self, -1)
+        self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
+        
+        self.SectionsComments = {}
+        for section in self.Controler.SECTIONS_NAMES:
+            section_comment = " %s section " % (section)
+            len_headers = EDGE_COLUMN - len(section_comment)
+            section_comment = self.COMMENT_HEADER * (len_headers / 2) + \
+                              section_comment + \
+                              self.COMMENT_HEADER * (len_headers - len_headers / 2)
+            
+            self.SectionsComments[section] = {
+                 "comment": section_comment,
+            }
+            
+        for i, section in enumerate(self.Controler.SECTIONS_NAMES):
+            section_infos = self.SectionsComments[section]
+            if i + 1 < len(self.Controler.SECTIONS_NAMES):
+                section_end = self.SectionsComments[
+                    self.Controler.SECTIONS_NAMES[i + 1]]["comment"]
+            else:
+                section_end = "$"
+            section_infos["pattern"] = re.compile(
+                section_infos["comment"] + "(.*)" + 
+                section_end, re.DOTALL)
+            
+        self.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT|wx.stc.STC_MOD_BEFOREDELETE)
+
+        self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop)
+        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
+        self.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification)
+    
+    def SetCodeLexer(self):
+        pass
+    
+    def ResetSearchResults(self):
+        self.Highlights = []
+        self.SearchParams = None
+        self.SearchResults = None
+        self.CurrentFindHighlight = None
+    
+    def OnModification(self, event):
+        if not self.DisableEvents:
+            mod_type = event.GetModificationType()
+            if not (mod_type&wx.stc.STC_PERFORMED_UNDO or mod_type&wx.stc.STC_PERFORMED_REDO):
+                if mod_type&wx.stc.STC_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())
+                    wx.CallAfter(self.RefreshModel)
+                elif mod_type&wx.stc.STC_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())
+                    wx.CallAfter(self.RefreshModel)
+        wx.CallAfter(self.RefreshSectionStyling)
+        event.Skip()
+    
+    def OnDoDrop(self, event):
+        try:
+            values = eval(event.GetDragText())
+        except:
+            values = event.GetDragText()
+        if isinstance(values, tuple):
+            message = None
+            if values[3] == self.Controler.GetCurrentLocation():
+                self.ResetBuffer()
+                event.SetDragText(values[0])
+                wx.CallAfter(self.RefreshModel)
+            else:
+                event.SetDragText("")
+        else:
+            self.ResetBuffer()
+            wx.CallAfter(self.RefreshModel)
+        event.Skip()
+
+    # Buffer the last model state
+    def RefreshBuffer(self):
+        self.Controler.BufferCodeFile()
+        if self.ParentWindow is not None:
+            self.ParentWindow.RefreshTitle()
+            self.ParentWindow.RefreshFileMenu()
+            self.ParentWindow.RefreshEditMenu()
+            self.ParentWindow.RefreshPageTitles()
+    
+    def StartBuffering(self):
+        self.Controler.StartBuffering()
+        if self.ParentWindow is not None:
+            self.ParentWindow.RefreshTitle()
+            self.ParentWindow.RefreshFileMenu()
+            self.ParentWindow.RefreshEditMenu()
+            self.ParentWindow.RefreshPageTitles()
+    
+    def ResetBuffer(self):
+        if self.CurrentAction != None:
+            self.Controler.EndBuffering()
+            self.CurrentAction = None
+
+    def GetCodeText(self):
+        parts = self.Controler.GetTextParts()
+        text = ""
+        for section in self.Controler.SECTIONS_NAMES:
+            section_comments = self.SectionsComments[section]
+            text += section_comments["comment"]
+            if parts[section] == "":
+                text += "\n"
+            else:
+                if not parts[section].startswith("\n"):
+                    text += "\n"
+                text += parts[section]
+                if not parts[section].endswith("\n"):
+                    text += "\n"
+        return text
+
+    def RefreshView(self, scroll_to_highlight=False):
+        self.ResetBuffer()
+        self.DisableEvents = True
+        old_cursor_pos = self.GetCurrentPos()
+        line = self.GetFirstVisibleLine()
+        column = self.GetXOffset()
+        old_text = self.GetText()
+        new_text = self.GetCodeText()
+        if old_text != new_text:
+            self.SetText(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.RefreshSectionStyling()
+        
+        self.ShowHighlights()
+        
+    def RefreshSectionStyling(self):
+        self.Colourise(0, -1)
+        
+        text = self.GetText()
+        for line in xrange(self.GetLineCount()):
+            self.SetLineState(line, 0)
+        
+        for section in self.Controler.SECTIONS_NAMES:
+            section_comments = self.SectionsComments[section]
+            start_pos = text.find(section_comments["comment"])
+            end_pos = start_pos + len(section_comments["comment"])
+            self.StartStyling(start_pos, 0xff)
+            self.SetStyling(end_pos - start_pos, STC_CODE_SECTION)
+            self.SetLineState(self.LineFromPosition(start_pos), 1)
+            
+        self.StartStyling(end_pos, 0x00)
+        self.SetStyling(len(self.GetText()) - end_pos, stc.STC_STYLE_DEFAULT)
+
+    def DoGetBestSize(self):
+        return self.ParentWindow.GetPanelBestSize()
+
+    def RefreshModel(self):
+        text = self.GetText()
+        parts = {}
+        for section in self.Controler.SECTIONS_NAMES:
+            section_comments = self.SectionsComments[section]
+            result = section_comments["pattern"].search(text)
+            if result is not None:
+                parts[section] = result.group(1)
+            else:
+                parts[section] = ""
+        self.Controler.SetTextParts(parts)
+        self.ResetSearchResults()
+    
+    def OnKeyPressed(self, event):
+        if self.CallTipActive():
+            self.CallTipCancel()
+        key = event.GetKeyCode()
+        current_pos = self.GetCurrentPos()
+        selected = self.GetSelection()
+        text_selected = selected[0] != selected[1]
+        
+        # Test if caret is before Windows like new line
+        text = self.GetText()
+        if current_pos < len(text) and ord(text[current_pos]) == 13:
+            newline_size = 2
+        else:
+            newline_size = 1
+        
+        # Disable to type any character in section header lines
+        if (self.GetLineState(self.LineFromPosition(current_pos)) and
+            not text_selected and
+            key not in NAVIGATION_KEYS + [
+                wx.WXK_RETURN,
+                wx.WXK_NUMPAD_ENTER]):
+            return
+        
+        # Disable to delete line between code and header lines
+        elif (self.GetCurLine()[0].strip() != "" and not text_selected and
+              (key == wx.WXK_BACK and
+               self.GetLineState(self.LineFromPosition(max(0, current_pos - 1))) or
+               key in [wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE] and
+               self.GetLineState(self.LineFromPosition(min(len(text), current_pos + newline_size))))):
+            return
+        
+        elif key == 32 and event.ControlDown():
+            pos = self.GetCurrentPos()
+
+            # Tips
+            if event.ShiftDown():
+                pass
+            # Code completion
+            else:
+                self.AutoCompSetIgnoreCase(False)  # so this needs to match
+                
+                keywords = self.KEYWORDS + [var["Name"]
+                                            for var in self.Controler.GetVariables()]
+                keywords.sort()
+                self.AutoCompShow(0, " ".join(keywords))
+        else:
+            event.Skip()
+
+    def OnKillFocus(self, event):
+        self.AutoCompCancel()
+        event.Skip()
+
+    def OnUpdateUI(self, event):
+        # check for matching braces
+        braceAtCaret = -1
+        braceOpposite = -1
+        charBefore = None
+        caretPos = self.GetCurrentPos()
+
+        if caretPos > 0:
+            charBefore = self.GetCharAt(caretPos - 1)
+            styleBefore = self.GetStyleAt(caretPos - 1)
+
+        # check before
+        if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
+            braceAtCaret = caretPos - 1
+
+        # check after
+        if braceAtCaret < 0:
+            charAfter = self.GetCharAt(caretPos)
+            styleAfter = self.GetStyleAt(caretPos)
+
+            if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
+                braceAtCaret = caretPos
+
+        if braceAtCaret >= 0:
+            braceOpposite = self.BraceMatch(braceAtCaret)
+
+        if braceAtCaret != -1  and braceOpposite == -1:
+            self.BraceBadLight(braceAtCaret)
+        else:
+            self.BraceHighlight(braceAtCaret, braceOpposite)
+        
+        selected_text = self.GetSelectedText()
+        if selected_text:
+            self.ParentWindow.SetCopyBuffer(selected_text, True)
+    
+    def OnMarginClick(self, event):
+        # fold and unfold as needed
+        if evt.GetMargin() == 2:
+            if evt.GetShift() and evt.GetControl():
+                self.FoldAll()
+            else:
+                lineClicked = self.LineFromPosition(evt.GetPosition())
+
+                if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
+                    if evt.GetShift():
+                        self.SetFoldExpanded(lineClicked, True)
+                        self.Expand(lineClicked, True, True, 1)
+                    elif evt.GetControl():
+                        if self.GetFoldExpanded(lineClicked):
+                            self.SetFoldExpanded(lineClicked, False)
+                            self.Expand(lineClicked, False, True, 0)
+                        else:
+                            self.SetFoldExpanded(lineClicked, True)
+                            self.Expand(lineClicked, True, True, 100)
+                    else:
+                        self.ToggleFold(lineClicked)
+        event.Skip()
+
+    def FoldAll(self):
+        lineCount = self.GetLineCount()
+        expanding = True
+
+        # find out if we are folding or unfolding
+        for lineNum in range(lineCount):
+            if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
+                expanding = not self.GetFoldExpanded(lineNum)
+                break
+
+        lineNum = 0
+
+        while lineNum < lineCount:
+            level = self.GetFoldLevel(lineNum)
+            if level & stc.STC_FOLDLEVELHEADERFLAG and \
+               (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
+
+                if expanding:
+                    self.SetFoldExpanded(lineNum, True)
+                    lineNum = self.Expand(lineNum, True)
+                    lineNum = lineNum - 1
+                else:
+                    lastChild = self.GetLastChild(lineNum, -1)
+                    self.SetFoldExpanded(lineNum, False)
+
+                    if lastChild > lineNum:
+                        self.HideLines(lineNum+1, lastChild)
+
+            lineNum = lineNum + 1
+
+
+
+    def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
+        lastChild = self.GetLastChild(line, level)
+        line = line + 1
+
+        while line <= lastChild:
+            if force:
+                if visLevels > 0:
+                    self.ShowLines(line, line)
+                else:
+                    self.HideLines(line, line)
+            else:
+                if doExpand:
+                    self.ShowLines(line, line)
+
+            if level == -1:
+                level = self.GetFoldLevel(line)
+
+            if level & stc.STC_FOLDLEVELHEADERFLAG:
+                if force:
+                    if visLevels > 1:
+                        self.SetFoldExpanded(line, True)
+                    else:
+                        self.SetFoldExpanded(line, False)
+
+                    line = self.Expand(line, doExpand, force, visLevels-1)
+
+                else:
+                    if doExpand and self.GetFoldExpanded(line):
+                        line = self.Expand(line, True, force, visLevels-1)
+                    else:
+                        line = self.Expand(line, False, force, visLevels-1)
+            else:
+                line = line + 1
+
+        return line
+
+    def Cut(self):
+        self.ResetBuffer()
+        self.DisableEvents = True
+        self.CmdKeyExecute(wx.stc.STC_CMD_CUT)
+        self.DisableEvents = False
+        self.RefreshModel()
+        self.RefreshBuffer()
+    
+    def Copy(self):
+        self.CmdKeyExecute(wx.stc.STC_CMD_COPY)
+        self.ParentWindow.RefreshEditMenu()
+    
+    def Paste(self):
+        self.ResetBuffer()
+        self.DisableEvents = True
+        self.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
+        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.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:
+                caret_pos = self.GetCurrentPos()
+                if self.SearchParams["wrap"]:
+                    self.CurrentFindHighlight = self.SearchResults[0]
+                else:
+                    self.CurrentFindHighlight = None
+                for start, end, highlight_type in self.SearchResults:
+                    if start[0] == 0:
+                        highlight_start_pos = start[1]
+                    else:
+                        highlight_start_pos = self.GetLineEndPosition(start[0] - 1) + start[1] + 1
+                    if highlight_start_pos >= caret_pos:
+                        self.CurrentFindHighlight = (start, end, highlight_type)
+                        break
+                if self.CurrentFindHighlight is not None:
+                    self.AddHighlight(*self.CurrentFindHighlight)
+            
+            self.ScrollToLine(self.CurrentFindHighlight[0][0])
+            
+        else:
+            if self.CurrentFindHighlight is not None:
+                self.RemoveHighlight(*self.CurrentFindHighlight)
+            self.CurrentFindHighlight = None
+
+#-------------------------------------------------------------------------------
+#                        Highlights showing functions
+#-------------------------------------------------------------------------------
+
+    def OnRefreshHighlightsTimer(self, event):
+        self.RefreshView(True)
+        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.GotoPos(self.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.GetLineEndPosition(start[0] - 1) + start[1] + 1
+            if end[0] == 0:
+                highlight_end_pos = end[1] + 1
+            else:
+                highlight_end_pos = self.GetLineEndPosition(end[0] - 1) + end[1] + 2
+            self.StartStyling(highlight_start_pos, 0xff)
+            self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
+            self.StartStyling(highlight_end_pos, 0x00)
+            self.SetStyling(len(self.GetText()) - highlight_end_pos, stc.STC_STYLE_DEFAULT)
+
+
+#-------------------------------------------------------------------------------
+#                         Helper for VariablesGrid values
+#-------------------------------------------------------------------------------
+
+class VariablesTable(CustomTable):
+    
+    def GetValue(self, row, col):
+        if row < self.GetNumberRows():
+            if col == 0:
+                return row + 1
+            else:
+                return str(self.data[row].get(self.GetColLabelValue(col, False), ""))
+    
+    def _updateColAttrs(self, grid):
+        """
+        wxGrid -> update the column attributes to add the
+        appropriate renderer given the column name.
+
+        Otherwise default to the default renderer.
+        """
+        
+        typelist = None
+        accesslist = None
+        for row in range(self.GetNumberRows()):
+            for col in range(self.GetNumberCols()):
+                editor = None
+                renderer = None
+                colname = self.GetColLabelValue(col, False)
+                
+                if colname in ["Name", "Initial"]:
+                    editor = wx.grid.GridCellTextEditor()
+                elif colname == "Class":
+                    editor = wx.grid.GridCellChoiceEditor()
+                    editor.SetParameters("input,memory,output")
+                elif colname == "Type":
+                    pass
+                else:
+                    grid.SetReadOnly(row, col, True)
+                
+                grid.SetCellEditor(row, col, editor)
+                grid.SetCellRenderer(row, col, renderer)
+                
+                grid.SetCellBackgroundColour(row, col, wx.WHITE)
+            self.ResizeRow(grid, row)
+
+
+class VariablesEditor(wx.Panel):
+    
+    def __init__(self, parent, window, controler):
+        wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
+        
+        main_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=4)
+        main_sizer.AddGrowableCol(1)
+        main_sizer.AddGrowableRow(0)
+        
+        controls_sizer = wx.BoxSizer(wx.VERTICAL)
+        main_sizer.AddSizer(controls_sizer, border=5, flag=wx.ALL)
+        
+        for name, bitmap, help in [
+                ("AddVariableButton", "add_element", _("Add variable")),
+                ("DeleteVariableButton", "remove_element", _("Remove variable")),
+                ("UpVariableButton", "up", _("Move variable up")),
+                ("DownVariableButton", "down", _("Move variable down"))]:
+            button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), 
+                  size=wx.Size(28, 28), style=wx.NO_BORDER)
+            button.SetToolTipString(help)
+            setattr(self, name, button)
+            controls_sizer.AddWindow(button, border=5, flag=wx.BOTTOM)
+        
+        self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL)
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
+        main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+        
+        self.SetSizer(main_sizer)
+                
+        self.ParentWindow = window
+        self.Controler = controler
+        
+        self.VariablesDefaultValue = {"Name" : "", "Type" : "INT", "Initial": ""}
+        self.Table = VariablesTable(self, [], ["#", "Name", "Type", "Initial"])
+        self.ColAlignements = [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+        self.ColSizes = [40, 200, 150, 150]
+        self.VariablesGrid.SetTable(self.Table)
+        self.VariablesGrid.SetButtons({"Add": self.AddVariableButton,
+                                       "Delete": self.DeleteVariableButton,
+                                       "Up": self.UpVariableButton,
+                                       "Down": self.DownVariableButton})
+        
+        def _AddVariable(new_row):
+            if new_row > 0:
+                row_content = self.Table.data[new_row - 1].copy()
+                result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content["Name"])
+                if result is not None:
+                    name = row_content["Name"][:result.start(1)]
+                    suffix = result.group(1)
+                    if suffix != "":
+                        start_idx = int(suffix)
+                    else:
+                        start_idx = 0
+                else:
+                    name = row_content["Name"]
+                    start_idx = 0
+                row_content["Name"] = self.Controler.GenerateNewName(
+                        name + "%d", start_idx)
+            else:
+                row_content = self.VariablesDefaultValue.copy()
+            self.Table.InsertRow(new_row, row_content)
+            self.RefreshModel()
+            self.RefreshView()
+            return new_row
+        setattr(self.VariablesGrid, "_AddRow", _AddVariable)
+        
+        def _DeleteVariable(row):
+            self.Table.RemoveRow(row)
+            self.RefreshModel()
+            self.RefreshView()
+        setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
+        
+        def _MoveVariable(row, move):
+            new_row = self.Table.MoveRow(row, move)
+            if new_row != row:
+                self.RefreshModel()
+                self.RefreshView()
+            return new_row
+        setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
+        
+        self.VariablesGrid.SetRowLabelSize(0)
+        for col in range(self.Table.GetNumberCols()):
+            attr = wx.grid.GridCellAttr()
+            attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
+            self.VariablesGrid.SetColAttr(col, attr)
+            self.VariablesGrid.SetColSize(col, self.ColSizes[col])
+        self.Table.ResetView(self.VariablesGrid)
+
+    def RefreshModel(self):
+        self.Controler.SetVariables(self.Table.GetData())
+        self.RefreshBuffer()
+        
+    # Buffer the last model state
+    def RefreshBuffer(self):
+        self.Controler.BufferCodeFile()
+        self.ParentWindow.RefreshTitle()
+        self.ParentWindow.RefreshFileMenu()
+        self.ParentWindow.RefreshEditMenu()
+        self.ParentWindow.RefreshPageTitles()
+
+    def RefreshView(self):
+        self.Table.SetData(self.Controler.GetVariables())
+        self.Table.ResetView(self.VariablesGrid)
+        self.VariablesGrid.RefreshButtons()
+    
+    def DoGetBestSize(self):
+        return self.ParentWindow.GetPanelBestSize()
+    
+    def OnVariablesGridCellChange(self, event):
+        row, col = event.GetRow(), event.GetCol()
+        colname = self.Table.GetColLabelValue(col, False)
+        value = self.Table.GetValue(row, col)
+        message = None
+        
+        if colname == "Name" and value != "":
+            if not TestIdentifier(value):
+                message = _("\"%s\" is not a valid identifier!") % value
+            elif value.upper() in IEC_KEYWORDS:
+                message = _("\"%s\" is a keyword. It can't be used!") % value
+            elif value.upper() in [var["Name"].upper() 
+                                   for var_row, var in enumerate(self.Table.data) 
+                                   if var_row != row]:
+                message = _("A variable with \"%s\" as name already exists!") % value
+            else:
+                self.RefreshModel()
+                wx.CallAfter(self.RefreshView)
+        else:
+            self.RefreshModel()
+            wx.CallAfter(self.RefreshView)
+        
+        if message is not None:
+            dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
+            dialog.ShowModal()
+            dialog.Destroy()
+            event.Veto()
+        else:
+            event.Skip()
+
+    def OnVariablesGridEditorShown(self, event):
+        row, col = event.GetRow(), event.GetCol() 
+        if self.Table.GetColLabelValue(col, False) == "Type":
+            type_menu = wx.Menu(title='')
+            base_menu = wx.Menu(title='')
+            for base_type in self.Controler.GetBaseTypes():
+                new_id = wx.NewId()
+                base_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
+                self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
+            type_menu.AppendMenu(wx.NewId(), "Base Types", base_menu)
+            datatype_menu = wx.Menu(title='')
+            for datatype in self.Controler.GetDataTypes():
+                new_id = wx.NewId()
+                datatype_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
+                self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
+            type_menu.AppendMenu(wx.NewId(), "User Data Types", datatype_menu)
+            rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
+            
+            self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
+            type_menu.Destroy()
+            event.Veto()
+        else:
+            event.Skip()
+
+    def GetVariableTypeFunction(self, base_type):
+        def VariableTypeFunction(event):
+            row = self.VariablesGrid.GetGridCursorRow()
+            self.Table.SetValueByName(row, "Type", base_type)
+            self.Table.ResetView(self.VariablesGrid)
+            self.RefreshModel()
+            self.RefreshView()
+            event.Skip()
+        return VariableTypeFunction
+
+    def OnVariablesGridCellLeftClick(self, event):
+        if event.GetCol() == 0:
+            row = event.GetRow()
+            data_type = self.Table.GetValueByName(row, "Type")
+            var_name = self.Table.GetValueByName(row, "Name")
+            data = wx.TextDataObject(str((var_name, "Global", data_type, 
+                    self.Controler.GetCurrentLocation())))
+            dragSource = wx.DropSource(self.VariablesGrid)
+            dragSource.SetData(data)
+            dragSource.DoDragDrop()
+            return
+        event.Skip()
+    
+
+#-------------------------------------------------------------------------------
+#                          CodeFileEditor Main Frame Class
+#-------------------------------------------------------------------------------
+
+class CodeFileEditor(ConfTreeNodeEditor):
+    
+    CONFNODEEDITOR_TABS = []
+    CODE_EDITOR = None
+    
+    def _create_CodePanel(self, prnt):
+        self.CodeEditorPanel = wx.SplitterWindow(prnt)
+        self.CodeEditorPanel.SetMinimumPaneSize(1)
+        
+        self.VariablesPanel = VariablesEditor(self.CodeEditorPanel, 
+                self.ParentWindow, self.Controler)
+        
+        if self.CODE_EDITOR is not None:
+            self.CodeEditor = self.CODE_EDITOR(self.CodeEditorPanel, 
+                        self.ParentWindow, self.Controler)
+            
+            self.CodeEditorPanel.SplitHorizontally(self.VariablesPanel, 
+                    self.CodeEditor, 150)
+        else:
+            self.CodeEditorPanel.Initialize(self.VariablesPanel)
+        
+        return self.CodeEditorPanel
+    
+    def __init__(self, parent, controler, window):
+        ConfTreeNodeEditor.__init__(self, parent, controler, window)
+        
+        wx.CallAfter(self.CodeEditorPanel.SetSashPosition, 150)
+    
+    def GetBufferState(self):
+        return self.Controler.GetBufferState()
+        
+    def Undo(self):
+        self.Controler.LoadPrevious()
+        self.RefreshView()
+            
+    def Redo(self):
+        self.Controler.LoadNext()
+        self.RefreshView()
+    
+    def RefreshView(self):
+        ConfTreeNodeEditor.RefreshView(self)
+        
+        self.VariablesPanel.RefreshView()
+        self.CodeEditor.RefreshView()
+    
+    def Find(self, direction, search_params):
+        self.CodeEditor.Find(direction, search_params)
+        
\ No newline at end of file
--- a/editors/ConfTreeNodeEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/ConfTreeNodeEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -29,24 +29,12 @@
              }
 
 SCROLLBAR_UNIT = 10
-WINDOW_COLOUR = wx.Colour(240, 240, 240)
 
 CWD = os.path.split(os.path.realpath(__file__))[0]
 
 def Bpath(*args):
     return os.path.join(CWD,*args)
 
-# Some helpers to tweak GenBitmapTextButtons
-# TODO: declare customized classes instead.
-gen_mini_GetBackgroundBrush = lambda obj:lambda dc: wx.Brush(obj.GetParent().GetBackgroundColour(), wx.SOLID)
-gen_textbutton_GetLabelSize = lambda obj:lambda:(wx.lib.buttons.GenButton._GetLabelSize(obj)[:-1] + (False,))
-
-def make_genbitmaptogglebutton_flat(button):
-    button.GetBackgroundBrush = gen_mini_GetBackgroundBrush(button)
-    button.labelDelta = 0
-    button.SetBezelWidth(0)
-    button.SetUseFocusIndicator(False)
-
 # Patch wx.lib.imageutils so that gray is supported on alpha images
 import wx.lib.imageutils
 from wx.lib.imageutils import grayOut as old_grayOut
@@ -116,7 +104,7 @@
         dc.DrawText(label, pos_x, pos_y)      # draw the text
 
 
-class GenStaticBitmap(wx.lib.statbmp.GenStaticBitmap):
+class GenStaticBitmap(wx.StaticBitmap):
     """ Customized GenStaticBitmap, fix transparency redraw bug on wx2.8/win32, 
     and accept image name as __init__ parameter, fail silently if file do not exist"""
     def __init__(self, parent, ID, bitmapname,
@@ -124,20 +112,15 @@
                  style = 0,
                  name = "genstatbmp"):
         
-        wx.lib.statbmp.GenStaticBitmap.__init__(self, parent, ID, 
-                 GetBitmap(bitmapname),
+        bitmap = GetBitmap(bitmapname)
+        if bitmap is None:
+            bitmap = wx.EmptyBitmap(0, 0)
+        
+        wx.StaticBitmap.__init__(self, parent, ID, 
+                 bitmap,
                  pos, size,
                  style,
                  name)
-        
-    def OnPaint(self, event):
-        dc = wx.PaintDC(self)
-        colour = self.GetParent().GetBackgroundColour()
-        dc.SetPen(wx.Pen(colour))
-        dc.SetBrush(wx.Brush(colour ))
-        dc.DrawRectangle(0, 0, *dc.GetSizeTuple())
-        if self._bitmap:
-            dc.DrawBitmap(self._bitmap, 0, 0, True)
 
 class ConfTreeNodeEditor(EditorPanel):
     
@@ -147,57 +130,21 @@
     
     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.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,20 +155,20 @@
                 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, 
-                      size=wx.Size(150, 25), style=wx.NO_BORDER)
+                self.ConfNodeName = wx.TextCtrl(self.Editor, 
+                      size=wx.Size(150, 25))
                 self.ConfNodeName.SetFont(
                     wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL, 
                             wx.BOLD, faceName = faces["helv"]))
@@ -234,10 +181,42 @@
                 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.Bind(wx.EVT_SIZE, self.OnParamsEditorResize)
+            self.ParamsEditor.Bind(wx.EVT_SCROLLWIN, self.OnParamsEditorScroll)
+            
+            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 +226,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,19 +268,17 @@
     
     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()
     
-    def EnableScrolling(self, enable):
-        self.ScrollingEnabled = enable
-    
     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 +299,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)
@@ -484,7 +463,6 @@
                         choices = self.ParentWindow.GetConfigEntry(element_path, [""])
                         textctrl = TextCtrlAutoComplete(name=element_infos["name"], 
                                                         parent=self.ParamsEditor, 
-                                                        appframe=self, 
                                                         choices=choices, 
                                                         element_path=element_path,
                                                         size=wx.Size(300, -1))
@@ -492,7 +470,9 @@
                         boxsizer.AddWindow(textctrl)
                         if element_infos["value"] is not None:
                             textctrl.ChangeValue(str(element_infos["value"]))
-                        textctrl.Bind(wx.EVT_TEXT, self.GetTextCtrlCallBackFunction(textctrl, element_path))
+                        callback = self.GetTextCtrlCallBackFunction(textctrl, element_path)
+                        textctrl.Bind(wx.EVT_TEXT_ENTER, callback)
+                        textctrl.Bind(wx.EVT_KILL_FOCUS, callback)
             first = False
     
     
@@ -502,13 +482,12 @@
             res = self.SetConfNodeParamsAttribute("BaseParams.IEC_Channel", confnode_IECChannel + dir)
             wx.CallAfter(self.RefreshIECChannelControlsState)
             wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE)
-            wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
             event.Skip()
         return OnConfNodeTreeItemChannelChanged
     
     def SetConfNodeParamsAttribute(self, *args, **kwargs):
         res, StructChanged = self.Controler.SetParamsAttribute(*args, **kwargs)
-        if StructChanged:
+        if StructChanged and self.ParamsEditor is not None:
             wx.CallAfter(self.RefreshConfNodeParamsSizer)
         wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU)
         return res
@@ -547,8 +526,8 @@
             if res != textctrl.GetValue():
                 if isinstance(textctrl, wx.SpinCtrl):
                     textctrl.SetValue(res)
-                else:
-                    textctrl.ChangeValue(res)
+                elif res is not None:
+                    textctrl.ChangeValue(str(res))
             if refresh:
                 wx.CallAfter(self.ParentWindow._Refresh, TITLE, FILEMENU, PROJECTTREE, PAGETITLES)
                 wx.CallAfter(self.ParentWindow.SelectProjectTreeItem, self.GetTagName())
@@ -585,11 +564,14 @@
         self.ParamsEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
     
-    def OnWindowResize(self, event):
+    def OnParamsEditorResize(self, event):
         self.RefreshScrollbars()
         event.Skip()
     
-    def OnMouseWheel(self, event):
-        if self.ScrollingEnabled:
-            event.Skip()
-    
+    def OnParamsEditorScroll(self, event):
+        control = self.ParamsEditor.FindFocus()
+        if isinstance(control, TextCtrlAutoComplete):
+            control.DismissListBox()
+            self.Refresh()
+        event.Skip()
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/editors/DebugViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,348 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from threading import Lock, Timer
+from time import time as gettime
+
+import wx
+
+REFRESH_PERIOD = 0.1        # Minimum time between 2 refresh
+DEBUG_REFRESH_LOCK = Lock() # Common refresh lock for all debug viewers
+
+#-------------------------------------------------------------------------------
+#                               Debug Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements common behavior of every viewers able to display debug 
+values
+"""
+
+class DebugViewer:
+    
+    def __init__(self, producer, debug, subscribe_tick=True):
+        """
+        Constructor
+        @param producer: Object receiving debug value and dispatching them to
+        consumers
+        @param debug: Flag indicating that Viewer is debugging
+        @param subscribe_tick: Flag indicating that viewer need tick value to
+        synchronize
+        """
+        self.Debug = debug
+        self.SubscribeTick = subscribe_tick
+        
+        # Flag indicating that consumer value update inhibited
+        # (DebugViewer is refreshing)
+        self.Inhibited = False
+        
+        # List of data consumers subscribed to DataProducer
+        self.DataConsumers = {}
+        
+        # Time stamp indicating when last refresh have been initiated
+        self.LastRefreshTime = gettime()
+        # Flag indicating that DebugViewer has acquire common debug lock
+        self.HasAcquiredLock = False
+        # Lock for access to the two preceding variable
+        self.AccessLock = Lock()
+        
+        # Timer to refresh Debug Viewer one last time in the case that a new
+        # value have been received during refresh was inhibited and no one
+        # after refresh was activated
+        self.LastRefreshTimer = None
+        # Lock for access to the timer
+        self.TimerAccessLock = Lock()
+        
+        # Set DataProducer and subscribe tick if needed
+        self.SetDataProducer(producer)
+        
+    def __del__(self):
+        """
+        Destructor
+        """
+        # Unsubscribe all data consumers
+        self.UnsubscribeAllDataConsumers()
+        
+        # Delete reference to DataProducer
+        self.DataProducer = None
+        
+        # Stop last refresh timer
+        if self.LastRefreshTimer is not None:
+            self.LastRefreshTimer.cancel()
+        
+        # Release Common debug lock if DebugViewer has acquired it
+        if self.HasAcquiredLock:
+            DEBUG_REFRESH_LOCK.release()
+    
+    def SetDataProducer(self, producer):
+        """
+        Set Data Producer
+        @param producer: Data Producer
+        """
+        # In the case that tick need to be subscribed and DebugViewer is
+        # debugging
+        if self.SubscribeTick and self.Debug:
+            
+            # Subscribe tick to new data producer
+            if producer is not None:
+                producer.SubscribeDebugIECVariable("__tick__", self)
+            
+            # Unsubscribe tick from old data producer
+            if getattr(self, "DataProducer", None) is not None:
+                self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
+        
+        # Save new data producer
+        self.DataProducer = producer
+    
+    def IsDebugging(self):
+        """
+        Get flag indicating if Debug Viewer is debugging
+        @return: Debugging flag
+        """
+        return self.Debug
+    
+    def Inhibit(self, inhibit):
+        """
+        Set consumer value update inhibit flag
+        @param inhibit: Inhibit flag
+        """
+        # Inhibit every data consumers in list
+        for consumer, iec_path in self.DataConsumers.iteritems():
+            consumer.Inhibit(inhibit)
+        
+        # Save inhibit flag
+        self.Inhibited = inhibit
+    
+    def AddDataConsumer(self, iec_path, consumer):
+        """
+        Subscribe data consumer to DataProducer
+        @param iec_path: Path in PLC of variable needed by data consumer
+        @param consumer: Data consumer to subscribe
+        @return: List of value already received [(tick, data),...] (None if
+        subscription failed)
+        """
+        # Return immediately if no DataProducer defined
+        if self.DataProducer is None:
+            return None
+        
+        # Subscribe data consumer to DataProducer
+        result = self.DataProducer.SubscribeDebugIECVariable(
+                        iec_path, consumer)
+        if result is not None and consumer != self:
+            
+            # Store data consumer if successfully subscribed and inform
+            # consumer of variable data type
+            self.DataConsumers[consumer] = iec_path
+            consumer.SetDataType(self.GetDataType(iec_path))
+        
+        return result
+    
+    def RemoveDataConsumer(self, consumer):
+        """
+        Unsubscribe data consumer from DataProducer
+        @param consumer: Data consumer to unsubscribe
+        """
+        # Remove consumer from data consumer list
+        iec_path = self.DataConsumers.pop(consumer, None)
+        
+        # Unsubscribe consumer from DataProducer
+        if iec_path is not None:
+            self.DataProducer.UnsubscribeDebugIECVariable(
+                        iec_path, consumer)
+    
+    def SubscribeAllDataConsumers(self):
+        """
+        Called to Subscribe all data consumers contained in DebugViewer.
+        May be overridden by inherited classes.
+        """
+        # Subscribe tick if needed
+        if self.SubscribeTick and self.Debug and self.DataProducer is not None:
+            self.DataProducer.SubscribeDebugIECVariable("__tick__", self)
+    
+    def UnsubscribeAllDataConsumers(self, tick=True):
+        """
+        Called to Unsubscribe all data consumers.
+        """
+        if self.DataProducer is not None:
+            
+            # Unscribe tick if needed
+            if self.SubscribeTick and tick and self.Debug:
+                self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
+            
+            # Unsubscribe all data consumers in list
+            for consumer, iec_path in self.DataConsumers.iteritems():
+                self.DataProducer.UnsubscribeDebugIECVariable(
+                            iec_path, consumer)
+        
+        self.DataConsumers = {}
+    
+    def GetDataType(self, iec_path):
+        """
+        Return variable data type.
+        @param iec_path: Path in PLC of variable
+        @return: variable data type (None if not found)
+        """
+        if self.DataProducer is not None:
+            
+            # Search for variable informations in project compilation files
+            data_type = self.DataProducer.GetDebugIECVariableType(
+                            iec_path.upper())
+            if data_type is not None:
+                return data_type
+            
+            # Search for variable informations in project data
+            infos = self.DataProducer.GetInstanceInfos(iec_path)
+            if infos is not None:
+                return infos["type"]
+        
+        return None
+    
+    def IsNumType(self, data_type):
+        """
+        Indicate if data type given is a numeric data type
+        @param data_type: Data type to test
+        @return: True if data type given is numeric
+        """
+        if self.DataProducer is not None:
+            return self.DataProducer.IsNumType(data_type)
+        
+        return False
+    
+    def ForceDataValue(self, iec_path, value):
+        """
+        Force PLC variable value
+        @param iec_path: Path in PLC of variable to force
+        @param value: Value forced
+        """
+        if self.DataProducer is not None:
+            self.DataProducer.ForceDebugIECVariable(iec_path, value)
+    
+    def ReleaseDataValue(self, iec_path):
+        """
+        Release PLC variable value
+        @param iec_path: Path in PLC of variable to release
+        """
+        if self.DataProducer is not None:
+            self.DataProducer.ReleaseDebugIECVariable(iec_path)
+    
+    def NewDataAvailable(self, tick, *args, **kwargs):
+        """
+        Called by DataProducer for each tick captured
+        @param tick: PLC tick captured
+        All other parameters are passed to refresh function 
+        """
+        # Stop last refresh timer
+        self.TimerAccessLock.acquire()
+        if self.LastRefreshTimer is not None:
+            self.LastRefreshTimer.cancel()
+            self.LastRefreshTimer=None
+        self.TimerAccessLock.release()
+        
+        # Only try to refresh DebugViewer if it is visible on screen and not
+        # already refreshing
+        if self.IsShown() and not self.Inhibited:
+            
+            # Try to get acquire common refresh lock if minimum period between
+            # two refresh has expired
+            if gettime() - self.LastRefreshTime > REFRESH_PERIOD and \
+               DEBUG_REFRESH_LOCK.acquire(False):
+                self.StartRefreshing(*args, **kwargs)
+            
+            # If common lock wasn't acquired for any reason, restart last
+            # refresh timer
+            else:
+                self.StartLastRefreshTimer(*args, **kwargs)
+        
+        # In the case that DebugViewer isn't visible on screen and has already
+        # acquired common refresh lock, reset DebugViewer
+        elif not self.IsShown() and self.HasAcquiredLock:
+            DebugViewer.RefreshNewData(self)
+    
+    def ShouldRefresh(self, *args, **kwargs):
+        """
+        Callback function called when last refresh timer expired
+        All parameters are passed to refresh function
+        """
+        # Cancel if DebugViewer is not visible on screen
+        if self and self.IsShown():
+            
+            # Try to acquire common refresh lock
+            if DEBUG_REFRESH_LOCK.acquire(False):
+                self.StartRefreshing(*args, **kwargs)
+            
+            # Restart last refresh timer if common refresh lock acquired failed
+            else:
+                self.StartLastRefreshTimer(*args, **kwargs)
+    
+    def StartRefreshing(self, *args, **kwargs):
+        """
+        Called to initiate a refresh of DebugViewer
+        All parameters are passed to refresh function
+        """
+        # Update last refresh time stamp and flag for common refresh
+        # lock acquired
+        self.AccessLock.acquire()
+        self.HasAcquiredLock = True
+        self.LastRefreshTime = gettime()
+        self.AccessLock.release()
+        
+        # Inhibit data consumer value update
+        self.Inhibit(True)
+        
+        # Initiate DebugViewer refresh
+        wx.CallAfter(self.RefreshNewData, *args, **kwargs)
+    
+    def StartLastRefreshTimer(self, *args, **kwargs):
+        """
+        Called to start last refresh timer for the minimum time between 2
+        refresh
+        All parameters are passed to refresh function
+        """
+        self.TimerAccessLock.acquire()
+        self.LastRefreshTimer = Timer(
+            REFRESH_PERIOD, self.ShouldRefresh, args, kwargs)
+        self.LastRefreshTimer.start()
+        self.TimerAccessLock.release()
+    
+    def RefreshNewData(self, *args, **kwargs):
+        """
+        Called to refresh DebugViewer according to values received by data
+        consumers
+        May be overridden by inherited classes
+        Can receive any parameters depending on what is needed by inherited
+        class 
+        """
+        if self:
+            # Activate data consumer value update
+            self.Inhibit(False)
+            
+            # Release common refresh lock if acquired and update
+            # last refresh time
+            self.AccessLock.acquire()
+            if self.HasAcquiredLock:
+                DEBUG_REFRESH_LOCK.release()
+                self.HasAcquiredLock = False
+            if gettime() - self.LastRefreshTime > REFRESH_PERIOD:
+                self.LastRefreshTime = gettime()
+            self.AccessLock.release()
--- a/editors/EditorPanel.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/EditorPanel.py	Wed Jul 31 10:45:07 2013 +0900
@@ -39,7 +39,6 @@
     def _init_ctrls(self, parent):
         wx.SplitterWindow.__init__(self, parent,
               style=wx.SUNKEN_BORDER|wx.SP_3D)
-        self.SetNeedUpdating(True)
         self.SetMinimumPaneSize(1)
         
         self._init_MenuItems()
@@ -88,12 +87,6 @@
     def SetIcon(self, icon):
         self.Icon = icon
     
-    def GetState(self):
-        return None
-    
-    def SetState(self, state):
-        pass
-    
     def IsViewing(self, tagname):
         return self.GetTagName() == tagname
 
--- a/editors/GraphicViewer.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/GraphicViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -29,8 +29,9 @@
 import wx.lib.plot as plot
 import wx.lib.buttons
 
-from graphics.GraphicCommons import DebugViewer, MODE_SELECTION, MODE_MOTION
-from EditorPanel import EditorPanel
+from graphics.GraphicCommons import MODE_SELECTION, MODE_MOTION
+from editors.DebugViewer import DebugViewer
+from editors.EditorPanel import EditorPanel
 from util.BitmapLibrary import GetBitmap
 
 colours = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'brown', 'cyan',
--- a/editors/IECCodeViewer.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/IECCodeViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,9 +1,17 @@
 
 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 Paste(self):
+        if self.Controler is not None:
+            TextViewer.Paste(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/LDViewer.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/LDViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -184,17 +184,17 @@
 
     def RefreshView(self, variablepanel=True, selection=None):
         Viewer.RefreshView(self, variablepanel, selection)
-        wx.CallAfter(self.Refresh)
-        for i, rung in enumerate(self.Rungs):
-            bbox = rung.GetBoundingBox()
-            if i < len(self.RungComments):
-                if self.RungComments[i]:
-                    pos = self.RungComments[i].GetPosition()
-                    if pos[1] > bbox.y:
-                        self.RungComments.insert(i, None)
-            else:
-                self.RungComments.insert(i, None)
-    
+        if self.GetDrawingMode() != FREEDRAWING_MODE:
+            for i, rung in enumerate(self.Rungs):
+                bbox = rung.GetBoundingBox()
+                if i < len(self.RungComments):
+                    if self.RungComments[i]:
+                        pos = self.RungComments[i].GetPosition()
+                        if pos[1] > bbox.y:
+                            self.RungComments.insert(i, None)
+                else:
+                    self.RungComments.insert(i, None)
+        
     def loadInstance(self, instance, ids, selection):
         Viewer.loadInstance(self, instance, ids, selection)
         if self.GetDrawingMode() != FREEDRAWING_MODE:
--- a/editors/ProjectNodeEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/ProjectNodeEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -34,9 +34,8 @@
         ConfTreeNodeEditor.__init__(self, parent, controler, window, tagname)
         
         buttons_sizer = self.GenerateMethodButtonSizer()
-        self.ParamsEditorSizer.InsertSizer(0, buttons_sizer, 0, border=5, 
-                flag=wx.LEFT|wx.RIGHT|wx.TOP)
-        self.ParamsEditorSizer.Layout()
+        self.MainSizer.InsertSizer(0, buttons_sizer, 0, border=5, flag=wx.ALL)
+        self.MainSizer.Layout()
         
         self.VariableEditor = self.VariableEditorPanel
 
@@ -56,9 +55,8 @@
     
     def RefreshView(self, variablepanel=True):
         ConfTreeNodeEditor.RefreshView(self)
-        if variablepanel:
-            self.VariableEditor.RefreshView()
-        #self.ProjectProperties.RefreshView()
+        self.VariableEditorPanel.RefreshView()
+        self.ProjectProperties.RefreshView()
 
     def GetBufferState(self):
         return self.Controler.GetBufferState()
--- a/editors/ResourceEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/ResourceEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -140,7 +140,7 @@
                     editor = wx.grid.GridCellTextEditor()
                     renderer = wx.grid.GridCellStringRenderer()
                 elif colname == "Interval":
-                    editor = DurationCellEditor(self)
+                    editor = DurationCellEditor(self, colname)
                     renderer = wx.grid.GridCellStringRenderer()
                     if self.GetValueByName(row, "Triggering") != "Cyclic":
                         grid.SetReadOnly(row, col, True)
@@ -154,7 +154,7 @@
                         grid.SetReadOnly(row, col, True)
                 elif colname == "Triggering":
                     editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(",".join([""] + map(_, GetTaskTriggeringOptions())))
+                    editor.SetParameters(",".join(map(_, GetTaskTriggeringOptions())))
                 elif colname == "Type":
                     editor = wx.grid.GridCellChoiceEditor()
                     editor.SetParameters(self.Parent.TypeList)
--- a/editors/TextViewer.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/TextViewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -31,6 +31,7 @@
 from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
 from plcopen.structures import ST_BLOCK_START_KEYWORDS, ST_BLOCK_END_KEYWORDS, IEC_BLOCK_START_KEYWORDS, IEC_BLOCK_END_KEYWORDS, LOCATIONDATATYPES
 from EditorPanel import EditorPanel
+from controls.CustomStyledTextCtrl import CustomStyledTextCtrl, faces, GetCursorPos, NAVIGATION_KEYS
 
 #-------------------------------------------------------------------------------
 #                         Textual programs Viewer class
@@ -52,20 +53,6 @@
 [ID_TEXTVIEWER, ID_TEXTVIEWERTEXTCTRL,
 ] = [wx.NewId() for _init_ctrls in range(2)]
 
-if wx.Platform == '__WXMSW__':
-    faces = { 'times': 'Times New Roman',
-              'mono' : 'Courier New',
-              'helv' : 'Arial',
-              'other': 'Comic Sans MS',
-              'size' : 10,
-             }
-else:
-    faces = { 'times': 'Times',
-              'mono' : 'Courier',
-              'helv' : 'Helvetica',
-              'other': 'new century schoolbook',
-              'size' : 12,
-             }
 re_texts = {}
 re_texts["letter"] = "[A-Za-z]"
 re_texts["digit"] = "[0-9]"
@@ -79,27 +66,6 @@
     SEARCH_RESULT_HIGHLIGHT: STC_PLC_SEARCH_RESULT,
 }
 
-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
-
 def LineStartswith(line, symbols):
     return reduce(lambda x, y: x or y, map(lambda x: line.startswith(x), symbols), False)
 
@@ -115,7 +81,7 @@
                 event(self, function)
     
     def _init_Editor(self, prnt):
-        self.Editor = wx.stc.StyledTextCtrl(id=ID_TEXTVIEWERTEXTCTRL, 
+        self.Editor = CustomStyledTextCtrl(id=ID_TEXTVIEWERTEXTCTRL, 
                 parent=prnt, name="TextViewer", size=wx.Size(0, 0), style=0)
         self.Editor.ParentWindow = self
         
@@ -175,6 +141,7 @@
 
         self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded, id=ID_TEXTVIEWERTEXTCTRL)
         self.Editor.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
+        self.Editor.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
         self.Editor.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
         if self.Controler is not None:
             self.Editor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
@@ -196,14 +163,13 @@
         self.DisableEvents = True
         self.TextSyntax = None
         self.CurrentAction = None
-        self.Highlights = []
-        self.SearchParams = None
-        self.SearchResults = None
-        self.CurrentFindHighlight = None
+        
         self.InstancePath = instancepath
         self.ContextStack = []
         self.CallStack = []
         
+        self.ResetSearchResults()
+        
         self.RefreshHighlightsTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
         
@@ -247,13 +213,12 @@
     def GetCurrentPos(self):
         return self.Editor.GetCurrentPos()
     
-    def GetState(self):
-        return {"cursor_pos": self.Editor.GetCurrentPos()}
-    
-    def SetState(self, state):
-        if self and state.has_key("cursor_pos"):
-            self.Editor.GotoPos(state.get("cursor_pos", 0))
-        
+    def ResetSearchResults(self):
+        self.Highlights = []
+        self.SearchParams = None
+        self.SearchResults = None
+        self.CurrentFindHighlight = None
+    
     def OnModification(self, event):
         if not self.DisableEvents:
             mod_type = event.GetModificationType()
@@ -452,17 +417,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()
@@ -585,7 +553,8 @@
                 else:
                     self.SetStyling(current_pos - last_styled_pos, 31)
                 last_styled_pos = current_pos
-                state = SPACE
+                if state != DPRAGMA:
+                    state = SPACE
                 line = ""
                 line_number += 1
                 self.RefreshLineFolding(line_number)
@@ -617,7 +586,7 @@
                     state = SPACE
             elif state == DPRAGMA:
                 if line.endswith("}}"):
-                    self.SetStyling(current_pos - last_styled_pos + 2, 31)
+                    self.SetStyling(current_pos - last_styled_pos + 1, 31)
                     last_styled_pos = current_pos + 1
                     state = SPACE
             elif (line.endswith("'") or line.endswith('"')) and state not in [STRING, WSTRING]:
@@ -751,7 +720,13 @@
             if self.Editor.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG:
                 self.Editor.ToggleFold(line)
         event.Skip()
-        
+    
+    def OnUpdateUI(self, event):
+        selected = self.Editor.GetSelectedText()
+        if self.ParentWindow and selected != "":
+            self.ParentWindow.SetCopyBuffer(selected, True)
+        event.Skip()
+    
     def Cut(self):
         self.ResetBuffer()
         self.DisableEvents = True
@@ -762,6 +737,8 @@
     
     def Copy(self):
         self.Editor.CmdKeyExecute(wx.stc.STC_CMD_COPY)
+        if self.ParentWindow:
+            self.ParentWindow.RefreshEditMenu()
     
     def Paste(self):
         self.ResetBuffer()
@@ -771,6 +748,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)
@@ -786,7 +766,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:
@@ -811,13 +792,15 @@
     def RefreshModel(self):
         self.RefreshJumpList()
         self.Controler.SetEditedElementText(self.TagName, self.GetText())
+        self.ResetSearchResults()
     
     def OnKeyDown(self, event):
+        key = event.GetKeyCode()
         if self.Controler is not None:
         
             if self.Editor.CallTipActive():
                 self.Editor.CallTipCancel()
-            key = event.GetKeyCode()
+            
             key_handled = False
 
             line = self.Editor.GetCurrentLine()
@@ -871,6 +854,8 @@
                         key_handled = True
             if not key_handled:
                 event.Skip()
+        elif key in NAVIGATION_KEYS:
+            event.Skip()
 
     def OnKillFocus(self, event):
         self.Editor.AutoCompCancel()
--- a/editors/Viewer.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/editors/Viewer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,7 @@
 
 import re
 import math
-import time
+from time import time as gettime
 from types import TupleType
 from threading import Lock
 
@@ -35,6 +35,7 @@
 
 from dialogs import *
 from graphics import *
+from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
 from EditorPanel import EditorPanel
 
 SCROLLBAR_UNIT = 10
@@ -74,7 +75,11 @@
               'size' : 12,
              }
 
-ZOOM_FACTORS = [math.sqrt(2) ** x for x in xrange(-6, 7)]
+if wx.Platform == '__WXMSW__':
+    MAX_ZOOMIN = 4
+else:
+    MAX_ZOOMIN = 7
+ZOOM_FACTORS = [math.sqrt(2) ** x for x in xrange(-6, MAX_ZOOMIN)]
 
 def GetVariableCreationFunction(variable_type):
     def variableCreationFunction(viewer, id, specific_values):
@@ -266,6 +271,7 @@
                         self.ParentWindow.RefreshScrollBars()
                         self.ParentWindow.RefreshVisibleElements()
                         self.ParentWindow.RefreshVariablePanel()
+                        self.ParentWindow.ParentWindow.RefreshPouInstanceVariablesPanel()
                         self.ParentWindow.Refresh(False)
             elif values[1] == "location":
                 if pou_type == "program":
@@ -313,6 +319,7 @@
                     if not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
                         self.ParentWindow.Controler.AddEditedElementPouExternalVar(tagname, values[2], var_name)
                         self.ParentWindow.RefreshVariablePanel()
+                        self.ParentWindow.ParentWindow.RefreshPouInstanceVariablesPanel()
                     self.ParentWindow.AddVariableBlock(x, y, scaling, INPUT, var_name, values[2])
             elif values[1] == "Constant":
                 self.ParentWindow.AddVariableBlock(x, y, scaling, INPUT, values[0], None)
@@ -371,7 +378,7 @@
 manipulating graphic elements
 """
 
-class Viewer(EditorPanel, DebugViewer, DebugDataConsumer):
+class Viewer(EditorPanel, DebugViewer):
     
     if wx.VERSION < (2, 6, 0):
         def Bind(self, event, function, id = None):
@@ -433,15 +440,18 @@
             (ID_ALIGN_BOTTOM, wx.ITEM_NORMAL, _(u'Bottom'), '', self.OnAlignBottomMenu)])
     
     # Add Wire Menu items to the given menu
-    def AddWireMenuItems(self, menu, delete=False):
-        [ID_ADD_SEGMENT, ID_DELETE_SEGMENT] = [wx.NewId() for i in xrange(2)]
+    def AddWireMenuItems(self, menu, delete=False, replace=False):
+        [ID_ADD_SEGMENT, ID_DELETE_SEGMENT, ID_REPLACE_WIRE,
+         ] = [wx.NewId() for i in xrange(3)]
         
         # Create menu items
         self.AddMenuItems(menu, [
             (ID_ADD_SEGMENT, wx.ITEM_NORMAL, _(u'Add Wire Segment'), '', self.OnAddSegmentMenu),
-            (ID_DELETE_SEGMENT, wx.ITEM_NORMAL, _(u'Delete Wire Segment'), '', self.OnDeleteSegmentMenu)])
-    
+            (ID_DELETE_SEGMENT, wx.ITEM_NORMAL, _(u'Delete Wire Segment'), '', self.OnDeleteSegmentMenu),
+            (ID_REPLACE_WIRE, wx.ITEM_NORMAL, _(u'Replace Wire by connections'), '', self.OnReplaceWireMenu)])
+        
         menu.Enable(ID_DELETE_SEGMENT, delete)
+        menu.Enable(ID_REPLACE_WIRE, replace)
     
     # Add Divergence Menu items to the given menu
     def AddDivergenceMenuItems(self, menu, delete=False):
@@ -552,7 +562,6 @@
         
         EditorPanel.__init__(self, parent, tagname, window, controler, debug)
         DebugViewer.__init__(self, controler, debug)
-        DebugDataConsumer.__init__(self)
         
         # Adding a rubberband to Viewer
         self.rubberBand = RubberBand(viewer=self)
@@ -574,6 +583,12 @@
         self.InstancePath = instancepath
         self.StartMousePos = None
         self.StartScreenPos = None
+        
+        # Prevent search for highlighted element to be called too often
+        self.LastHighlightCheckTime = gettime()
+        # Prevent search for element producing tooltip to be called too often
+        self.LastToolTipCheckTime = gettime()
+        
         self.Buffering = False
         
         # Initialize Cursors
@@ -608,7 +623,7 @@
         self.MiniTextDC.SetFont(wx.Font(faces["size"] * 0.75, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["helv"]))
         
         self.CurrentScale = None
-        self.SetScale(len(ZOOM_FACTORS) / 2, False)
+        self.SetScale(ZOOM_FACTORS.index(1.0), False)
         
         self.RefreshHighlightsTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
@@ -718,18 +733,6 @@
     def GetViewScale(self):
         return self.ViewScale
 
-    def GetState(self):
-        return {"position": self.Editor.GetViewStart(),
-                "zoom": self.CurrentScale}
-
-    def SetState(self, state):
-        if self:
-            if state.has_key("zoom"):
-                self.SetScale(state["zoom"])
-            if state.has_key("position"):
-                self.Scroll(*state["position"])
-            self.RefreshVisibleElements()
-        
     def GetLogicalDC(self, buffered=False):
         if buffered:
             bitmap = wx.EmptyBitmap(*self.Editor.GetClientSize())
@@ -748,7 +751,14 @@
         self.Editor.RefreshRect(rect, eraseBackground)
     
     def Scroll(self, x, y):
+        if self.Debug and wx.Platform == '__WXMSW__':
+            self.Editor.Freeze()
         self.Editor.Scroll(x, y)
+        if self.Debug:
+            if wx.Platform == '__WXMSW__':
+                self.Editor.Thaw()
+            else:
+                self.Editor.Refresh()
     
     def GetScrollPos(self, orientation):
         return self.Editor.GetScrollPos(orientation)
@@ -852,11 +862,13 @@
     def GetElementIECPath(self, element):
         iec_path = None
         instance_path = self.GetInstancePath(True)
-        if isinstance(element, Wire) and element.EndConnected is not None:
-            block = element.EndConnected.GetParentBlock()
+        if isinstance(element, (Wire, Connector)):
+            if isinstance(element, Wire):
+                element = element.EndConnected
+            block = element.GetParentBlock()
             if isinstance(block, FBD_Block):
                 blockname = block.GetName()
-                connectorname = element.EndConnected.GetName()
+                connectorname = element.GetName()
                 if blockname != "":
                     iec_path = "%s.%s.%s"%(instance_path, blockname, connectorname)
                 else:
@@ -898,10 +910,10 @@
         self.ToolTipElement = None
     
     def Flush(self):
-        self.DeleteDataConsumers()
+        self.UnsubscribeAllDataConsumers(tick=False)
         for block in self.Blocks.itervalues():
             block.Flush()
-    
+        
     # Remove all elements
     def CleanView(self):
         for block in self.Blocks.itervalues():
@@ -1001,7 +1013,7 @@
             self.PagePen = wx.TRANSPARENT_PEN
         if refresh:
             self.RefreshVisibleElements()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
         
         
 #-------------------------------------------------------------------------------
@@ -1053,6 +1065,10 @@
             self.RefreshRect(self.GetScrolledRect(refresh_rect), False)
         else:
             DebugViewer.RefreshNewData(self)
+    
+    def SubscribeAllDataConsumers(self):
+        self.RefreshView()
+        DebugViewer.SubscribeAllDataConsumers(self)
         
     # Refresh Viewer elements
     def RefreshView(self, variablepanel=True, selection=None):
@@ -1062,7 +1078,7 @@
             self.AddDataConsumer("%s.Q" % self.InstancePath.upper(), self)
         
         if self.ToolTipElement is not None:
-            self.ToolTipElement.ClearToolTip()
+            self.ToolTipElement.DestroyToolTip()
             self.ToolTipElement = None
         
         self.Inhibit(True)
@@ -1079,6 +1095,12 @@
             instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, exclude = ids, debug = self.Debug)
             if instance is not None:
                 self.loadInstance(instance, ids, selection)
+        
+        if (selection is not None and 
+            isinstance(self.SelectedElement, Graphic_Group)):
+            self.SelectedElement.RefreshWireExclusion()
+            self.SelectedElement.RefreshBoundingBox()
+        
         self.RefreshScrollBars()
         
         for wire in self.Wires:
@@ -1096,14 +1118,21 @@
         if self.Debug:
             for block in self.Blocks.itervalues():
                 block.SpreadCurrent()
-                iec_path = self.GetElementIECPath(block)
-                if iec_path is not None:
-                    self.AddDataConsumer(iec_path.upper(), block)
+                if isinstance(block, FBD_Block):
+                    for output_connector in block.GetConnectors()["outputs"]:
+                        if len(output_connector.GetWires()) == 0:
+                            iec_path = self.GetElementIECPath(output_connector)
+                            if iec_path is not None:
+                                self.AddDataConsumer(iec_path.upper(), output_connector)
+                else:
+                    iec_path = self.GetElementIECPath(block)
+                    if iec_path is not None:
+                        self.AddDataConsumer(iec_path.upper(), block)
 
         self.Inhibit(False)
         self.RefreshVisibleElements()
         self.ShowHighlights()
-        self.Refresh(False)
+        self.Editor.Refresh(False)
     
     def GetPreviousSteps(self, connectors):
         steps = []
@@ -1183,11 +1212,11 @@
         if self.SelectedElement is None:
             self.SelectedElement = element
         elif isinstance(self.SelectedElement, Graphic_Group):
-            self.SelectedElement.SelectElement(element)
+            self.SelectedElement.AddElement(element)
         else:
             group = Graphic_Group(self)
-            group.SelectElement(self.SelectedElement)
-            group.SelectElement(element)
+            group.AddElement(self.SelectedElement)
+            group.AddElement(element)
             self.SelectedElement = group
         
     # Load instance from given informations
@@ -1245,50 +1274,81 @@
         element.SetPosition(instance["x"], instance["y"])
         element.SetSize(instance["width"], instance["height"])
         for i, output_connector in enumerate(instance["outputs"]):
-            if i < len(connectors["outputs"]):
+            if isinstance(element, FBD_Block):
+                connector = element.GetConnector(
+                    wx.Point(*output_connector["position"]),
+                    output_name = output_connector["name"])
+            elif i < len(connectors["outputs"]):
                 connector = connectors["outputs"][i]
+            else:
+                connector = None
+            if connector is not None:
                 if output_connector.get("negated", False):
                     connector.SetNegated(True)
                 if output_connector.get("edge", "none") != "none":
                     connector.SetEdge(output_connector["edge"])
-                connector.SetPosition(wx.Point(*output_connector["position"]))
+                if connectors["outputs"].index(connector) == i:
+                    connector.SetPosition(wx.Point(*output_connector["position"]))
         for i, input_connector in enumerate(instance["inputs"]):
-            if i < len(connectors["inputs"]):
+            if isinstance(element, FBD_Block):
+                connector = element.GetConnector(
+                    wx.Point(*input_connector["position"]),
+                    input_name = input_connector["name"])
+            elif i < len(connectors["inputs"]):
                 connector = connectors["inputs"][i]
-                connector.SetPosition(wx.Point(*input_connector["position"]))
+            else:
+                connector = None
+            if connector is not None:
+                if connectors["inputs"].index(connector) == i:
+                    connector.SetPosition(wx.Point(*input_connector["position"]))
                 if input_connector.get("negated", False):
                     connector.SetNegated(True)
                 if input_connector.get("edge", "none") != "none":
                     connector.SetEdge(input_connector["edge"])
-                self.CreateWires(connector, instance["id"], input_connector["links"], ids, selection)
+                if not self.CreateWires(connector, instance["id"], input_connector["links"], ids, selection):
+                    element.RefreshModel()
+        element.RefreshConnectors()
         if selection is not None and selection[0].get(instance["id"], False):
             self.SelectInGroup(element)
 
     def CreateWires(self, start_connector, id, links, ids, selection=None):
+        links_connected = True
         for link in links:
             refLocalId = link["refLocalId"]
-            if refLocalId is not None:
-                if refLocalId not in ids:
-                    new_instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, refLocalId, debug = self.Debug)
-                    if new_instance is not None:
-                        self.loadInstance(new_instance, ids, selection)
-                connected = self.FindElementById(refLocalId)
-                if connected is not None:
-                    points = link["points"]
-                    end_connector = connected.GetConnector(wx.Point(points[-1][0], points[-1][1]), link["formalParameter"])
-                    if end_connector is not None:
-                        wire = Wire(self)
-                        wire.SetPoints(points)
-                        start_connector.Connect((wire, 0), False)
-                        end_connector.Connect((wire, -1), False)
-                        wire.ConnectStartPoint(None, start_connector)
-                        wire.ConnectEndPoint(None, end_connector)
-                        self.AddWire(wire)
-                        if selection is not None and (\
-                           selection[1].get((id, refLocalId), False) or \
-                           selection[1].get((refLocalId, id), False)):
-                            self.SelectInGroup(wire)
-
+            if refLocalId is None:
+                links_connected = False
+                continue
+            
+            if refLocalId not in ids:
+                new_instance = self.Controler.GetEditedElementInstanceInfos(self.TagName, refLocalId, debug = self.Debug)
+                if new_instance is not None:
+                    self.loadInstance(new_instance, ids, selection)
+            
+            connected = self.FindElementById(refLocalId)
+            if connected is None:
+                links_connected = False
+                continue
+            
+            points = link["points"]
+            end_connector = connected.GetConnector(wx.Point(points[-1][0], points[-1][1]), link["formalParameter"])
+            if end_connector is not None:
+                wire = Wire(self)
+                wire.SetPoints(points)
+                start_connector.Connect((wire, 0), False)
+                end_connector.Connect((wire, -1), False)
+                wire.ConnectStartPoint(None, start_connector)
+                wire.ConnectEndPoint(None, end_connector)
+                connected.RefreshConnectors()
+                self.AddWire(wire)
+                if selection is not None and (\
+                   selection[1].get((id, refLocalId), False) or \
+                   selection[1].get((refLocalId, id), False)):
+                    self.SelectInGroup(wire)
+            else:
+                links_connected = False
+        
+        return links_connected
+                        
     def IsOfType(self, type, reference):
         return self.Controler.IsOfType(type, reference, self.Debug)
     
@@ -1356,8 +1416,7 @@
         if self.SelectedElement is not None:
             self.SelectedElement.SetSelected(False)
         self.SelectedElement = Graphic_Group(self)
-        for element in self.GetElements():
-            self.SelectedElement.SelectElement(element)
+        self.SelectedElement.SetElements(self.GetElements())
         self.SelectedElement.SetSelected(True)
     
 #-------------------------------------------------------------------------------
@@ -1449,7 +1508,18 @@
     
     def PopupWireMenu(self, delete=True):
         menu = wx.Menu(title='')
-        self.AddWireMenuItems(menu, delete)
+        
+        # If Check that wire can be replace by connections or abort
+        connected = self.SelectedElement.GetConnected()
+        start_connector = (
+            self.SelectedElement.GetEndConnected()
+            if self.SelectedElement.GetStartConnected() in connected
+            else self.SelectedElement.GetStartConnected())
+        
+        self.AddWireMenuItems(menu, delete,
+            start_connector.GetDirection() == EAST and 
+            not isinstance(start_connector.GetParentBlock(), SFC_Step))
+        
         menu.AppendSeparator()
         self.AddDefaultMenuItems(menu, block=True)
         self.Editor.PopupMenu(menu)
@@ -1487,37 +1557,37 @@
         if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
             self.SelectedElement.AlignElements(ALIGN_LEFT, None)
             self.RefreshBuffer()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
     
     def OnAlignCenterMenu(self, event):
         if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
             self.SelectedElement.AlignElements(ALIGN_CENTER, None)
             self.RefreshBuffer()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
     
     def OnAlignRightMenu(self, event):
         if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
             self.SelectedElement.AlignElements(ALIGN_RIGHT, None)
             self.RefreshBuffer()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
     
     def OnAlignTopMenu(self, event):
         if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
             self.SelectedElement.AlignElements(None, ALIGN_TOP)
             self.RefreshBuffer()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
     
     def OnAlignMiddleMenu(self, event):
         if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
             self.SelectedElement.AlignElements(None, ALIGN_MIDDLE)
             self.RefreshBuffer()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
     
     def OnAlignBottomMenu(self, event):
         if self.SelectedElement is not None and isinstance(self.SelectedElement, Graphic_Group):
             self.SelectedElement.AlignElements(None, ALIGN_BOTTOM)
             self.RefreshBuffer()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
         
     def OnNoModifierMenu(self, event):
         if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
@@ -1552,7 +1622,111 @@
         if self.SelectedElement is not None and self.IsWire(self.SelectedElement):
             self.SelectedElement.DeleteSegment()
             self.SelectedElement.Refresh()
-
+    
+    def OnReplaceWireMenu(self, event):
+        # Check that selected element is a wire before applying replace
+        if (self.SelectedElement is not None and 
+            self.IsWire(self.SelectedElement)):
+            
+            # Get wire redraw bbox to erase it from screen
+            wire = self.SelectedElement
+            redraw_rect = wire.GetRedrawRect()
+            
+            # Get connector at both ends of wire
+            connected = wire.GetConnected()
+            if wire.GetStartConnected() in connected:
+                start_connector = wire.GetEndConnected()
+                end_connector = wire.GetStartConnected()
+                wire.UnConnectStartPoint()
+                point_to_connect = 0
+            else:
+                start_connector = wire.GetStartConnected()
+                end_connector = wire.GetEndConnected()
+                wire.UnConnectEndPoint()
+                point_to_connect = -1
+            
+            # Get a new default connection name
+            connection_name = self.Controler.GenerateNewName(
+                    self.TagName, None, "Connection%d", 0)
+            
+            # Create a connector to connect to wire
+            id = self.GetNewId()
+            connection = FBD_Connector(self, CONNECTOR, connection_name, id)
+            connection.SetSize(*self.GetScaledSize(*connection.GetMinSize()))
+            
+            # Calculate position of connector at the right of start connector 
+            connector = connection.GetConnectors()["inputs"][0]
+            rel_pos = connector.GetRelPosition()
+            direction = connector.GetDirection()
+            start_point = start_connector.GetPosition(False)
+            end_point = (start_point[0] + LD_WIRE_SIZE, start_point[1])
+            connection.SetPosition(end_point[0] - rel_pos[0], 
+                                   end_point[1] - rel_pos[1])
+            
+            # Connect connector to wire
+            connector.Connect((wire, point_to_connect))
+            if point_to_connect == 0:
+                wire.SetPoints([end_point, start_point])
+            else:
+                wire.SetPoints([start_point, end_point])
+            # Update redraw bbox with new wire trace so that it will be redraw
+            # on screen
+            redraw_rect.Union(wire.GetRedrawRect())
+            
+            # Add connector to Viewer and model
+            self.AddBlock(connection)
+            self.Controler.AddEditedElementConnection(self.TagName, id, 
+                                                      CONNECTOR)
+            connection.RefreshModel()
+            # Update redraw bbox with new connector bbox so that it will be
+            # drawn on screen
+            redraw_rect.Union(connection.GetRedrawRect())
+            
+            # Add new continuation
+            id = self.GetNewId()
+            connection = FBD_Connector(self, CONTINUATION, connection_name, id)
+            connection.SetSize(*self.GetScaledSize(*connection.GetMinSize()))
+            
+            # Calculate position of connection at the left of end connector
+            connector = connection.GetConnectors()["outputs"][0]
+            rel_pos = connector.GetRelPosition()
+            direction = connector.GetDirection()
+            end_point = end_connector.GetPosition(False)
+            start_point = (end_point[0] - LD_WIRE_SIZE, end_point[1])
+            connection.SetPosition(start_point[0] - rel_pos[0], 
+                                   start_point[1] - rel_pos[1])
+            
+            # Add Wire to Viewer and connect it to blocks
+            new_wire = Wire(self, 
+                [wx.Point(*start_point), connector.GetDirection()], 
+                [wx.Point(*end_point), end_connector.GetDirection()])
+            self.AddWire(new_wire)
+            connector.Connect((new_wire, 0), False)
+            end_connector.Connect((new_wire, -1), False)
+            new_wire.ConnectStartPoint(None, connector)
+            new_wire.ConnectEndPoint(None, end_connector)
+            # Update redraw bbox with new wire bbox so that it will be drawn on
+            # screen
+            redraw_rect.Union(new_wire.GetRedrawRect())
+            
+            # Add connection to Viewer and model
+            self.AddBlock(connection)
+            self.Controler.AddEditedElementConnection(self.TagName, id, 
+                                                      CONTINUATION)
+            connection.RefreshModel()
+            # Update redraw bbox with new connection bbox so that it will be
+            # drawn on screen
+            redraw_rect.Union(connection.GetRedrawRect())
+            
+            # Refresh model for new wire
+            end_connector.RefreshParentBlock()
+            
+            # Redraw 
+            self.RefreshBuffer()
+            self.RefreshScrollBars()
+            self.RefreshVisibleElements()
+            self.RefreshRect(self.GetScrolledRect(redraw_rect), False)
+            
     def OnAddBranchMenu(self, event):
         if self.SelectedElement is not None and self.IsBlock(self.SelectedElement):
             self.AddDivergenceBranch(self.SelectedElement)
@@ -1577,7 +1751,7 @@
             self.SelectedElement.Delete()
             self.SelectedElement = None
             self.RefreshBuffer()
-            self.Refresh(False)
+            self.Editor.Refresh(False)
 
     def OnClearExecutionOrderMenu(self, event):
         self.Controler.ClearEditedElementExecutionOrder(self.TagName)
@@ -1594,6 +1768,12 @@
             wx.CallAfter(func, self.rubberBand.GetCurrentExtent(), *args)
         return AddMenuCallBack
 
+    def GetAddToWireMenuCallBack(self, func, *args):
+        args += (self.SelectedElement,)
+        def AddToWireMenuCallBack(event):
+            func(wx.Rect(0, 0, 0, 0), *args)
+        return AddToWireMenuCallBack
+
     def GetClipboardCallBack(self, func):
         def ClipboardCallback(event):
             wx.CallAfter(func)
@@ -1604,23 +1784,32 @@
 #-------------------------------------------------------------------------------
 
     def OnViewerMouseEvent(self, event):
-        if not event.Entering():
-            self.ResetBuffer()
+        self.ResetBuffer()
+        if event.Leaving() and self.ToolTipElement is not None:
+            self.ToolTipElement.DestroyToolTip()
+        elif (not event.Entering() and
+            gettime() - self.LastToolTipCheckTime > REFRESH_PERIOD):
+            self.LastToolTipCheckTime = gettime()
             element = None
             if not event.Leaving() and not event.LeftUp() and not event.LeftDClick():
-                element = self.FindElement(event, True, False)
+                dc = self.GetLogicalDC()
+                pos = event.GetLogicalPosition(dc)
+                element = self.FindBlockConnector(pos)
+                if element is None or len(element.GetWires()) > 0:
+                    element = self.FindElement(event, True, False)
             if self.ToolTipElement is not None:
-                self.ToolTipElement.ClearToolTip()
+                self.ToolTipElement.DestroyToolTip()
             self.ToolTipElement = element
             if self.ToolTipElement is not None:
                 tooltip_pos = self.Editor.ClientToScreen(event.GetPosition())
                 tooltip_pos.x += 10
                 tooltip_pos.y += 10
-                self.ToolTipElement.CreateToolTip(tooltip_pos)
+                self.ToolTipElement.DisplayToolTip(tooltip_pos)
         event.Skip()
     
     def OnViewerLeftDown(self, event):
         self.Editor.CaptureMouse()
+        self.StartMousePos = event.GetPosition()
         if self.Mode == MODE_SELECTION:
             dc = self.GetLogicalDC()
             pos = event.GetLogicalPosition(dc)
@@ -1674,10 +1863,15 @@
                 elif not self.Debug and connector is not None and not event.ControlDown():
                     self.DrawingWire = True
                     scaled_pos = GetScaledEventPosition(event, dc, self.Scaling)
-                    if (connector.GetDirection() == EAST):
-                        wire = Wire(self, [wx.Point(pos.x, pos.y), EAST], [wx.Point(scaled_pos.x, scaled_pos.y), WEST])
-                    else:
-                        wire = Wire(self, [wx.Point(pos.x, pos.y), WEST], [wx.Point(scaled_pos.x, scaled_pos.y), EAST])
+                    directions = {
+                        EAST: [EAST, WEST],
+                        WEST: [WEST, EAST],
+                        NORTH: [NORTH, SOUTH],
+                        SOUTH: [SOUTH, NORTH]}[connector.GetDirection()]
+                    wire = Wire(self, *map(list, zip(
+                                           [wx.Point(pos.x, pos.y), 
+                                            wx.Point(scaled_pos.x, scaled_pos.y)],
+                                           directions)))
                     wire.oldPos = scaled_pos
                     wire.Handle = (HANDLE_POINT, 0)
                     wire.ProcessDragging(0, 0, event, None)
@@ -1691,6 +1885,7 @@
                     self.HighlightedElement = wire
                     self.RefreshVisibleElements()
                     self.SelectedElement.SetHighlighted(True)
+                    self.SelectedElement.StartConnected.HighlightParentBlock(True)
                 else:
                     if self.SelectedElement is not None and self.SelectedElement != element:
                         self.SelectedElement.SetSelected(False)
@@ -1698,7 +1893,6 @@
                     if element is not None:
                         self.SelectedElement = element
                         if self.Debug:
-                            self.StartMousePos = event.GetPosition()
                             Graphic_Element.OnLeftDown(self.SelectedElement, event, dc, self.Scaling)
                         else:
                             self.SelectedElement.OnLeftDown(event, dc, self.Scaling)
@@ -1712,7 +1906,6 @@
             self.rubberBand.Reset()
             self.rubberBand.OnLeftDown(event, self.GetLogicalDC(), self.Scaling)
         elif self.Mode == MODE_MOTION:
-            self.StartMousePos = event.GetPosition()
             self.StartScreenPos = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL)
         event.Skip()
 
@@ -1783,14 +1976,72 @@
                     self.SelectedElement.HighlightPoint(pos)
                     self.RefreshBuffer()
                 elif connector is None or self.SelectedElement.GetDragging():
-                    self.DrawingWire = False
-                    rect = self.SelectedElement.GetRedrawRect()
-                    wire = self.SelectedElement
-                    self.SelectedElement = self.SelectedElement.StartConnected.GetParentBlock()
-                    self.SelectedElement.SetSelected(True)
-                    rect.Union(self.SelectedElement.GetRedrawRect())
-                    wire.Delete()
-                    self.RefreshRect(self.GetScrolledRect(rect), False)
+                    start_connector = self.SelectedElement.GetStartConnected()
+                    start_direction = start_connector.GetDirection()
+                    
+                    items = []
+                    
+                    if self.CurrentLanguage == "SFC" and start_direction == SOUTH:
+                        items.extend([
+                            (_(u'Initial Step'), self.GetAddToWireMenuCallBack(self.AddNewStep, True)),
+                            (_(u'Step'), self.GetAddToWireMenuCallBack(self.AddNewStep, False)),
+                            (_(u'Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, False)),
+                            (_(u'Divergence'), self.GetAddToWireMenuCallBack(self.AddNewDivergence)),
+                            (_(u'Jump'), self.GetAddToWireMenuCallBack(self.AddNewJump)),
+                        ])
+                    
+                    elif start_direction == EAST:
+                        
+                        if isinstance(start_connector.GetParentBlock(), SFC_Step):
+                            items.append(
+                                (_(u'Action Block'), self.GetAddToWireMenuCallBack(self.AddNewActionBlock))
+                            )
+                        else:
+                            items.extend([
+                                (_(u'Block'), self.GetAddToWireMenuCallBack(self.AddNewBlock)),
+                                (_(u'Variable'), self.GetAddToWireMenuCallBack(self.AddNewVariable, True)),
+                                (_(u'Connection'), self.GetAddToWireMenuCallBack(self.AddNewConnection)),
+                            ])
+                            
+                            if self.CurrentLanguage != "FBD":
+                                items.append(
+                                    (_(u'Contact'), self.GetAddToWireMenuCallBack(self.AddNewContact))
+                                )
+                            if self.CurrentLanguage == "LD":
+                                items.extend([
+                                    (_(u'Coil'), self.GetAddToWireMenuCallBack(self.AddNewCoil)),
+                                    (_(u'Power Rail'), self.GetAddToWireMenuCallBack(self.AddNewPowerRail)),
+                                ])
+                            if self.CurrentLanguage == "SFC":
+                                items.append(
+                                    (_(u'Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, True))
+                                )
+                                
+                    if len(items) > 0:
+                        if self.Editor.HasCapture():
+                            self.Editor.ReleaseMouse()
+                        
+                        # Popup contextual menu
+                        menu = wx.Menu()
+                        self.AddMenuItems(menu, 
+                            [(wx.NewId(), wx.ITEM_NORMAL, text, '', callback)
+                             for text, callback in items])
+                        self.PopupMenu(menu)
+                    
+                    self.SelectedElement.StartConnected.HighlightParentBlock(False)
+                    if self.DrawingWire:
+                        self.DrawingWire = False
+                        rect = self.SelectedElement.GetRedrawRect()
+                        wire = self.SelectedElement
+                        self.SelectedElement = self.SelectedElement.StartConnected.GetParentBlock()
+                        self.SelectedElement.SetSelected(True)
+                        rect.Union(self.SelectedElement.GetRedrawRect())
+                        wire.Delete()
+                        self.RefreshRect(self.GetScrolledRect(rect), False)
+                    else:
+                        self.SelectedElement.SetSelected(True)
+                        if not self.SelectedElement.IsConnectedCompatible():
+                            self.SelectedElement.SetValid(False)
             else:
                 if self.Debug:
                     Graphic_Element.OnLeftUp(self.SelectedElement, event, dc, self.Scaling)
@@ -1798,7 +2049,6 @@
                     self.SelectedElement.OnLeftUp(event, dc, self.Scaling)
                 wx.CallAfter(self.SetCurrentCursor, 0)
         elif self.Mode == MODE_MOTION:
-            self.StartMousePos = None
             self.StartScreenPos = None
         if self.Mode != MODE_SELECTION and not self.SavedMode:
             wx.CallAfter(self.ParentWindow.ResetCurrentMode)
@@ -1853,7 +2103,7 @@
         event.Skip()
     
     def OnViewerLeftDClick(self, event):
-        element = self.FindElement(event, connectors=False)
+        element = self.FindElement(event)
         if self.Mode == MODE_SELECTION and element is not None:
             if self.SelectedElement is not None and self.SelectedElement != element:
                 self.SelectedElement.SetSelected(False)
@@ -1866,15 +2116,24 @@
             
             if self.Debug:
                 if isinstance(self.SelectedElement, FBD_Block):
-                    instance_type = self.SelectedElement.GetType()
-                    pou_type = {
-                        "program": ITEM_PROGRAM,
-                        "functionBlock": ITEM_FUNCTIONBLOCK,
-                    }.get(self.Controler.GetPouType(instance_type))
-                    if pou_type is not None and instance_type in self.Controler.GetProjectPouNames(self.Debug):
-                        self.ParentWindow.OpenDebugViewer(pou_type, 
-                            "%s.%s"%(self.GetInstancePath(True), self.SelectedElement.GetName()),
-                            self.Controler.ComputePouName(instance_type))
+                    dc = self.GetLogicalDC()
+                    pos = event.GetLogicalPosition(dc)
+                    connector = self.SelectedElement.TestConnector(pos, EAST)
+                    if connector is not None and len(connector.GetWires()) == 0:
+                        iec_path = self.GetElementIECPath(connector)
+                        if iec_path is not None:
+                            self.ParentWindow.OpenDebugViewer(
+                                ITEM_VAR_LOCAL, iec_path, connector.GetType())
+                    else:
+                        instance_type = self.SelectedElement.GetType()
+                        pou_type = {
+                            "program": ITEM_PROGRAM,
+                            "functionBlock": ITEM_FUNCTIONBLOCK,
+                        }.get(self.Controler.GetPouType(instance_type))
+                        if pou_type is not None and instance_type in self.Controler.GetProjectPouNames(self.Debug):
+                            self.ParentWindow.OpenDebugViewer(pou_type, 
+                                "%s.%s"%(self.GetInstancePath(True), self.SelectedElement.GetName()),
+                                self.Controler.ComputePouName(instance_type))
                 else:
                     iec_path = self.GetElementIECPath(self.SelectedElement)
                     if iec_path is not None:
@@ -1925,13 +2184,15 @@
                     self.RefreshScrollBars()
                 self.RefreshVisibleElements()
         else:
-            if not event.Dragging():
+            if (not event.Dragging() and
+                gettime() - self.LastHighlightCheckTime > REFRESH_PERIOD):
+                self.LastHighlightCheckTime = gettime()
                 highlighted = self.FindElement(event, connectors=False) 
                 if self.HighlightedElement is not None and self.HighlightedElement != highlighted:
                     self.HighlightedElement.SetHighlighted(False)
                     self.HighlightedElement = None
                 if highlighted is not None:
-                    if isinstance(highlighted, (Wire, Graphic_Group)):
+                    if not self.Debug and isinstance(highlighted, (Wire, Graphic_Group)):
                         highlighted.HighlightPoint(pos)
                     if self.HighlightedElement != highlighted:
                         highlighted.SetHighlighted(True)
@@ -1947,16 +2208,25 @@
                         self.SelectedElement.GeneratePoints()
                         if movex != 0 or movey != 0:
                             self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
-                    else:
+                    elif not self.Debug:
                         self.SelectedElement.HighlightPoint(pos)
                 else:
                     movex, movey = self.SelectedElement.OnMotion(event, dc, self.Scaling)
                     if movex != 0 or movey != 0:
                         self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
+                    self.RefreshVisibleElements()
             elif self.Debug and self.StartMousePos is not None and event.Dragging():
                 pos = event.GetPosition()
                 if abs(self.StartMousePos.x - pos.x) > 5 or abs(self.StartMousePos.y - pos.y) > 5:
-                    iec_path = self.GetElementIECPath(self.SelectedElement)
+                    element = self.SelectedElement
+                    if isinstance(self.SelectedElement, FBD_Block):
+                        dc = self.GetLogicalDC()
+                        connector = self.SelectedElement.TestConnector(
+                            wx.Point(dc.DeviceToLogicalX(self.StartMousePos.x), 
+                                     dc.DeviceToLogicalY(self.StartMousePos.y)))
+                        if connector is not None:
+                            element = connector
+                    iec_path = self.GetElementIECPath(element)
                     if iec_path is not None:
                         self.StartMousePos = None
                         if self.HighlightedElement is not None:
@@ -1973,8 +2243,6 @@
         event.Skip()
 
     def OnLeaveViewer(self, event):
-        if self.StartScreenPos is None:
-            self.StartMousePos = None
         if self.SelectedElement is not None and self.SelectedElement.GetDragging():
             event.Skip()
         elif self.HighlightedElement is not None:
@@ -2053,6 +2321,7 @@
                 self.StartBuffering()
                 self.SelectedElement.RefreshModel()
                 self.RefreshScrollBars()
+                self.RefreshVisibleElements()
                 self.RefreshRect(self.GetScrolledRect(self.SelectedElement.GetRedrawRect(movex, movey)), False)
         elif not self.Debug and keycode == wx.WXK_SPACE and self.SelectedElement is not None and self.SelectedElement.Dragging:
             if self.IsBlock(self.SelectedElement) or self.IsComment(self.SelectedElement):
@@ -2099,7 +2368,7 @@
         self.RefreshBuffer()
         self.RefreshScrollBars()
         self.RefreshVisibleElements()
-        self.Refresh(False)
+        self.Editor.Refresh(False)
 
 #-------------------------------------------------------------------------------
 #                          Model adding functions
@@ -2111,13 +2380,39 @@
             height = round(float(height) / float(self.Scaling[1]) + 0.4) * self.Scaling[1]
         return width, height
     
-    def AddNewBlock(self, bbox):
-        dialog = FBDBlockDialog(self.ParentWindow, self.Controler)
+    def AddNewElement(self, element, bbox, wire=None, connector=None):
+        min_width, min_height = (element.GetMinSize(True)
+                                 if isinstance(element, (LD_PowerRail,
+                                                         SFC_Divergence))
+                                 else element.GetMinSize())
+        element.SetSize(*self.GetScaledSize(
+            max(bbox.width, min_width), max(bbox.height, min_height)))
+        if wire is not None:
+            if connector is None:
+                connector = element.GetConnectors()["inputs"][0]
+            point = wire.GetPoint(-1)
+            rel_pos = connector.GetRelPosition()
+            direction = connector.GetDirection()
+            element.SetPosition(
+                point[0] - rel_pos[0] - direction[0] * CONNECTOR_SIZE,
+                point[1] - rel_pos[1] - direction[1] * CONNECTOR_SIZE,
+            )
+            connector.Connect((wire, -1))
+            wire.Refresh()
+            self.DrawingWire = False
+        else:
+            element.SetPosition(bbox.x, bbox.y)
+        self.AddBlock(element)
+        element.RefreshModel()
+        self.RefreshBuffer()
+        self.RefreshScrollBars()
+        self.RefreshVisibleElements()
+        element.Refresh()
+    
+    def AddNewBlock(self, bbox, wire=None):
+        dialog = FBDBlockDialog(self.ParentWindow, self.Controler, self.TagName)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetBlockList(self.Controler.GetBlockTypes(self.TagName, self.Debug))
-        dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
-        dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
-        dialog.SetMinBlockSize((bbox.width, bbox.height))
+        dialog.SetMinElementSize((bbox.width, bbox.height))
         if dialog.ShowModal() == wx.ID_OK:
             id = self.GetNewId()
             values = dialog.GetValues()
@@ -2126,78 +2421,54 @@
                     values["extension"], values["inputs"], 
                     executionControl = values["executionControl"],
                     executionOrder = values["executionOrder"])
-            block.SetPosition(bbox.x, bbox.y)
-            block.SetSize(*self.GetScaledSize(values["width"], values["height"]))
-            self.AddBlock(block)
             self.Controler.AddEditedElementBlock(self.TagName, id, values["type"], values.get("name", None))
-            self.RefreshBlockModel(block)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            self.RefreshVariablePanel()
-            self.ParentWindow.RefreshPouInstanceVariablesPanel()
-            block.Refresh()
+            connector = None
+            for input_connector in block.GetConnectors()["inputs"]:
+                if input_connector.IsCompatible(
+                        wire.GetStartConnectedType()):
+                    connector = input_connector
+                    break
+            self.AddNewElement(block, bbox, wire, connector)
         dialog.Destroy()
     
-    def AddNewVariable(self, bbox):
-        words = self.TagName.split("::")
-        if words[0] == "T":
-            dialog = FBDVariableDialog(self.ParentWindow, self.Controler, words[2])
-        else:
-            dialog = FBDVariableDialog(self.ParentWindow, self.Controler)
+    def AddNewVariable(self, bbox, exclude_input=False, wire=None):
+        dialog = FBDVariableDialog(self.ParentWindow, self.Controler, self.TagName, exclude_input)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetMinVariableSize((bbox.width, bbox.height))
-        varlist = []
-        vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
-        if vars:
-            for var in vars:
-                if var["Edit"]:
-                    varlist.append((var["Name"], var["Class"], var["Type"]))
-        returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
-        if returntype:
-            varlist.append((self.Controler.GetEditedElementName(self.TagName), "Output", returntype))
-        dialog.SetVariables(varlist)
+        dialog.SetMinElementSize((bbox.width, bbox.height))
         if dialog.ShowModal() == wx.ID_OK:
             id = self.GetNewId()
             values = dialog.GetValues()
-            variable = FBD_Variable(self, values["type"], values["name"], values["value_type"], id)
-            variable.SetPosition(bbox.x, bbox.y)
-            variable.SetSize(*self.GetScaledSize(values["width"], values["height"]))
-            self.AddBlock(variable)
-            self.Controler.AddEditedElementVariable(self.TagName, id, values["type"])
-            self.RefreshVariableModel(variable)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            variable.Refresh()
+            variable = FBD_Variable(self, values["class"], values["expression"], values["var_type"], id)
+            variable.SetExecutionOrder(values["executionOrder"])
+            self.Controler.AddEditedElementVariable(self.TagName, id, values["class"])
+            self.AddNewElement(variable, bbox, wire)
         dialog.Destroy()
 
-    def AddNewConnection(self, bbox):
-        dialog = ConnectionDialog(self.ParentWindow, self.Controler)
-        dialog.SetPreviewFont(self.GetFont())
-        dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
-        dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
-        dialog.SetMinConnectionSize((bbox.width, bbox.height))
-        if dialog.ShowModal() == wx.ID_OK:
+    def AddNewConnection(self, bbox, wire=None):
+        if wire is not None:
+            values = {
+                "type": CONNECTOR,
+                "name": self.Controler.GenerateNewName(
+                    self.TagName, None, "Connection%d", 0)}
+        else:
+            dialog = ConnectionDialog(self.ParentWindow, self.Controler, self.TagName)
+            dialog.SetPreviewFont(self.GetFont())
+            dialog.SetMinElementSize((bbox.width, bbox.height))
+            values = (dialog.GetValues()
+                      if dialog.ShowModal() == wx.ID_OK
+                      else None)
+            dialog.Destroy()
+        if values is not None:
             id = self.GetNewId()
-            values = dialog.GetValues()
             connection = FBD_Connector(self, values["type"], values["name"], id)
-            connection.SetPosition(bbox.x, bbox.y)
-            connection.SetSize(*self.GetScaledSize(values["width"], values["height"]))
-            self.AddBlock(connection)
             self.Controler.AddEditedElementConnection(self.TagName, id, values["type"])
-            self.RefreshConnectionModel(connection)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            connection.Refresh()
-        dialog.Destroy()
-
+            self.AddNewElement(connection, bbox, wire)
+    
     def AddNewComment(self, bbox):
-        if wx.VERSION >= (2, 5, 0):
-            dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), "", wx.OK|wx.CANCEL|wx.TE_MULTILINE)
-        else:
-            dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), "", wx.OK|wx.CANCEL)
+        dialog = wx.TextEntryDialog(self.ParentWindow, 
+                                    _("Edit comment"), 
+                                    _("Please enter comment text"), 
+                                    "", wx.OK|wx.CANCEL|wx.TE_MULTILINE)
         dialog.SetClientSize(wx.Size(400, 200))
         if dialog.ShowModal() == wx.ID_OK:
             value = dialog.GetValue()
@@ -2215,158 +2486,112 @@
             comment.Refresh()
         dialog.Destroy()
 
-    def AddNewContact(self, bbox):
-        dialog = LDElementDialog(self.ParentWindow, self.Controler, "contact")
+    def AddNewContact(self, bbox, wire=None):
+        dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "contact")
         dialog.SetPreviewFont(self.GetFont())
-        varlist = []
-        vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
-        if vars:
-            for var in vars:
-                if var["Type"] == "BOOL":
-                    varlist.append(var["Name"])
-        dialog.SetVariables(varlist)
-        dialog.SetValues({"name":"","type":CONTACT_NORMAL})
-        dialog.SetElementSize((bbox.width, bbox.height))
+        dialog.SetMinElementSize((bbox.width, bbox.height))
         if dialog.ShowModal() == wx.ID_OK:
             id = self.GetNewId()
             values = dialog.GetValues()
-            contact = LD_Contact(self, values["type"], values["name"], id)
-            contact.SetPosition(bbox.x, bbox.y)
-            contact.SetSize(*self.GetScaledSize(values["width"], values["height"]))
-            self.AddBlock(contact)
+            contact = LD_Contact(self, values["modifier"], values["variable"], id)
             self.Controler.AddEditedElementContact(self.TagName, id)
-            self.RefreshContactModel(contact)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            contact.Refresh()
+            self.AddNewElement(contact, bbox, wire)
         dialog.Destroy()
 
-    def AddNewCoil(self, bbox):
-        dialog = LDElementDialog(self.ParentWindow, self.Controler, "coil")
+    def AddNewCoil(self, bbox, wire=None):
+        dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "coil")
         dialog.SetPreviewFont(self.GetFont())
-        varlist = []
-        vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
-        if vars:
-            for var in vars:
-                if var["Class"] != "Input" and var["Type"] == "BOOL":
-                    varlist.append(var["Name"])
-        returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
-        if returntype == "BOOL":
-            varlist.append(self.Controler.GetEditedElementName(self.TagName))
-        dialog.SetVariables(varlist)
-        dialog.SetValues({"name":"","type":COIL_NORMAL})
-        dialog.SetElementSize((bbox.width, bbox.height))
+        dialog.SetMinElementSize((bbox.width, bbox.height))
         if dialog.ShowModal() == wx.ID_OK:
             id = self.GetNewId()
             values = dialog.GetValues()
-            coil = LD_Coil(self, values["type"], values["name"], id)
-            coil.SetPosition(bbox.x, bbox.y)
-            coil.SetSize(*self.GetScaledSize(values["width"], values["height"]))
-            self.AddBlock(coil)
+            coil = LD_Coil(self, values["modifier"], values["variable"], id)
             self.Controler.AddEditedElementCoil(self.TagName, id)
-            self.RefreshCoilModel(coil)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            coil.Refresh()
+            self.AddNewElement(coil, bbox, wire)
         dialog.Destroy()
 
-    def AddNewPowerRail(self, bbox):
-        dialog = LDPowerRailDialog(self.ParentWindow, self.Controler)
+    def AddNewPowerRail(self, bbox, wire=None):
+        if wire is not None:
+            values = {
+                "type": RIGHTRAIL,
+                "pin_number": 1}
+        else:
+            dialog = LDPowerRailDialog(self.ParentWindow, self.Controler, self.TagName)
+            dialog.SetPreviewFont(self.GetFont())
+            dialog.SetMinElementSize((bbox.width, bbox.height))
+            values = (dialog.GetValues()
+                      if dialog.ShowModal() == wx.ID_OK
+                      else None)
+            dialog.Destroy()
+        if values is not None:
+            id = self.GetNewId()
+            powerrail = LD_PowerRail(self, values["type"], id, values["pin_number"])
+            self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
+            self.AddNewElement(powerrail, bbox, wire)
+
+    def AddNewStep(self, bbox, initial=False, wire=None):
+        if wire is not None:
+            values = {
+                "name": self.Controler.GenerateNewName(
+                    self.TagName, None, "Step%d", 0),
+                "input": True,
+                "output": False,
+                "action":False}
+        else:
+            dialog = SFCStepDialog(self.ParentWindow, self.Controler, self.TagName, initial)
+            dialog.SetPreviewFont(self.GetFont())
+            dialog.SetMinElementSize((bbox.width, bbox.height))
+            values = (dialog.GetValues()
+                      if dialog.ShowModal() == wx.ID_OK
+                      else None)
+            dialog.Destroy()
+        if values is not None:
+            id = self.GetNewId()
+            step = SFC_Step(self, values["name"], initial, id)
+            self.Controler.AddEditedElementStep(self.TagName, id)
+            for connector in ["input", "output", "action"]:
+                getattr(step, ("Add" 
+                               if values[connector] 
+                               else "Remove") + connector.capitalize())()
+            self.AddNewElement(step, bbox, wire)
+    
+    def AddNewTransition(self, bbox, connection=False, wire=None):
+        if wire is not None and connection:
+            values = {
+                "type": "connection",
+                "value": None,
+                "priority": 0}
+        else:
+            dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.TagName, self.GetDrawingMode() == FREEDRAWING_MODE)
+            dialog.SetPreviewFont(self.GetFont())
+            dialog.SetMinElementSize((bbox.width, bbox.height))
+            values = (dialog.GetValues()
+                      if dialog.ShowModal() == wx.ID_OK
+                      else None)
+            dialog.Destroy()
+        if values is not None:
+            id = self.GetNewId()
+            transition = SFC_Transition(self, values["type"], values["value"], values["priority"], id)
+            self.Controler.AddEditedElementTransition(self.TagName, id)
+            if connection:
+                connector = transition.GetConditionConnector()
+            else:
+                connector = transition.GetConnectors()["inputs"][0]
+            self.AddNewElement(transition, bbox, wire, connector)
+    
+    def AddNewDivergence(self, bbox, wire=None):
+        dialog = SFCDivergenceDialog(self.ParentWindow, self.Controler, self.TagName)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetMinSize((bbox.width, bbox.height))
-        if dialog.ShowModal() == wx.ID_OK:
-            id = self.GetNewId()
-            values = dialog.GetValues()
-            powerrail = LD_PowerRail(self, values["type"], id, values["number"])
-            powerrail.SetPosition(bbox.x, bbox.y)
-            powerrail.SetSize(*self.GetScaledSize(values["width"], values["height"]))
-            self.AddBlock(powerrail)
-            self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
-            self.RefreshPowerRailModel(powerrail)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            powerrail.Refresh()
-        dialog.Destroy()
-
-    def AddNewStep(self, bbox, initial = False):
-        dialog = SFCStepDialog(self.ParentWindow, self.Controler, initial)
-        dialog.SetPreviewFont(self.GetFont())
-        dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
-        dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
-        dialog.SetStepNames([block.GetName() for block in self.Blocks.itervalues() if isinstance(block, SFC_Step)])
-        dialog.SetMinStepSize((bbox.width, bbox.height))
-        if dialog.ShowModal() == wx.ID_OK:
-            id = self.GetNewId()
-            values = dialog.GetValues()
-            step = SFC_Step(self, values["name"], initial, id)
-            if values["input"]:
-                step.AddInput()
-            else:
-                step.RemoveInput()
-            if values["output"]:
-                step.AddOutput()
-            else:
-                step.RemoveOutput()
-            if values["action"]:
-                step.AddAction()    
-            else:
-                step.RemoveAction()
-            step.SetPosition(bbox.x, bbox.y)
-            min_width, min_height = step.GetMinSize()
-            step.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
-            self.AddBlock(step)
-            self.Controler.AddEditedElementStep(self.TagName, id)
-            self.RefreshStepModel(step)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            step.Refresh()
-        dialog.Destroy()
-
-    def AddNewTransition(self, bbox):
-        dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.GetDrawingMode() == FREEDRAWING_MODE)
-        dialog.SetPreviewFont(self.GetFont())
-        dialog.SetTransitions(self.Controler.GetEditedElementTransitions(self.TagName, self.Debug))
-        if dialog.ShowModal() == wx.ID_OK:
-            id = self.GetNewId()
-            values = dialog.GetValues()
-            transition = SFC_Transition(self, values["type"], values["value"], values["priority"], id)
-            transition.SetPosition(bbox.x, bbox.y)
-            min_width, min_height = transition.GetMinSize()
-            transition.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
-            self.AddBlock(transition)
-            self.Controler.AddEditedElementTransition(self.TagName, id)
-            self.RefreshTransitionModel(transition)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            transition.Refresh()
-        dialog.Destroy()
-
-    def AddNewDivergence(self, bbox):
-        dialog = SFCDivergenceDialog(self.ParentWindow, self.Controler)
-        dialog.SetPreviewFont(self.GetFont())
-        dialog.SetMinSize((bbox.width, bbox.height))
+        dialog.SetMinElementSize((bbox.width, bbox.height))
         if dialog.ShowModal() == wx.ID_OK:
             id = self.GetNewId()
             values = dialog.GetValues()
             divergence = SFC_Divergence(self, values["type"], values["number"], id)
-            divergence.SetPosition(bbox.x, bbox.y)
-            min_width, min_height = divergence.GetMinSize(True)
-            divergence.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
-            self.AddBlock(divergence)
             self.Controler.AddEditedElementDivergence(self.TagName, id, values["type"])
-            self.RefreshDivergenceModel(divergence)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            divergence.Refresh()
+            self.AddNewElement(divergence, bbox, wire)
         dialog.Destroy()
 
-    def AddNewJump(self, bbox):
+    def AddNewJump(self, bbox, wire=None):
         choices = []
         for block in self.Blocks.itervalues():
             if isinstance(block, SFC_Step):
@@ -2376,39 +2601,21 @@
               choices, wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
         if dialog.ShowModal() == wx.ID_OK:
             id = self.GetNewId()
-            value = dialog.GetStringSelection()
-            jump = SFC_Jump(self, value, id)
-            jump.SetPosition(bbox.x, bbox.y)
-            min_width, min_height = jump.GetMinSize()
-            jump.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
-            self.AddBlock(jump)
+            jump = SFC_Jump(self, dialog.GetStringSelection(), id)
             self.Controler.AddEditedElementJump(self.TagName, id)
-            self.RefreshJumpModel(jump)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            jump.Refresh()
+            self.AddNewElement(jump, bbox, wire)
         dialog.Destroy()
 
-    def AddNewActionBlock(self, bbox):
+    def AddNewActionBlock(self, bbox, wire=None):
         dialog = ActionBlockDialog(self.ParentWindow)
         dialog.SetQualifierList(self.Controler.GetQualifierTypes())
         dialog.SetActionList(self.Controler.GetEditedElementActions(self.TagName, self.Debug))
         dialog.SetVariableList(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
         if dialog.ShowModal() == wx.ID_OK:
-            actions = dialog.GetValues()
             id = self.GetNewId()
-            actionblock = SFC_ActionBlock(self, actions, id)
-            actionblock.SetPosition(bbox.x, bbox.y)
-            min_width, min_height = actionblock.GetMinSize()
-            actionblock.SetSize(*self.GetScaledSize(max(bbox.width, min_width), max(bbox.height, min_height)))
-            self.AddBlock(actionblock)
+            actionblock = SFC_ActionBlock(self, dialog.GetValues(), id)
             self.Controler.AddEditedElementActionBlock(self.TagName, id)
-            self.RefreshActionBlockModel(actionblock)
-            self.RefreshBuffer()
-            self.RefreshScrollBars()
-            self.RefreshVisibleElements()
-            actionblock.Refresh()
+            self.AddNewElement(actionblock, bbox, wire)
         dialog.Destroy()
 
 #-------------------------------------------------------------------------------
@@ -2416,15 +2623,9 @@
 #-------------------------------------------------------------------------------
 
     def EditBlockContent(self, block):
-        dialog = FBDBlockDialog(self.ParentWindow, self.Controler)
+        dialog = FBDBlockDialog(self.ParentWindow, self.Controler, self.TagName)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetBlockList(self.Controler.GetBlockTypes(self.TagName, self.Debug))
-        dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
-        variable_names = self.Controler.GetEditedElementVariables(self.TagName, self.Debug)
-        if block.GetName() != "":
-            variable_names.remove(block.GetName())
-        dialog.SetPouElementNames(variable_names)
-        dialog.SetMinBlockSize(block.GetSize())
+        dialog.SetMinElementSize(block.GetSize())
         old_values = {"name" : block.GetName(), 
                       "type" : block.GetType(), 
                       "extension" : block.GetExtension(), 
@@ -2456,38 +2657,24 @@
         dialog.Destroy()
 
     def EditVariableContent(self, variable):
-        words = self.TagName.split("::")
-        if words[0] == "T":
-            dialog = FBDVariableDialog(self.ParentWindow, self.Controler, words[2])
-        else:
-            dialog = FBDVariableDialog(self.ParentWindow, self.Controler)
+        dialog = FBDVariableDialog(self.ParentWindow, self.Controler, self.TagName)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetMinVariableSize(variable.GetSize())
-        varlist = []
-        vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
-        if vars:
-            for var in vars:
-                if var["Edit"]:
-                    varlist.append((var["Name"], var["Class"], var["Type"]))
-        returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
-        if returntype:
-            varlist.append((self.Controler.GetEditedElementName(self.TagName), "Output", returntype))
-        dialog.SetVariables(varlist)
-        old_values = {"name" : variable.GetName(), "type" : variable.GetType(), 
-            "executionOrder" : variable.GetExecutionOrder()}
+        dialog.SetMinElementSize(variable.GetSize())
+        old_values = {"expression" : variable.GetName(), "class" : variable.GetType(), 
+                      "executionOrder" : variable.GetExecutionOrder()}
         dialog.SetValues(old_values)
         if dialog.ShowModal() == wx.ID_OK:
             new_values = dialog.GetValues()
             rect = variable.GetRedrawRect(1, 1)
-            variable.SetName(new_values["name"])
-            variable.SetType(new_values["type"], new_values["value_type"])
+            variable.SetName(new_values["expression"])
+            variable.SetType(new_values["class"], new_values["var_type"])
             variable.SetSize(*self.GetScaledSize(new_values["width"], new_values["height"]))
             variable.SetExecutionOrder(new_values["executionOrder"])
             rect = rect.Union(variable.GetRedrawRect())
-            if old_values["type"] != new_values["type"]:
+            if old_values["class"] != new_values["class"]:
                 id = variable.GetId()
                 self.Controler.RemoveEditedElementInstance(self.TagName, id)
-                self.Controler.AddEditedElementVariable(self.TagName, id, new_values["type"])
+                self.Controler.AddEditedElementVariable(self.TagName, id, new_values["class"])
             self.RefreshVariableModel(variable)
             self.RefreshBuffer()
             if old_values["executionOrder"] != new_values["executionOrder"]:
@@ -2499,11 +2686,9 @@
         dialog.Destroy()
 
     def EditConnectionContent(self, connection):
-        dialog = ConnectionDialog(self.ParentWindow, self.Controler, True)
+        dialog = ConnectionDialog(self.ParentWindow, self.Controler, self.TagName, True)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
-        dialog.SetPouElementNames(self.Controler.GetEditedElementVariables(self.TagName, self.Debug))
-        dialog.SetMinConnectionSize(connection.GetSize())
+        dialog.SetMinElementSize(connection.GetSize())
         values = {"name" : connection.GetName(), "type" : connection.GetType()}
         dialog.SetValues(values)
         result = dialog.ShowModal()
@@ -2533,23 +2718,16 @@
                 connection.Refresh(rect)
         
     def EditContactContent(self, contact):
-        dialog = LDElementDialog(self.ParentWindow, self.Controler, "contact")
+        dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "contact")
         dialog.SetPreviewFont(self.GetFont())
-        varlist = []
-        vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
-        if vars:
-            for var in vars:
-                if var["Type"] == "BOOL":
-                    varlist.append(var["Name"])
-        dialog.SetVariables(varlist)
-        values = {"name" : contact.GetName(), "type" : contact.GetType()}
-        dialog.SetValues(values)
-        dialog.SetElementSize(contact.GetSize())
+        dialog.SetMinElementSize(contact.GetSize())
+        dialog.SetValues({"variable" : contact.GetName(), 
+                          "modifier" : contact.GetType()})
         if dialog.ShowModal() == wx.ID_OK:
             values = dialog.GetValues()
             rect = contact.GetRedrawRect(1, 1)
-            contact.SetName(values["name"])
-            contact.SetType(values["type"])
+            contact.SetName(values["variable"])
+            contact.SetType(values["modifier"])
             contact.SetSize(*self.GetScaledSize(values["width"], values["height"]))
             rect = rect.Union(contact.GetRedrawRect())
             self.RefreshContactModel(contact)
@@ -2560,26 +2738,16 @@
         dialog.Destroy()
 
     def EditCoilContent(self, coil):
-        dialog = LDElementDialog(self.ParentWindow, self.Controler, "coil")
+        dialog = LDElementDialog(self.ParentWindow, self.Controler, self.TagName, "coil")
         dialog.SetPreviewFont(self.GetFont())
-        varlist = []
-        vars = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
-        if vars:
-            for var in vars:
-                if var["Class"] != "Input" and var["Type"] == "BOOL":
-                    varlist.append(var["Name"])
-        returntype = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, self.Debug)
-        if returntype == "BOOL":
-            varlist.append(self.Controler.GetEditedElementName(self.TagName))
-        dialog.SetVariables(varlist)
-        values = {"name" : coil.GetName(), "type" : coil.GetType()}
-        dialog.SetValues(values)
-        dialog.SetElementSize(coil.GetSize())
+        dialog.SetMinElementSize(coil.GetSize())
+        dialog.SetValues({"variable" : coil.GetName(), 
+                          "modifier" : coil.GetType()})
         if dialog.ShowModal() == wx.ID_OK:
             values = dialog.GetValues()
             rect = coil.GetRedrawRect(1, 1)
-            coil.SetName(values["name"])
-            coil.SetType(values["type"])
+            coil.SetName(values["variable"])
+            coil.SetType(values["modifier"])
             coil.SetSize(*self.GetScaledSize(values["width"], values["height"]))
             rect = rect.Union(coil.GetRedrawRect())
             self.RefreshCoilModel(coil)
@@ -2590,23 +2758,21 @@
         dialog.Destroy()
 
     def EditPowerRailContent(self, powerrail):
-        connectors = powerrail.GetConnectors()
-        type = powerrail.GetType()
-        if type == LEFTRAIL:
-            pin_number = len(connectors["outputs"])
-        else:
-            pin_number = len(connectors["inputs"])
-        dialog = LDPowerRailDialog(self.ParentWindow, self.Controler, type, pin_number)
+        dialog = LDPowerRailDialog(self.ParentWindow, self.Controler, self.TagName)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetMinSize(powerrail.GetSize())
+        dialog.SetMinElementSize(powerrail.GetSize())
+        powerrail_type = powerrail.GetType()
+        dialog.SetValues({
+            "type": powerrail.GetType(),
+            "pin_number": len(powerrail.GetConnectors()[
+                    ("outputs" if powerrail_type == LEFTRAIL else "inputs")])})
         if dialog.ShowModal() == wx.ID_OK:
-            old_type = powerrail.GetType()
             values = dialog.GetValues()
             rect = powerrail.GetRedrawRect(1, 1)
-            powerrail.SetType(values["type"], values["number"])
+            powerrail.SetType(values["type"], values["pin_number"])
             powerrail.SetSize(*self.GetScaledSize(values["width"], values["height"]))
             rect = rect.Union(powerrail.GetRedrawRect())
-            if old_type != values["type"]:
+            if powerrail_type != values["type"]:
                 id = powerrail.GetId()
                 self.Controler.RemoveEditedElementInstance(self.TagName, id)
                 self.Controler.AddEditedElementPowerRail(self.TagName, id, values["type"])
@@ -2618,18 +2784,15 @@
         dialog.Destroy()
 
     def EditStepContent(self, step):
-        dialog = SFCStepDialog(self.ParentWindow, self.Controler, step.GetInitial())
+        dialog = SFCStepDialog(self.ParentWindow, self.Controler, self.TagName, step.GetInitial())
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetPouNames(self.Controler.GetProjectPouNames(self.Debug))
-        dialog.SetVariables(self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug))
-        dialog.SetStepNames([block.GetName() for block in self.Blocks.itervalues() if isinstance(block, SFC_Step) and block.GetName() != step.GetName()])
-        dialog.SetMinStepSize(step.GetSize())
-        values = {"name" : step.GetName()}
+        dialog.SetMinElementSize(step.GetSize())
         connectors = step.GetConnectors()
-        values["input"] = len(connectors["inputs"]) > 0
-        values["output"] = len(connectors["outputs"]) > 0
-        values["action"] = step.GetActionConnector() != None
-        dialog.SetValues(values)
+        dialog.SetValues({
+            "name" : step.GetName(),
+            "input": len(connectors["inputs"]) > 0,
+            "output": len(connectors["outputs"]) > 0,
+            "action": step.GetActionConnector() != None})
         if dialog.ShowModal() == wx.ID_OK:
             values = dialog.GetValues()
             rect = step.GetRedrawRect(1, 1)
@@ -2655,11 +2818,10 @@
             step.Refresh(rect)
         
     def EditTransitionContent(self, transition):
-        dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.GetDrawingMode() == FREEDRAWING_MODE)
+        dialog = SFCTransitionDialog(self.ParentWindow, self.Controler, self.TagName, self.GetDrawingMode() == FREEDRAWING_MODE)
         dialog.SetPreviewFont(self.GetFont())
-        dialog.SetTransitions(self.Controler.GetEditedElementTransitions(self.TagName, self.Debug))
+        dialog.SetMinElementSize(transition.GetSize())
         dialog.SetValues({"type":transition.GetType(),"value":transition.GetCondition(), "priority":transition.GetPriority()})
-        dialog.SetElementSize(transition.GetSize())
         if dialog.ShowModal() == wx.ID_OK:
             values = dialog.GetValues()
             rect = transition.GetRedrawRect(1, 1)
@@ -2714,10 +2876,11 @@
         dialog.Destroy()
 
     def EditCommentContent(self, comment):
-        if wx.VERSION >= (2, 5, 0):
-            dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), comment.GetContent(), wx.OK|wx.CANCEL|wx.TE_MULTILINE)
-        else:
-            dialog = wx.TextEntryDialog(self.ParentWindow, _("Edit comment"), _("Please enter comment text"), comment.GetContent(), wx.OK|wx.CANCEL)
+        dialog = wx.TextEntryDialog(self.ParentWindow, 
+                                    _("Edit comment"), 
+                                    _("Please enter comment text"), 
+                                    comment.GetContent(), 
+                                    wx.OK|wx.CANCEL|wx.TE_MULTILINE)
         dialog.SetClientSize(wx.Size(400, 200))
         if dialog.ShowModal() == wx.ID_OK:
             value = dialog.GetValue()
@@ -3091,7 +3254,11 @@
             if blocktype is None:
                 blocktype = "Block"
             format = "%s%%d" % blocktype
-        return self.Controler.GenerateNewName(self.TagName, None, format, exclude, self.Debug)
+        return self.Controler.GenerateNewName(self.TagName, 
+                                              None, 
+                                              format, 
+                                              exclude=exclude, 
+                                              debug=self.Debug)
 
     def IsNamedElement(self, element):
         return isinstance(element, FBD_Block) and element.GetName() != "" or isinstance(element, SFC_Step)
@@ -3178,6 +3345,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:
@@ -3245,15 +3413,27 @@
 #-------------------------------------------------------------------------------
 
     def OnScrollWindow(self, event):
-        if self.Editor.HasCapture() and self.StartMousePos:
+        if self.Editor.HasCapture() and self.StartMousePos is not None:
             return
         if wx.Platform == '__WXMSW__':
             wx.CallAfter(self.RefreshVisibleElements)
+            self.Editor.Freeze()
+            wx.CallAfter(self.Editor.Thaw)
         elif event.GetOrientation() == wx.HORIZONTAL:
             self.RefreshVisibleElements(xp = event.GetPosition())
         else:
             self.RefreshVisibleElements(yp = event.GetPosition())
-        event.Skip()
+        
+        # Handle scroll in debug to fully redraw area and ensuring
+        # instance path is fully draw without flickering
+        if self.Debug and wx.Platform != '__WXMSW__':
+            x, y = self.GetViewStart()
+            if event.GetOrientation() == wx.HORIZONTAL:
+                self.Scroll(event.GetPosition(), y)
+            else:
+                self.Scroll(x, event.GetPosition())
+        else:
+            event.Skip()
 
     def OnScrollStop(self, event):
         self.RefreshScrollBars()
@@ -3276,7 +3456,7 @@
                 yp = max(0, min(y - rotation * 3, self.Editor.GetVirtualSize()[1] / self.Editor.GetScrollPixelsPerUnit()[1]))
                 self.RefreshVisibleElements(yp = yp)
                 self.Scroll(x, yp)
-        
+            
     def OnMoveWindow(self, event):
         self.RefreshScrollBars()
         self.RefreshVisibleElements()
@@ -3334,21 +3514,28 @@
         
         if not printing:
             if self.Debug:
+                scalex, scaley = dc.GetUserScale()
+                dc.SetUserScale(1, 1)
+                
                 is_action = self.TagName.split("::")[0] == "A"
                 text = _("Debug: %s") % self.InstancePath
                 if is_action and self.Value is not None:
                     text += " ("
-                dc.DrawText(text, 2, 2)
+                text_offset_x, text_offset_y = self.CalcUnscrolledPosition(2, 2)
+                dc.DrawText(text, text_offset_x, text_offset_y)
                 if is_action and self.Value is not None:
                     value_text = self.VALUE_TRANSLATION[self.Value]
                     tw, th = dc.GetTextExtent(text)
                     if self.Value:
                         dc.SetTextForeground(wx.GREEN)
-                    dc.DrawText(value_text, tw + 2, 2)
+                    dc.DrawText(value_text, text_offset_x + tw, text_offset_y)
                     if self.Value:
                         dc.SetTextForeground(wx.BLACK)
                     vw, vh = dc.GetTextExtent(value_text)
-                    dc.DrawText(")", tw + vw + 4, 2)
+                    dc.DrawText(")", text_offset_x + tw + vw + 2, text_offset_y)
+                
+                dc.SetUserScale(scalex, scaley)
+                
             if self.rubberBand.IsShown():
                 self.rubberBand.Draw(dc)
             dc.EndDrawing()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/DebugDataConsumer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import datetime
+
+#-------------------------------------------------------------------------------
+#                        Date and Time conversion function
+#-------------------------------------------------------------------------------
+
+SECOND = 1000000      # Number of microseconds in one second
+MINUTE = 60 * SECOND  # Number of microseconds in one minute
+HOUR = 60 * MINUTE    # Number of microseconds in one hour
+DAY = 24 * HOUR       # Number of microseconds in one day
+
+# Date corresponding to Epoch (1970 January the first)
+DATE_ORIGIN = datetime.datetime(1970, 1, 1)
+
+def get_microseconds(value):
+    """
+    Function converting time duration expressed in day, second and microseconds
+    into one expressed in microseconds
+    @param value: Time duration to convert
+    @return: Time duration expressed in microsecond
+    """
+    return float(value.days * DAY + \
+                 value.seconds * SECOND + \
+                 value.microseconds)
+    return 
+
+def generate_time(value):
+    """
+    Function converting time duration expressed in day, second and microseconds
+    into a IEC 61131 TIME literal
+    @param value: Time duration to convert
+    @return: IEC 61131 TIME literal
+    """
+    microseconds = get_microseconds(value)
+    
+    # Get absolute microseconds value and save if it was negative
+    negative = microseconds < 0
+    microseconds = abs(microseconds)
+    
+    # TIME literal prefix
+    data = "T#"
+    if negative:
+        data += "-"
+    
+    # In TIME literal format, it isn't mandatory to indicate null values
+    # if no greater non-null values are available. This variable is used to
+    # inhibit formatting until a non-null value is found
+    not_null = False
+    
+    for val, format in [
+            (int(microseconds) / DAY, "%dd"),                # Days
+            ((int(microseconds) % DAY) / HOUR, "%dh"),       # Hours
+            ((int(microseconds) % HOUR) / MINUTE, "%dm"),    # Minutes
+            ((int(microseconds) % MINUTE) / SECOND, "%ds")]: # Seconds
+        
+        # Add value to TIME literal if value is non-null or another non-null 
+        # value have already be found
+        if val > 0 or not_null:
+            data += format % val
+            
+            # Update non-null variable
+            not_null = True
+    
+    # In any case microseconds have to be added to TIME literal 
+    data += "%gms" % (microseconds % SECOND / 1000.)
+    
+    return data
+
+def generate_date(value):
+    """
+    Function converting time duration expressed in day, second and microseconds
+    into a IEC 61131 DATE literal
+    @param value: Time duration to convert
+    @return: IEC 61131 DATE literal
+    """
+    return (DATE_ORIGIN + value).strftime("DATE#%Y-%m-%d")
+
+def generate_datetime(value):
+    """
+    Function converting time duration expressed in day, second and microseconds
+    into a IEC 61131 DATE_AND_TIME literal
+    @param value: Time duration to convert
+    @return: IEC 61131 DATE_AND_TIME literal
+    """
+    return (DATE_ORIGIN + value).strftime("DT#%Y-%m-%d-%H:%M:%S.%f")
+
+def generate_timeofday(value):
+    """
+    Function converting time duration expressed in day, second and microseconds
+    into a IEC 61131 TIME_OF_DAY literal
+    @param value: Time duration to convert
+    @return: IEC 61131 TIME_OF_DAY literal
+    """
+    microseconds = get_microseconds(value)
+    
+    # TIME_OF_DAY literal prefix
+    data = "TOD#"
+    
+    for val, format in [
+            (int(microseconds) / HOUR, "%2.2d:"),               # Hours
+            ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"),    # Minutes
+            ((int(microseconds) % MINUTE) / SECOND, "%2.2d."),  # Seconds
+            (microseconds % SECOND, "%6.6d")]:                  # Microseconds
+        
+        # Add value to TIME_OF_DAY literal
+        data += format % val
+    
+    return data
+
+# Dictionary of translation functions from value send by debugger to IEC 
+# literal stored by type
+TYPE_TRANSLATOR = {
+    "TIME": generate_time,
+    "DATE": generate_date,
+    "DT": generate_datetime,
+    "TOD": generate_timeofday,
+    "STRING": lambda v: "'%s'" % v,
+    "WSTRING": lambda v: '"%s"' % v,
+    "REAL": lambda v: "%.6g" % v,
+    "LREAL": lambda v: "%.6g" % v}
+
+#-------------------------------------------------------------------------------
+#                            Debug Data Consumer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements an element that consumes debug values
+Value update can be inhibited during the time the associated Debug Viewer is
+refreshing
+"""
+
+class DebugDataConsumer:
+    
+    def __init__(self):
+        """
+        Constructor
+        """
+        # Debug value and forced flag
+        self.Value = None
+        self.Forced = False
+        
+        # Store debug value and forced flag when value update is inhibited
+        self.LastValue = None
+        self.LastForced = False
+        
+        # Value IEC data type
+        self.DataType = None
+        
+        # Flag that value update is inhibited
+        self.Inhibited = False
+    
+    def Inhibit(self, inhibit):
+        """
+        Set flag to inhibit or activate value update
+        @param inhibit: Inhibit flag
+        """
+        # Save inhibit flag
+        self.Inhibited = inhibit
+        
+        # When reactivated update value and forced flag with stored values
+        if not inhibit and self.LastValue is not None:
+            self.SetForced(self.LastForced)
+            self.SetValue(self.LastValue)
+            
+            # Reset stored values
+            self.LastValue = None
+            self.LastForced = False
+    
+    def SetDataType(self, data_type):
+        """
+        Set value IEC data type
+        @param data_type: Value IEC data type
+        """
+        self.DataType = data_type
+    
+    def NewValue(self, tick, value, forced=False, raw="BOOL"):
+        """
+        Function called by debug thread when a new debug value is available
+        @param tick: PLC tick when value was captured
+        @param value: Value captured
+        @param forced: Forced flag, True if value is forced (default: False)
+        @param raw: Data type of values not translated (default: 'BOOL')
+        """
+        # Translate value to IEC literal
+        if self.DataType != raw:
+            value = TYPE_TRANSLATOR.get(self.DataType, str)(value)
+        
+        # Store value and forced flag when value update is inhibited
+        if self.Inhibited:
+            self.LastValue = value
+            self.LastForced = forced
+        
+        # Update value and forced flag in any other case
+        else:
+            self.SetForced(forced)
+            self.SetValue(value)
+    
+    def SetValue(self, value):
+        """
+        Update value.
+        May be overridden by inherited classes
+        @param value: New value
+        """
+        self.Value = value
+    
+    def GetValue(self):
+        """
+        Return current value
+        @return: Current value
+        """
+        return self.Value
+    
+    def SetForced(self, forced):
+        """
+        Update Forced flag.
+        May be overridden by inherited classes
+        @param forced: New forced flag
+        """
+        self.Forced = forced
+    
+    def IsForced(self):
+        """
+        Indicate if current value is forced
+        @return: Current forced flag
+        """
+        return self.Forced
--- a/graphics/FBD_Objects.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/FBD_Objects.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,7 @@
 
 import wx
 
-from GraphicCommons import *
+from graphics.GraphicCommons import *
 from plcopen.structures import *
 
 #-------------------------------------------------------------------------------
@@ -179,17 +179,20 @@
             output.MoveConnected(exclude)
     
     # Returns the block connector that starts with the point given if it exists 
-    def GetConnector(self, position, name = None):
-        # if a name is given
-        if name is not None:
-            # Test each input and output connector
-            #for input in self.Inputs:
-            #    if name == input.GetName():
-            #        return input
+    def GetConnector(self, position, output_name = None, input_name = None):
+        if input_name is not None:
+            # Test each input connector
+            for input in self.Inputs:
+                if input_name == input.GetName():
+                    return input
+        if output_name is not None:
+            # Test each output connector
             for output in self.Outputs:
-                if name == output.GetName():
+                if output_name == output.GetName():
                     return output
-        return self.FindNearestConnector(position, self.Inputs + self.Outputs)
+        if input_name is None and output_name is None:
+            return self.FindNearestConnector(position, self.Inputs + self.Outputs)
+        return None
         
     def GetInputTypes(self):
         return tuple([input.GetType(True) for input in self.Inputs if input.GetName() != "EN"])
@@ -263,43 +266,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 Mar 13 12:34:55 2013 +0900
+++ b/graphics/GraphicCommons.py	Wed Jul 31 10:45:07 2013 +0900
@@ -23,12 +23,14 @@
 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 import wx
-from time import time as gettime
 from math import *
 from types import *
 import datetime
 from threading import Lock,Timer
 
+from graphics.ToolTipProducer import ToolTipProducer
+from graphics.DebugDataConsumer import DebugDataConsumer
+
 #-------------------------------------------------------------------------------
 #                               Common constants
 #-------------------------------------------------------------------------------
@@ -102,9 +104,6 @@
 # Define highlight refresh inhibition period in second
 REFRESH_HIGHLIGHT_PERIOD = 0.1
 
-# Define tooltip wait for displaying period in second
-TOOLTIP_WAIT_PERIOD = 0.5
-
 HANDLE_CURSORS = {
     (1, 1) : 2,
     (3, 3) : 2,
@@ -187,56 +186,6 @@
         return dir_target
     return v_base
 
-SECOND = 1000000
-MINUTE = 60 * SECOND
-HOUR = 60 * MINUTE
-DAY = 24 * HOUR
-
-def generate_time(value):
-    microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
-    negative = microseconds < 0
-    microseconds = abs(microseconds)
-    data = "T#"
-    not_null = False
-    if negative:
-        data += "-"
-    for val, format in [(int(microseconds) / DAY, "%dd"),
-                        ((int(microseconds) % DAY) / HOUR, "%dh"),
-                        ((int(microseconds) % HOUR) / MINUTE, "%dm"),
-                        ((int(microseconds) % MINUTE) / SECOND, "%ds")]:
-        if val > 0 or not_null:
-            data += format % val
-            not_null = True
-    data += "%gms" % (microseconds % SECOND / 1000.)
-    return data
-
-def generate_date(value):
-    base_date = datetime.datetime(1970, 1, 1)
-    date = base_date + value 
-    return date.strftime("DATE#%Y-%m-%d")
-
-def generate_datetime(value):
-    base_date = datetime.datetime(1970, 1, 1)
-    date_time = base_date + value 
-    return date_time.strftime("DT#%Y-%m-%d-%H:%M:%S.%f")
-
-def generate_timeofday(value):
-    microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
-    negative = microseconds < 0
-    microseconds = abs(microseconds)
-    data = "TOD#"
-    for val, format in [(int(microseconds) / HOUR, "%2.2d:"),
-                        ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"),
-                        ((int(microseconds) % MINUTE) / SECOND, "%2.2d."),
-                        (microseconds % SECOND, "%6.6d")]:
-        data += format % val
-    return data
-
-TYPE_TRANSLATOR = {"TIME": generate_time,
-                   "DATE": generate_date,
-                   "DT": generate_datetime,
-                   "TOD": generate_timeofday}
-
 def MiterPen(colour, width=1, style=wx.SOLID):
     pen = wx.Pen(colour, width, style)
     pen.SetJoin(wx.JOIN_MITER)
@@ -244,413 +193,6 @@
     return pen
 
 #-------------------------------------------------------------------------------
-#                            Debug Data Consumer Class
-#-------------------------------------------------------------------------------
-
-class DebugDataConsumer:
-    
-    def __init__(self):
-        self.LastValue = None
-        self.Value = None
-        self.DataType = None
-        self.LastForced = False
-        self.Forced = False
-        self.Inhibited = False
-    
-    def Inhibit(self, inhibit):
-        self.Inhibited = inhibit
-        if not inhibit and self.LastValue is not None:
-            self.SetForced(self.LastForced)
-            self.SetValue(self.LastValue)
-            self.LastValue = None
-    
-    def SetDataType(self, data_type):
-        self.DataType = data_type
-    
-    def NewValue(self, tick, value, forced=False):
-        value = TYPE_TRANSLATOR.get(self.DataType, lambda x:x)(value)
-        if self.Inhibited:
-            self.LastValue = value
-            self.LastForced = forced
-        else:
-            self.SetForced(forced)
-            self.SetValue(value)
-    
-    def SetValue(self, value):
-        self.Value = value
-    
-    def GetValue(self):
-        return self.Value
-    
-    def SetForced(self, forced):
-        self.Forced = forced
-    
-    def IsForced(self):
-        return self.Forced
-
-#-------------------------------------------------------------------------------
-#                               Debug Viewer Class
-#-------------------------------------------------------------------------------
-
-REFRESH_PERIOD = 0.1
-DEBUG_REFRESH_LOCK = Lock()
-
-class DebugViewer:
-    
-    def __init__(self, producer, debug, register_tick=True):
-        self.DataProducer = None
-        self.Debug = debug
-        self.RegisterTick = register_tick
-        self.Inhibited = False
-        
-        self.DataConsumers = {}
-        
-        self.LastRefreshTime = gettime()
-        self.HasAcquiredLock = False
-        self.AccessLock = Lock()
-        self.TimerAccessLock = Lock()
-        
-        self.LastRefreshTimer = None
-        
-        self.SetDataProducer(producer)
-        
-    def __del__(self):
-        self.DataProducer = None
-        self.DeleteDataConsumers()
-        if self.LastRefreshTimer is not None:
-            self.LastRefreshTimer.Stop()
-        if self.HasAcquiredLock:
-            DEBUG_REFRESH_LOCK.release()
-    
-    def SetDataProducer(self, producer):
-        if self.RegisterTick and self.Debug:
-            if producer is not None:
-                producer.SubscribeDebugIECVariable("__tick__", self)
-            if self.DataProducer is not None:
-                self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)        
-        self.DataProducer = producer
-    
-    def IsDebugging(self):
-        return self.Debug
-    
-    def Inhibit(self, inhibit):
-        for consumer, iec_path in self.DataConsumers.iteritems():
-            consumer.Inhibit(inhibit)
-        self.Inhibited = inhibit
-    
-    def AddDataConsumer(self, iec_path, consumer):
-        if self.DataProducer is None:
-            return None
-        result = self.DataProducer.SubscribeDebugIECVariable(iec_path, consumer)
-        if result is not None and consumer != self:
-            self.DataConsumers[consumer] = iec_path
-            consumer.SetDataType(self.GetDataType(iec_path))
-        return result
-    
-    def RemoveDataConsumer(self, consumer):
-        iec_path = self.DataConsumers.pop(consumer, None)
-        if iec_path is not None:
-            self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
-    
-    def RegisterVariables(self):
-        pass
-    
-    def GetDataType(self, iec_path):
-        if self.DataProducer is not None:
-            infos = self.DataProducer.GetInstanceInfos(iec_path)
-            if infos is not None:
-                return infos["type"]
-            return self.DataProducer.GetDebugIECVariableType(iec_path.upper())
-        return None
-    
-    def IsNumType(self, data_type):
-        return self.DataProducer.IsNumType(data_type)
-    
-    def ForceDataValue(self, iec_path, value):
-        if self.DataProducer is not None:
-            self.DataProducer.ForceDebugIECVariable(iec_path, value)
-    
-    def ReleaseDataValue(self, iec_path):
-        if self.DataProducer is not None:
-            self.DataProducer.ReleaseDebugIECVariable(iec_path)
-    
-    def DeleteDataConsumers(self):
-        if self.DataProducer is not None:
-            for consumer, iec_path in self.DataConsumers.iteritems():
-                self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
-        self.DataConsumers = {}
-    
-    def ShouldRefresh(self):
-        if self:
-            wx.CallAfter(self._ShouldRefresh)
-        
-    def _ShouldRefresh(self):
-        if self:
-            if DEBUG_REFRESH_LOCK.acquire(False):
-                self.AccessLock.acquire()
-                self.HasAcquiredLock = True
-                self.AccessLock.release()
-                self.RefreshNewData()
-            else:
-                self.TimerAccessLock.acquire()
-                self.LastRefreshTimer = Timer(REFRESH_PERIOD, self.ShouldRefresh)
-                self.LastRefreshTimer.start()
-                self.TimerAccessLock.release()
-    
-    def NewDataAvailable(self, tick, *args, **kwargs):
-        self.TimerAccessLock.acquire()
-        if self.LastRefreshTimer is not None:
-            self.LastRefreshTimer.cancel()
-            self.LastRefreshTimer=None
-        self.TimerAccessLock.release()
-        if self.IsShown() and not self.Inhibited:
-            if gettime() - self.LastRefreshTime > REFRESH_PERIOD and DEBUG_REFRESH_LOCK.acquire(False):
-                self.AccessLock.acquire()
-                self.HasAcquiredLock = True
-                self.AccessLock.release()
-                self.LastRefreshTime = gettime()
-                self.Inhibit(True)
-                wx.CallAfter(self.RefreshViewOnNewData, *args, **kwargs)
-            else:
-                self.TimerAccessLock.acquire()
-                self.LastRefreshTimer = Timer(REFRESH_PERIOD, self.ShouldRefresh)
-                self.LastRefreshTimer.start()
-                self.TimerAccessLock.release()
-        elif not self.IsShown() and self.HasAcquiredLock:
-            DebugViewer.RefreshNewData(self)
-            
-    def RefreshViewOnNewData(self, *args, **kwargs):
-        if self:
-            self.RefreshNewData(*args, **kwargs)
-    
-    def RefreshNewData(self, *args, **kwargs):
-        self.Inhibit(False)
-        self.AccessLock.acquire()
-        if self.HasAcquiredLock:
-            DEBUG_REFRESH_LOCK.release()
-            self.HasAcquiredLock = False
-        if gettime() - self.LastRefreshTime > REFRESH_PERIOD:
-            self.LastRefreshTime = gettime()
-        self.AccessLock.release()
-
-#-------------------------------------------------------------------------------
-#                               Viewer Rubberband
-#-------------------------------------------------------------------------------
-
-"""
-Class that implements a rubberband
-"""
-
-class RubberBand:
-    
-    # Create a rubberband by indicated on which window it must be drawn
-    def __init__(self, viewer):
-        self.Viewer = viewer
-        self.drawingSurface = viewer.Editor
-        self.Reset()
-    
-    # Method that initializes the internal attributes of the rubberband
-    def Reset(self):
-        self.startPoint = None
-        self.currentBox = None
-        self.lastBox = None
-    
-    # Method that return if a box is currently edited
-    def IsShown(self):
-        return self.currentBox != None
-    
-    # Method that returns the currently edited box
-    def GetCurrentExtent(self):
-        if self.currentBox is None:
-            return self.lastBox
-        return self.currentBox
-    
-    # Method called when a new box starts to be edited
-    def OnLeftDown(self, event, dc, scaling):
-        pos = GetScaledEventPosition(event, dc, scaling)
-        # Save the point for calculate the box position and size
-        self.startPoint = pos
-        self.currentBox = wx.Rect(pos.x, pos.y, 0, 0)
-        self.drawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
-        self.Redraw()
-    
-    # Method called when dragging with a box edited
-    def OnMotion(self, event, dc, scaling):
-        pos = GetScaledEventPosition(event, dc, scaling)
-        # Save the last position and size of the box for erasing it
-        self.lastBox = wx.Rect(self.currentBox.x, self.currentBox.y, self.currentBox.width,
-            self.currentBox.height)
-        # Calculate new position and size of the box 
-        if pos.x >= self.startPoint.x:
-            self.currentBox.x = self.startPoint.x
-            self.currentBox.width = pos.x - self.startPoint.x + 1
-        else:
-            self.currentBox.x = pos.x
-            self.currentBox.width = self.startPoint.x - pos.x + 1
-        if pos.y >= self.startPoint.y:
-            self.currentBox.y = self.startPoint.y
-            self.currentBox.height = pos.y - self.startPoint.y + 1
-        else:
-            self.currentBox.y = pos.y
-            self.currentBox.height = self.startPoint.y - pos.y + 1
-        self.Redraw()
-    
-    # Method called when dragging is stopped
-    def OnLeftUp(self, event, dc, scaling):
-        self.drawingSurface.SetCursor(wx.NullCursor)
-        self.lastBox = self.currentBox
-        self.currentBox = None
-        self.Redraw()
-
-    # Method that erase the last box and draw the new box
-    def Redraw(self, dc = None):
-        if dc is None:
-            dc = self.Viewer.GetLogicalDC()
-        scalex, scaley = dc.GetUserScale()
-        dc.SetUserScale(1, 1)
-        dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
-        dc.SetBrush(wx.TRANSPARENT_BRUSH)
-        dc.SetLogicalFunction(wx.XOR)
-        if self.lastBox:
-            # Erase last box
-            dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley, 
-                             self.lastBox.width * scalex, self.lastBox.height * scaley)
-        if self.currentBox:
-            # Draw current box
-            dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley, 
-                             self.currentBox.width * scalex, self.currentBox.height * scaley)
-        dc.SetUserScale(scalex, scaley)
-    
-    # Erase last box
-    def Erase(self, dc = None):
-        if dc is None:
-            dc = self.Viewer.GetLogicalDC()
-        scalex, scaley = dc.GetUserScale()
-        dc.SetUserScale(1, 1)
-        dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
-        dc.SetBrush(wx.TRANSPARENT_BRUSH)
-        dc.SetLogicalFunction(wx.XOR)
-        if self.lastBox:
-            dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley, 
-                             self.lastBox.width * scalex, self.lastBox.height * scalex)
-        dc.SetUserScale(scalex, scaley)
-
-    # Draw current box
-    def Draw(self, dc = None):
-        if dc is None:
-            dc = self.Viewer.GetLogicalDC()
-        scalex, scaley = dc.GetUserScale()
-        dc.SetUserScale(1, 1)
-        dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
-        dc.SetBrush(wx.TRANSPARENT_BRUSH)
-        dc.SetLogicalFunction(wx.XOR)
-        if self.currentBox:
-            # Draw current box
-            dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley, 
-                             self.currentBox.width * scalex, self.currentBox.height * scaley)
-        dc.SetUserScale(scalex, scaley)
-
-#-------------------------------------------------------------------------------
-#                               Viewer ToolTip
-#-------------------------------------------------------------------------------
-
-"""
-Class that implements a custom tool tip
-"""
-
-if wx.Platform == '__WXMSW__':
-    faces = { 'times': 'Times New Roman',
-              'mono' : 'Courier New',
-              'helv' : 'Arial',
-              'other': 'Comic Sans MS',
-              'size' : 10,
-             }
-else:
-    faces = { 'times': 'Times',
-              'mono' : 'Courier',
-              'helv' : 'Helvetica',
-              'other': 'new century schoolbook',
-              'size' : 12,
-             }
-
-TOOLTIP_MAX_CHARACTERS = 30
-TOOLTIP_MAX_LINE = 5
-
-class ToolTip(wx.PopupWindow):
-    
-    def __init__(self, parent, tip):
-        wx.PopupWindow.__init__(self, parent)
-        
-        self.CurrentPosition = wx.Point(0, 0)
-        
-        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
-        self.SetTip(tip)
-        
-        self.Bind(wx.EVT_PAINT, self.OnPaint)
-        
-    def SetTip(self, tip):
-        lines = []
-        for line in tip.splitlines():
-            if line != "":
-                words = line.split()
-                new_line = words[0]
-                for word in words[1:]:
-                    if len(new_line + " " + word) <= TOOLTIP_MAX_CHARACTERS:
-                        new_line += " " + word
-                    else:
-                        lines.append(new_line)
-                        new_line = word
-                lines.append(new_line)
-            else:
-                lines.append(line)
-        if len(lines) > TOOLTIP_MAX_LINE:
-            self.Tip = lines[:TOOLTIP_MAX_LINE]
-            if len(self.Tip[-1]) < TOOLTIP_MAX_CHARACTERS - 3:
-                self.Tip[-1] += "..."
-            else:
-                self.Tip[-1] = self.Tip[-1][:TOOLTIP_MAX_CHARACTERS - 3] + "..."
-        else:
-            self.Tip = lines
-        wx.CallAfter(self.RefreshTip)
-    
-    def MoveToolTip(self, pos):
-        self.CurrentPosition = pos
-        self.SetPosition(pos)
-    
-    def GetTipExtent(self):
-        max_width = 0
-        max_height = 0
-        for line in self.Tip:
-            w, h = self.GetTextExtent(line)
-            max_width = max(max_width, w)
-            max_height += h
-        return max_width, max_height
-    
-    def RefreshTip(self):
-        if self:
-            w, h = self.GetTipExtent()
-            self.SetSize(wx.Size(w + 4, h + 4))
-            self.SetPosition(self.CurrentPosition)
-            self.Refresh()
-        
-    def OnPaint(self, event):
-        dc = wx.AutoBufferedPaintDC(self)
-        dc.Clear()
-        dc.SetPen(MiterPen(wx.BLACK))
-        dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
-        dc.SetFont(wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"]))
-        dc.BeginDrawing()
-        w, h = self.GetTipExtent()
-        dc.DrawRectangle(0, 0, w + 4, h + 4)
-        offset = 0
-        for line in self.Tip:
-            dc.DrawText(line, 2, offset + 2)
-            w, h = dc.GetTextExtent(line)
-            offset += h
-        dc.EndDrawing()
-        event.Skip()
-
-#-------------------------------------------------------------------------------
 #                    Helpers for highlighting text
 #-------------------------------------------------------------------------------
 
@@ -691,10 +233,11 @@
 Class that implements a generic graphic element
 """
 
-class Graphic_Element:
+class Graphic_Element(ToolTipProducer):
     
     # Create a new graphic element
     def __init__(self, parent, id = None):
+        ToolTipProducer.__init__(self, parent)
         self.Parent = parent
         self.Id = id
         self.oldPos = None
@@ -708,13 +251,6 @@
         self.Size = wx.Size(0, 0)
         self.BoundingBox = wx.Rect(0, 0, 0, 0)
         self.Visible = False
-        self.ToolTip = None
-        self.ToolTipPos = None
-        self.ToolTipTimer = wx.Timer(self.Parent, -1)
-        self.Parent.Bind(wx.EVT_TIMER, self.OnToolTipTimer, self.ToolTipTimer)
-    
-    def __del__(self):
-        self.ToolTipTimer.Stop()
     
     def GetDefinition(self):
         return [self.Id], []
@@ -968,7 +504,7 @@
         # If the cursor is dragging and the element have been clicked
         if event.Dragging() and self.oldPos:
             # Calculate the movement of cursor
-            pos = event.GetLogicalPosition(dc)
+            pos = GetScaledEventPosition(event, dc, scaling)
             movex = pos.x - self.oldPos.x
             movey = pos.y - self.oldPos.y
             # If movement is greater than MIN_MOVE then a dragging is initiated
@@ -1087,36 +623,6 @@
             return movex, movey
         return 0, 0
     
-    def OnToolTipTimer(self, event):
-        value = self.GetToolTipValue()
-        if value is not None and self.ToolTipPos is not None:
-            self.ToolTip = ToolTip(self.Parent, value)
-            self.ToolTip.MoveToolTip(self.ToolTipPos)
-            self.ToolTip.Show()
-        
-    def GetToolTipValue(self):
-        return None
-    
-    def CreateToolTip(self, pos):
-        value = self.GetToolTipValue()
-        if value is not None:
-            self.ToolTipPos = pos
-            self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
-        
-    def MoveToolTip(self, pos):
-        if self.ToolTip is not None:
-            self.ToolTip.MoveToolTip(pos)
-        elif self.ToolTipPos is not None:
-            self.ToolTipPos = pos
-            self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
-    
-    def ClearToolTip(self):
-        self.ToolTipTimer.Stop()
-        self.ToolTipPos = None
-        if self.ToolTip is not None:
-            self.ToolTip.Destroy()
-            self.ToolTip = None
-    
     # Override this method for defining the method to call for adding an highlight to this element
     def AddHighlight(self, infos, start, end, highlight_type):
         pass
@@ -1347,9 +853,12 @@
                 if movex != 0 or movey != 0:
                     element.Move(movex, movey)
                     element.RefreshModel()
-        self.RefreshWireExclusion()
         self.RefreshBoundingBox()
     
+    # Add the given element to the group of elements
+    def AddElement(self, element):
+        self.Elements.append(element)
+        
     # Remove or select the given element if it is or not in the group
     def SelectElement(self, element):
         if element in self.Elements:
@@ -1480,10 +989,14 @@
         self.Parent.PopupGroupMenu()
 
     # Refreshes the model of all the elements of this group
-    def RefreshModel(self):
+    def RefreshModel(self, move=True):
         for element in self.Elements:
-            element.RefreshModel()
-
+            element.RefreshModel(move)
+
+    # Draws the handles of this element if it is selected
+    def Draw(self, dc):
+        for element in self.Elements:
+            element.Draw(dc)
 
 #-------------------------------------------------------------------------------
 #                         Connector for all types of blocks
@@ -1493,10 +1006,12 @@
 Class that implements a connector for any type of block
 """
 
-class Connector:
+class Connector(DebugDataConsumer, ToolTipProducer):
     
     # Create a new connector
     def __init__(self, parent, name, type, position, direction, negated = False, edge = "none", onlyone = False):
+        DebugDataConsumer.__init__(self)
+        ToolTipProducer.__init__(self, parent.Parent)
         self.ParentBlock = parent
         self.Name = name
         self.Type = type
@@ -1513,6 +1028,8 @@
         self.Valid = True
         self.Value = None
         self.Forced = False
+        self.ValueSize = None
+        self.ComputedValue = None
         self.Selected = False
         self.Highlights = []
         self.RefreshNameSize()
@@ -1536,7 +1053,18 @@
             height = 5
         else:
             height = CONNECTOR_SIZE
-        return wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
+        rect =  wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
+        if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
+            self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
+        if self.ValueSize is not None:
+            width, height = self.ValueSize
+            rect = rect.Union(wx.Rect(
+                    parent_pos[0] + self.Pos.x + CONNECTOR_SIZE * self.Direction[0] + \
+                                    width * (self.Direction[0] - 1) / 2,
+                    parent_pos[1] + self.Pos.y + CONNECTOR_SIZE * self.Direction[1] + \
+                                    height * (self.Direction[1] - 1),
+                    width, height))
+        return rect
     
     # Change the connector selection
     def SetSelected(self, selected):
@@ -1600,6 +1128,33 @@
         self.Name = name
         self.RefreshNameSize()
 
+    def SetForced(self, forced):
+        if self.Forced != forced:
+            self.Forced = forced
+            if self.Visible:
+                self.Parent.ElementNeedRefresh(self)
+    
+    def GetComputedValue(self):
+        if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
+            return self.Value
+        return None
+    
+    def GetToolTipValue(self):
+        return self.GetComputedValue()
+    
+    def SetValue(self, value):
+        if self.Value != value:
+            self.Value = value
+            computed_value = self.GetComputedValue()
+            if computed_value is not None:
+                self.ComputedValue = computed_value
+                self.SetToolTipText(self.ComputedValue)
+                if len(self.ComputedValue) > 4:
+                    self.ComputedValue = self.ComputedValue[:4] + "..."
+            self.ValueSize = None
+            if self.ParentBlock.Visible:
+                self.ParentBlock.Parent.ElementNeedRefresh(self)
+    
     def RefreshForced(self):
         self.Forced = False
         for wire, handle in self.Wires:
@@ -1682,6 +1237,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)
     
@@ -1935,6 +1494,21 @@
         if not getattr(dc, "printing", False):
             DrawHighlightedText(dc, self.Name, self.Highlights, xtext, ytext)
 
+        if self.Value is not None and not isinstance(self.Value, BooleanType) and self.Value != "undefined":
+            dc.SetFont(self.ParentBlock.Parent.GetMiniFont())
+            dc.SetTextForeground(wx.NamedColour("purple"))
+            if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
+                self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
+            if self.ValueSize is not None:
+                width, height = self.ValueSize
+                dc.DrawText(self.ComputedValue, 
+                    parent_pos[0] + self.Pos.x + CONNECTOR_SIZE * self.Direction[0] + \
+                                    width * (self.Direction[0] - 1) / 2,
+                    parent_pos[1] + self.Pos.y + CONNECTOR_SIZE * self.Direction[1] + \
+                                    height * (self.Direction[1] - 1))
+            dc.SetFont(self.ParentBlock.Parent.GetFont())
+            dc.SetTextForeground(wx.BLACK)
+
 #-------------------------------------------------------------------------------
 #                           Common Wire Element
 #-------------------------------------------------------------------------------
@@ -1980,17 +1554,6 @@
         self.StartConnected = None
         self.EndConnected = None
     
-    def GetToolTipValue(self):
-        if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
-            wire_type = self.GetEndConnectedType()
-            if wire_type == "STRING":
-                return "'%s'"%self.Value
-            elif wire_type == "WSTRING":
-                return "\"%s\""%self.Value
-            else:
-                return str(self.Value)
-        return None
-    
     # Returns the RedrawRect
     def GetRedrawRect(self, movex = 0, movey = 0):
         rect = Graphic_Element.GetRedrawRect(self, movex, movey)
@@ -2022,7 +1585,6 @@
     def Clone(self, parent, connectors = {}, dx = 0, dy = 0):
         start_connector = connectors.get(self.StartConnected, None)
         end_connector = connectors.get(self.EndConnected, None)
-        print self.StartConnected, "=>", start_connector, ",", self.EndConnected, "=>", end_connector
         if start_connector is not None and end_connector is not None:
             wire = Wire(parent)
             wire.SetPoints([(point.x + dx, point.y + dy) for point in self.Points])
@@ -2149,19 +1711,21 @@
             if self.Visible:
                 self.Parent.ElementNeedRefresh(self)
 
+    def GetComputedValue(self):
+        if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
+            return self.Value
+        return None
+    
+    def GetToolTipValue(self):
+        return self.GetComputedValue()
+
     def SetValue(self, value):
         if self.Value != value:
             self.Value = value
-            if value is not None and not isinstance(value, BooleanType):
-                wire_type = self.GetEndConnectedType()
-                if wire_type == "STRING":
-                    self.ComputedValue = "'%s'"%value
-                elif wire_type == "WSTRING":
-                    self.ComputedValue = "\"%s\""%value
-                else:
-                    self.ComputedValue = str(value)
-                if self.ToolTip is not None:
-                    self.ToolTip.SetTip(self.ComputedValue)
+            computed_value = self.GetComputedValue()
+            if computed_value is not None:
+                self.ComputedValue = computed_value
+                self.SetToolTipText(self.ComputedValue)
                 if len(self.ComputedValue) > 4:
                     self.ComputedValue = self.ComputedValue[:4] + "..."
             self.ValueSize = None
--- a/graphics/LD_Objects.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/LD_Objects.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,8 @@
 
 import wx
 
-from GraphicCommons import *
+from graphics.GraphicCommons import *
+from graphics.DebugDataConsumer import DebugDataConsumer
 from plcopen.structures import *
 
 #-------------------------------------------------------------------------------
@@ -119,8 +120,10 @@
         self.RefreshBoundingBox()
     
     # Returns the block minimum size
-    def GetMinSize(self):
-        return LD_POWERRAIL_WIDTH, self.Extensions[0] + self.Extensions[1]
+    def GetMinSize(self, default=False):
+        height = (LD_LINE_SIZE * (len(self.Connectors) - 1)
+                  if default else 0)
+        return LD_POWERRAIL_WIDTH, height + self.Extensions[0] + self.Extensions[1]
     
     # Add a connector or a blank to this power rail at the last place
     def AddConnector(self):
@@ -278,7 +281,7 @@
     # Method called when a RightUp event have been generated
     def OnRightUp(self, event, dc, scaling):
         handle_type, handle = self.Handle
-        if handle_type == HANDLE_CONNECTOR:
+        if handle_type == HANDLE_CONNECTOR and self.Dragging and self.oldPos:
             wires = handle.GetWires()
             if len(wires) == 1:
                 if handle == wires[0][0].StartConnected:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/RubberBand.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import wx
+
+from graphics.GraphicCommons import GetScaledEventPosition
+
+#-------------------------------------------------------------------------------
+#                               Viewer RubberBand
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a rubberband for graphic Viewers
+"""
+
+class RubberBand:
+    
+    def __init__(self, viewer):
+        """
+        Constructor
+        @param viewer: Viewer on which rubberband must be drawn
+        """
+        self.Viewer = viewer
+        
+        # wx.Panel on which rubberband will be drawn
+        self.DrawingSurface = viewer.Editor
+        
+        self.Reset()
+    
+    def Reset(self):
+        """
+        Initialize internal attributes of rubberband
+        """
+        self.StartPoint = None
+        self.CurrentBBox = None
+        self.LastBBox = None
+    
+    def IsShown(self):
+        """
+        Indicate if rubberband is drawn on viewer
+        @return: True if rubberband is drawn
+        """
+        return self.CurrentBBox != None
+    
+    def GetCurrentExtent(self):
+        """
+        Return the rubberband bounding box
+        @return: Rubberband bounding box (wx.Rect object)
+        """
+        # In case of rubberband not shown, return the last rubberband
+        # bounding box
+        if self.IsShown():
+            return self.CurrentBBox
+        return self.LastBBox
+    
+    def OnLeftDown(self, event, dc, scaling):
+        """
+        Called when left mouse is pressed on Viewer. Starts to edit a new
+        rubberband bounding box
+        @param event: Mouse event
+        @param dc: Device Context of Viewer
+        @param scaling: PLCOpen scaling applied on Viewer
+        """
+        # Save the point where mouse was pressed in Viewer unit, position may
+        # be modified by scroll and zoom applied on viewer
+        self.StartPoint = GetScaledEventPosition(event, dc, scaling)
+        
+        # Initialize rubberband bounding box
+        self.CurrentBBox = wx.Rect(self.StartPoint.x, self.StartPoint.y, 0, 0)
+        
+        # Change viewer mouse cursor to reflect a rubberband bounding box is
+        # edited
+        self.DrawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
+        
+        self.Redraw()
+    
+    def OnMotion(self, event, dc, scaling):
+        """
+        Called when mouse is dragging over Viewer. Update the current edited
+        rubberband bounding box
+        @param event: Mouse event
+        @param dc: Device Context of Viewer
+        @param scaling: PLCOpen scaling applied on Viewer
+        """
+        # Get mouse position in Viewer unit, position may be modified by scroll
+        # and zoom applied on viewer
+        pos = GetScaledEventPosition(event, dc, scaling)
+        
+        # Save the last bounding box drawn for erasing it later
+        self.LastBBox = wx.Rect(0, 0, 0, 0)
+        self.LastBBox.Union(self.CurrentBBox)
+        
+        # Calculate new position and size of the box 
+        self.CurrentBBox.x = min(pos.x, self.StartPoint.x)
+        self.CurrentBBox.y = min(pos.y, self.StartPoint.y)
+        self.CurrentBBox.width = abs(pos.x - self.StartPoint.x) + 1
+        self.CurrentBBox.height = abs(pos.y - self.StartPoint.y) + 1
+        
+        self.Redraw()
+    
+    def OnLeftUp(self, event, dc, scaling):
+        """
+        Called when mouse is release from Viewer. Erase the current edited
+        rubberband bounding box
+        @param event: Mouse event
+        @param dc: Device Context of Viewer
+        @param scaling: PLCOpen scaling applied on Viewer
+        """
+        # Change viewer mouse cursor to default
+        self.DrawingSurface.SetCursor(wx.NullCursor)
+        
+        # Save the last edited bounding box
+        self.LastBBox = self.CurrentBBox
+        self.CurrentBBox = None
+        
+        self.Redraw()
+    
+    def DrawBoundingBoxes(self, bboxes, dc=None):
+        """
+        Draw a list of bounding box on Viewer in the order given using XOR 
+        logical function
+        @param bboxes: List of bounding boxes to draw on viewer
+        @param dc: Device Context of Viewer (default None)
+        """
+        # Get viewer Device Context if not given
+        if dc is None:
+            dc = self.Viewer.GetLogicalDC()
+        
+        # Save current viewer scale factors before resetting them in order to
+        # avoid rubberband pen to be scaled
+        scalex, scaley = dc.GetUserScale()
+        dc.SetUserScale(1, 1)
+        
+        # Set DC drawing style
+        dc.SetPen(wx.Pen(wx.WHITE, style=wx.DOT))
+        dc.SetBrush(wx.TRANSPARENT_BRUSH)
+        dc.SetLogicalFunction(wx.XOR)
+        
+        # Draw the bounding boxes using viewer scale factor
+        for bbox in bboxes:
+            if bbox is not None:
+                dc.DrawRectangle(
+                    bbox.x * scalex, bbox.y * scaley, 
+                    bbox.width * scalex, bbox.height * scaley)
+        
+        dc.SetLogicalFunction(wx.COPY)
+        
+        # Restore Viewer scale factor
+        dc.SetUserScale(scalex, scaley)
+    
+    def Redraw(self, dc = None):
+        """
+        Redraw rubberband on Viewer
+        @param dc: Device Context of Viewer (default None)
+        """
+        # Erase last bbox and draw current bbox
+        self.DrawBoundingBoxes([self.LastBBox, self.CurrentBBox], dc)
+    
+    def Erase(self, dc = None):
+        """
+        Erase rubberband from Viewer
+        @param dc: Device Context of Viewer (default None)
+        """
+        # Erase last bbox
+        self.DrawBoundingBoxes([self.LastBBox], dc)
+
+    def Draw(self, dc = None):
+        """
+        Draw rubberband on Viewer
+        @param dc: Device Context of Viewer (default None)
+        """
+        # Erase last bbox and draw current bbox
+        self.DrawBoundingBoxes([self.CurrentBBox], dc)
--- a/graphics/SFC_Objects.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/SFC_Objects.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,7 +24,8 @@
 
 import wx
 
-from GraphicCommons import *
+from graphics.GraphicCommons import *
+from graphics.DebugDataConsumer import DebugDataConsumer
 from plcopen.structures import *
 
 def GetWireSize(block):
@@ -1359,11 +1360,25 @@
     
     # Method called when a LeftDown event have been generated
     def OnLeftDown(self, event, dc, scaling):
-        connector = None
-        if event.ControlDown():
-            pos = GetScaledEventPosition(event, dc, scaling)
-            # Test if a connector have been handled
-            connector = self.TestConnector(pos, exclude=False)
+        self.RealConnectors = {"Inputs":[],"Outputs":[]}
+        for input in self.Inputs:
+            position = input.GetRelPosition()
+            self.RealConnectors["Inputs"].append(float(position.x)/float(self.Size[0]))
+        for output in self.Outputs:
+            position = output.GetRelPosition()
+            self.RealConnectors["Outputs"].append(float(position.x)/float(self.Size[0]))
+        Graphic_Element.OnLeftDown(self, event, dc, scaling)
+    
+    # Method called when a LeftUp event have been generated
+    def OnLeftUp(self, event, dc, scaling):
+        Graphic_Element.OnLeftUp(self, event, dc, scaling)
+        self.RealConnectors = None
+    
+    # Method called when a RightDown event have been generated
+    def OnRightDown(self, event, dc, scaling):
+        pos = GetScaledEventPosition(event, dc, scaling)
+        # Test if a connector have been handled
+        connector = self.TestConnector(pos, exclude=False)
         if connector:
             self.Handle = (HANDLE_CONNECTOR, connector)
             wx.CallAfter(self.Parent.SetCurrentCursor, 1)
@@ -1371,17 +1386,11 @@
             # Initializes the last position
             self.oldPos = GetScaledEventPosition(event, dc, scaling)
         else:
-            self.RealConnectors = {"Inputs":[],"Outputs":[]}
-            for input in self.Inputs:
-                position = input.GetRelPosition()
-                self.RealConnectors["Inputs"].append(float(position.x)/float(self.Size[0]))
-            for output in self.Outputs:
-                position = output.GetRelPosition()
-                self.RealConnectors["Outputs"].append(float(position.x)/float(self.Size[0]))
-            Graphic_Element.OnLeftDown(self, event, dc, scaling)
-    
-    # Method called when a LeftUp event have been generated
-    def OnLeftUp(self, event, dc, scaling):
+            Graphic_Element.OnRightDown(self, event, dc, scaling)
+    
+    # Method called when a RightUp event have been generated
+    def OnRightUp(self, event, dc, scaling):
+        pos = GetScaledEventPosition(event, dc, scaling)
         handle_type, handle = self.Handle
         if handle_type == HANDLE_CONNECTOR and self.Dragging and self.oldPos:
             wires = handle.GetWires()
@@ -1393,21 +1402,17 @@
                         block.RefreshInputModel()
                     else:
                         block.RefreshOutputModel()
-        Graphic_Element.OnLeftUp(self, event, dc, scaling)
-        self.RealConnectors = None
-    
-    # Method called when a RightUp event have been generated
-    def OnRightUp(self, event, dc, scaling):
-        pos = GetScaledEventPosition(event, dc, scaling)
-        # Popup the menu with special items for a block and a connector if one is handled
-        connector = self.TestConnector(pos, exclude=False)
-        if connector:
-            self.Handle = (HANDLE_CONNECTOR, connector)
-            self.Parent.PopupDivergenceMenu(True)
-        else:
-            # Popup the divergence menu without delete branch
-            self.Parent.PopupDivergenceMenu(False)
-    
+            Graphic_Element.OnRightUp(self, event, dc, scaling)
+        else:
+            # Popup the menu with special items for a block and a connector if one is handled
+            connector = self.TestConnector(pos, exclude=False)
+            if connector:
+                self.Handle = (HANDLE_CONNECTOR, connector)
+                self.Parent.PopupDivergenceMenu(True)
+            else:
+                # Popup the divergence menu without delete branch
+                self.Parent.PopupDivergenceMenu(False)
+        
     # Refreshes the divergence state according to move defined and handle selected
     def ProcessDragging(self, movex, movey, event, scaling):
         handle_type, handle = self.Handle
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graphics/ToolTipProducer.py	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
+#based on the plcopen standard. 
+#
+#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+#See COPYING file for copyrights details.
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public
+#License as published by the Free Software Foundation; either
+#version 2.1 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#General Public License for more details.
+#
+#You should have received a copy of the GNU General Public
+#License along with this library; if not, write to the Free Software
+#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import wx
+
+from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
+    
+#-------------------------------------------------------------------------------
+#                           Tool Tip Producer class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements an element that generate Tool Tip
+"""
+
+class ToolTipProducer:
+    
+    def __init__(self, parent):
+        """
+        Constructor
+        @param parent: Parent Viewer
+        """
+        self.Parent = parent
+        
+        self.ToolTip = None
+        self.ToolTipPos = None
+        
+        # Timer for firing Tool tip display
+        self.ToolTipTimer = wx.Timer(self.Parent, -1)
+        self.Parent.Bind(wx.EVT_TIMER, 
+            self.OnToolTipTimer, 
+            self.ToolTipTimer)
+    
+    def __del__(self):
+        """
+        Destructor
+        """
+        self.DestroyToolTip()
+    
+    def OnToolTipTimer(self, event):
+        """
+        Callback for Tool Tip firing timer Event
+        @param event: Tool tip text
+        """
+        # Get Tool Tip text
+        value = self.GetToolTipValue()
+        
+        if value is not None and self.ToolTipPos is not None:
+            # Create Tool Tip
+            self.ToolTip = CustomToolTip(self.Parent, value)
+            self.ToolTip.SetToolTipPosition(self.ToolTipPos)
+            self.ToolTip.Show()
+    
+    def GetToolTipValue(self):
+        """
+        Return tool tip text
+        Have to be overridden by inherited classes 
+        @return: Tool tip text (None if not overridden) 
+        """
+        return None
+    
+    def DisplayToolTip(self, pos):
+        """
+        Display Tool tip
+        @param pos: Tool tip position
+        """
+        # Destroy current displayed Tool tip
+        self.DestroyToolTip()
+        
+        # Save Tool Tip position
+        self.ToolTipPos = pos
+        # Start Tool tip firing timer
+        self.ToolTipTimer.Start(
+            int(TOOLTIP_WAIT_PERIOD * 1000), 
+            oneShot=True)
+    
+    def SetToolTipText(self, text):
+        """
+        Set current Tool tip text
+        @param text: Tool tip Text
+        """
+        if self.ToolTip is not None:
+            self.ToolTip.SetTip(text)
+    
+    def DestroyToolTip(self):
+        """
+        Destroy current displayed Tool Tip
+        """
+        # Stop Tool tip firing timer
+        self.ToolTipTimer.Stop()
+        self.ToolTipPos = None
+        
+        # Destroy Tool Tip
+        if self.ToolTip is not None:
+            self.ToolTip.Destroy()
+            self.ToolTip = None
--- a/graphics/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/graphics/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -27,4 +27,6 @@
 from GraphicCommons import *
 from FBD_Objects import *
 from LD_Objects import *
-from SFC_Objects import *
\ No newline at end of file
+from SFC_Objects import *
+from RubberBand import RubberBand
+from DebugDataConsumer import DebugDataConsumer
\ No newline at end of file
--- a/i18n/Beremiz_fr_FR.po	Wed Mar 13 12:34:55 2013 +0900
+++ b/i18n/Beremiz_fr_FR.po	Wed Jul 31 10:45:07 2013 +0900
@@ -7,8 +7,8 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-09-07 01:17+0200\n"
-"PO-Revision-Date: 2012-09-07 01:31+0100\n"
+"POT-Creation-Date: 2013-03-26 22:55+0100\n"
+"PO-Revision-Date: 2013-03-26 23:08+0100\n"
 "Last-Translator: Laurent BESSARD <laurent.bessard@gmail.com>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language: \n"
@@ -16,7 +16,7 @@
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: ../PLCOpenEditor.py:520
+#: ../PLCOpenEditor.py:405
 msgid ""
 "\n"
 "An error has occurred.\n"
@@ -38,7 +38,7 @@
 "\n"
 "Erreur:\n"
 
-#: ../Beremiz.py:1071
+#: ../Beremiz.py:1119
 #, python-format
 msgid ""
 "\n"
@@ -87,7 +87,7 @@
 msgid "   Temp"
 msgstr "  Temporaire"
 
-#: ../PLCOpenEditor.py:530
+#: ../PLCOpenEditor.py:415
 msgid " : "
 msgstr " : "
 
@@ -99,7 +99,7 @@
 msgid " and %s"
 msgstr " et %s"
 
-#: ../ProjectController.py:890
+#: ../ProjectController.py:917
 msgid " generation failed !\n"
 msgstr "la construction a échouée !\n"
 
@@ -123,8 +123,8 @@
 msgid "\"%s\" can't use itself!"
 msgstr "\"%s\" ne peut pas s'utiliser lui-même !"
 
-#: ../IDEFrame.py:1706
-#: ../IDEFrame.py:1725
+#: ../IDEFrame.py:1587
+#: ../IDEFrame.py:1606
 #, python-format
 msgid "\"%s\" config already exists!"
 msgstr "La configuration \"%s\" existe déjà !"
@@ -134,46 +134,46 @@
 msgid "\"%s\" configuration already exists !!!"
 msgstr "La configuration \"%s\" existe déjà !!!"
 
-#: ../IDEFrame.py:1660
+#: ../IDEFrame.py:1541
 #, python-format
 msgid "\"%s\" data type already exists!"
 msgstr "Le type de données \"%s\" existe déjà !"
 
-#: ../PLCControler.py:2040
-#: ../PLCControler.py:2044
+#: ../PLCControler.py:2165
+#: ../PLCControler.py:2169
 #, python-format
 msgid "\"%s\" element can't be pasted here!!!"
 msgstr "L'élément \"%s\" ne peut être collé ici !!!"
 
-#: ../editors/TextViewer.py:305
-#: ../editors/TextViewer.py:325
-#: ../editors/Viewer.py:252
+#: ../editors/TextViewer.py:298
+#: ../editors/TextViewer.py:318
+#: ../editors/Viewer.py:250
 #: ../dialogs/PouTransitionDialog.py:105
-#: ../dialogs/ConnectionDialog.py:150
+#: ../dialogs/ConnectionDialog.py:157
 #: ../dialogs/PouActionDialog.py:102
 #: ../dialogs/FBDBlockDialog.py:162
 #, python-format
 msgid "\"%s\" element for this pou already exists!"
 msgstr "Un élément \"%s\" existe déjà dans ce POU !"
 
-#: ../Beremiz.py:894
+#: ../Beremiz.py:921
 #, python-format
 msgid "\"%s\" folder is not a valid Beremiz project\n"
 msgstr "Le dossier \"%s\" ne contient pas de projet Beremiz valide\n"
 
-#: ../plcopen/structures.py:106
+#: ../plcopen/structures.py:105
 #, python-format
 msgid "\"%s\" function cancelled in \"%s\" POU: No input connected"
 msgstr "L'appel à la fonction \"%s\" dans le POU \"%s\" a été abandonné : aucune entrée connectée"
 
-#: ../controls/VariablePanel.py:656
-#: ../IDEFrame.py:1651
-#: ../editors/DataTypeEditor.py:548
-#: ../editors/DataTypeEditor.py:577
+#: ../controls/VariablePanel.py:659
+#: ../IDEFrame.py:1532
+#: ../editors/DataTypeEditor.py:554
+#: ../editors/DataTypeEditor.py:583
 #: ../dialogs/PouNameDialog.py:49
 #: ../dialogs/PouTransitionDialog.py:101
 #: ../dialogs/SFCStepNameDialog.py:51
-#: ../dialogs/ConnectionDialog.py:146
+#: ../dialogs/ConnectionDialog.py:153
 #: ../dialogs/FBDVariableDialog.py:199
 #: ../dialogs/PouActionDialog.py:98
 #: ../dialogs/PouDialog.py:118
@@ -183,29 +183,29 @@
 msgid "\"%s\" is a keyword. It can't be used!"
 msgstr "\"%s\" est un mot réservé. Il ne peut être utilisé !"
 
-#: ../editors/Viewer.py:240
+#: ../editors/Viewer.py:238
 #, python-format
 msgid "\"%s\" is already used by \"%s\"!"
 msgstr "\"%s\" est déjà utilisé par \"%s\" !"
 
-#: ../plcopen/plcopen.py:2786
+#: ../plcopen/plcopen.py:2836
 #, python-format
 msgid "\"%s\" is an invalid value!"
 msgstr "\"%s\" n'est pas une valeur valide !"
 
-#: ../PLCOpenEditor.py:362
-#: ../PLCOpenEditor.py:399
+#: ../PLCOpenEditor.py:341
+#: ../PLCOpenEditor.py:378
 #, python-format
 msgid "\"%s\" is not a valid folder!"
 msgstr "\"%s\" n'est pas un répertoire valide !"
 
-#: ../controls/VariablePanel.py:654
-#: ../IDEFrame.py:1649
-#: ../editors/DataTypeEditor.py:572
+#: ../controls/VariablePanel.py:657
+#: ../IDEFrame.py:1530
+#: ../editors/DataTypeEditor.py:578
 #: ../dialogs/PouNameDialog.py:47
 #: ../dialogs/PouTransitionDialog.py:99
 #: ../dialogs/SFCStepNameDialog.py:49
-#: ../dialogs/ConnectionDialog.py:144
+#: ../dialogs/ConnectionDialog.py:151
 #: ../dialogs/PouActionDialog.py:96
 #: ../dialogs/PouDialog.py:116
 #: ../dialogs/SFCStepDialog.py:120
@@ -214,22 +214,22 @@
 msgid "\"%s\" is not a valid identifier!"
 msgstr "\"%s\" n'est pas un identifiant valide !"
 
-#: ../IDEFrame.py:214
-#: ../IDEFrame.py:2445
-#: ../IDEFrame.py:2464
+#: ../IDEFrame.py:221
+#: ../IDEFrame.py:2313
+#: ../IDEFrame.py:2332
 #, python-format
 msgid "\"%s\" is used by one or more POUs. It can't be removed!"
 msgstr "Le POU \"%s\" est utilisé par un ou plusieurs POUs. Il ne peut être supprimé !"
 
-#: ../controls/VariablePanel.py:311
-#: ../IDEFrame.py:1669
-#: ../editors/TextViewer.py:303
-#: ../editors/TextViewer.py:323
-#: ../editors/TextViewer.py:360
-#: ../editors/Viewer.py:250
-#: ../editors/Viewer.py:295
-#: ../editors/Viewer.py:312
-#: ../dialogs/ConnectionDialog.py:148
+#: ../controls/VariablePanel.py:313
+#: ../IDEFrame.py:1550
+#: ../editors/TextViewer.py:296
+#: ../editors/TextViewer.py:316
+#: ../editors/TextViewer.py:353
+#: ../editors/Viewer.py:248
+#: ../editors/Viewer.py:293
+#: ../editors/Viewer.py:311
+#: ../dialogs/ConnectionDialog.py:155
 #: ../dialogs/PouDialog.py:120
 #: ../dialogs/FBDBlockDialog.py:160
 #, python-format
@@ -252,18 +252,18 @@
 msgid "\"%s\" step already exists!"
 msgstr "L'étape \"%s\" existe déjà !"
 
-#: ../editors/DataTypeEditor.py:543
+#: ../editors/DataTypeEditor.py:549
 #, python-format
 msgid "\"%s\" value already defined!"
 msgstr "La valeur \"%s\" est déjà définie !"
 
-#: ../editors/DataTypeEditor.py:719
+#: ../editors/DataTypeEditor.py:744
 #: ../dialogs/ArrayTypeDialog.py:97
 #, python-format
 msgid "\"%s\" value isn't a valid array dimension!"
 msgstr "\"%s\" n'est pas une dimension de tableau valide !"
 
-#: ../editors/DataTypeEditor.py:726
+#: ../editors/DataTypeEditor.py:751
 #: ../dialogs/ArrayTypeDialog.py:103
 #, python-format
 msgid ""
@@ -273,12 +273,12 @@
 "\"%s\" n'est pas une dimension de tableau valide !\n"
 "La valeur de droite doit être supérieur à celle de gauche."
 
-#: ../PLCControler.py:793
+#: ../PLCControler.py:847
 #, python-format
 msgid "%s \"%s\" can't be pasted as a %s."
 msgstr "Le %s \"%s\" ne peut être collé en tant que %s."
 
-#: ../PLCControler.py:1422
+#: ../PLCControler.py:1476
 #, python-format
 msgid "%s Data Types"
 msgstr "Types de données de %s"
@@ -288,90 +288,90 @@
 msgid "%s Graphics"
 msgstr "Graphique %s"
 
-#: ../PLCControler.py:1417
+#: ../PLCControler.py:1471
 #, python-format
 msgid "%s POUs"
 msgstr "POUs de %s"
 
-#: ../canfestival/SlaveEditor.py:42
-#: ../canfestival/NetworkEditor.py:72
+#: ../canfestival/SlaveEditor.py:46
+#: ../canfestival/NetworkEditor.py:67
 #, python-format
 msgid "%s Profile"
 msgstr "Profil %s"
 
-#: ../plcopen/plcopen.py:1780
 #: ../plcopen/plcopen.py:1790
 #: ../plcopen/plcopen.py:1800
 #: ../plcopen/plcopen.py:1810
-#: ../plcopen/plcopen.py:1819
+#: ../plcopen/plcopen.py:1820
+#: ../plcopen/plcopen.py:1829
 #, python-format
 msgid "%s body don't have instances!"
 msgstr "Le code d'un %s n'a pas d'instances !"
 
-#: ../plcopen/plcopen.py:1842
-#: ../plcopen/plcopen.py:1849
+#: ../plcopen/plcopen.py:1852
+#: ../plcopen/plcopen.py:1859
 #, python-format
 msgid "%s body don't have text!"
 msgstr "Le code d'un %s n'a pas de texte !"
 
-#: ../IDEFrame.py:364
+#: ../IDEFrame.py:369
 msgid "&Add Element"
 msgstr "&Ajouter un élément"
 
-#: ../IDEFrame.py:334
+#: ../IDEFrame.py:339
 msgid "&Configuration"
 msgstr "&Configuration"
 
-#: ../IDEFrame.py:325
+#: ../IDEFrame.py:330
 msgid "&Data Type"
 msgstr "&Type de donnée"
 
-#: ../IDEFrame.py:368
+#: ../IDEFrame.py:373
 msgid "&Delete"
 msgstr "&Supprimer"
 
-#: ../IDEFrame.py:317
+#: ../IDEFrame.py:322
 msgid "&Display"
 msgstr "&Affichage"
 
-#: ../IDEFrame.py:316
+#: ../IDEFrame.py:321
 msgid "&Edit"
 msgstr "&Editer"
 
-#: ../IDEFrame.py:315
+#: ../IDEFrame.py:320
 msgid "&File"
 msgstr "&Fichier"
 
-#: ../IDEFrame.py:327
+#: ../IDEFrame.py:332
 msgid "&Function"
 msgstr "&Fonction"
 
-#: ../IDEFrame.py:318
+#: ../IDEFrame.py:323
 msgid "&Help"
 msgstr "&Aide"
 
-#: ../IDEFrame.py:331
+#: ../IDEFrame.py:336
 msgid "&Program"
 msgstr "&Programme"
 
-#: ../PLCOpenEditor.py:148
+#: ../PLCOpenEditor.py:129
 msgid "&Properties"
 msgstr "&Propriétés"
 
-#: ../Beremiz.py:310
+#: ../Beremiz.py:312
 msgid "&Recent Projects"
 msgstr "Projets &récent"
 
-#: ../Beremiz.py:352
+#: ../Beremiz.py:354
 msgid "&Resource"
 msgstr "&Ressource"
 
-#: ../controls/SearchResultPanel.py:237
+#: ../controls/SearchResultPanel.py:252
 #, python-format
 msgid "'%s' - %d match in project"
 msgstr "'%s' - %d correspondance dans le projet"
 
-#: ../controls/SearchResultPanel.py:239
+#: ../controls/SearchResultPanel.py:254
 #, python-format
 msgid "'%s' - %d matches in project"
 msgstr "'%s' - %d correspondances dans le projet"
@@ -381,14 +381,14 @@
 msgid "'%s' is located at %s\n"
 msgstr "'%s' is disponible à l'adresse %s\n"
 
-#: ../controls/SearchResultPanel.py:289
+#: ../controls/SearchResultPanel.py:304
 #, python-format
 msgid "(%d matches)"
 msgstr "(%d correspondances)"
 
-#: ../PLCOpenEditor.py:508
-#: ../PLCOpenEditor.py:510
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:393
+#: ../PLCOpenEditor.py:395
+#: ../PLCOpenEditor.py:396
 msgid ",   "
 msgstr ",   "
 
@@ -400,25 +400,25 @@
 msgid ", %s"
 msgstr ", %s"
 
-#: ../PLCOpenEditor.py:506
+#: ../PLCOpenEditor.py:391
 msgid ". "
 msgstr ". "
 
-#: ../ProjectController.py:1268
+#: ../ProjectController.py:1294
 msgid "... debugger recovered\n"
 msgstr "... déboggueur operationel\n"
 
-#: ../IDEFrame.py:1672
-#: ../IDEFrame.py:1714
-#: ../IDEFrame.py:1733
+#: ../IDEFrame.py:1553
+#: ../IDEFrame.py:1595
+#: ../IDEFrame.py:1614
 #: ../dialogs/PouDialog.py:122
 #, python-format
 msgid "A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?"
 msgstr "Un POU a un élément nommé \"%s\". Cela peut générer des conflits. Voulez-vous continuer ?"
 
-#: ../controls/VariablePanel.py:658
-#: ../IDEFrame.py:1684
-#: ../IDEFrame.py:1695
+#: ../controls/VariablePanel.py:661
+#: ../IDEFrame.py:1565
+#: ../IDEFrame.py:1576
 #: ../dialogs/PouNameDialog.py:51
 #: ../dialogs/PouTransitionDialog.py:103
 #: ../dialogs/SFCStepNameDialog.py:53
@@ -428,34 +428,34 @@
 msgid "A POU named \"%s\" already exists!"
 msgstr "Un POU nommé \"%s\" existe déjà !"
 
-#: ../ConfigTreeNode.py:371
+#: ../ConfigTreeNode.py:388
 #, python-format
 msgid "A child named \"%s\" already exist -> \"%s\"\n"
 msgstr "Un noeud enfant nommé \"%s\" existe déjà -> \"%s\"\n"
 
-#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/BrowseLocationsDialog.py:212
 msgid "A location must be selected!"
 msgstr "Une adresse doit être sélectionné !"
 
-#: ../controls/VariablePanel.py:660
-#: ../IDEFrame.py:1686
-#: ../IDEFrame.py:1697
+#: ../controls/VariablePanel.py:663
+#: ../IDEFrame.py:1567
+#: ../IDEFrame.py:1578
 #: ../dialogs/SFCStepNameDialog.py:55
 #: ../dialogs/SFCStepDialog.py:126
 #, python-format
 msgid "A variable with \"%s\" as name already exists in this pou!"
 msgstr "Une variable nommée \"%s\" existe déjà dans ce POU !"
 
-#: ../Beremiz.py:362
-#: ../PLCOpenEditor.py:181
+#: ../Beremiz.py:364
+#: ../PLCOpenEditor.py:162
 msgid "About"
 msgstr "A propos"
 
-#: ../Beremiz.py:931
+#: ../Beremiz.py:957
 msgid "About Beremiz"
 msgstr "A propos de Beremiz"
 
-#: ../PLCOpenEditor.py:376
+#: ../PLCOpenEditor.py:355
 msgid "About PLCOpenEditor"
 msgstr "A propos de PLCOpenEditor"
 
@@ -468,7 +468,7 @@
 msgid "Action"
 msgstr "Action"
 
-#: ../editors/Viewer.py:495
+#: ../editors/Viewer.py:494
 msgid "Action Block"
 msgstr "Ajouter un bloc fonctionnel"
 
@@ -480,7 +480,7 @@
 msgid "Action Name:"
 msgstr "Nom de l'action :"
 
-#: ../plcopen/plcopen.py:1480
+#: ../plcopen/plcopen.py:1490
 #, python-format
 msgid "Action with name %s doesn't exist!"
 msgstr "L'action nommée %s n'existe pas !"
@@ -493,30 +493,35 @@
 msgid "Actions:"
 msgstr "Actions :"
 
-#: ../canfestival/SlaveEditor.py:54
-#: ../canfestival/NetworkEditor.py:84
+#: ../editors/Viewer.py:999
+msgid "Active"
+msgstr "Actif"
+
+#: ../canfestival/SlaveEditor.py:57
+#: ../canfestival/NetworkEditor.py:78
+#: ../Beremiz.py:987
 #: ../editors/Viewer.py:527
 msgid "Add"
 msgstr "Ajouter"
 
-#: ../IDEFrame.py:1925
-#: ../IDEFrame.py:1956
+#: ../IDEFrame.py:1801
+#: ../IDEFrame.py:1832
 msgid "Add Action"
 msgstr "Ajouter une action"
 
-#: ../features.py:7
+#: ../features.py:8
 msgid "Add C code accessing located variables synchronously"
 msgstr "Ajoute un code C ayant accès à des variables localisées de façon synchrone"
 
-#: ../IDEFrame.py:1908
+#: ../IDEFrame.py:1784
 msgid "Add Configuration"
 msgstr "Ajouter une configuration"
 
-#: ../IDEFrame.py:1888
+#: ../IDEFrame.py:1764
 msgid "Add DataType"
 msgstr "Ajouter un type de donnée"
 
-#: ../editors/Viewer.py:453
+#: ../editors/Viewer.py:452
 msgid "Add Divergence Branch"
 msgstr "Ajouter une branche à la divergence"
 
@@ -524,25 +529,25 @@
 msgid "Add IP"
 msgstr "Ajouter IP"
 
-#: ../IDEFrame.py:1896
+#: ../IDEFrame.py:1772
 msgid "Add POU"
 msgstr "Ajouter un POU"
 
-#: ../features.py:8
+#: ../features.py:9
 msgid "Add Python code executed asynchronously"
 msgstr "Ajoute un code Python executé de façon asynchone"
 
-#: ../IDEFrame.py:1936
-#: ../IDEFrame.py:1982
+#: ../IDEFrame.py:1812
+#: ../IDEFrame.py:1858
 msgid "Add Resource"
 msgstr "Ajouter une resource"
 
-#: ../IDEFrame.py:1914
-#: ../IDEFrame.py:1953
+#: ../IDEFrame.py:1790
+#: ../IDEFrame.py:1829
 msgid "Add Transition"
 msgstr "Ajouter une transition"
 
-#: ../editors/Viewer.py:442
+#: ../editors/Viewer.py:441
 msgid "Add Wire Segment"
 msgstr "Ajouter un segment au fil"
 
@@ -550,7 +555,7 @@
 msgid "Add a new initial step"
 msgstr "Ajouter une nouvelle étape initiale"
 
-#: ../editors/Viewer.py:2289
+#: ../editors/Viewer.py:2363
 #: ../editors/SFCViewer.py:696
 msgid "Add a new jump"
 msgstr "Ajouter un nouveau renvoi"
@@ -559,7 +564,7 @@
 msgid "Add a new step"
 msgstr "Ajouter une nouvelle étape"
 
-#: ../features.py:9
+#: ../features.py:10
 msgid "Add a simple WxGlade based GUI."
 msgstr "Ajoute une interface simple utilisant WxGlade"
 
@@ -567,23 +572,24 @@
 msgid "Add action"
 msgstr "Ajouter une action"
 
-#: ../editors/DataTypeEditor.py:345
+#: ../editors/DataTypeEditor.py:351
 msgid "Add element"
 msgstr "Ajouter un élément"
 
-#: ../editors/ResourceEditor.py:251
+#: ../editors/ResourceEditor.py:259
 msgid "Add instance"
 msgstr "Ajouter une instance"
 
-#: ../canfestival/NetworkEditor.py:86
+#: ../canfestival/NetworkEditor.py:80
 msgid "Add slave"
 msgstr "Ajouter un esclave"
 
-#: ../editors/ResourceEditor.py:222
+#: ../editors/ResourceEditor.py:230
 msgid "Add task"
 msgstr "Ajouter une tâche"
 
-#: ../controls/VariablePanel.py:378
+#: ../controls/VariablePanel.py:380
+#: ../c_ext/CFileEditor.py:517
 msgid "Add variable"
 msgstr "Ajouter une variable"
 
@@ -591,33 +597,43 @@
 msgid "Addition"
 msgstr "Addition"
 
-#: ../plcopen/structures.py:250
+#: ../plcopen/structures.py:249
 msgid "Additional function blocks"
 msgstr "Blocs fonctionnels additionnels"
 
-#: ../editors/Viewer.py:1395
+#: ../editors/Viewer.py:510
+msgid "Adjust Block Size"
+msgstr "Ajuster la taille des blocs"
+
+#: ../editors/Viewer.py:1458
 msgid "Alignment"
 msgstr "Alignement"
 
 #: ../controls/VariablePanel.py:75
-#: ../dialogs/BrowseLocationsDialog.py:35
-#: ../dialogs/BrowseLocationsDialog.py:116
+#: ../dialogs/BrowseLocationsDialog.py:34
+#: ../dialogs/BrowseLocationsDialog.py:43
+#: ../dialogs/BrowseLocationsDialog.py:136
+#: ../dialogs/BrowseLocationsDialog.py:139
 msgid "All"
-msgstr "Toutes"
+msgstr "Tout"
 
 #: ../editors/FileManagementPanel.py:35
 msgid "All files (*.*)|*.*|CSV files (*.csv)|*.csv"
 msgstr "Tous les fichiers|*.*|Fichiers CSV (*.csv)|*.csv"
 
-#: ../ProjectController.py:1335
+#: ../ProjectController.py:1373
 msgid "Already connected. Please disconnect\n"
 msgstr "Déjà connecté. Veuillez déconnecter\n"
 
-#: ../editors/DataTypeEditor.py:587
+#: ../editors/DataTypeEditor.py:593
 #, python-format
 msgid "An element named \"%s\" already exists in this structure!"
 msgstr "Un élément nommé \"%s\" existe déjà dans la structure !"
 
+#: ../dialogs/ConnectionDialog.py:98
+msgid "Apply name modification to all continuations with the same name"
+msgstr "Appliquer la modification de nom à toutes les prolongements portant ce même nom"
+
 #: ../plcopen/iec_std.csv:31
 msgid "Arc cosine"
 msgstr "Arc cosinus"
@@ -634,8 +650,9 @@
 msgid "Arithmetic"
 msgstr "Arithmétique"
 
-#: ../controls/VariablePanel.py:729
-#: ../editors/DataTypeEditor.py:52
+#: ../controls/VariablePanel.py:732
+#: ../editors/DataTypeEditor.py:54
+#: ../editors/DataTypeEditor.py:634
 msgid "Array"
 msgstr "Tableau"
 
@@ -673,19 +690,19 @@
 msgid "Bad location size : %s"
 msgstr "Mauvaise taille d'adresse : %s"
 
-#: ../editors/DataTypeEditor.py:168
-#: ../editors/DataTypeEditor.py:198
-#: ../editors/DataTypeEditor.py:290
+#: ../editors/DataTypeEditor.py:174
+#: ../editors/DataTypeEditor.py:204
+#: ../editors/DataTypeEditor.py:296
 #: ../dialogs/ArrayTypeDialog.py:55
 msgid "Base Type:"
 msgstr "Type de base :"
 
-#: ../controls/VariablePanel.py:699
-#: ../editors/DataTypeEditor.py:617
+#: ../controls/VariablePanel.py:702
+#: ../editors/DataTypeEditor.py:624
 msgid "Base Types"
 msgstr "Types de base"
 
-#: ../Beremiz.py:486
+#: ../Beremiz.py:511
 msgid "Beremiz"
 msgstr "Beremiz"
 
@@ -717,7 +734,7 @@
 msgid "Bitwise inverting"
 msgstr "Inversion bit à bit"
 
-#: ../editors/Viewer.py:465
+#: ../editors/Viewer.py:464
 msgid "Block"
 msgstr "Block"
 
@@ -725,7 +742,7 @@
 msgid "Block Properties"
 msgstr "Propriétés du bloc"
 
-#: ../editors/Viewer.py:434
+#: ../editors/Viewer.py:433
 msgid "Bottom"
 msgstr "Bas"
 
@@ -734,31 +751,35 @@
 msgid "Browse %s values library"
 msgstr "Explorer la liste des valeurs du paramètre '%s'"
 
-#: ../dialogs/BrowseLocationsDialog.py:55
+#: ../dialogs/BrowseLocationsDialog.py:61
 msgid "Browse Locations"
 msgstr "Naviger dans les adresses"
 
-#: ../ProjectController.py:1484
+#: ../ProjectController.py:1519
 msgid "Build"
 msgstr "Compiler"
 
-#: ../ProjectController.py:1051
+#: ../ProjectController.py:1079
 msgid "Build directory already clean\n"
 msgstr "Le répertoire de compilation est déjà nettoyé\n"
 
-#: ../ProjectController.py:1485
+#: ../ProjectController.py:1520
 msgid "Build project into build folder"
 msgstr "Compiler le projet dans le répertoire ce compilation"
 
-#: ../ProjectController.py:910
+#: ../ProjectController.py:937
 msgid "C Build crashed !\n"
 msgstr "La compilation du C a mal fonctionné !\n"
 
-#: ../ProjectController.py:907
+#: ../ProjectController.py:934
 msgid "C Build failed.\n"
 msgstr "La compilation du C a échouée !\n"
 
-#: ../ProjectController.py:895
+#: ../c_ext/CFileEditor.py:731
+msgid "C code"
+msgstr "Code C"
+
+#: ../ProjectController.py:922
 msgid "C code generated successfully.\n"
 msgstr "Code C généré avec succès.\n"
 
@@ -767,18 +788,26 @@
 msgid "C compilation of %s failed.\n"
 msgstr "La compilation C de %s a échouée.\n"
 
-#: ../features.py:7
+#: ../features.py:8
 msgid "C extension"
 msgstr "Extension C"
 
-#: ../features.py:6
+#: ../canfestival/NetworkEditor.py:29
+msgid "CANOpen network"
+msgstr "Réseau CANOpen"
+
+#: ../canfestival/SlaveEditor.py:21
+msgid "CANOpen slave"
+msgstr "Esclave CANOpen"
+
+#: ../features.py:7
 msgid "CANopen support"
 msgstr "Support CANopen"
 
-#: ../plcopen/plcopen.py:1722
-#: ../plcopen/plcopen.py:1736
-#: ../plcopen/plcopen.py:1757
-#: ../plcopen/plcopen.py:1773
+#: ../plcopen/plcopen.py:1732
+#: ../plcopen/plcopen.py:1746
+#: ../plcopen/plcopen.py:1767
+#: ../plcopen/plcopen.py:1783
 msgid "Can only generate execution order on FBD networks!"
 msgstr "L'ordre d'exécution ne peut être généré que dans les FBD !"
 
@@ -786,7 +815,7 @@
 msgid "Can only give a location to local or global variables"
 msgstr "Une adresse ne peut être affecté qu'à des variables locales ou globales"
 
-#: ../PLCOpenEditor.py:357
+#: ../PLCOpenEditor.py:336
 #, python-format
 msgid "Can't generate program to file %s!"
 msgstr "Le programme n'a pu être généré dans le fichier \"%s\" !"
@@ -795,21 +824,21 @@
 msgid "Can't give a location to a function block instance"
 msgstr "Une adresse ne peut être affectée à une instance de Function Block"
 
-#: ../PLCOpenEditor.py:397
+#: ../PLCOpenEditor.py:376
 #, python-format
 msgid "Can't save project to file %s!"
 msgstr "Le projet n'a pu être sauvé dans le fichier \"%s\" !"
 
-#: ../controls/VariablePanel.py:298
+#: ../controls/VariablePanel.py:300
 msgid "Can't set an initial value to a function block instance"
 msgstr "Une valeur initiale ne peut être affectée une instance de Function Block"
 
-#: ../ConfigTreeNode.py:470
+#: ../ConfigTreeNode.py:490
 #, python-format
 msgid "Cannot create child %s of type %s "
 msgstr "Impossible d'ajouter un élément \"%s\" de type \"%s\""
 
-#: ../ConfigTreeNode.py:400
+#: ../ConfigTreeNode.py:417
 #, python-format
 msgid "Cannot find lower free IEC channel than %d\n"
 msgstr "Impossible de trouver un numéro IEC inférieur à %d libre\n"
@@ -818,7 +847,7 @@
 msgid "Cannot get PLC status - connection failed.\n"
 msgstr "Impossible d'obtenir le statut de l'automate - la connexion a échoué.\n"
 
-#: ../ProjectController.py:715
+#: ../ProjectController.py:737
 msgid "Cannot open/parse VARIABLES.csv!\n"
 msgstr "Impossible d'ouvrir ou d'analyser le fichier VARIABLES.csv !\n"
 
@@ -832,27 +861,27 @@
 msgid "Case sensitive"
 msgstr "Respecter la casse"
 
-#: ../editors/Viewer.py:429
+#: ../editors/Viewer.py:428
 msgid "Center"
 msgstr "Centre"
 
-#: ../Beremiz_service.py:322
+#: ../Beremiz_service.py:326
 msgid "Change IP of interface to bind"
 msgstr "Changer l'adresse IP de l'interface à lier"
 
-#: ../Beremiz_service.py:321
+#: ../Beremiz_service.py:325
 msgid "Change Name"
 msgstr "Changer le nom"
 
-#: ../IDEFrame.py:1974
+#: ../IDEFrame.py:1850
 msgid "Change POU Type To"
 msgstr "Changer le type du POU pour"
 
-#: ../Beremiz_service.py:325
+#: ../Beremiz_service.py:327
 msgid "Change Port Number"
 msgstr "Changer le numéro de port"
 
-#: ../Beremiz_service.py:327
+#: ../Beremiz_service.py:328
 msgid "Change working directory"
 msgstr "Changer le dossier de travail"
 
@@ -864,19 +893,19 @@
 msgid "Choose a SVG file"
 msgstr "Choisissez un fichier SVG"
 
-#: ../ProjectController.py:353
+#: ../ProjectController.py:364
 msgid "Choose a directory to save project"
 msgstr "Choisissez un dossier où enregistrer le projet"
 
-#: ../canfestival/canfestival.py:118
-#: ../PLCOpenEditor.py:313
-#: ../PLCOpenEditor.py:347
-#: ../PLCOpenEditor.py:391
+#: ../canfestival/canfestival.py:136
+#: ../PLCOpenEditor.py:294
+#: ../PLCOpenEditor.py:326
+#: ../PLCOpenEditor.py:370
 msgid "Choose a file"
 msgstr "Choisissez un fichier"
 
-#: ../Beremiz.py:831
-#: ../Beremiz.py:866
+#: ../Beremiz.py:858
+#: ../Beremiz.py:893
 msgid "Choose a project"
 msgstr "Choisissez un projet"
 
@@ -885,15 +914,15 @@
 msgid "Choose a value for %s:"
 msgstr "Choisissez une valeur pour le paramètre %s :"
 
-#: ../Beremiz_service.py:373
+#: ../Beremiz_service.py:378
 msgid "Choose a working directory "
 msgstr "Choisissez un dossier de travail"
 
-#: ../ProjectController.py:281
+#: ../ProjectController.py:288
 msgid "Chosen folder doesn't contain a program. It's not a valid project!"
 msgstr "Le répertoire ne contient pas de programme. Ce n'est pas un projet valide !"
 
-#: ../ProjectController.py:247
+#: ../ProjectController.py:255
 msgid "Chosen folder isn't empty. You can't use it for a new project!"
 msgstr "Le répertoire n'est pas vide. Vous ne pouvez pas l'utiliser pour créer un nouveau projet !"
 
@@ -902,7 +931,7 @@
 msgid "Class"
 msgstr "Classe"
 
-#: ../controls/VariablePanel.py:369
+#: ../controls/VariablePanel.py:371
 msgid "Class Filter:"
 msgstr "Filtre de classe :"
 
@@ -910,19 +939,19 @@
 msgid "Class:"
 msgstr "Classe :"
 
-#: ../ProjectController.py:1488
+#: ../ProjectController.py:1523
 msgid "Clean"
 msgstr "Nettoyer"
 
-#: ../ProjectController.py:1490
+#: ../ProjectController.py:1525
 msgid "Clean project build folder"
 msgstr "Nettoyer le répertoire de compilation"
 
-#: ../ProjectController.py:1048
+#: ../ProjectController.py:1076
 msgid "Cleaning the build directory\n"
 msgstr "Répertoire de compilation en cours de nettoyage\n"
 
-#: ../IDEFrame.py:411
+#: ../IDEFrame.py:416
 msgid "Clear Errors"
 msgstr "Effacer les erreurs"
 
@@ -934,29 +963,29 @@
 msgid "Clear the graph values"
 msgstr "Vider les valeurs du graphique"
 
-#: ../Beremiz.py:598
-#: ../PLCOpenEditor.py:221
+#: ../Beremiz.py:633
+#: ../PLCOpenEditor.py:202
 msgid "Close Application"
 msgstr "Fermer l'application"
 
-#: ../IDEFrame.py:1089
-#: ../Beremiz.py:319
-#: ../Beremiz.py:552
-#: ../PLCOpenEditor.py:131
+#: ../IDEFrame.py:972
+#: ../Beremiz.py:321
+#: ../Beremiz.py:587
+#: ../PLCOpenEditor.py:112
 msgid "Close Project"
 msgstr "Fermer le projet"
 
-#: ../Beremiz.py:317
-#: ../PLCOpenEditor.py:129
+#: ../Beremiz.py:319
+#: ../PLCOpenEditor.py:110
 msgid "Close Tab"
 msgstr "Fermer l'onglet"
 
-#: ../editors/Viewer.py:481
+#: ../editors/Viewer.py:480
 msgid "Coil"
 msgstr "Relai"
 
-#: ../editors/Viewer.py:501
-#: ../editors/LDViewer.py:503
+#: ../editors/Viewer.py:500
+#: ../editors/LDViewer.py:506
 msgid "Comment"
 msgstr "Commentaire"
 
@@ -972,7 +1001,7 @@
 msgid "Comparison"
 msgstr "Comparaison"
 
-#: ../ProjectController.py:538
+#: ../ProjectController.py:552
 msgid "Compiling IEC Program into C code...\n"
 msgstr "Compilation du program en IEC vers du code C en cours...\n"
 
@@ -980,6 +1009,14 @@
 msgid "Concatenation"
 msgstr "Concaténation"
 
+#: ../editors/ConfTreeNodeEditor.py:249
+msgid "Config"
+msgstr "Configuration"
+
+#: ../editors/ProjectNodeEditor.py:13
+msgid "Config variables"
+msgstr "Variables de configuration"
+
 #: ../dialogs/SearchInProjectDialog.py:47
 msgid "Configuration"
 msgstr "Configuration"
@@ -988,20 +1025,25 @@
 msgid "Configurations"
 msgstr "Configurations"
 
-#: ../ProjectController.py:1503
+#: ../ProjectController.py:1538
 msgid "Connect"
 msgstr "Connecter"
 
-#: ../ProjectController.py:1504
+#: ../ProjectController.py:1539
 msgid "Connect to the target PLC"
 msgstr "Connecter à l'automate cible"
 
+#: ../ProjectController.py:1125
+#, python-format
+msgid "Connected to URI: %s"
+msgstr "Connecté à l'URI : %s"
+
 #: ../connectors/PYRO/__init__.py:40
 #, python-format
 msgid "Connecting to URI : %s\n"
 msgstr "Connection à l'URI %s en cours...\n"
 
-#: ../editors/Viewer.py:467
+#: ../editors/Viewer.py:466
 #: ../dialogs/SFCTransitionDialog.py:76
 msgid "Connection"
 msgstr "Connexion"
@@ -1010,11 +1052,11 @@
 msgid "Connection Properties"
 msgstr "Propriétés de la connexion"
 
-#: ../ProjectController.py:1359
+#: ../ProjectController.py:1397
 msgid "Connection canceled!\n"
 msgstr "La connection a été abandonnée !\n"
 
-#: ../ProjectController.py:1384
+#: ../ProjectController.py:1422
 #, python-format
 msgid "Connection failed to %s!\n"
 msgstr "La connection à \"%s\" a échouée !\n"
@@ -1024,6 +1066,7 @@
 msgid "Connection to '%s' failed.\n"
 msgstr "La connexion à l'adresse '%s' a échouée.\n"
 
+#: ../editors/Viewer.py:1426
 #: ../dialogs/ConnectionDialog.py:56
 msgid "Connector"
 msgstr "Connecteur"
@@ -1032,11 +1075,15 @@
 msgid "Connectors:"
 msgstr "Connecteurs :"
 
+#: ../Beremiz.py:420
+msgid "Console"
+msgstr "Console"
+
 #: ../controls/VariablePanel.py:65
 msgid "Constant"
 msgstr "Constante"
 
-#: ../editors/Viewer.py:477
+#: ../editors/Viewer.py:476
 msgid "Contact"
 msgstr "Contact"
 
@@ -1044,6 +1091,7 @@
 msgid "Content Description (optional):"
 msgstr "Description du contenu (optionel) :"
 
+#: ../editors/Viewer.py:1427
 #: ../dialogs/ConnectionDialog.py:61
 msgid "Continuation"
 msgstr "Prolongement"
@@ -1064,21 +1112,21 @@
 msgid "Conversion to time-of-day"
 msgstr "Conversion en heure de la journée"
 
-#: ../IDEFrame.py:348
-#: ../IDEFrame.py:401
+#: ../IDEFrame.py:353
+#: ../IDEFrame.py:406
 #: ../editors/Viewer.py:536
 msgid "Copy"
 msgstr "Copier"
 
-#: ../IDEFrame.py:1961
+#: ../IDEFrame.py:1837
 msgid "Copy POU"
 msgstr "Copier ce POU"
 
-#: ../editors/FileManagementPanel.py:283
+#: ../editors/FileManagementPanel.py:65
 msgid "Copy file from left folder to right"
 msgstr "Copier un fichier du dossier de gauche vers celui de droite"
 
-#: ../editors/FileManagementPanel.py:282
+#: ../editors/FileManagementPanel.py:64
 msgid "Copy file from right folder to left"
 msgstr "Copier un fichier du dossier de droite vers celui de gauche"
 
@@ -1086,7 +1134,7 @@
 msgid "Cosine"
 msgstr "Cosinus"
 
-#: ../ConfigTreeNode.py:582
+#: ../ConfigTreeNode.py:602
 #, python-format
 msgid ""
 "Could not add child \"%s\", type %s :\n"
@@ -1095,7 +1143,7 @@
 "Impossible d'ajouter le noeud enfant \"%s\",  de type %s :\n"
 "%s\n"
 
-#: ../ConfigTreeNode.py:559
+#: ../ConfigTreeNode.py:579
 #, python-format
 msgid ""
 "Couldn't load confnode base parameters %s :\n"
@@ -1104,7 +1152,7 @@
 "Impossible de charger les paramètres de base du plugin %s :\n"
 " %s"
 
-#: ../ConfigTreeNode.py:570
+#: ../ConfigTreeNode.py:590
 #, python-format
 msgid ""
 "Couldn't load confnode parameters %s :\n"
@@ -1113,20 +1161,20 @@
 "Impossible de charger les paramètres du plugin %s :\n"
 " %s"
 
-#: ../PLCControler.py:765
-#: ../PLCControler.py:802
+#: ../PLCControler.py:819
+#: ../PLCControler.py:856
 msgid "Couldn't paste non-POU object."
 msgstr "Impossible de coller autre chose qu'un POU."
 
-#: ../ProjectController.py:1317
+#: ../ProjectController.py:1344
 msgid "Couldn't start PLC !\n"
 msgstr "Impossible de démarrer l'automate !\n"
 
-#: ../ProjectController.py:1325
+#: ../ProjectController.py:1352
 msgid "Couldn't stop PLC !\n"
 msgstr "Impossible d'arrêter l'automate !\n"
 
-#: ../ProjectController.py:1295
+#: ../ProjectController.py:1321
 msgid "Couldn't stop debugger.\n"
 msgstr "Impossible d'arrêter le débogage de l'automate !\n"
 
@@ -1142,42 +1190,42 @@
 msgid "Create a new action"
 msgstr "Créer une nouvelle action"
 
-#: ../IDEFrame.py:135
+#: ../IDEFrame.py:142
 msgid "Create a new action block"
 msgstr "Créer un nouveau bloc d'actions"
 
-#: ../IDEFrame.py:84
-#: ../IDEFrame.py:114
-#: ../IDEFrame.py:147
+#: ../IDEFrame.py:91
+#: ../IDEFrame.py:121
+#: ../IDEFrame.py:154
 msgid "Create a new block"
 msgstr "Créer un nouveau bloc"
 
-#: ../IDEFrame.py:108
+#: ../IDEFrame.py:115
 msgid "Create a new branch"
 msgstr "Créer une nouvelle branche"
 
-#: ../IDEFrame.py:102
+#: ../IDEFrame.py:109
 msgid "Create a new coil"
 msgstr "Créer un nouveau relai"
 
-#: ../IDEFrame.py:78
-#: ../IDEFrame.py:93
-#: ../IDEFrame.py:123
+#: ../IDEFrame.py:85
+#: ../IDEFrame.py:100
+#: ../IDEFrame.py:130
 msgid "Create a new comment"
 msgstr "Créer un nouveau copmmentaire"
 
-#: ../IDEFrame.py:87
-#: ../IDEFrame.py:117
-#: ../IDEFrame.py:150
+#: ../IDEFrame.py:94
+#: ../IDEFrame.py:124
+#: ../IDEFrame.py:157
 msgid "Create a new connection"
 msgstr "Créer une nouvelle connexion"
 
-#: ../IDEFrame.py:105
-#: ../IDEFrame.py:156
+#: ../IDEFrame.py:112
+#: ../IDEFrame.py:163
 msgid "Create a new contact"
 msgstr "Créer un nouveau contact"
 
-#: ../IDEFrame.py:138
+#: ../IDEFrame.py:145
 msgid "Create a new divergence"
 msgstr "Créer une nouvelle divergence"
 
@@ -1185,45 +1233,45 @@
 msgid "Create a new divergence or convergence"
 msgstr "Créer une nouvelle divergence ou convergence"
 
-#: ../IDEFrame.py:126
+#: ../IDEFrame.py:133
 msgid "Create a new initial step"
 msgstr "Créer une nouvelle étape initiale"
 
-#: ../IDEFrame.py:141
+#: ../IDEFrame.py:148
 msgid "Create a new jump"
 msgstr "Créer un nouveau renvoi"
 
-#: ../IDEFrame.py:96
-#: ../IDEFrame.py:153
+#: ../IDEFrame.py:103
+#: ../IDEFrame.py:160
 msgid "Create a new power rail"
 msgstr "Créer une nouvelle barre d'alimentation"
 
-#: ../IDEFrame.py:99
+#: ../IDEFrame.py:106
 msgid "Create a new rung"
 msgstr "Créer un nouvel échelon"
 
-#: ../IDEFrame.py:129
+#: ../IDEFrame.py:136
 msgid "Create a new step"
 msgstr "Créer une nouvelle étape"
 
-#: ../IDEFrame.py:132
+#: ../IDEFrame.py:139
 #: ../dialogs/PouTransitionDialog.py:42
 msgid "Create a new transition"
 msgstr "Créer une nouvelle transition"
 
-#: ../IDEFrame.py:81
-#: ../IDEFrame.py:111
-#: ../IDEFrame.py:144
+#: ../IDEFrame.py:88
+#: ../IDEFrame.py:118
+#: ../IDEFrame.py:151
 msgid "Create a new variable"
 msgstr "Créer une nouvelle variable"
 
-#: ../IDEFrame.py:346
-#: ../IDEFrame.py:400
+#: ../IDEFrame.py:351
+#: ../IDEFrame.py:405
 #: ../editors/Viewer.py:535
 msgid "Cut"
 msgstr "Couper"
 
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
 msgid "Cyclic"
 msgstr "Périodique"
 
@@ -1239,13 +1287,13 @@
 msgid "DEPRECATED"
 msgstr "OBSOLETE"
 
-#: ../canfestival/SlaveEditor.py:50
-#: ../canfestival/NetworkEditor.py:80
+#: ../canfestival/SlaveEditor.py:53
+#: ../canfestival/NetworkEditor.py:74
 msgid "DS-301 Profile"
 msgstr "Profil DS-301"
 
-#: ../canfestival/SlaveEditor.py:51
-#: ../canfestival/NetworkEditor.py:81
+#: ../canfestival/SlaveEditor.py:54
+#: ../canfestival/NetworkEditor.py:75
 msgid "DS-302 Profile"
 msgstr "Profil DS-302"
 
@@ -1282,60 +1330,61 @@
 msgid "Days:"
 msgstr "Jours :"
 
-#: ../ProjectController.py:1405
-msgid "Debug connect matching running PLC\n"
-msgstr "L'automate connecté correspond au project ouvert.\n"
-
-#: ../ProjectController.py:1408
-msgid "Debug do not match PLC - stop/transfert/start to re-enable\n"
-msgstr "L'automate connecté ne correspond pas au project ouvert - Arrêter/transférez/démarrer pour pouvoir débogguer.\n"
-
-#: ../controls/PouInstanceVariablesPanel.py:52
+#: ../ProjectController.py:1444
+msgid "Debug does not match PLC - stop/transfert/start to re-enable\n"
+msgstr "Les informations de débogage ne correspond pas l'automate connecté  - Arrêter/transférez/démarrer pour pouvoir débogguer.\n"
+
+#: ../controls/PouInstanceVariablesPanel.py:59
 msgid "Debug instance"
 msgstr "Déboguer l'instance"
 
-#: ../editors/Viewer.py:3222
+#: ../editors/Viewer.py:1016
+#: ../editors/Viewer.py:3326
 #, python-format
 msgid "Debug: %s"
-msgstr "Déboggage : %s"
-
-#: ../ProjectController.py:1122
+msgstr "Débogage : %s"
+
+#: ../ProjectController.py:1153
 #, python-format
 msgid "Debug: Unknown variable '%s'\n"
 msgstr "Débogage : Variable '%s' inconnue\n"
 
-#: ../ProjectController.py:1120
+#: ../ProjectController.py:1151
 #, python-format
 msgid "Debug: Unsupported type to debug '%s'\n"
 msgstr "Débogage : Type non supporté dans le débogage '%'\n"
 
-#: ../IDEFrame.py:608
+#: ../IDEFrame.py:612
 msgid "Debugger"
 msgstr "Déboggueur"
 
-#: ../ProjectController.py:1285
+#: ../ProjectController.py:1311
 msgid "Debugger disabled\n"
 msgstr "Débogueur désactivé\n"
 
-#: ../ProjectController.py:1297
+#: ../ProjectController.py:1441
+msgid "Debugger ready\n"
+msgstr "Débogueur \n"
+
+#: ../ProjectController.py:1323
 msgid "Debugger stopped.\n"
 msgstr "Débogueur désactivé\n"
 
-#: ../IDEFrame.py:1990
-#: ../Beremiz.py:958
+#: ../IDEFrame.py:1866
+#: ../Beremiz.py:991
 #: ../editors/Viewer.py:511
 msgid "Delete"
 msgstr "Supprimer"
 
-#: ../editors/Viewer.py:454
+#: ../editors/Viewer.py:453
 msgid "Delete Divergence Branch"
 msgstr "Supprimer une branche de divergence"
 
-#: ../editors/FileManagementPanel.py:371
+#: ../editors/FileManagementPanel.py:153
 msgid "Delete File"
 msgstr "Supprimer un fichier"
 
-#: ../editors/Viewer.py:443
+#: ../editors/Viewer.py:442
 msgid "Delete Wire Segment"
 msgstr "Supprimer un segment de fil"
 
@@ -1347,11 +1396,11 @@
 msgid "Deletion (within)"
 msgstr "Suppression (au milieu)"
 
-#: ../editors/DataTypeEditor.py:146
+#: ../editors/DataTypeEditor.py:152
 msgid "Derivation Type:"
 msgstr "Type de dérivation :"
 
-#: ../plcopen/structures.py:264
+#: ../plcopen/structures.py:263
 msgid ""
 "Derivative\n"
 "The derivative function block produces an output XOUT proportional to the rate of change of the input XIN."
@@ -1359,11 +1408,11 @@
 "Dérivée\n"
 "Le Function Block derivative produit une sortie XOUT proportionnelle au rapport de changement de l'entrée XIN."
 
-#: ../controls/VariablePanel.py:360
+#: ../controls/VariablePanel.py:362
 msgid "Description:"
 msgstr "Description :"
 
-#: ../editors/DataTypeEditor.py:314
+#: ../editors/DataTypeEditor.py:320
 #: ../dialogs/ArrayTypeDialog.py:61
 msgid "Dimensions:"
 msgstr "Dimensions :"
@@ -1372,23 +1421,23 @@
 msgid "Direction"
 msgstr "Direction"
 
-#: ../dialogs/BrowseLocationsDialog.py:78
+#: ../dialogs/BrowseLocationsDialog.py:86
 msgid "Direction:"
 msgstr "Direction :"
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Directly"
 msgstr "Direct"
 
-#: ../ProjectController.py:1512
+#: ../ProjectController.py:1547
 msgid "Disconnect"
 msgstr "Déconnecter"
 
-#: ../ProjectController.py:1514
+#: ../ProjectController.py:1549
 msgid "Disconnect from PLC"
 msgstr "Déconnecter l'automate"
 
-#: ../editors/Viewer.py:496
+#: ../editors/Viewer.py:495
 msgid "Divergence"
 msgstr "Divergence"
 
@@ -1396,7 +1445,7 @@
 msgid "Division"
 msgstr "Division"
 
-#: ../editors/FileManagementPanel.py:370
+#: ../editors/FileManagementPanel.py:152
 #, python-format
 msgid "Do you really want to delete the file '%s'?"
 msgstr "Êtes-vous sûr de vouloir supprimer le fichier '%s' ?"
@@ -1406,11 +1455,11 @@
 msgid "Documentation"
 msgstr "Documentation"
 
-#: ../PLCOpenEditor.py:351
+#: ../PLCOpenEditor.py:330
 msgid "Done"
 msgstr "Terminé"
 
-#: ../plcopen/structures.py:227
+#: ../plcopen/structures.py:226
 msgid ""
 "Down-counter\n"
 "The down-counter can be used to signal when a count has reached zero, on counting down from a preset value."
@@ -1422,11 +1471,11 @@
 msgid "Duration"
 msgstr "Durée"
 
-#: ../canfestival/canfestival.py:118
+#: ../canfestival/canfestival.py:139
 msgid "EDS files (*.eds)|*.eds|All files|*.*"
 msgstr "Fichiers EDS (*.eds)|*.eds|Tous les fichiers|*.*"
 
-#: ../editors/Viewer.py:510
+#: ../editors/Viewer.py:509
 msgid "Edit Block"
 msgstr "Editer le block"
 
@@ -1458,14 +1507,14 @@
 msgid "Edit array type properties"
 msgstr "Editer les propriétés d'un type de données tableau"
 
-#: ../editors/Viewer.py:2112
-#: ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630
-#: ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186
+#: ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706
+#: ../editors/Viewer.py:2708
 msgid "Edit comment"
 msgstr "Editer le commentaire"
 
-#: ../editors/FileManagementPanel.py:284
+#: ../editors/FileManagementPanel.py:66
 msgid "Edit file"
 msgstr "Editer un fichier"
 
@@ -1473,11 +1522,11 @@
 msgid "Edit item"
 msgstr "Editer l'élément"
 
-#: ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2670
 msgid "Edit jump target"
 msgstr "Editer la cible du renvoi"
 
-#: ../ProjectController.py:1526
+#: ../ProjectController.py:1561
 msgid "Edit raw IEC code added to code generated by PLCGenerator"
 msgstr "Editer le code IEC ajouté au code généré par PLCGenerator"
 
@@ -1489,35 +1538,35 @@
 msgid "Edit transition"
 msgstr "Editer la transition"
 
-#: ../IDEFrame.py:580
+#: ../IDEFrame.py:584
 msgid "Editor ToolBar"
 msgstr "Barre d'outils d'édition"
 
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
 msgid "Editor selection"
 msgstr "Selection d'un éditeur"
 
-#: ../editors/DataTypeEditor.py:341
+#: ../editors/DataTypeEditor.py:347
 msgid "Elements :"
 msgstr "Eléments :"
 
-#: ../IDEFrame.py:343
+#: ../IDEFrame.py:348
 msgid "Enable Undo/Redo"
 msgstr "Activer Défaire/Refaire"
 
-#: ../Beremiz_service.py:380
+#: ../Beremiz_service.py:385
 msgid "Enter a name "
 msgstr "Saisissez un nom"
 
-#: ../Beremiz_service.py:365
+#: ../Beremiz_service.py:370
 msgid "Enter a port number "
 msgstr "Saisissez un numéro de port"
 
-#: ../Beremiz_service.py:355
+#: ../Beremiz_service.py:360
 msgid "Enter the IP of the interface to bind"
 msgstr "Saisissez l'adresse IP de l'interface à lier"
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Enumerated"
 msgstr "Enumération"
 
@@ -1525,43 +1574,41 @@
 msgid "Equal to"
 msgstr "Egal à"
 
-#: ../Beremiz_service.py:270
-#: ../Beremiz_service.py:394
-#: ../controls/VariablePanel.py:330
-#: ../controls/VariablePanel.py:678
-#: ../controls/DebugVariablePanel.py:164
-#: ../IDEFrame.py:1083
-#: ../IDEFrame.py:1672
-#: ../IDEFrame.py:1709
-#: ../IDEFrame.py:1714
-#: ../IDEFrame.py:1728
-#: ../IDEFrame.py:1733
-#: ../IDEFrame.py:2422
-#: ../Beremiz.py:1083
-#: ../PLCOpenEditor.py:358
-#: ../PLCOpenEditor.py:363
-#: ../PLCOpenEditor.py:531
-#: ../PLCOpenEditor.py:541
-#: ../editors/TextViewer.py:376
-#: ../editors/DataTypeEditor.py:543
-#: ../editors/DataTypeEditor.py:548
-#: ../editors/DataTypeEditor.py:572
-#: ../editors/DataTypeEditor.py:577
-#: ../editors/DataTypeEditor.py:587
-#: ../editors/DataTypeEditor.py:719
-#: ../editors/DataTypeEditor.py:726
-#: ../editors/Viewer.py:366
-#: ../editors/LDViewer.py:663
-#: ../editors/LDViewer.py:879
-#: ../editors/LDViewer.py:883
-#: ../editors/FileManagementPanel.py:210
-#: ../ProjectController.py:221
+#: ../Beremiz_service.py:271
+#: ../controls/VariablePanel.py:332
+#: ../controls/VariablePanel.py:681
+#: ../controls/DebugVariablePanel.py:379
+#: ../IDEFrame.py:966
+#: ../IDEFrame.py:1553
+#: ../IDEFrame.py:1590
+#: ../IDEFrame.py:1595
+#: ../IDEFrame.py:1609
+#: ../IDEFrame.py:1614
+#: ../IDEFrame.py:2290
+#: ../Beremiz.py:1131
+#: ../PLCOpenEditor.py:337
+#: ../PLCOpenEditor.py:342
+#: ../PLCOpenEditor.py:416
+#: ../PLCOpenEditor.py:426
+#: ../editors/TextViewer.py:369
+#: ../editors/DataTypeEditor.py:549
+#: ../editors/DataTypeEditor.py:554
+#: ../editors/DataTypeEditor.py:578
+#: ../editors/DataTypeEditor.py:583
+#: ../editors/DataTypeEditor.py:593
+#: ../editors/DataTypeEditor.py:744
+#: ../editors/DataTypeEditor.py:751
+#: ../editors/Viewer.py:365
+#: ../editors/LDViewer.py:666
+#: ../editors/LDViewer.py:882
+#: ../editors/LDViewer.py:886
+#: ../ProjectController.py:225
 #: ../dialogs/PouNameDialog.py:53
 #: ../dialogs/PouTransitionDialog.py:107
-#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/BrowseLocationsDialog.py:212
 #: ../dialogs/ProjectDialog.py:71
 #: ../dialogs/SFCStepNameDialog.py:59
-#: ../dialogs/ConnectionDialog.py:152
+#: ../dialogs/ConnectionDialog.py:159
 #: ../dialogs/FBDVariableDialog.py:201
 #: ../dialogs/PouActionDialog.py:104
 #: ../dialogs/BrowseValuesLibraryDialog.py:83
@@ -1574,20 +1621,20 @@
 #: ../dialogs/ArrayTypeDialog.py:97
 #: ../dialogs/ArrayTypeDialog.py:103
 #: ../dialogs/FBDBlockDialog.py:164
-#: ../dialogs/ForceVariableDialog.py:169
+#: ../dialogs/ForceVariableDialog.py:179
 msgid "Error"
 msgstr "Erreur"
 
-#: ../ProjectController.py:587
+#: ../ProjectController.py:601
 msgid "Error : At least one configuration and one resource must be declared in PLC !\n"
 msgstr "Erreur : Au moins une configuration ou une ressource doit être déclarée dans l'automate !\n"
 
-#: ../ProjectController.py:579
+#: ../ProjectController.py:593
 #, python-format
 msgid "Error : IEC to C compiler returned %d\n"
 msgstr "Erreur : Le compilateur d'IEC en C a retourné %d\n"
 
-#: ../ProjectController.py:520
+#: ../ProjectController.py:534
 #, python-format
 msgid ""
 "Error in ST/IL/SFC code generator :\n"
@@ -1596,24 +1643,24 @@
 "Erreur dans le générateur de code ST/IL/SFC :\n"
 "%s\n"
 
-#: ../ConfigTreeNode.py:182
+#: ../ConfigTreeNode.py:183
 #, python-format
 msgid "Error while saving \"%s\"\n"
 msgstr "Erreur lors de l'enregistrement de \"%s\"\n"
 
-#: ../canfestival/canfestival.py:122
+#: ../canfestival/canfestival.py:144
 msgid "Error: Export slave failed\n"
 msgstr "Erreur : L'export de l'esclave a échoué\n"
 
-#: ../canfestival/canfestival.py:270
+#: ../canfestival/canfestival.py:345
 msgid "Error: No Master generated\n"
 msgstr "Erreur : Aucun maître généré\n"
 
-#: ../canfestival/canfestival.py:265
+#: ../canfestival/canfestival.py:340
 msgid "Error: No PLC built\n"
 msgstr "Erreur : Aucun automate compilé\n"
 
-#: ../ProjectController.py:1378
+#: ../ProjectController.py:1416
 #, python-format
 msgid "Exception while connecting %s!\n"
 msgstr "Une exception est apparu au cours de la connexion %s !\n"
@@ -1627,7 +1674,7 @@
 msgid "Execution Order:"
 msgstr "Ordre d'exécution :"
 
-#: ../features.py:10
+#: ../features.py:11
 msgid "Experimental web based HMI"
 msgstr "IHM expérimentale utilisant les technologies web"
 
@@ -1639,15 +1686,16 @@
 msgid "Exponentiation"
 msgstr "Exponentiel"
 
-#: ../canfestival/canfestival.py:128
+#: ../canfestival/canfestival.py:150
 msgid "Export CanOpen slave to EDS file"
 msgstr "Exporter un esclave CANopen sous la forme d'un fichier EDS"
 
+#: ../controls/DebugVariablePanel.py:1472
 #: ../editors/GraphicViewer.py:144
 msgid "Export graph values to clipboard"
 msgstr "Exporter les valeurs du graphique vers le presse-papier"
 
-#: ../canfestival/canfestival.py:127
+#: ../canfestival/canfestival.py:149
 msgid "Export slave"
 msgstr "Exporter un esclave"
 
@@ -1659,7 +1707,7 @@
 msgid "External"
 msgstr "Externe"
 
-#: ../ProjectController.py:591
+#: ../ProjectController.py:605
 msgid "Extracting Located Variables...\n"
 msgstr "Extraction des variables adressées en cours...\n"
 
@@ -1670,16 +1718,16 @@
 msgid "FBD"
 msgstr "FBD"
 
-#: ../ProjectController.py:1445
+#: ../ProjectController.py:1480
 msgid "Failed : Must build before transfer.\n"
 msgstr "Echec : Le projet doit être compilé avant d'être transféré.\n"
 
-#: ../editors/Viewer.py:405
+#: ../editors/Viewer.py:404
 #: ../dialogs/LDElementDialog.py:84
 msgid "Falling Edge"
 msgstr "Front descendant"
 
-#: ../plcopen/structures.py:217
+#: ../plcopen/structures.py:216
 msgid ""
 "Falling edge detector\n"
 "The output produces a single pulse when a falling edge is detected."
@@ -1687,7 +1735,7 @@
 "Détecteur de front descendant\n"
 "La sortie produit une impulsion unique lorsqu'un front descendant est détecté."
 
-#: ../ProjectController.py:900
+#: ../ProjectController.py:927
 msgid "Fatal : cannot get builder.\n"
 msgstr "Erreur fatale : impossible de trouver un compilateur.\n"
 
@@ -1701,22 +1749,17 @@
 msgid "Fields %s haven't a valid value!"
 msgstr "Les champs %s n'ont pas une valeur valide !"
 
-#: ../editors/FileManagementPanel.py:209
-#, python-format
-msgid "File '%s' already exists!"
-msgstr "Le fichier '%s' existe déjà !"
-
-#: ../IDEFrame.py:353
+#: ../IDEFrame.py:358
 #: ../dialogs/FindInPouDialog.py:30
 #: ../dialogs/FindInPouDialog.py:99
 msgid "Find"
 msgstr "Rechercher"
 
-#: ../IDEFrame.py:355
+#: ../IDEFrame.py:360
 msgid "Find Next"
 msgstr "Recherche suivante"
 
-#: ../IDEFrame.py:357
+#: ../IDEFrame.py:362
 msgid "Find Previous"
 msgstr "Recherche précédente"
 
@@ -1732,12 +1775,12 @@
 msgid "Force runtime reload\n"
 msgstr "Redémarrage du runtime forcé\n"
 
-#: ../controls/DebugVariablePanel.py:295
-#: ../editors/Viewer.py:1353
+#: ../controls/DebugVariablePanel.py:1934
+#: ../editors/Viewer.py:1385
 msgid "Force value"
 msgstr "Forcer la valeur"
 
-#: ../dialogs/ForceVariableDialog.py:152
+#: ../dialogs/ForceVariableDialog.py:162
 msgid "Forcing Variable Value"
 msgstr "Forcer la valeur de la variable"
 
@@ -1750,7 +1793,7 @@
 msgid "Form isn't complete. %s must be filled!"
 msgstr "Le formulaire est incomplet. %s doit être complété !"
 
-#: ../dialogs/ConnectionDialog.py:142
+#: ../dialogs/ConnectionDialog.py:149
 #: ../dialogs/FBDBlockDialog.py:154
 msgid "Form isn't complete. Name must be filled!"
 msgstr "Le formulaire est incomplet. Le nom doit être complété !"
@@ -1771,16 +1814,16 @@
 msgid "Function"
 msgstr "Fonction"
 
-#: ../IDEFrame.py:329
+#: ../IDEFrame.py:334
 msgid "Function &Block"
 msgstr "&Bloc Fonctionnel"
 
-#: ../IDEFrame.py:1969
+#: ../IDEFrame.py:1845
 #: ../dialogs/SearchInProjectDialog.py:45
 msgid "Function Block"
 msgstr "Bloc fonctionnel"
 
-#: ../controls/VariablePanel.py:741
+#: ../controls/VariablePanel.py:744
 msgid "Function Block Types"
 msgstr "Types de blocs fonctionnels"
 
@@ -1792,11 +1835,7 @@
 msgid "Function Blocks can't be used in Functions!"
 msgstr "Les blocs fonctionnels ne peuvent être utilisés dans des functions !"
 
-#: ../editors/Viewer.py:238
-msgid "Function Blocks can't be used in Transitions!"
-msgstr "Les blocs fonctionnels ne peuvent être utilisés dans des transitions"
-
-#: ../PLCControler.py:2055
+#: ../PLCControler.py:2180
 #, python-format
 msgid "FunctionBlock \"%s\" can't be pasted in a Function!!!"
 msgstr "Le bloc fonctionnel \"%s\" ne peuvent être collés dans une function !"
@@ -1805,11 +1844,11 @@
 msgid "Functions"
 msgstr "Fonctions"
 
-#: ../PLCOpenEditor.py:138
+#: ../PLCOpenEditor.py:119
 msgid "Generate Program"
 msgstr "Générer le program"
 
-#: ../ProjectController.py:510
+#: ../ProjectController.py:524
 msgid "Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"
 msgstr "Création du code ST/IL/SFC de l'automate IEC-61131 en cours...\n"
 
@@ -1817,6 +1856,7 @@
 msgid "Global"
 msgstr "Globale"
 
+#: ../controls/DebugVariablePanel.py:1471
 #: ../editors/GraphicViewer.py:131
 msgid "Go to current value"
 msgstr "Aller à la valeur actuelle"
@@ -1841,7 +1881,7 @@
 msgid "Height:"
 msgstr "Hauteur :"
 
-#: ../editors/FileManagementPanel.py:303
+#: ../editors/FileManagementPanel.py:85
 msgid "Home Directory:"
 msgstr "Répertoire utilisateur :"
 
@@ -1853,7 +1893,7 @@
 msgid "Hours:"
 msgstr "Heures :"
 
-#: ../plcopen/structures.py:279
+#: ../plcopen/structures.py:278
 msgid ""
 "Hysteresis\n"
 "The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."
@@ -1861,7 +1901,7 @@
 "Hystérésis\n"
 "Le bloc functionnel hystérésis fourni un booléen en sortie suivant une courbe d'hystérésis entre les deux entrées réelles (REAL) XIN1 et XIN2."
 
-#: ../ProjectController.py:827
+#: ../ProjectController.py:851
 msgid "IEC-61131-3 code generation failed !\n"
 msgstr "La création du code IEC-61131-3 a échouée !\n"
 
@@ -1871,8 +1911,8 @@
 msgid "IL"
 msgstr "IL"
 
-#: ../Beremiz_service.py:356
-#: ../Beremiz_service.py:357
+#: ../Beremiz_service.py:361
+#: ../Beremiz_service.py:362
 msgid "IP is not valid!"
 msgstr "l'IP est invalide !"
 
@@ -1882,11 +1922,16 @@
 msgstr "Importer un SVG"
 
 #: ../controls/VariablePanel.py:76
+#: ../editors/Viewer.py:1412
 #: ../dialogs/FBDVariableDialog.py:34
 msgid "InOut"
 msgstr "Entrée-Sortie"
 
-#: ../controls/VariablePanel.py:263
+#: ../editors/Viewer.py:999
+msgid "Inactive"
+msgstr "Inactif"
+
+#: ../controls/VariablePanel.py:265
 #, python-format
 msgid "Incompatible data types between \"%s\" and \"%s\""
 msgstr "Types de donnée imcompatible entre \"%s\" et \"%s\""
@@ -1905,20 +1950,20 @@
 msgid "Indicator"
 msgstr "Indicateur"
 
-#: ../editors/Viewer.py:492
+#: ../editors/Viewer.py:491
 msgid "Initial Step"
 msgstr "Étape initiale"
 
 #: ../controls/VariablePanel.py:58
 #: ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
+#: ../editors/DataTypeEditor.py:50
 msgid "Initial Value"
 msgstr "Valeur initiale"
 
-#: ../editors/DataTypeEditor.py:178
-#: ../editors/DataTypeEditor.py:209
-#: ../editors/DataTypeEditor.py:265
-#: ../editors/DataTypeEditor.py:303
+#: ../editors/DataTypeEditor.py:184
+#: ../editors/DataTypeEditor.py:215
+#: ../editors/DataTypeEditor.py:271
+#: ../editors/DataTypeEditor.py:309
 msgid "Initial Value:"
 msgstr "Valeur initiale :"
 
@@ -1933,7 +1978,8 @@
 msgstr "Inline"
 
 #: ../controls/VariablePanel.py:76
-#: ../dialogs/BrowseLocationsDialog.py:36
+#: ../editors/Viewer.py:1410
+#: ../dialogs/BrowseLocationsDialog.py:35
 #: ../dialogs/FBDVariableDialog.py:33
 #: ../dialogs/SFCStepDialog.py:61
 msgid "Input"
@@ -1947,16 +1993,16 @@
 msgid "Insertion (into)"
 msgstr "Insertion (au milieu)"
 
-#: ../plcopen/plcopen.py:1833
+#: ../plcopen/plcopen.py:1843
 #, python-format
 msgid "Instance with id %d doesn't exist!"
 msgstr "L'instance dont l'id est %d n'existe pas !"
 
-#: ../editors/ResourceEditor.py:247
+#: ../editors/ResourceEditor.py:255
 msgid "Instances:"
 msgstr "Instances :"
 
-#: ../plcopen/structures.py:259
+#: ../plcopen/structures.py:258
 msgid ""
 "Integral\n"
 "The integral function block integrates the value of input XIN over time."
@@ -1968,16 +2014,16 @@
 msgid "Interface"
 msgstr "Interface"
 
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
 msgid "Interrupt"
 msgstr "Interruption"
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Interval"
 msgstr "Interval"
 
-#: ../PLCControler.py:2032
-#: ../PLCControler.py:2070
+#: ../PLCControler.py:2157
+#: ../PLCControler.py:2195
 msgid "Invalid plcopen element(s)!!!"
 msgstr "Les éléments plcopen ne sont pas valides !!! "
 
@@ -1987,13 +2033,13 @@
 msgid "Invalid type \"%s\"-> %d != %d  for location\"%s\""
 msgstr "Type invalide \"%s\"-> %d != %d pour cette adresse \"%s\""
 
-#: ../dialogs/ForceVariableDialog.py:167
+#: ../dialogs/ForceVariableDialog.py:177
 #, python-format
 msgid "Invalid value \"%s\" for \"%s\" variable!"
 msgstr "Valeur \"%s\" invalide pour une variable de type \"%s\" !"
 
-#: ../controls/DebugVariablePanel.py:153
-#: ../controls/DebugVariablePanel.py:156
+#: ../controls/DebugVariablePanel.py:319
+#: ../controls/DebugVariablePanel.py:322
 #, python-format
 msgid "Invalid value \"%s\" for debug variable"
 msgstr "Chemin de variable à déboguer \"%s\" invalide"
@@ -2018,7 +2064,7 @@
 "Valeur invalide !\n"
 "Vous devez rentrer une valeur numérique."
 
-#: ../editors/Viewer.py:497
+#: ../editors/Viewer.py:496
 msgid "Jump"
 msgstr "Renvoi"
 
@@ -2051,19 +2097,19 @@
 msgid "Language:"
 msgstr "Langue :"
 
-#: ../ProjectController.py:1451
+#: ../ProjectController.py:1486
 msgid "Latest build already matches current target. Transfering anyway...\n"
 msgstr "La dernière compilation correspond à la cible actuelle...\n"
 
-#: ../Beremiz_service.py:324
+#: ../Beremiz_service.py:331
 msgid "Launch WX GUI inspector"
 msgstr "Lancer un inspecteur d'IHM WX"
 
-#: ../Beremiz_service.py:323
+#: ../Beremiz_service.py:330
 msgid "Launch a live Python shell"
 msgstr "Lancer une console Python"
 
-#: ../editors/Viewer.py:428
+#: ../editors/Viewer.py:427
 msgid "Left"
 msgstr "Gauche"
 
@@ -2083,7 +2129,7 @@
 msgid "Less than or equal to"
 msgstr "Inférieur ou égal à"
 
-#: ../IDEFrame.py:600
+#: ../IDEFrame.py:604
 msgid "Library"
 msgstr "Librairie"
 
@@ -2100,7 +2146,11 @@
 msgid "Local"
 msgstr "Locale"
 
-#: ../ProjectController.py:1353
+#: ../canfestival/canfestival.py:322
+msgid "Local entries"
+msgstr "Entrées locales"
+
+#: ../ProjectController.py:1391
 msgid "Local service discovery failed!\n"
 msgstr "Echec de la sélection d'un service!\n"
 
@@ -2108,14 +2158,10 @@
 msgid "Location"
 msgstr "Adresse"
 
-#: ../dialogs/BrowseLocationsDialog.py:61
+#: ../dialogs/BrowseLocationsDialog.py:68
 msgid "Locations available:"
 msgstr "Adresses disponibles :"
 
-#: ../Beremiz.py:393
-msgid "Log Console"
-msgstr "Console de log"
-
 #: ../plcopen/iec_std.csv:25
 msgid "Logarithm to base 10"
 msgstr "Logarithme de base 10"
@@ -2125,20 +2171,20 @@
 msgid "MDNS resolution failure for '%s'\n"
 msgstr "Echec de la résolution MDNS pour '%s'\n"
 
-#: ../canfestival/SlaveEditor.py:37
-#: ../canfestival/NetworkEditor.py:67
+#: ../canfestival/SlaveEditor.py:41
+#: ../canfestival/NetworkEditor.py:62
 msgid "Map Variable"
 msgstr "Variable mappable"
 
-#: ../features.py:6
+#: ../features.py:7
 msgid "Map located variables over CANopen"
 msgstr "Mappe des variables localisées sur un bus CANopen"
 
-#: ../canfestival/NetworkEditor.py:89
+#: ../canfestival/NetworkEditor.py:83
 msgid "Master"
 msgstr "Maître"
 
-#: ../ConfigTreeNode.py:480
+#: ../ConfigTreeNode.py:500
 #, python-format
 msgid "Max count (%d) reached for this confnode of type %s "
 msgstr "Nombre limite(%d) atteint pour les plugin de type %s"
@@ -2147,15 +2193,15 @@
 msgid "Maximum"
 msgstr "Maximum"
 
-#: ../editors/DataTypeEditor.py:232
+#: ../editors/DataTypeEditor.py:238
 msgid "Maximum:"
 msgstr "Maximum :"
 
-#: ../dialogs/BrowseLocationsDialog.py:38
+#: ../dialogs/BrowseLocationsDialog.py:37
 msgid "Memory"
 msgstr "Mémoire"
 
-#: ../IDEFrame.py:568
+#: ../IDEFrame.py:572
 msgid "Menu ToolBar"
 msgstr "Barre d'outils du menu principal"
 
@@ -2163,7 +2209,7 @@
 msgid "Microseconds:"
 msgstr "Microsecondes :"
 
-#: ../editors/Viewer.py:433
+#: ../editors/Viewer.py:432
 msgid "Middle"
 msgstr "Milieu"
 
@@ -2175,7 +2221,7 @@
 msgid "Minimum"
 msgstr "Minimum"
 
-#: ../editors/DataTypeEditor.py:219
+#: ../editors/DataTypeEditor.py:225
 msgid "Minimum:"
 msgstr "Minimum :"
 
@@ -2191,8 +2237,8 @@
 msgid "Modifier:"
 msgstr "Modificateur :"
 
-#: ../PLCGenerator.py:703
-#: ../PLCGenerator.py:936
+#: ../PLCGenerator.py:732
+#: ../PLCGenerator.py:975
 #, python-format
 msgid "More than one connector found corresponding to \"%s\" continuation in \"%s\" POU"
 msgstr "Plusieurs connecteurs trouvés pour le prolongement \"%s\" dans le POU \"%s\""
@@ -2205,11 +2251,11 @@
 msgid "Move action up"
 msgstr "Déplacer une action vers le haut"
 
-#: ../controls/DebugVariablePanel.py:185
+#: ../controls/DebugVariablePanel.py:1532
 msgid "Move debug variable down"
 msgstr "Déplacer une variable à déboguer vers le bas"
 
-#: ../controls/DebugVariablePanel.py:184
+#: ../controls/DebugVariablePanel.py:1531
 msgid "Move debug variable up"
 msgstr "Déplacer une variable à déboguer vers le haut"
 
@@ -2217,34 +2263,34 @@
 msgid "Move down"
 msgstr "Déplacer vers le haut"
 
-#: ../editors/DataTypeEditor.py:348
+#: ../editors/DataTypeEditor.py:354
 msgid "Move element down"
 msgstr "Déplcer un élément vers le bas"
 
-#: ../editors/DataTypeEditor.py:347
+#: ../editors/DataTypeEditor.py:353
 msgid "Move element up"
 msgstr "Déplacer un élément vers le haut"
 
-#: ../editors/ResourceEditor.py:254
+#: ../editors/ResourceEditor.py:262
 msgid "Move instance down"
 msgstr "Déplacer une instance vers le bas"
 
-#: ../editors/ResourceEditor.py:253
+#: ../editors/ResourceEditor.py:261
 msgid "Move instance up"
 msgstr "Déplacer une instance vers le haut"
 
-#: ../editors/ResourceEditor.py:225
+#: ../editors/ResourceEditor.py:233
 msgid "Move task down"
 msgstr "Déplcer une tâche vers le bas"
 
-#: ../editors/ResourceEditor.py:224
+#: ../editors/ResourceEditor.py:232
 msgid "Move task up"
 msgstr "Déplacer une tâche vers le haut"
 
-#: ../IDEFrame.py:75
-#: ../IDEFrame.py:90
-#: ../IDEFrame.py:120
-#: ../IDEFrame.py:161
+#: ../IDEFrame.py:82
+#: ../IDEFrame.py:97
+#: ../IDEFrame.py:127
+#: ../IDEFrame.py:168
 msgid "Move the view"
 msgstr "Déplacer la vue"
 
@@ -2252,11 +2298,13 @@
 msgid "Move up"
 msgstr "Déplacer vers le bas"
 
-#: ../controls/VariablePanel.py:381
+#: ../controls/VariablePanel.py:383
+#: ../c_ext/CFileEditor.py:520
 msgid "Move variable down"
 msgstr "Déplacer une variable vers le bas"
 
-#: ../controls/VariablePanel.py:380
+#: ../controls/VariablePanel.py:382
+#: ../c_ext/CFileEditor.py:519
 msgid "Move variable up"
 msgstr "Déplacer une variable vers le haut"
 
@@ -2268,19 +2316,19 @@
 msgid "Multiplication"
 msgstr "Multiplication"
 
-#: ../editors/FileManagementPanel.py:301
+#: ../editors/FileManagementPanel.py:83
 msgid "My Computer:"
 msgstr "Poste de travail :"
 
 #: ../controls/VariablePanel.py:58
 #: ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
-#: ../editors/ResourceEditor.py:67
-#: ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50
+#: ../editors/ResourceEditor.py:68
+#: ../editors/ResourceEditor.py:77
 msgid "Name"
 msgstr "Nom"
 
-#: ../Beremiz_service.py:381
+#: ../Beremiz_service.py:386
 msgid "Name must not be null!"
 msgstr "Le nom ne doit pas être vide !"
 
@@ -2296,15 +2344,15 @@
 msgid "Natural logarithm"
 msgstr "Logarithme népérien"
 
-#: ../editors/Viewer.py:403
+#: ../editors/Viewer.py:402
 #: ../dialogs/LDElementDialog.py:67
 msgid "Negated"
 msgstr "Inversé"
 
-#: ../Beremiz.py:307
-#: ../Beremiz.py:342
-#: ../PLCOpenEditor.py:125
-#: ../PLCOpenEditor.py:167
+#: ../Beremiz.py:309
+#: ../Beremiz.py:344
+#: ../PLCOpenEditor.py:106
+#: ../PLCOpenEditor.py:148
 msgid "New"
 msgstr "Nouveau"
 
@@ -2312,30 +2360,30 @@
 msgid "New item"
 msgstr "Nouvel élément"
 
-#: ../editors/Viewer.py:402
+#: ../editors/Viewer.py:401
 msgid "No Modifier"
 msgstr "Pas de modificateur"
 
-#: ../PLCControler.py:2929
+#: ../PLCControler.py:3054
 msgid "No PLC project found"
 msgstr "Pas de projet d'automate trouvé"
 
-#: ../ProjectController.py:1478
+#: ../ProjectController.py:1513
 msgid "No PLC to transfer (did build succeed ?)\n"
 msgstr "Aucun automate à transférer (la compilation a-t-elle réussi ?)\n"
 
-#: ../PLCGenerator.py:1321
+#: ../PLCGenerator.py:1360
 #, python-format
 msgid "No body defined in \"%s\" POU"
 msgstr "Pas de code défini dans le POU \"%s\""
 
-#: ../PLCGenerator.py:722
-#: ../PLCGenerator.py:945
+#: ../PLCGenerator.py:751
+#: ../PLCGenerator.py:984
 #, python-format
 msgid "No connector found corresponding to \"%s\" continuation in \"%s\" POU"
 msgstr "Pas de connecteur trouvé pour le prolongement \"%s\" dans le POU \"%s\""
 
-#: ../PLCOpenEditor.py:370
+#: ../PLCOpenEditor.py:349
 msgid ""
 "No documentation available.\n"
 "Coming soon."
@@ -2343,19 +2391,15 @@
 "Pas de documentation.\n"
 "Bientôt disponible."
 
-#: ../PLCGenerator.py:744
+#: ../PLCGenerator.py:773
 #, python-format
 msgid "No informations found for \"%s\" block"
 msgstr "Aucune information trouvée pour le block \"%s\""
 
-#: ../plcopen/structures.py:167
+#: ../plcopen/structures.py:166
 msgid "No output variable found"
 msgstr "Pas de variable de sortie trouvée."
 
-#: ../Beremiz_service.py:394
-msgid "No running PLC"
-msgstr "Aucun automate en cours d'exécution"
-
 #: ../controls/SearchResultPanel.py:169
 msgid "No search results available."
 msgstr "Pas de résultat de recherche disponible."
@@ -2379,16 +2423,11 @@
 msgid "No valid value selected!"
 msgstr "Aucune valeur valide sélectionnée !"
 
-#: ../PLCGenerator.py:1319
+#: ../PLCGenerator.py:1358
 #, python-format
 msgid "No variable defined in \"%s\" POU"
 msgstr "Pas de varaibles définies dans le POU \"%s\""
 
-#: ../canfestival/SlaveEditor.py:49
-#: ../canfestival/NetworkEditor.py:79
-msgid "Node infos"
-msgstr "Propriétés du noeud"
-
 #: ../canfestival/config_utils.py:354
 #, python-format
 msgid "Non existing node ID : %d (variable %s)"
@@ -2419,7 +2458,7 @@
 msgid "Numerical"
 msgstr "Numérique"
 
-#: ../plcopen/structures.py:247
+#: ../plcopen/structures.py:246
 msgid ""
 "Off-delay timer\n"
 "The off-delay timer can be used to delay setting an output false, for fixed period after input goes false."
@@ -2427,7 +2466,7 @@
 "Temporisation avec retard à l'extinction\n"
 "La temporisation avec retard à l'extinction peut être utilisé pour retarder le passage de la sortie à l'état faux, d'une période fixe après le passage de l'entrée à l'état faux"
 
-#: ../plcopen/structures.py:242
+#: ../plcopen/structures.py:241
 msgid ""
 "On-delay timer\n"
 "The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."
@@ -2439,10 +2478,10 @@
 msgid "Only Elements"
 msgstr "Uniquement les éléments"
 
-#: ../Beremiz.py:309
-#: ../Beremiz.py:343
-#: ../PLCOpenEditor.py:127
-#: ../PLCOpenEditor.py:168
+#: ../Beremiz.py:311
+#: ../Beremiz.py:345
+#: ../PLCOpenEditor.py:108
+#: ../PLCOpenEditor.py:149
 msgid "Open"
 msgstr "Ouvrir"
 
@@ -2450,7 +2489,7 @@
 msgid "Open Inkscape"
 msgstr "Ouverture de Inkscape"
 
-#: ../ProjectController.py:1530
+#: ../ProjectController.py:1565
 msgid "Open a file explorer to manage project files"
 msgstr "Ouvrir un explorateur de fichier pour gérer les fichiers de projet"
 
@@ -2471,29 +2510,30 @@
 msgid "Organization (optional):"
 msgstr "Groupe (optionnel) :"
 
-#: ../canfestival/SlaveEditor.py:47
-#: ../canfestival/NetworkEditor.py:77
+#: ../canfestival/SlaveEditor.py:51
+#: ../canfestival/NetworkEditor.py:72
 msgid "Other Profile"
 msgstr "Autre profil"
 
 #: ../controls/VariablePanel.py:76
-#: ../dialogs/BrowseLocationsDialog.py:37
+#: ../editors/Viewer.py:1411
+#: ../dialogs/BrowseLocationsDialog.py:36
 #: ../dialogs/FBDVariableDialog.py:35
 #: ../dialogs/SFCStepDialog.py:65
 msgid "Output"
 msgstr "Sortie"
 
-#: ../canfestival/SlaveEditor.py:36
-#: ../canfestival/NetworkEditor.py:66
+#: ../canfestival/SlaveEditor.py:40
+#: ../canfestival/NetworkEditor.py:61
 msgid "PDO Receive"
 msgstr "PDO reçu"
 
-#: ../canfestival/SlaveEditor.py:35
-#: ../canfestival/NetworkEditor.py:65
+#: ../canfestival/SlaveEditor.py:39
+#: ../canfestival/NetworkEditor.py:60
 msgid "PDO Transmit"
 msgstr "PDO transmis"
 
-#: ../plcopen/structures.py:269
+#: ../plcopen/structures.py:268
 msgid ""
 "PID\n"
 "The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."
@@ -2505,19 +2545,17 @@
 msgid "PLC :\n"
 msgstr "Automate :\n"
 
-#: ../ProjectController.py:1096
-#: ../ProjectController.py:1398
-#, python-format
-msgid "PLC is %s\n"
-msgstr "L'automate est dans l'état %s\n"
-
-#: ../PLCOpenEditor.py:313
-#: ../PLCOpenEditor.py:391
+#: ../Beremiz.py:425
+msgid "PLC Log"
+msgstr "Log de l'automate"
+
+#: ../PLCOpenEditor.py:294
+#: ../PLCOpenEditor.py:370
 msgid "PLCOpen files (*.xml)|*.xml|All files|*.*"
 msgstr "Fichiers PLCOpen (*.xml)|*.xml|Tous les fichiers|*.*"
 
-#: ../PLCOpenEditor.py:175
-#: ../PLCOpenEditor.py:231
+#: ../PLCOpenEditor.py:156
+#: ../PLCOpenEditor.py:212
 msgid "PLCOpenEditor"
 msgstr "PLCOpenEditor"
 
@@ -2537,8 +2575,8 @@
 msgid "POU Type:"
 msgstr "Type du POU :"
 
-#: ../Beremiz.py:322
-#: ../PLCOpenEditor.py:141
+#: ../Beremiz.py:324
+#: ../PLCOpenEditor.py:122
 msgid "Page Setup"
 msgstr "Mise en page..."
 
@@ -2546,22 +2584,22 @@
 msgid "Page Size (optional):"
 msgstr "Taille de la page (optionnel) :"
 
-#: ../PLCOpenEditor.py:476
+#: ../IDEFrame.py:2492
 #, python-format
 msgid "Page: %d"
 msgstr "Page: %d"
 
-#: ../controls/PouInstanceVariablesPanel.py:41
+#: ../controls/PouInstanceVariablesPanel.py:48
 msgid "Parent instance"
 msgstr "Instance parent"
 
-#: ../IDEFrame.py:350
-#: ../IDEFrame.py:402
+#: ../IDEFrame.py:355
+#: ../IDEFrame.py:407
 #: ../editors/Viewer.py:537
 msgid "Paste"
 msgstr "Coller"
 
-#: ../IDEFrame.py:1900
+#: ../IDEFrame.py:1776
 msgid "Paste POU"
 msgstr "Coller un POU"
 
@@ -2573,16 +2611,16 @@
 msgid "Pin number:"
 msgstr "Nombre de pattes :"
 
-#: ../editors/Viewer.py:2289
-#: ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2363
+#: ../editors/Viewer.py:2670
 #: ../editors/SFCViewer.py:696
 msgid "Please choose a target"
 msgstr "Choisissez une cible"
 
-#: ../editors/Viewer.py:2112
-#: ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630
-#: ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186
+#: ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706
+#: ../editors/Viewer.py:2708
 msgid "Please enter comment text"
 msgstr "Saisissez le texte du commentaire"
 
@@ -2592,16 +2630,16 @@
 msgid "Please enter step name"
 msgstr "Saisissez le nom de l'étape"
 
-#: ../dialogs/ForceVariableDialog.py:153
+#: ../dialogs/ForceVariableDialog.py:163
 #, python-format
 msgid "Please enter value for a \"%s\" variable:"
 msgstr "Veuillez entrer la valeur pour une variable de type \"%s\" :"
 
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
 msgid "Port number must be 0 <= port <= 65535!"
 msgstr "Le numéro de port doit être compris entre 0 et 65535 !"
 
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
 msgid "Port number must be an integer!"
 msgstr "Le numéro de port doit être un entier !"
 
@@ -2609,7 +2647,7 @@
 msgid "Position:"
 msgstr "Position :"
 
-#: ../editors/Viewer.py:476
+#: ../editors/Viewer.py:475
 msgid "Power Rail"
 msgstr "Barre d'alimentation"
 
@@ -2617,8 +2655,8 @@
 msgid "Power Rail Properties"
 msgstr "Propriétés de la barre d'alimentation"
 
-#: ../Beremiz.py:324
-#: ../PLCOpenEditor.py:143
+#: ../Beremiz.py:326
+#: ../PLCOpenEditor.py:124
 msgid "Preview"
 msgstr "Aperçu avant impression"
 
@@ -2633,18 +2671,18 @@
 msgid "Preview:"
 msgstr "Aperçu :"
 
-#: ../Beremiz.py:326
-#: ../Beremiz.py:346
-#: ../PLCOpenEditor.py:145
-#: ../PLCOpenEditor.py:171
+#: ../Beremiz.py:328
+#: ../Beremiz.py:348
+#: ../PLCOpenEditor.py:126
+#: ../PLCOpenEditor.py:152
 msgid "Print"
 msgstr "Imprimer"
 
-#: ../IDEFrame.py:1155
+#: ../IDEFrame.py:1038
 msgid "Print preview"
 msgstr "Aperçu avant impression"
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Priority"
 msgstr "Priorité"
 
@@ -2652,6 +2690,11 @@
 msgid "Priority:"
 msgstr "Priorité :"
 
+#: ../runtime/PLCObject.py:318
+#, python-format
+msgid "Problem starting PLC : error %d"
+msgstr "Problème au démarrage du PLC : erreur %d"
+
 #: ../controls/ProjectPropertiesPanel.py:80
 msgid "Product Name (required):"
 msgstr "Nom du produit (obligatoire) :"
@@ -2664,12 +2707,12 @@
 msgid "Product Version (required):"
 msgstr "Version du produit (obligatoire) :"
 
-#: ../IDEFrame.py:1972
+#: ../IDEFrame.py:1848
 #: ../dialogs/SearchInProjectDialog.py:46
 msgid "Program"
 msgstr "Programme"
 
-#: ../PLCOpenEditor.py:360
+#: ../PLCOpenEditor.py:339
 msgid "Program was successfully generated!"
 msgstr "Le programme a été généré avec succès !"
 
@@ -2682,7 +2725,7 @@
 msgstr "Les programmes ne peuvent être utilisés par les autres POUs !"
 
 #: ../controls/ProjectPropertiesPanel.py:84
-#: ../IDEFrame.py:553
+#: ../IDEFrame.py:557
 msgid "Project"
 msgstr "Projet"
 
@@ -2691,7 +2734,7 @@
 msgid "Project '%s':"
 msgstr "Projet  '%s' :"
 
-#: ../ProjectController.py:1529
+#: ../ProjectController.py:1564
 msgid "Project Files"
 msgstr "Fichiers de projet"
 
@@ -2703,7 +2746,7 @@
 msgid "Project Version (optional):"
 msgstr "Version du projet (optionnel) :"
 
-#: ../PLCControler.py:2916
+#: ../PLCControler.py:3041
 msgid ""
 "Project file syntax error:\n"
 "\n"
@@ -2711,20 +2754,25 @@
 "Erreur de syntaxe dans le fichier du projet :\n"
 "\n"
 
+#: ../editors/ProjectNodeEditor.py:14
 #: ../dialogs/ProjectDialog.py:32
 msgid "Project properties"
 msgstr "Propriétés du projet"
 
-#: ../ConfigTreeNode.py:506
+#: ../ConfigTreeNode.py:526
 #, python-format
 msgid "Project tree layout do not match confnode.xml %s!=%s "
 msgstr "L'organisation du projet ne correspond pas à plugin.xml %s!=%s"
 
+#: ../dialogs/ConnectionDialog.py:96
+msgid "Propagate Name"
+msgstr "Propager le nom"
+
 #: ../PLCControler.py:96
 msgid "Properties"
 msgstr "Propriétés"
 
-#: ../plcopen/structures.py:237
+#: ../plcopen/structures.py:236
 msgid ""
 "Pulse timer\n"
 "The pulse timer can be used to generate output pulses of a given time duration."
@@ -2732,7 +2780,11 @@
 "Temporisation à impulsion\n"
 "La temporisation à impulsion peut être utilisée pour générer sur la sortie des impulsions d'une durée déterminée."
 
-#: ../features.py:8
+#: ../py_ext/PythonEditor.py:61
+msgid "Python code"
+msgstr "Code Python"
+
+#: ../features.py:9
 msgid "Python file"
 msgstr "Fichier Python"
 
@@ -2740,13 +2792,13 @@
 msgid "Qualifier"
 msgstr "Qualificatif"
 
-#: ../Beremiz_service.py:328
-#: ../Beremiz.py:329
-#: ../PLCOpenEditor.py:151
+#: ../Beremiz_service.py:333
+#: ../Beremiz.py:331
+#: ../PLCOpenEditor.py:132
 msgid "Quit"
 msgstr "Quitter"
 
-#: ../plcopen/structures.py:202
+#: ../plcopen/structures.py:201
 msgid ""
 "RS bistable\n"
 "The RS bistable is a latch where the Reset dominates."
@@ -2754,7 +2806,7 @@
 "Bascule RS\n"
 "La bascule RS est une bascule où le Reset est dominant."
 
-#: ../plcopen/structures.py:274
+#: ../plcopen/structures.py:273
 msgid ""
 "Ramp\n"
 "The RAMP function block is modelled on example given in the standard."
@@ -2762,15 +2814,16 @@
 "Rampe\n"
 "Le bloc fonctionnel RAMP est basé sur l'exemple du standard."
 
+#: ../controls/DebugVariablePanel.py:1462
 #: ../editors/GraphicViewer.py:89
 msgid "Range:"
 msgstr "Echelle :"
 
-#: ../ProjectController.py:1525
+#: ../ProjectController.py:1560
 msgid "Raw IEC code"
 msgstr "Ajout code IEC"
 
-#: ../plcopen/structures.py:254
+#: ../plcopen/structures.py:253
 msgid ""
 "Real time clock\n"
 "The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."
@@ -2778,13 +2831,13 @@
 "Horloge temps réel\n"
 "L'horloge temps réel est utilisée dans de nombreux cas tels que l'horodatage, la définition des dates et heures dans des rapports de commandes, dans des messages d'alarme et bien d'autres."
 
-#: ../Beremiz.py:1039
+#: ../Beremiz.py:1072
 #, python-format
 msgid "Really delete node '%s'?"
 msgstr "Êtes-vous sûr de vouloir supprimer le noeud '%s' ?"
 
-#: ../IDEFrame.py:340
-#: ../IDEFrame.py:398
+#: ../IDEFrame.py:345
+#: ../IDEFrame.py:403
 msgid "Redo"
 msgstr "Refaire"
 
@@ -2793,7 +2846,7 @@
 msgid "Reference"
 msgstr "Référence"
 
-#: ../IDEFrame.py:408
+#: ../IDEFrame.py:413
 #: ../dialogs/DiscoveryDialog.py:105
 msgid "Refresh"
 msgstr "Actualiser"
@@ -2806,8 +2859,8 @@
 msgid "Regular expressions"
 msgstr "Expressions régulières"
 
-#: ../controls/DebugVariablePanel.py:299
-#: ../editors/Viewer.py:1356
+#: ../controls/DebugVariablePanel.py:1938
+#: ../editors/Viewer.py:1388
 msgid "Release value"
 msgstr "Relacher la valeur"
 
@@ -2815,7 +2868,7 @@
 msgid "Remainder (modulo)"
 msgstr "Modulo"
 
-#: ../Beremiz.py:1040
+#: ../Beremiz.py:1073
 #, python-format
 msgid "Remove %s node"
 msgstr "Enlever un noeud %s"
@@ -2824,39 +2877,40 @@
 msgid "Remove action"
 msgstr "Supprimer une action"
 
-#: ../controls/DebugVariablePanel.py:183
+#: ../controls/DebugVariablePanel.py:1530
 msgid "Remove debug variable"
 msgstr "Supprimer une variable à déboguer"
 
-#: ../editors/DataTypeEditor.py:346
+#: ../editors/DataTypeEditor.py:352
 msgid "Remove element"
 msgstr "Supprimer un élément"
 
-#: ../editors/FileManagementPanel.py:281
+#: ../editors/FileManagementPanel.py:63
 msgid "Remove file from left folder"
 msgstr "Supprimer un fichier du dossier de gauche"
 
-#: ../editors/ResourceEditor.py:252
+#: ../editors/ResourceEditor.py:260
 msgid "Remove instance"
 msgstr "Supprimer une instance"
 
-#: ../canfestival/NetworkEditor.py:87
+#: ../canfestival/NetworkEditor.py:81
 msgid "Remove slave"
 msgstr "Enlever l'esclave"
 
-#: ../editors/ResourceEditor.py:223
+#: ../editors/ResourceEditor.py:231
 msgid "Remove task"
 msgstr "Supprimer la tâche"
 
-#: ../controls/VariablePanel.py:379
+#: ../controls/VariablePanel.py:381
+#: ../c_ext/CFileEditor.py:518
 msgid "Remove variable"
 msgstr "Supprimer une variable"
 
-#: ../IDEFrame.py:1976
+#: ../IDEFrame.py:1852
 msgid "Rename"
 msgstr "Renommer"
 
-#: ../editors/FileManagementPanel.py:399
+#: ../editors/FileManagementPanel.py:181
 msgid "Replace File"
 msgstr "Remplacer un fichier"
 
@@ -2872,7 +2926,7 @@
 msgid "Reset Execution Order"
 msgstr "Réinitialiser l'order d'exécution"
 
-#: ../IDEFrame.py:423
+#: ../IDEFrame.py:428
 msgid "Reset Perspective"
 msgstr "Réinitialiser l'interface"
 
@@ -2892,11 +2946,11 @@
 msgid "Retain"
 msgstr "Persistante"
 
-#: ../controls/VariablePanel.py:352
+#: ../controls/VariablePanel.py:354
 msgid "Return Type:"
 msgstr "Type de retour :"
 
-#: ../editors/Viewer.py:430
+#: ../editors/Viewer.py:429
 msgid "Right"
 msgstr "Droite"
 
@@ -2904,12 +2958,12 @@
 msgid "Right PowerRail"
 msgstr "Barre d'alimentation à droite"
 
-#: ../editors/Viewer.py:404
+#: ../editors/Viewer.py:403
 #: ../dialogs/LDElementDialog.py:80
 msgid "Rising Edge"
 msgstr "Front montant"
 
-#: ../plcopen/structures.py:212
+#: ../plcopen/structures.py:211
 msgid ""
 "Rising edge detector\n"
 "The output produces a single pulse when a rising edge is detected."
@@ -2929,22 +2983,22 @@
 msgid "Rounding up/down"
 msgstr "Arrondi"
 
-#: ../ProjectController.py:1493
+#: ../ProjectController.py:1528
 msgid "Run"
 msgstr "Exécuter"
 
-#: ../ProjectController.py:841
-#: ../ProjectController.py:850
+#: ../ProjectController.py:865
+#: ../ProjectController.py:874
 msgid "Runtime extensions C code generation failed !\n"
 msgstr "La génération du code des plugins a échoué !\n"
 
-#: ../canfestival/SlaveEditor.py:34
-#: ../canfestival/NetworkEditor.py:64
+#: ../canfestival/SlaveEditor.py:38
+#: ../canfestival/NetworkEditor.py:59
 msgid "SDO Client"
 msgstr "Client SDO"
 
-#: ../canfestival/SlaveEditor.py:33
-#: ../canfestival/NetworkEditor.py:63
+#: ../canfestival/SlaveEditor.py:37
+#: ../canfestival/NetworkEditor.py:58
 msgid "SDO Server"
 msgstr "Serveur SDO"
 
@@ -2953,7 +3007,7 @@
 msgid "SFC"
 msgstr "SFC"
 
-#: ../plcopen/structures.py:197
+#: ../plcopen/structures.py:196
 msgid ""
 "SR bistable\n"
 "The SR bistable is a latch where the Set dominates."
@@ -2967,7 +3021,7 @@
 msgid "ST"
 msgstr "ST"
 
-#: ../PLCOpenEditor.py:347
+#: ../PLCOpenEditor.py:326
 msgid "ST files (*.st)|*.st|All files|*.*"
 msgstr "Fichiers ST (*.st)|*.st|Tous les fichiers|*.*"
 
@@ -2975,24 +3029,24 @@
 msgid "SVG files (*.svg)|*.svg|All files|*.*"
 msgstr "Fichiers SVG (*.svg)|*.svg|Tous les fichiers|*.*"
 
-#: ../features.py:10
+#: ../features.py:11
 msgid "SVGUI"
 msgstr "SVGUI"
 
-#: ../Beremiz.py:313
-#: ../Beremiz.py:344
-#: ../PLCOpenEditor.py:134
-#: ../PLCOpenEditor.py:169
+#: ../Beremiz.py:315
+#: ../Beremiz.py:346
+#: ../PLCOpenEditor.py:115
+#: ../PLCOpenEditor.py:150
 msgid "Save"
 msgstr "Enregistrer"
 
-#: ../Beremiz.py:345
-#: ../PLCOpenEditor.py:136
-#: ../PLCOpenEditor.py:170
+#: ../Beremiz.py:347
+#: ../PLCOpenEditor.py:117
+#: ../PLCOpenEditor.py:151
 msgid "Save As..."
 msgstr "Enregistrer sous..."
 
-#: ../Beremiz.py:315
+#: ../Beremiz.py:317
 msgid "Save as"
 msgstr "Enregistrer sous..."
 
@@ -3000,13 +3054,13 @@
 msgid "Scope"
 msgstr "Contexte"
 
-#: ../IDEFrame.py:592
+#: ../IDEFrame.py:596
 #: ../dialogs/SearchInProjectDialog.py:105
 msgid "Search"
 msgstr "Rechercher"
 
-#: ../IDEFrame.py:360
-#: ../IDEFrame.py:404
+#: ../IDEFrame.py:365
+#: ../IDEFrame.py:409
 #: ../dialogs/SearchInProjectDialog.py:52
 msgid "Search in Project"
 msgstr "Rechercher dans le projet"
@@ -3015,25 +3069,26 @@
 msgid "Seconds:"
 msgstr "Secondes :"
 
-#: ../IDEFrame.py:366
+#: ../IDEFrame.py:371
 msgid "Select All"
 msgstr "Tout sélectionner"
 
+#: ../controls/LocationCellEditor.py:97
 #: ../controls/VariablePanel.py:277
-#: ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../editors/TextViewer.py:323
+#: ../editors/Viewer.py:275
 msgid "Select a variable class:"
 msgstr "Sélectionner une direction pour la variable :"
 
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
 msgid "Select an editor:"
 msgstr "Sélectionner un éditeur :"
 
-#: ../controls/PouInstanceVariablesPanel.py:197
+#: ../controls/PouInstanceVariablesPanel.py:209
 msgid "Select an instance"
 msgstr "Sélectionnez une instance"
 
-#: ../IDEFrame.py:576
+#: ../IDEFrame.py:580
 msgid "Select an object"
 msgstr "Sélectionner un objet"
 
@@ -3049,7 +3104,7 @@
 msgid "Selection Divergence"
 msgstr "Divergence simple"
 
-#: ../plcopen/structures.py:207
+#: ../plcopen/structures.py:206
 msgid ""
 "Semaphore\n"
 "The semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."
@@ -3073,19 +3128,19 @@
 msgid "Shift right"
 msgstr "Décalage à droite"
 
-#: ../ProjectController.py:1519
+#: ../ProjectController.py:1554
 msgid "Show IEC code generated by PLCGenerator"
 msgstr "Afficher le code IEC généré par PLCGenerator"
 
-#: ../canfestival/canfestival.py:288
+#: ../canfestival/canfestival.py:363
 msgid "Show Master"
 msgstr "Afficher le maître"
 
-#: ../canfestival/canfestival.py:289
+#: ../canfestival/canfestival.py:364
 msgid "Show Master generated by config_utils"
 msgstr "Afficher le maître généré par config_utils"
 
-#: ../ProjectController.py:1517
+#: ../ProjectController.py:1552
 msgid "Show code"
 msgstr "Afficher le code"
 
@@ -3101,7 +3156,7 @@
 msgid "Sine"
 msgstr "Sinus"
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Single"
 msgstr "Evènement"
 
@@ -3109,53 +3164,53 @@
 msgid "Square root (base 2)"
 msgstr "Racine carré (base 2)"
 
-#: ../plcopen/structures.py:193
+#: ../plcopen/structures.py:192
 msgid "Standard function blocks"
 msgstr "Blocs fonctionnels standards"
 
-#: ../Beremiz_service.py:319
-#: ../ProjectController.py:1495
+#: ../Beremiz_service.py:321
+#: ../ProjectController.py:1530
 msgid "Start PLC"
 msgstr "Démarrer l'automate"
 
-#: ../ProjectController.py:819
+#: ../ProjectController.py:843
 #, python-format
 msgid "Start build in %s\n"
 msgstr "Début de la compilation dans %s\n"
 
-#: ../ProjectController.py:1314
+#: ../ProjectController.py:1341
 msgid "Starting PLC\n"
 msgstr "Démarrer l'automate\n"
 
-#: ../Beremiz.py:403
+#: ../Beremiz.py:435
 msgid "Status ToolBar"
 msgstr "Barre d'outils de statut"
 
-#: ../editors/Viewer.py:493
+#: ../editors/Viewer.py:492
 msgid "Step"
 msgstr "Étape"
 
-#: ../ProjectController.py:1498
+#: ../ProjectController.py:1533
 msgid "Stop"
 msgstr "Arrêter"
 
-#: ../Beremiz_service.py:320
+#: ../Beremiz_service.py:322
 msgid "Stop PLC"
 msgstr "Arrêter l'automate"
 
-#: ../ProjectController.py:1500
+#: ../ProjectController.py:1535
 msgid "Stop Running PLC"
 msgstr "Arrêter l'automate en cours d'exécution"
 
-#: ../ProjectController.py:1292
+#: ../ProjectController.py:1318
 msgid "Stopping debugger...\n"
 msgstr "Arrêt du débogage en cours\n"
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Structure"
 msgstr "Structure"
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Subrange"
 msgstr "Sous-ensemble"
 
@@ -3163,7 +3218,7 @@
 msgid "Subtraction"
 msgstr "Soustraction"
 
-#: ../ProjectController.py:915
+#: ../ProjectController.py:942
 msgid "Successfully built.\n"
 msgstr "Compilé avec succès.\n"
 
@@ -3175,11 +3230,11 @@
 msgid "Tangent"
 msgstr "Tangente"
 
-#: ../editors/ResourceEditor.py:76
+#: ../editors/ResourceEditor.py:77
 msgid "Task"
 msgstr "Tâche"
 
-#: ../editors/ResourceEditor.py:218
+#: ../editors/ResourceEditor.py:226
 msgid "Tasks:"
 msgstr "Tâches :"
 
@@ -3187,7 +3242,7 @@
 msgid "Temp"
 msgstr "Temporaire"
 
-#: ../editors/FileManagementPanel.py:398
+#: ../editors/FileManagementPanel.py:180
 #, python-format
 msgid ""
 "The file '%s' already exist.\n"
@@ -3196,22 +3251,22 @@
 "Le fichier '%s' existe déjà.\n"
 "Voulez-vous le remplacer ?"
 
-#: ../editors/LDViewer.py:879
+#: ../editors/LDViewer.py:882
 msgid "The group of block must be coherent!"
 msgstr "Le groupe de blocs doit être cohérent !"
 
-#: ../IDEFrame.py:1091
-#: ../Beremiz.py:555
+#: ../IDEFrame.py:974
+#: ../Beremiz.py:590
 msgid "There are changes, do you want to save?"
 msgstr "Le projet a été modifié. Voulez-vous l'enregistrer ?"
 
-#: ../IDEFrame.py:1709
-#: ../IDEFrame.py:1728
+#: ../IDEFrame.py:1590
+#: ../IDEFrame.py:1609
 #, python-format
 msgid "There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?"
 msgstr "Un POU a pour nom \"%s\". Cela peut générer des conflits. Voulez-vous continuer ?"
 
-#: ../IDEFrame.py:1178
+#: ../IDEFrame.py:1061
 msgid ""
 "There was a problem printing.\n"
 "Perhaps your current printer is not set correctly?"
@@ -3219,7 +3274,7 @@
 "Un problème est apparu lors de l'impression.\n"
 "Peut-être que votre imprimante n'est pas correctement configurée ?"
 
-#: ../editors/LDViewer.py:888
+#: ../editors/LDViewer.py:891
 msgid "This option isn't available yet!"
 msgstr "Cette option n'a pas encore disponible"
 
@@ -3267,31 +3322,31 @@
 msgid "Time-of-day subtraction"
 msgstr "Soustraction d'horodatage"
 
-#: ../editors/Viewer.py:432
+#: ../editors/Viewer.py:431
 msgid "Top"
 msgstr "Haut"
 
-#: ../ProjectController.py:1507
+#: ../ProjectController.py:1542
 msgid "Transfer"
 msgstr "Transférer"
 
-#: ../ProjectController.py:1509
+#: ../ProjectController.py:1544
 msgid "Transfer PLC"
 msgstr "Transférer l'automate"
 
-#: ../ProjectController.py:1474
+#: ../ProjectController.py:1509
 msgid "Transfer completed successfully.\n"
 msgstr "Transfert effectué avec succès.\n"
 
-#: ../ProjectController.py:1476
+#: ../ProjectController.py:1511
 msgid "Transfer failed\n"
 msgstr "Le transfert a échoué\n"
 
-#: ../editors/Viewer.py:494
+#: ../editors/Viewer.py:493
 msgid "Transition"
 msgstr "Transition"
 
-#: ../PLCGenerator.py:1212
+#: ../PLCGenerator.py:1252
 #, python-format
 msgid "Transition \"%s\" body must contain an output variable or coil referring to its name"
 msgstr "Le code de la transition \"%s\"  doit contenir une variable de sortie ou un relai dont la référence est son nom"
@@ -3304,17 +3359,17 @@
 msgid "Transition Name:"
 msgstr "Nom de la transition :"
 
-#: ../PLCGenerator.py:1301
+#: ../PLCGenerator.py:1340
 #, python-format
 msgid "Transition with content \"%s\" not connected to a next step in \"%s\" POU"
 msgstr "La transition contenant \"%s\" n'est pas connectée à une étape en sortie dans le POU \"%s\" !"
 
-#: ../PLCGenerator.py:1292
+#: ../PLCGenerator.py:1331
 #, python-format
 msgid "Transition with content \"%s\" not connected to a previous step in \"%s\" POU"
 msgstr "La transition contenant \"%s\" n'est pas connectée à une étape en entrée dans le POU \"%s\" !"
 
-#: ../plcopen/plcopen.py:1442
+#: ../plcopen/plcopen.py:1447
 #, python-format
 msgid "Transition with name %s doesn't exist!"
 msgstr "La transition nommée %s n'existe pas !"
@@ -3323,18 +3378,22 @@
 msgid "Transitions"
 msgstr "Transitions"
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Triggering"
 msgstr "Activation"
 
 #: ../controls/VariablePanel.py:58
 #: ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
-#: ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50
+#: ../editors/ResourceEditor.py:77
 #: ../dialogs/ActionBlockDialog.py:37
 msgid "Type"
 msgstr "Type"
 
+#: ../dialogs/BrowseLocationsDialog.py:44
+msgid "Type and derivated"
+msgstr "Type et ses dérivés"
+
 #: ../canfestival/config_utils.py:335
 #: ../canfestival/config_utils.py:617
 #, python-format
@@ -3345,12 +3404,17 @@
 msgid "Type conversion"
 msgstr "Conversion de type"
 
-#: ../editors/DataTypeEditor.py:155
+#: ../editors/DataTypeEditor.py:161
 msgid "Type infos:"
 msgstr "Propriétés du type :"
 
+#: ../dialogs/BrowseLocationsDialog.py:45
+msgid "Type strict"
+msgstr "Type uniquement"
+
 #: ../dialogs/SFCDivergenceDialog.py:51
 #: ../dialogs/LDPowerRailDialog.py:51
+#: ../dialogs/BrowseLocationsDialog.py:95
 #: ../dialogs/ConnectionDialog.py:52
 #: ../dialogs/SFCTransitionDialog.py:53
 #: ../dialogs/FBDBlockDialog.py:48
@@ -3368,33 +3432,33 @@
 msgid "Unable to get Xenomai's %s \n"
 msgstr "Unable to get Xenomai's %s \n"
 
-#: ../PLCGenerator.py:865
-#: ../PLCGenerator.py:924
+#: ../PLCGenerator.py:904
+#: ../PLCGenerator.py:963
 #, python-format
 msgid "Undefined block type \"%s\" in \"%s\" POU"
 msgstr "Type de block \"%s\" indéfini dans le POU \"%s\""
 
-#: ../PLCGenerator.py:240
+#: ../PLCGenerator.py:252
 #, python-format
 msgid "Undefined pou type \"%s\""
 msgstr "Type de POU \"%s\" indéterminé !"
 
-#: ../IDEFrame.py:338
-#: ../IDEFrame.py:397
+#: ../IDEFrame.py:343
+#: ../IDEFrame.py:402
 msgid "Undo"
 msgstr "Défaire"
 
-#: ../ProjectController.py:254
+#: ../ProjectController.py:262
 msgid "Unknown"
 msgstr "Inconnu"
 
-#: ../editors/Viewer.py:336
+#: ../editors/Viewer.py:335
 #, python-format
 msgid "Unknown variable \"%s\" for this POU!"
 msgstr "Variable \"%s\" inconnue dans ce POU !"
 
-#: ../ProjectController.py:251
-#: ../ProjectController.py:252
+#: ../ProjectController.py:259
+#: ../ProjectController.py:260
 msgid "Unnamed"
 msgstr "SansNom"
 
@@ -3408,7 +3472,7 @@
 msgid "Unrecognized data size \"%s\""
 msgstr "Taille de donnée \"%s\" non identifié !"
 
-#: ../plcopen/structures.py:222
+#: ../plcopen/structures.py:221
 msgid ""
 "Up-counter\n"
 "The up-counter can be used to signal when a count has reached a maximum value."
@@ -3416,7 +3480,7 @@
 "Compteur incrémental\n"
 "Le compteur incrémental peut être utilisé pour signaler lorsque le compteur a atteint la valeur maximale."
 
-#: ../plcopen/structures.py:232
+#: ../plcopen/structures.py:231
 msgid ""
 "Up-down counter\n"
 "The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other."
@@ -3424,13 +3488,13 @@
 "Compteur bidirectionnel\n"
 "Le compteur bidirectionnel a deux entrées CU et CD. Il peut être utilisé pour compter de façon incrémentale ou décrémentale sur l'une ou l'autre des entrées."
 
-#: ../controls/VariablePanel.py:709
-#: ../editors/DataTypeEditor.py:623
+#: ../controls/VariablePanel.py:712
+#: ../editors/DataTypeEditor.py:631
 msgid "User Data Types"
 msgstr "Types de donnée du projet"
 
-#: ../canfestival/SlaveEditor.py:38
-#: ../canfestival/NetworkEditor.py:68
+#: ../canfestival/SlaveEditor.py:42
+#: ../canfestival/NetworkEditor.py:63
 msgid "User Type"
 msgstr "Type utilisateur"
 
@@ -3438,7 +3502,7 @@
 msgid "User-defined POUs"
 msgstr "POUs du projet"
 
-#: ../controls/DebugVariablePanel.py:40
+#: ../controls/DebugVariablePanel.py:58
 #: ../dialogs/ActionBlockDialog.py:37
 msgid "Value"
 msgstr "Valeur"
@@ -3447,12 +3511,12 @@
 msgid "Values"
 msgstr "Valeurs"
 
-#: ../editors/DataTypeEditor.py:252
+#: ../editors/DataTypeEditor.py:258
 msgid "Values:"
 msgstr "Valeurs"
 
-#: ../controls/DebugVariablePanel.py:40
-#: ../editors/Viewer.py:466
+#: ../controls/DebugVariablePanel.py:58
+#: ../editors/Viewer.py:465
 #: ../dialogs/ActionBlockDialog.py:41
 msgid "Variable"
 msgstr "Variable"
@@ -3461,14 +3525,15 @@
 msgid "Variable Properties"
 msgstr "Propriétés de la variable"
 
+#: ../controls/LocationCellEditor.py:97
 #: ../controls/VariablePanel.py:277
-#: ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../editors/TextViewer.py:323
+#: ../editors/Viewer.py:275
 msgid "Variable class"
 msgstr "Direction de la variable"
 
-#: ../editors/TextViewer.py:374
-#: ../editors/Viewer.py:338
+#: ../editors/TextViewer.py:367
+#: ../editors/Viewer.py:337
 msgid "Variable don't belong to this POU!"
 msgstr "La variable n'appartient pas à ce POU !"
 
@@ -3484,16 +3549,16 @@
 msgid "WXGLADE GUI"
 msgstr "IHM WXGlade"
 
-#: ../ProjectController.py:1276
+#: ../ProjectController.py:1302
 msgid "Waiting debugger to recover...\n"
 msgstr "En attente de la mise en route du déboggueur...\n"
 
-#: ../editors/LDViewer.py:888
+#: ../editors/LDViewer.py:891
 #: ../dialogs/PouDialog.py:126
 msgid "Warning"
 msgstr "Attention"
 
-#: ../ProjectController.py:515
+#: ../ProjectController.py:529
 msgid "Warnings in ST/IL/SFC code generator :\n"
 msgstr "Mises en garde du generateur de code ST/IL/SFC :\n"
 
@@ -3509,7 +3574,7 @@
 msgid "Wrap search"
 msgstr "Boucler"
 
-#: ../features.py:9
+#: ../features.py:10
 msgid "WxGlade GUI"
 msgstr "Interface WxGlade"
 
@@ -3529,7 +3594,7 @@
 "Vous n'avez pas les permissions d'écriture.\n"
 "Ouvrir wxGlade tout de même ?"
 
-#: ../ProjectController.py:220
+#: ../ProjectController.py:224
 msgid ""
 "You must have permission to work on the project\n"
 "Work on a project copy ?"
@@ -3537,11 +3602,11 @@
 "Vous n'avez pas la permission de travailler sur le projet.\n"
 "Travailler sur une copie du projet ?"
 
-#: ../editors/LDViewer.py:883
+#: ../editors/LDViewer.py:886
 msgid "You must select the block or group of blocks around which a branch should be added!"
 msgstr "Vous devez sélectionné le bloc ou le group autour duquel un ebranche doit être ajoutée !"
 
-#: ../editors/LDViewer.py:663
+#: ../editors/LDViewer.py:666
 msgid "You must select the wire where a contact should be added!"
 msgstr "Vous devez sélectionner le fil sur lequel le contact doit être ajouté !"
 
@@ -3551,11 +3616,11 @@
 msgid "You must type a name!"
 msgstr "Vous devez saisir un nom !"
 
-#: ../dialogs/ForceVariableDialog.py:165
+#: ../dialogs/ForceVariableDialog.py:175
 msgid "You must type a value!"
 msgstr "Vous devez saisir une valeur !"
 
-#: ../IDEFrame.py:414
+#: ../IDEFrame.py:419
 msgid "Zoom"
 msgstr "Zoom"
 
@@ -3563,7 +3628,7 @@
 msgid "Zoom:"
 msgstr "Zoom :"
 
-#: ../PLCOpenEditor.py:356
+#: ../PLCOpenEditor.py:335
 #, python-format
 msgid "error: %s\n"
 msgstr "erreur: %s\n"
@@ -3573,8 +3638,8 @@
 msgid "exited with status %s (pid %s)\n"
 msgstr "a quitté avec le status %s (pid %s)\n"
 
-#: ../PLCOpenEditor.py:508
-#: ../PLCOpenEditor.py:510
+#: ../PLCOpenEditor.py:393
+#: ../PLCOpenEditor.py:395
 msgid "file : "
 msgstr "fichier :"
 
@@ -3582,7 +3647,7 @@
 msgid "function"
 msgstr "fonction"
 
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
 msgid "function : "
 msgstr "fonction :"
 
@@ -3590,7 +3655,7 @@
 msgid "functionBlock"
 msgstr "Bloc fonctionnel"
 
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
 msgid "line : "
 msgstr "ligne :"
 
@@ -3610,7 +3675,7 @@
 msgid "string right of"
 msgstr "Caractères à droite de"
 
-#: ../PLCOpenEditor.py:354
+#: ../PLCOpenEditor.py:333
 #, python-format
 msgid "warning: %s\n"
 msgstr "attention: %s\n"
@@ -3646,9 +3711,6 @@
 msgid "CAN_Driver"
 msgstr "Driver CAN"
 
-msgid "Debug_mode"
-msgstr "Mode de débogage"
-
 msgid "CExtension"
 msgstr "Extension C"
 
@@ -3700,6 +3762,28 @@
 msgid "Disable_Extensions"
 msgstr "Disable_Extensions"
 
+#~ msgid "Debug connect matching running PLC\n"
+#~ msgstr "L'automate connecté correspond au project ouvert.\n"
+
+#~ msgid "File '%s' already exists!"
+#~ msgstr "Le fichier '%s' existe déjà !"
+
+#~ msgid "Function Blocks can't be used in Transitions!"
+#~ msgstr ""
+#~ "Les blocs fonctionnels ne peuvent être utilisés dans des transitions"
+
+#~ msgid "No running PLC"
+#~ msgstr "Aucun automate en cours d'exécution"
+
+#~ msgid "Node infos"
+#~ msgstr "Propriétés du noeud"
+
+#~ msgid "PLC is %s\n"
+#~ msgstr "L'automate est dans l'état %s\n"
+
+#~ msgid "Debug_mode"
+#~ msgstr "Mode de débogage"
+
 #, fuzzy
 #~ msgid "Close Project\tCTRL+SHIFT+W"
 #~ msgstr ""
@@ -3814,9 +3898,6 @@
 #~ msgid "Block Types"
 #~ msgstr "Types de blocs"
 
-#~ msgid "CSV Log"
-#~ msgstr "Log CVS"
-
 #~ msgid "Delete Task"
 #~ msgstr "Supprimer une tâche"
 
@@ -3844,9 +3925,6 @@
 #~ msgid "Plugins"
 #~ msgstr "Plugins"
 
-#~ msgid "Types"
-#~ msgstr "Types"
-
 #~ msgid "Create a new POU from"
 #~ msgstr "Créer un nouveau POU à partir de"
 
--- a/i18n/messages.pot	Wed Mar 13 12:34:55 2013 +0900
+++ b/i18n/messages.pot	Wed Jul 31 10:45:07 2013 +0900
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-09-07 01:17+0200\n"
+"POT-Creation-Date: 2013-03-26 22:55+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,7 +17,7 @@
 "Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: ../PLCOpenEditor.py:520
+#: ../PLCOpenEditor.py:405
 msgid ""
 "\n"
 "An error has occurred.\n"
@@ -30,7 +30,7 @@
 "Error:\n"
 msgstr ""
 
-#: ../Beremiz.py:1071
+#: ../Beremiz.py:1119
 #, python-format
 msgid ""
 "\n"
@@ -69,7 +69,7 @@
 msgid "   Temp"
 msgstr ""
 
-#: ../PLCOpenEditor.py:530
+#: ../PLCOpenEditor.py:415
 msgid " : "
 msgstr ""
 
@@ -79,7 +79,7 @@
 msgid " and %s"
 msgstr ""
 
-#: ../ProjectController.py:890
+#: ../ProjectController.py:917
 msgid " generation failed !\n"
 msgstr ""
 
@@ -103,7 +103,7 @@
 msgid "\"%s\" can't use itself!"
 msgstr ""
 
-#: ../IDEFrame.py:1706 ../IDEFrame.py:1725
+#: ../IDEFrame.py:1587 ../IDEFrame.py:1606
 #, python-format
 msgid "\"%s\" config already exists!"
 msgstr ""
@@ -113,38 +113,38 @@
 msgid "\"%s\" configuration already exists !!!"
 msgstr ""
 
-#: ../IDEFrame.py:1660
+#: ../IDEFrame.py:1541
 #, python-format
 msgid "\"%s\" data type already exists!"
 msgstr ""
 
-#: ../PLCControler.py:2040 ../PLCControler.py:2044
+#: ../PLCControler.py:2165 ../PLCControler.py:2169
 #, python-format
 msgid "\"%s\" element can't be pasted here!!!"
 msgstr ""
 
-#: ../editors/TextViewer.py:305 ../editors/TextViewer.py:325
-#: ../editors/Viewer.py:252 ../dialogs/PouTransitionDialog.py:105
-#: ../dialogs/ConnectionDialog.py:150 ../dialogs/PouActionDialog.py:102
+#: ../editors/TextViewer.py:298 ../editors/TextViewer.py:318
+#: ../editors/Viewer.py:250 ../dialogs/PouTransitionDialog.py:105
+#: ../dialogs/ConnectionDialog.py:157 ../dialogs/PouActionDialog.py:102
 #: ../dialogs/FBDBlockDialog.py:162
 #, python-format
 msgid "\"%s\" element for this pou already exists!"
 msgstr ""
 
-#: ../Beremiz.py:894
+#: ../Beremiz.py:921
 #, python-format
 msgid "\"%s\" folder is not a valid Beremiz project\n"
 msgstr ""
 
-#: ../plcopen/structures.py:106
+#: ../plcopen/structures.py:105
 #, python-format
 msgid "\"%s\" function cancelled in \"%s\" POU: No input connected"
 msgstr ""
 
-#: ../controls/VariablePanel.py:656 ../IDEFrame.py:1651
-#: ../editors/DataTypeEditor.py:548 ../editors/DataTypeEditor.py:577
+#: ../controls/VariablePanel.py:659 ../IDEFrame.py:1532
+#: ../editors/DataTypeEditor.py:554 ../editors/DataTypeEditor.py:583
 #: ../dialogs/PouNameDialog.py:49 ../dialogs/PouTransitionDialog.py:101
-#: ../dialogs/SFCStepNameDialog.py:51 ../dialogs/ConnectionDialog.py:146
+#: ../dialogs/SFCStepNameDialog.py:51 ../dialogs/ConnectionDialog.py:153
 #: ../dialogs/FBDVariableDialog.py:199 ../dialogs/PouActionDialog.py:98
 #: ../dialogs/PouDialog.py:118 ../dialogs/SFCStepDialog.py:122
 #: ../dialogs/FBDBlockDialog.py:158
@@ -152,41 +152,41 @@
 msgid "\"%s\" is a keyword. It can't be used!"
 msgstr ""
 
-#: ../editors/Viewer.py:240
+#: ../editors/Viewer.py:238
 #, python-format
 msgid "\"%s\" is already used by \"%s\"!"
 msgstr ""
 
-#: ../plcopen/plcopen.py:2786
+#: ../plcopen/plcopen.py:2836
 #, python-format
 msgid "\"%s\" is an invalid value!"
 msgstr ""
 
-#: ../PLCOpenEditor.py:362 ../PLCOpenEditor.py:399
+#: ../PLCOpenEditor.py:341 ../PLCOpenEditor.py:378
 #, python-format
 msgid "\"%s\" is not a valid folder!"
 msgstr ""
 
-#: ../controls/VariablePanel.py:654 ../IDEFrame.py:1649
-#: ../editors/DataTypeEditor.py:572 ../dialogs/PouNameDialog.py:47
+#: ../controls/VariablePanel.py:657 ../IDEFrame.py:1530
+#: ../editors/DataTypeEditor.py:578 ../dialogs/PouNameDialog.py:47
 #: ../dialogs/PouTransitionDialog.py:99 ../dialogs/SFCStepNameDialog.py:49
-#: ../dialogs/ConnectionDialog.py:144 ../dialogs/PouActionDialog.py:96
+#: ../dialogs/ConnectionDialog.py:151 ../dialogs/PouActionDialog.py:96
 #: ../dialogs/PouDialog.py:116 ../dialogs/SFCStepDialog.py:120
 #: ../dialogs/FBDBlockDialog.py:156
 #, python-format
 msgid "\"%s\" is not a valid identifier!"
 msgstr ""
 
-#: ../IDEFrame.py:214 ../IDEFrame.py:2445 ../IDEFrame.py:2464
+#: ../IDEFrame.py:221 ../IDEFrame.py:2313 ../IDEFrame.py:2332
 #, python-format
 msgid "\"%s\" is used by one or more POUs. It can't be removed!"
 msgstr ""
 
-#: ../controls/VariablePanel.py:311 ../IDEFrame.py:1669
-#: ../editors/TextViewer.py:303 ../editors/TextViewer.py:323
-#: ../editors/TextViewer.py:360 ../editors/Viewer.py:250
-#: ../editors/Viewer.py:295 ../editors/Viewer.py:312
-#: ../dialogs/ConnectionDialog.py:148 ../dialogs/PouDialog.py:120
+#: ../controls/VariablePanel.py:313 ../IDEFrame.py:1550
+#: ../editors/TextViewer.py:296 ../editors/TextViewer.py:316
+#: ../editors/TextViewer.py:353 ../editors/Viewer.py:248
+#: ../editors/Viewer.py:293 ../editors/Viewer.py:311
+#: ../dialogs/ConnectionDialog.py:155 ../dialogs/PouDialog.py:120
 #: ../dialogs/FBDBlockDialog.py:160
 #, python-format
 msgid "\"%s\" pou already exists!"
@@ -207,29 +207,29 @@
 msgid "\"%s\" step already exists!"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:543
+#: ../editors/DataTypeEditor.py:549
 #, python-format
 msgid "\"%s\" value already defined!"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:719 ../dialogs/ArrayTypeDialog.py:97
+#: ../editors/DataTypeEditor.py:744 ../dialogs/ArrayTypeDialog.py:97
 #, python-format
 msgid "\"%s\" value isn't a valid array dimension!"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:726 ../dialogs/ArrayTypeDialog.py:103
+#: ../editors/DataTypeEditor.py:751 ../dialogs/ArrayTypeDialog.py:103
 #, python-format
 msgid ""
 "\"%s\" value isn't a valid array dimension!\n"
 "Right value must be greater than left value."
 msgstr ""
 
-#: ../PLCControler.py:793
+#: ../PLCControler.py:847
 #, python-format
 msgid "%s \"%s\" can't be pasted as a %s."
 msgstr ""
 
-#: ../PLCControler.py:1422
+#: ../PLCControler.py:1476
 #, python-format
 msgid "%s Data Types"
 msgstr ""
@@ -239,86 +239,86 @@
 msgid "%s Graphics"
 msgstr ""
 
-#: ../PLCControler.py:1417
+#: ../PLCControler.py:1471
 #, python-format
 msgid "%s POUs"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:42 ../canfestival/NetworkEditor.py:72
+#: ../canfestival/SlaveEditor.py:46 ../canfestival/NetworkEditor.py:67
 #, python-format
 msgid "%s Profile"
 msgstr ""
 
-#: ../plcopen/plcopen.py:1780 ../plcopen/plcopen.py:1790
-#: ../plcopen/plcopen.py:1800 ../plcopen/plcopen.py:1810
-#: ../plcopen/plcopen.py:1819
+#: ../plcopen/plcopen.py:1790 ../plcopen/plcopen.py:1800
+#: ../plcopen/plcopen.py:1810 ../plcopen/plcopen.py:1820
+#: ../plcopen/plcopen.py:1829
 #, python-format
 msgid "%s body don't have instances!"
 msgstr ""
 
-#: ../plcopen/plcopen.py:1842 ../plcopen/plcopen.py:1849
+#: ../plcopen/plcopen.py:1852 ../plcopen/plcopen.py:1859
 #, python-format
 msgid "%s body don't have text!"
 msgstr ""
 
-#: ../IDEFrame.py:364
+#: ../IDEFrame.py:369
 msgid "&Add Element"
 msgstr ""
 
-#: ../IDEFrame.py:334
+#: ../IDEFrame.py:339
 msgid "&Configuration"
 msgstr ""
 
-#: ../IDEFrame.py:325
+#: ../IDEFrame.py:330
 msgid "&Data Type"
 msgstr ""
 
-#: ../IDEFrame.py:368
+#: ../IDEFrame.py:373
 msgid "&Delete"
 msgstr ""
 
-#: ../IDEFrame.py:317
+#: ../IDEFrame.py:322
 msgid "&Display"
 msgstr ""
 
-#: ../IDEFrame.py:316
+#: ../IDEFrame.py:321
 msgid "&Edit"
 msgstr ""
 
-#: ../IDEFrame.py:315
+#: ../IDEFrame.py:320
 msgid "&File"
 msgstr ""
 
-#: ../IDEFrame.py:327
+#: ../IDEFrame.py:332
 msgid "&Function"
 msgstr ""
 
-#: ../IDEFrame.py:318
+#: ../IDEFrame.py:323
 msgid "&Help"
 msgstr ""
 
-#: ../IDEFrame.py:331
+#: ../IDEFrame.py:336
 msgid "&Program"
 msgstr ""
 
-#: ../PLCOpenEditor.py:148
+#: ../PLCOpenEditor.py:129
 msgid "&Properties"
 msgstr ""
 
-#: ../Beremiz.py:310
+#: ../Beremiz.py:312
 msgid "&Recent Projects"
 msgstr ""
 
-#: ../Beremiz.py:352
+#: ../Beremiz.py:354
 msgid "&Resource"
 msgstr ""
 
-#: ../controls/SearchResultPanel.py:237
+#: ../controls/SearchResultPanel.py:252
 #, python-format
 msgid "'%s' - %d match in project"
 msgstr ""
 
-#: ../controls/SearchResultPanel.py:239
+#: ../controls/SearchResultPanel.py:254
 #, python-format
 msgid "'%s' - %d matches in project"
 msgstr ""
@@ -328,12 +328,12 @@
 msgid "'%s' is located at %s\n"
 msgstr ""
 
-#: ../controls/SearchResultPanel.py:289
+#: ../controls/SearchResultPanel.py:304
 #, python-format
 msgid "(%d matches)"
 msgstr ""
 
-#: ../PLCOpenEditor.py:508 ../PLCOpenEditor.py:510 ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:393 ../PLCOpenEditor.py:395 ../PLCOpenEditor.py:396
 msgid ",   "
 msgstr ""
 
@@ -343,21 +343,21 @@
 msgid ", %s"
 msgstr ""
 
-#: ../PLCOpenEditor.py:506
+#: ../PLCOpenEditor.py:391
 msgid ". "
 msgstr ""
 
-#: ../ProjectController.py:1268
+#: ../ProjectController.py:1294
 msgid "... debugger recovered\n"
 msgstr ""
 
-#: ../IDEFrame.py:1672 ../IDEFrame.py:1714 ../IDEFrame.py:1733
+#: ../IDEFrame.py:1553 ../IDEFrame.py:1595 ../IDEFrame.py:1614
 #: ../dialogs/PouDialog.py:122
 #, python-format
 msgid "A POU has an element named \"%s\". This could cause a conflict. Do you wish to continue?"
 msgstr ""
 
-#: ../controls/VariablePanel.py:658 ../IDEFrame.py:1684 ../IDEFrame.py:1695
+#: ../controls/VariablePanel.py:661 ../IDEFrame.py:1565 ../IDEFrame.py:1576
 #: ../dialogs/PouNameDialog.py:51 ../dialogs/PouTransitionDialog.py:103
 #: ../dialogs/SFCStepNameDialog.py:53 ../dialogs/PouActionDialog.py:100
 #: ../dialogs/SFCStepDialog.py:124
@@ -365,30 +365,30 @@
 msgid "A POU named \"%s\" already exists!"
 msgstr ""
 
-#: ../ConfigTreeNode.py:371
+#: ../ConfigTreeNode.py:388
 #, python-format
 msgid "A child named \"%s\" already exist -> \"%s\"\n"
 msgstr ""
 
-#: ../dialogs/BrowseLocationsDialog.py:175
+#: ../dialogs/BrowseLocationsDialog.py:212
 msgid "A location must be selected!"
 msgstr ""
 
-#: ../controls/VariablePanel.py:660 ../IDEFrame.py:1686 ../IDEFrame.py:1697
+#: ../controls/VariablePanel.py:663 ../IDEFrame.py:1567 ../IDEFrame.py:1578
 #: ../dialogs/SFCStepNameDialog.py:55 ../dialogs/SFCStepDialog.py:126
 #, python-format
 msgid "A variable with \"%s\" as name already exists in this pou!"
 msgstr ""
 
-#: ../Beremiz.py:362 ../PLCOpenEditor.py:181
+#: ../Beremiz.py:364 ../PLCOpenEditor.py:162
 msgid "About"
 msgstr ""
 
-#: ../Beremiz.py:931
+#: ../Beremiz.py:957
 msgid "About Beremiz"
 msgstr ""
 
-#: ../PLCOpenEditor.py:376
+#: ../PLCOpenEditor.py:355
 msgid "About PLCOpenEditor"
 msgstr ""
 
@@ -400,7 +400,7 @@
 msgid "Action"
 msgstr ""
 
-#: ../editors/Viewer.py:495
+#: ../editors/Viewer.py:494
 msgid "Action Block"
 msgstr ""
 
@@ -412,7 +412,7 @@
 msgid "Action Name:"
 msgstr ""
 
-#: ../plcopen/plcopen.py:1480
+#: ../plcopen/plcopen.py:1490
 #, python-format
 msgid "Action with name %s doesn't exist!"
 msgstr ""
@@ -425,28 +425,32 @@
 msgid "Actions:"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:54 ../canfestival/NetworkEditor.py:84
-#: ../editors/Viewer.py:527
+#: ../editors/Viewer.py:999
+msgid "Active"
+msgstr ""
+
+#: ../canfestival/SlaveEditor.py:57 ../canfestival/NetworkEditor.py:78
+#: ../Beremiz.py:987 ../editors/Viewer.py:527
 msgid "Add"
 msgstr ""
 
-#: ../IDEFrame.py:1925 ../IDEFrame.py:1956
+#: ../IDEFrame.py:1801 ../IDEFrame.py:1832
 msgid "Add Action"
 msgstr ""
 
-#: ../features.py:7
+#: ../features.py:8
 msgid "Add C code accessing located variables synchronously"
 msgstr ""
 
-#: ../IDEFrame.py:1908
+#: ../IDEFrame.py:1784
 msgid "Add Configuration"
 msgstr ""
 
-#: ../IDEFrame.py:1888
+#: ../IDEFrame.py:1764
 msgid "Add DataType"
 msgstr ""
 
-#: ../editors/Viewer.py:453
+#: ../editors/Viewer.py:452
 msgid "Add Divergence Branch"
 msgstr ""
 
@@ -454,23 +458,23 @@
 msgid "Add IP"
 msgstr ""
 
-#: ../IDEFrame.py:1896
+#: ../IDEFrame.py:1772
 msgid "Add POU"
 msgstr ""
 
-#: ../features.py:8
+#: ../features.py:9
 msgid "Add Python code executed asynchronously"
 msgstr ""
 
-#: ../IDEFrame.py:1936 ../IDEFrame.py:1982
+#: ../IDEFrame.py:1812 ../IDEFrame.py:1858
 msgid "Add Resource"
 msgstr ""
 
-#: ../IDEFrame.py:1914 ../IDEFrame.py:1953
+#: ../IDEFrame.py:1790 ../IDEFrame.py:1829
 msgid "Add Transition"
 msgstr ""
 
-#: ../editors/Viewer.py:442
+#: ../editors/Viewer.py:441
 msgid "Add Wire Segment"
 msgstr ""
 
@@ -478,7 +482,7 @@
 msgid "Add a new initial step"
 msgstr ""
 
-#: ../editors/Viewer.py:2289 ../editors/SFCViewer.py:696
+#: ../editors/Viewer.py:2363 ../editors/SFCViewer.py:696
 msgid "Add a new jump"
 msgstr ""
 
@@ -486,7 +490,7 @@
 msgid "Add a new step"
 msgstr ""
 
-#: ../features.py:9
+#: ../features.py:10
 msgid "Add a simple WxGlade based GUI."
 msgstr ""
 
@@ -494,23 +498,23 @@
 msgid "Add action"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:345
+#: ../editors/DataTypeEditor.py:351
 msgid "Add element"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:251
+#: ../editors/ResourceEditor.py:259
 msgid "Add instance"
 msgstr ""
 
-#: ../canfestival/NetworkEditor.py:86
+#: ../canfestival/NetworkEditor.py:80
 msgid "Add slave"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:222
+#: ../editors/ResourceEditor.py:230
 msgid "Add task"
 msgstr ""
 
-#: ../controls/VariablePanel.py:378
+#: ../controls/VariablePanel.py:380 ../c_ext/CFileEditor.py:517
 msgid "Add variable"
 msgstr ""
 
@@ -518,16 +522,22 @@
 msgid "Addition"
 msgstr ""
 
-#: ../plcopen/structures.py:250
+#: ../plcopen/structures.py:249
 msgid "Additional function blocks"
 msgstr ""
 
-#: ../editors/Viewer.py:1395
+#: ../editors/Viewer.py:510
+msgid "Adjust Block Size"
+msgstr ""
+
+#: ../editors/Viewer.py:1458
 msgid "Alignment"
 msgstr ""
 
-#: ../controls/VariablePanel.py:75 ../dialogs/BrowseLocationsDialog.py:35
-#: ../dialogs/BrowseLocationsDialog.py:116
+#: ../controls/VariablePanel.py:75 ../dialogs/BrowseLocationsDialog.py:34
+#: ../dialogs/BrowseLocationsDialog.py:43
+#: ../dialogs/BrowseLocationsDialog.py:136
+#: ../dialogs/BrowseLocationsDialog.py:139
 msgid "All"
 msgstr ""
 
@@ -535,15 +545,19 @@
 msgid "All files (*.*)|*.*|CSV files (*.csv)|*.csv"
 msgstr ""
 
-#: ../ProjectController.py:1335
+#: ../ProjectController.py:1373
 msgid "Already connected. Please disconnect\n"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:587
+#: ../editors/DataTypeEditor.py:593
 #, python-format
 msgid "An element named \"%s\" already exists in this structure!"
 msgstr ""
 
+#: ../dialogs/ConnectionDialog.py:98
+msgid "Apply name modification to all continuations with the same name"
+msgstr ""
+
 #: ../plcopen/iec_std.csv:31
 msgid "Arc cosine"
 msgstr ""
@@ -560,7 +574,8 @@
 msgid "Arithmetic"
 msgstr ""
 
-#: ../controls/VariablePanel.py:729 ../editors/DataTypeEditor.py:52
+#: ../controls/VariablePanel.py:732 ../editors/DataTypeEditor.py:54
+#: ../editors/DataTypeEditor.py:634
 msgid "Array"
 msgstr ""
 
@@ -597,16 +612,16 @@
 msgid "Bad location size : %s"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:168 ../editors/DataTypeEditor.py:198
-#: ../editors/DataTypeEditor.py:290 ../dialogs/ArrayTypeDialog.py:55
+#: ../editors/DataTypeEditor.py:174 ../editors/DataTypeEditor.py:204
+#: ../editors/DataTypeEditor.py:296 ../dialogs/ArrayTypeDialog.py:55
 msgid "Base Type:"
 msgstr ""
 
-#: ../controls/VariablePanel.py:699 ../editors/DataTypeEditor.py:617
+#: ../controls/VariablePanel.py:702 ../editors/DataTypeEditor.py:624
 msgid "Base Types"
 msgstr ""
 
-#: ../Beremiz.py:486
+#: ../Beremiz.py:511
 msgid "Beremiz"
 msgstr ""
 
@@ -638,7 +653,7 @@
 msgid "Bitwise inverting"
 msgstr ""
 
-#: ../editors/Viewer.py:465
+#: ../editors/Viewer.py:464
 msgid "Block"
 msgstr ""
 
@@ -646,7 +661,7 @@
 msgid "Block Properties"
 msgstr ""
 
-#: ../editors/Viewer.py:434
+#: ../editors/Viewer.py:433
 msgid "Bottom"
 msgstr ""
 
@@ -655,31 +670,35 @@
 msgid "Browse %s values library"
 msgstr ""
 
-#: ../dialogs/BrowseLocationsDialog.py:55
+#: ../dialogs/BrowseLocationsDialog.py:61
 msgid "Browse Locations"
 msgstr ""
 
-#: ../ProjectController.py:1484
+#: ../ProjectController.py:1519
 msgid "Build"
 msgstr ""
 
-#: ../ProjectController.py:1051
+#: ../ProjectController.py:1079
 msgid "Build directory already clean\n"
 msgstr ""
 
-#: ../ProjectController.py:1485
+#: ../ProjectController.py:1520
 msgid "Build project into build folder"
 msgstr ""
 
-#: ../ProjectController.py:910
+#: ../ProjectController.py:937
 msgid "C Build crashed !\n"
 msgstr ""
 
-#: ../ProjectController.py:907
+#: ../ProjectController.py:934
 msgid "C Build failed.\n"
 msgstr ""
 
-#: ../ProjectController.py:895
+#: ../c_ext/CFileEditor.py:731
+msgid "C code"
+msgstr ""
+
+#: ../ProjectController.py:922
 msgid "C code generated successfully.\n"
 msgstr ""
 
@@ -688,16 +707,24 @@
 msgid "C compilation of %s failed.\n"
 msgstr ""
 
+#: ../features.py:8
+msgid "C extension"
+msgstr ""
+
+#: ../canfestival/NetworkEditor.py:29
+msgid "CANOpen network"
+msgstr ""
+
+#: ../canfestival/SlaveEditor.py:21
+msgid "CANOpen slave"
+msgstr ""
+
 #: ../features.py:7
-msgid "C extension"
-msgstr ""
-
-#: ../features.py:6
 msgid "CANopen support"
 msgstr ""
 
-#: ../plcopen/plcopen.py:1722 ../plcopen/plcopen.py:1736
-#: ../plcopen/plcopen.py:1757 ../plcopen/plcopen.py:1773
+#: ../plcopen/plcopen.py:1732 ../plcopen/plcopen.py:1746
+#: ../plcopen/plcopen.py:1767 ../plcopen/plcopen.py:1783
 msgid "Can only generate execution order on FBD networks!"
 msgstr ""
 
@@ -705,7 +732,7 @@
 msgid "Can only give a location to local or global variables"
 msgstr ""
 
-#: ../PLCOpenEditor.py:357
+#: ../PLCOpenEditor.py:336
 #, python-format
 msgid "Can't generate program to file %s!"
 msgstr ""
@@ -714,21 +741,21 @@
 msgid "Can't give a location to a function block instance"
 msgstr ""
 
-#: ../PLCOpenEditor.py:397
+#: ../PLCOpenEditor.py:376
 #, python-format
 msgid "Can't save project to file %s!"
 msgstr ""
 
-#: ../controls/VariablePanel.py:298
+#: ../controls/VariablePanel.py:300
 msgid "Can't set an initial value to a function block instance"
 msgstr ""
 
-#: ../ConfigTreeNode.py:470
+#: ../ConfigTreeNode.py:490
 #, python-format
 msgid "Cannot create child %s of type %s "
 msgstr ""
 
-#: ../ConfigTreeNode.py:400
+#: ../ConfigTreeNode.py:417
 #, python-format
 msgid "Cannot find lower free IEC channel than %d\n"
 msgstr ""
@@ -737,7 +764,7 @@
 msgid "Cannot get PLC status - connection failed.\n"
 msgstr ""
 
-#: ../ProjectController.py:715
+#: ../ProjectController.py:737
 msgid "Cannot open/parse VARIABLES.csv!\n"
 msgstr ""
 
@@ -750,27 +777,27 @@
 msgid "Case sensitive"
 msgstr ""
 
-#: ../editors/Viewer.py:429
+#: ../editors/Viewer.py:428
 msgid "Center"
 msgstr ""
 
-#: ../Beremiz_service.py:322
+#: ../Beremiz_service.py:326
 msgid "Change IP of interface to bind"
 msgstr ""
 
-#: ../Beremiz_service.py:321
+#: ../Beremiz_service.py:325
 msgid "Change Name"
 msgstr ""
 
-#: ../IDEFrame.py:1974
+#: ../IDEFrame.py:1850
 msgid "Change POU Type To"
 msgstr ""
 
-#: ../Beremiz_service.py:325
+#: ../Beremiz_service.py:327
 msgid "Change Port Number"
 msgstr ""
 
-#: ../Beremiz_service.py:327
+#: ../Beremiz_service.py:328
 msgid "Change working directory"
 msgstr ""
 
@@ -782,16 +809,16 @@
 msgid "Choose a SVG file"
 msgstr ""
 
-#: ../ProjectController.py:353
+#: ../ProjectController.py:364
 msgid "Choose a directory to save project"
 msgstr ""
 
-#: ../canfestival/canfestival.py:118 ../PLCOpenEditor.py:313
-#: ../PLCOpenEditor.py:347 ../PLCOpenEditor.py:391
+#: ../canfestival/canfestival.py:136 ../PLCOpenEditor.py:294
+#: ../PLCOpenEditor.py:326 ../PLCOpenEditor.py:370
 msgid "Choose a file"
 msgstr ""
 
-#: ../Beremiz.py:831 ../Beremiz.py:866
+#: ../Beremiz.py:858 ../Beremiz.py:893
 msgid "Choose a project"
 msgstr ""
 
@@ -800,15 +827,15 @@
 msgid "Choose a value for %s:"
 msgstr ""
 
-#: ../Beremiz_service.py:373
+#: ../Beremiz_service.py:378
 msgid "Choose a working directory "
 msgstr ""
 
-#: ../ProjectController.py:281
+#: ../ProjectController.py:288
 msgid "Chosen folder doesn't contain a program. It's not a valid project!"
 msgstr ""
 
-#: ../ProjectController.py:247
+#: ../ProjectController.py:255
 msgid "Chosen folder isn't empty. You can't use it for a new project!"
 msgstr ""
 
@@ -816,7 +843,7 @@
 msgid "Class"
 msgstr ""
 
-#: ../controls/VariablePanel.py:369
+#: ../controls/VariablePanel.py:371
 msgid "Class Filter:"
 msgstr ""
 
@@ -824,19 +851,19 @@
 msgid "Class:"
 msgstr ""
 
-#: ../ProjectController.py:1488
+#: ../ProjectController.py:1523
 msgid "Clean"
 msgstr ""
 
-#: ../ProjectController.py:1490
+#: ../ProjectController.py:1525
 msgid "Clean project build folder"
 msgstr ""
 
-#: ../ProjectController.py:1048
+#: ../ProjectController.py:1076
 msgid "Cleaning the build directory\n"
 msgstr ""
 
-#: ../IDEFrame.py:411
+#: ../IDEFrame.py:416
 msgid "Clear Errors"
 msgstr ""
 
@@ -848,24 +875,24 @@
 msgid "Clear the graph values"
 msgstr ""
 
-#: ../Beremiz.py:598 ../PLCOpenEditor.py:221
+#: ../Beremiz.py:633 ../PLCOpenEditor.py:202
 msgid "Close Application"
 msgstr ""
 
-#: ../IDEFrame.py:1089 ../Beremiz.py:319 ../Beremiz.py:552
-#: ../PLCOpenEditor.py:131
+#: ../IDEFrame.py:972 ../Beremiz.py:321 ../Beremiz.py:587
+#: ../PLCOpenEditor.py:112
 msgid "Close Project"
 msgstr ""
 
-#: ../Beremiz.py:317 ../PLCOpenEditor.py:129
+#: ../Beremiz.py:319 ../PLCOpenEditor.py:110
 msgid "Close Tab"
 msgstr ""
 
-#: ../editors/Viewer.py:481
+#: ../editors/Viewer.py:480
 msgid "Coil"
 msgstr ""
 
-#: ../editors/Viewer.py:501 ../editors/LDViewer.py:503
+#: ../editors/Viewer.py:500 ../editors/LDViewer.py:506
 msgid "Comment"
 msgstr ""
 
@@ -881,7 +908,7 @@
 msgid "Comparison"
 msgstr ""
 
-#: ../ProjectController.py:538
+#: ../ProjectController.py:552
 msgid "Compiling IEC Program into C code...\n"
 msgstr ""
 
@@ -889,6 +916,14 @@
 msgid "Concatenation"
 msgstr ""
 
+#: ../editors/ConfTreeNodeEditor.py:249
+msgid "Config"
+msgstr ""
+
+#: ../editors/ProjectNodeEditor.py:13
+msgid "Config variables"
+msgstr ""
+
 #: ../dialogs/SearchInProjectDialog.py:47
 msgid "Configuration"
 msgstr ""
@@ -897,20 +932,25 @@
 msgid "Configurations"
 msgstr ""
 
-#: ../ProjectController.py:1503
+#: ../ProjectController.py:1538
 msgid "Connect"
 msgstr ""
 
-#: ../ProjectController.py:1504
+#: ../ProjectController.py:1539
 msgid "Connect to the target PLC"
 msgstr ""
 
+#: ../ProjectController.py:1125
+#, python-format
+msgid "Connected to URI: %s"
+msgstr ""
+
 #: ../connectors/PYRO/__init__.py:40
 #, python-format
 msgid "Connecting to URI : %s\n"
 msgstr ""
 
-#: ../editors/Viewer.py:467 ../dialogs/SFCTransitionDialog.py:76
+#: ../editors/Viewer.py:466 ../dialogs/SFCTransitionDialog.py:76
 msgid "Connection"
 msgstr ""
 
@@ -918,11 +958,11 @@
 msgid "Connection Properties"
 msgstr ""
 
-#: ../ProjectController.py:1359
+#: ../ProjectController.py:1397
 msgid "Connection canceled!\n"
 msgstr ""
 
-#: ../ProjectController.py:1384
+#: ../ProjectController.py:1422
 #, python-format
 msgid "Connection failed to %s!\n"
 msgstr ""
@@ -932,7 +972,7 @@
 msgid "Connection to '%s' failed.\n"
 msgstr ""
 
-#: ../dialogs/ConnectionDialog.py:56
+#: ../editors/Viewer.py:1426 ../dialogs/ConnectionDialog.py:56
 msgid "Connector"
 msgstr ""
 
@@ -940,11 +980,15 @@
 msgid "Connectors:"
 msgstr ""
 
+#: ../Beremiz.py:420
+msgid "Console"
+msgstr ""
+
 #: ../controls/VariablePanel.py:65
 msgid "Constant"
 msgstr ""
 
-#: ../editors/Viewer.py:477
+#: ../editors/Viewer.py:476
 msgid "Contact"
 msgstr ""
 
@@ -952,7 +996,7 @@
 msgid "Content Description (optional):"
 msgstr ""
 
-#: ../dialogs/ConnectionDialog.py:61
+#: ../editors/Viewer.py:1427 ../dialogs/ConnectionDialog.py:61
 msgid "Continuation"
 msgstr ""
 
@@ -972,19 +1016,19 @@
 msgid "Conversion to time-of-day"
 msgstr ""
 
-#: ../IDEFrame.py:348 ../IDEFrame.py:401 ../editors/Viewer.py:536
+#: ../IDEFrame.py:353 ../IDEFrame.py:406 ../editors/Viewer.py:536
 msgid "Copy"
 msgstr ""
 
-#: ../IDEFrame.py:1961
+#: ../IDEFrame.py:1837
 msgid "Copy POU"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:283
+#: ../editors/FileManagementPanel.py:65
 msgid "Copy file from left folder to right"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:282
+#: ../editors/FileManagementPanel.py:64
 msgid "Copy file from right folder to left"
 msgstr ""
 
@@ -992,40 +1036,40 @@
 msgid "Cosine"
 msgstr ""
 
-#: ../ConfigTreeNode.py:582
+#: ../ConfigTreeNode.py:602
 #, python-format
 msgid ""
 "Could not add child \"%s\", type %s :\n"
 "%s\n"
 msgstr ""
 
-#: ../ConfigTreeNode.py:559
+#: ../ConfigTreeNode.py:579
 #, python-format
 msgid ""
 "Couldn't load confnode base parameters %s :\n"
 " %s"
 msgstr ""
 
-#: ../ConfigTreeNode.py:570
+#: ../ConfigTreeNode.py:590
 #, python-format
 msgid ""
 "Couldn't load confnode parameters %s :\n"
 " %s"
 msgstr ""
 
-#: ../PLCControler.py:765 ../PLCControler.py:802
+#: ../PLCControler.py:819 ../PLCControler.py:856
 msgid "Couldn't paste non-POU object."
 msgstr ""
 
-#: ../ProjectController.py:1317
+#: ../ProjectController.py:1344
 msgid "Couldn't start PLC !\n"
 msgstr ""
 
-#: ../ProjectController.py:1325
+#: ../ProjectController.py:1352
 msgid "Couldn't stop PLC !\n"
 msgstr ""
 
-#: ../ProjectController.py:1295
+#: ../ProjectController.py:1321
 msgid "Couldn't stop debugger.\n"
 msgstr ""
 
@@ -1041,35 +1085,35 @@
 msgid "Create a new action"
 msgstr ""
 
-#: ../IDEFrame.py:135
+#: ../IDEFrame.py:142
 msgid "Create a new action block"
 msgstr ""
 
-#: ../IDEFrame.py:84 ../IDEFrame.py:114 ../IDEFrame.py:147
+#: ../IDEFrame.py:91 ../IDEFrame.py:121 ../IDEFrame.py:154
 msgid "Create a new block"
 msgstr ""
 
-#: ../IDEFrame.py:108
+#: ../IDEFrame.py:115
 msgid "Create a new branch"
 msgstr ""
 
-#: ../IDEFrame.py:102
+#: ../IDEFrame.py:109
 msgid "Create a new coil"
 msgstr ""
 
-#: ../IDEFrame.py:78 ../IDEFrame.py:93 ../IDEFrame.py:123
+#: ../IDEFrame.py:85 ../IDEFrame.py:100 ../IDEFrame.py:130
 msgid "Create a new comment"
 msgstr ""
 
-#: ../IDEFrame.py:87 ../IDEFrame.py:117 ../IDEFrame.py:150
+#: ../IDEFrame.py:94 ../IDEFrame.py:124 ../IDEFrame.py:157
 msgid "Create a new connection"
 msgstr ""
 
-#: ../IDEFrame.py:105 ../IDEFrame.py:156
+#: ../IDEFrame.py:112 ../IDEFrame.py:163
 msgid "Create a new contact"
 msgstr ""
 
-#: ../IDEFrame.py:138
+#: ../IDEFrame.py:145
 msgid "Create a new divergence"
 msgstr ""
 
@@ -1077,39 +1121,39 @@
 msgid "Create a new divergence or convergence"
 msgstr ""
 
-#: ../IDEFrame.py:126
+#: ../IDEFrame.py:133
 msgid "Create a new initial step"
 msgstr ""
 
-#: ../IDEFrame.py:141
+#: ../IDEFrame.py:148
 msgid "Create a new jump"
 msgstr ""
 
-#: ../IDEFrame.py:96 ../IDEFrame.py:153
+#: ../IDEFrame.py:103 ../IDEFrame.py:160
 msgid "Create a new power rail"
 msgstr ""
 
-#: ../IDEFrame.py:99
+#: ../IDEFrame.py:106
 msgid "Create a new rung"
 msgstr ""
 
-#: ../IDEFrame.py:129
+#: ../IDEFrame.py:136
 msgid "Create a new step"
 msgstr ""
 
-#: ../IDEFrame.py:132 ../dialogs/PouTransitionDialog.py:42
+#: ../IDEFrame.py:139 ../dialogs/PouTransitionDialog.py:42
 msgid "Create a new transition"
 msgstr ""
 
-#: ../IDEFrame.py:81 ../IDEFrame.py:111 ../IDEFrame.py:144
+#: ../IDEFrame.py:88 ../IDEFrame.py:118 ../IDEFrame.py:151
 msgid "Create a new variable"
 msgstr ""
 
-#: ../IDEFrame.py:346 ../IDEFrame.py:400 ../editors/Viewer.py:535
+#: ../IDEFrame.py:351 ../IDEFrame.py:405 ../editors/Viewer.py:535
 msgid "Cut"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
 msgid "Cyclic"
 msgstr ""
 
@@ -1121,11 +1165,11 @@
 msgid "DEPRECATED"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:50 ../canfestival/NetworkEditor.py:80
+#: ../canfestival/SlaveEditor.py:53 ../canfestival/NetworkEditor.py:74
 msgid "DS-301 Profile"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:51 ../canfestival/NetworkEditor.py:81
+#: ../canfestival/SlaveEditor.py:54 ../canfestival/NetworkEditor.py:75
 msgid "DS-302 Profile"
 msgstr ""
 
@@ -1158,58 +1202,58 @@
 msgid "Days:"
 msgstr ""
 
-#: ../ProjectController.py:1405
-msgid "Debug connect matching running PLC\n"
-msgstr ""
-
-#: ../ProjectController.py:1408
-msgid "Debug do not match PLC - stop/transfert/start to re-enable\n"
-msgstr ""
-
-#: ../controls/PouInstanceVariablesPanel.py:52
+#: ../ProjectController.py:1444
+msgid "Debug does not match PLC - stop/transfert/start to re-enable\n"
+msgstr ""
+
+#: ../controls/PouInstanceVariablesPanel.py:59
 msgid "Debug instance"
 msgstr ""
 
-#: ../editors/Viewer.py:3222
+#: ../editors/Viewer.py:1016 ../editors/Viewer.py:3326
 #, python-format
 msgid "Debug: %s"
 msgstr ""
 
-#: ../ProjectController.py:1122
+#: ../ProjectController.py:1153
 #, python-format
 msgid "Debug: Unknown variable '%s'\n"
 msgstr ""
 
-#: ../ProjectController.py:1120
+#: ../ProjectController.py:1151
 #, python-format
 msgid "Debug: Unsupported type to debug '%s'\n"
 msgstr ""
 
-#: ../IDEFrame.py:608
+#: ../IDEFrame.py:612
 msgid "Debugger"
 msgstr ""
 
-#: ../ProjectController.py:1285
+#: ../ProjectController.py:1311
 msgid "Debugger disabled\n"
 msgstr ""
 
-#: ../ProjectController.py:1297
+#: ../ProjectController.py:1441
+msgid "Debugger ready\n"
+msgstr ""
+
+#: ../ProjectController.py:1323
 msgid "Debugger stopped.\n"
 msgstr ""
 
-#: ../IDEFrame.py:1990 ../Beremiz.py:958 ../editors/Viewer.py:511
+#: ../IDEFrame.py:1866 ../Beremiz.py:991 ../editors/Viewer.py:511
 msgid "Delete"
 msgstr ""
 
-#: ../editors/Viewer.py:454
+#: ../editors/Viewer.py:453
 msgid "Delete Divergence Branch"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:371
+#: ../editors/FileManagementPanel.py:153
 msgid "Delete File"
 msgstr ""
 
-#: ../editors/Viewer.py:443
+#: ../editors/Viewer.py:442
 msgid "Delete Wire Segment"
 msgstr ""
 
@@ -1221,21 +1265,21 @@
 msgid "Deletion (within)"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:146
+#: ../editors/DataTypeEditor.py:152
 msgid "Derivation Type:"
 msgstr ""
 
-#: ../plcopen/structures.py:264
+#: ../plcopen/structures.py:263
 msgid ""
 "Derivative\n"
 "The derivative function block produces an output XOUT proportional to the rate of change of the input XIN."
 msgstr ""
 
-#: ../controls/VariablePanel.py:360
+#: ../controls/VariablePanel.py:362
 msgid "Description:"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:314 ../dialogs/ArrayTypeDialog.py:61
+#: ../editors/DataTypeEditor.py:320 ../dialogs/ArrayTypeDialog.py:61
 msgid "Dimensions:"
 msgstr ""
 
@@ -1243,23 +1287,23 @@
 msgid "Direction"
 msgstr ""
 
-#: ../dialogs/BrowseLocationsDialog.py:78
+#: ../dialogs/BrowseLocationsDialog.py:86
 msgid "Direction:"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Directly"
 msgstr ""
 
-#: ../ProjectController.py:1512
+#: ../ProjectController.py:1547
 msgid "Disconnect"
 msgstr ""
 
-#: ../ProjectController.py:1514
+#: ../ProjectController.py:1549
 msgid "Disconnect from PLC"
 msgstr ""
 
-#: ../editors/Viewer.py:496
+#: ../editors/Viewer.py:495
 msgid "Divergence"
 msgstr ""
 
@@ -1267,7 +1311,7 @@
 msgid "Division"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:370
+#: ../editors/FileManagementPanel.py:152
 #, python-format
 msgid "Do you really want to delete the file '%s'?"
 msgstr ""
@@ -1276,11 +1320,11 @@
 msgid "Documentation"
 msgstr ""
 
-#: ../PLCOpenEditor.py:351
+#: ../PLCOpenEditor.py:330
 msgid "Done"
 msgstr ""
 
-#: ../plcopen/structures.py:227
+#: ../plcopen/structures.py:226
 msgid ""
 "Down-counter\n"
 "The down-counter can be used to signal when a count has reached zero, on counting down from a preset value."
@@ -1290,11 +1334,11 @@
 msgid "Duration"
 msgstr ""
 
-#: ../canfestival/canfestival.py:118
+#: ../canfestival/canfestival.py:139
 msgid "EDS files (*.eds)|*.eds|All files|*.*"
 msgstr ""
 
-#: ../editors/Viewer.py:510
+#: ../editors/Viewer.py:509
 msgid "Edit Block"
 msgstr ""
 
@@ -1326,12 +1370,12 @@
 msgid "Edit array type properties"
 msgstr ""
 
-#: ../editors/Viewer.py:2112 ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630 ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186 ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706 ../editors/Viewer.py:2708
 msgid "Edit comment"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:284
+#: ../editors/FileManagementPanel.py:66
 msgid "Edit file"
 msgstr ""
 
@@ -1339,11 +1383,11 @@
 msgid "Edit item"
 msgstr ""
 
-#: ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2670
 msgid "Edit jump target"
 msgstr ""
 
-#: ../ProjectController.py:1526
+#: ../ProjectController.py:1561
 msgid "Edit raw IEC code added to code generated by PLCGenerator"
 msgstr ""
 
@@ -1355,35 +1399,35 @@
 msgid "Edit transition"
 msgstr ""
 
-#: ../IDEFrame.py:580
+#: ../IDEFrame.py:584
 msgid "Editor ToolBar"
 msgstr ""
 
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
 msgid "Editor selection"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:341
+#: ../editors/DataTypeEditor.py:347
 msgid "Elements :"
 msgstr ""
 
-#: ../IDEFrame.py:343
+#: ../IDEFrame.py:348
 msgid "Enable Undo/Redo"
 msgstr ""
 
-#: ../Beremiz_service.py:380
+#: ../Beremiz_service.py:385
 msgid "Enter a name "
 msgstr ""
 
-#: ../Beremiz_service.py:365
+#: ../Beremiz_service.py:370
 msgid "Enter a port number "
 msgstr ""
 
-#: ../Beremiz_service.py:355
+#: ../Beremiz_service.py:360
 msgid "Enter the IP of the interface to bind"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Enumerated"
 msgstr ""
 
@@ -1391,23 +1435,22 @@
 msgid "Equal to"
 msgstr ""
 
-#: ../Beremiz_service.py:270 ../Beremiz_service.py:394
-#: ../controls/VariablePanel.py:330 ../controls/VariablePanel.py:678
-#: ../controls/DebugVariablePanel.py:164 ../IDEFrame.py:1083
-#: ../IDEFrame.py:1672 ../IDEFrame.py:1709 ../IDEFrame.py:1714
-#: ../IDEFrame.py:1728 ../IDEFrame.py:1733 ../IDEFrame.py:2422
-#: ../Beremiz.py:1083 ../PLCOpenEditor.py:358 ../PLCOpenEditor.py:363
-#: ../PLCOpenEditor.py:531 ../PLCOpenEditor.py:541
-#: ../editors/TextViewer.py:376 ../editors/DataTypeEditor.py:543
-#: ../editors/DataTypeEditor.py:548 ../editors/DataTypeEditor.py:572
-#: ../editors/DataTypeEditor.py:577 ../editors/DataTypeEditor.py:587
-#: ../editors/DataTypeEditor.py:719 ../editors/DataTypeEditor.py:726
-#: ../editors/Viewer.py:366 ../editors/LDViewer.py:663
-#: ../editors/LDViewer.py:879 ../editors/LDViewer.py:883
-#: ../editors/FileManagementPanel.py:210 ../ProjectController.py:221
-#: ../dialogs/PouNameDialog.py:53 ../dialogs/PouTransitionDialog.py:107
-#: ../dialogs/BrowseLocationsDialog.py:175 ../dialogs/ProjectDialog.py:71
-#: ../dialogs/SFCStepNameDialog.py:59 ../dialogs/ConnectionDialog.py:152
+#: ../Beremiz_service.py:271 ../controls/VariablePanel.py:332
+#: ../controls/VariablePanel.py:681 ../controls/DebugVariablePanel.py:379
+#: ../IDEFrame.py:966 ../IDEFrame.py:1553 ../IDEFrame.py:1590
+#: ../IDEFrame.py:1595 ../IDEFrame.py:1609 ../IDEFrame.py:1614
+#: ../IDEFrame.py:2290 ../Beremiz.py:1131 ../PLCOpenEditor.py:337
+#: ../PLCOpenEditor.py:342 ../PLCOpenEditor.py:416 ../PLCOpenEditor.py:426
+#: ../editors/TextViewer.py:369 ../editors/DataTypeEditor.py:549
+#: ../editors/DataTypeEditor.py:554 ../editors/DataTypeEditor.py:578
+#: ../editors/DataTypeEditor.py:583 ../editors/DataTypeEditor.py:593
+#: ../editors/DataTypeEditor.py:744 ../editors/DataTypeEditor.py:751
+#: ../editors/Viewer.py:365 ../editors/LDViewer.py:666
+#: ../editors/LDViewer.py:882 ../editors/LDViewer.py:886
+#: ../ProjectController.py:225 ../dialogs/PouNameDialog.py:53
+#: ../dialogs/PouTransitionDialog.py:107
+#: ../dialogs/BrowseLocationsDialog.py:212 ../dialogs/ProjectDialog.py:71
+#: ../dialogs/SFCStepNameDialog.py:59 ../dialogs/ConnectionDialog.py:159
 #: ../dialogs/FBDVariableDialog.py:201 ../dialogs/PouActionDialog.py:104
 #: ../dialogs/BrowseValuesLibraryDialog.py:83 ../dialogs/PouDialog.py:132
 #: ../dialogs/SFCTransitionDialog.py:147
@@ -1415,44 +1458,44 @@
 #: ../dialogs/DurationEditorDialog.py:163
 #: ../dialogs/SearchInProjectDialog.py:157 ../dialogs/SFCStepDialog.py:130
 #: ../dialogs/ArrayTypeDialog.py:97 ../dialogs/ArrayTypeDialog.py:103
-#: ../dialogs/FBDBlockDialog.py:164 ../dialogs/ForceVariableDialog.py:169
+#: ../dialogs/FBDBlockDialog.py:164 ../dialogs/ForceVariableDialog.py:179
 msgid "Error"
 msgstr ""
 
-#: ../ProjectController.py:587
+#: ../ProjectController.py:601
 msgid "Error : At least one configuration and one resource must be declared in PLC !\n"
 msgstr ""
 
-#: ../ProjectController.py:579
+#: ../ProjectController.py:593
 #, python-format
 msgid "Error : IEC to C compiler returned %d\n"
 msgstr ""
 
-#: ../ProjectController.py:520
+#: ../ProjectController.py:534
 #, python-format
 msgid ""
 "Error in ST/IL/SFC code generator :\n"
 "%s\n"
 msgstr ""
 
-#: ../ConfigTreeNode.py:182
+#: ../ConfigTreeNode.py:183
 #, python-format
 msgid "Error while saving \"%s\"\n"
 msgstr ""
 
-#: ../canfestival/canfestival.py:122
+#: ../canfestival/canfestival.py:144
 msgid "Error: Export slave failed\n"
 msgstr ""
 
-#: ../canfestival/canfestival.py:270
+#: ../canfestival/canfestival.py:345
 msgid "Error: No Master generated\n"
 msgstr ""
 
-#: ../canfestival/canfestival.py:265
+#: ../canfestival/canfestival.py:340
 msgid "Error: No PLC built\n"
 msgstr ""
 
-#: ../ProjectController.py:1378
+#: ../ProjectController.py:1416
 #, python-format
 msgid "Exception while connecting %s!\n"
 msgstr ""
@@ -1465,7 +1508,7 @@
 msgid "Execution Order:"
 msgstr ""
 
-#: ../features.py:10
+#: ../features.py:11
 msgid "Experimental web based HMI"
 msgstr ""
 
@@ -1477,15 +1520,15 @@
 msgid "Exponentiation"
 msgstr ""
 
-#: ../canfestival/canfestival.py:128
+#: ../canfestival/canfestival.py:150
 msgid "Export CanOpen slave to EDS file"
 msgstr ""
 
-#: ../editors/GraphicViewer.py:144
+#: ../controls/DebugVariablePanel.py:1472 ../editors/GraphicViewer.py:144
 msgid "Export graph values to clipboard"
 msgstr ""
 
-#: ../canfestival/canfestival.py:127
+#: ../canfestival/canfestival.py:149
 msgid "Export slave"
 msgstr ""
 
@@ -1497,7 +1540,7 @@
 msgid "External"
 msgstr ""
 
-#: ../ProjectController.py:591
+#: ../ProjectController.py:605
 msgid "Extracting Located Variables...\n"
 msgstr ""
 
@@ -1507,21 +1550,21 @@
 msgid "FBD"
 msgstr ""
 
-#: ../ProjectController.py:1445
+#: ../ProjectController.py:1480
 msgid "Failed : Must build before transfer.\n"
 msgstr ""
 
-#: ../editors/Viewer.py:405 ../dialogs/LDElementDialog.py:84
+#: ../editors/Viewer.py:404 ../dialogs/LDElementDialog.py:84
 msgid "Falling Edge"
 msgstr ""
 
-#: ../plcopen/structures.py:217
+#: ../plcopen/structures.py:216
 msgid ""
 "Falling edge detector\n"
 "The output produces a single pulse when a falling edge is detected."
 msgstr ""
 
-#: ../ProjectController.py:900
+#: ../ProjectController.py:927
 msgid "Fatal : cannot get builder.\n"
 msgstr ""
 
@@ -1535,21 +1578,16 @@
 msgid "Fields %s haven't a valid value!"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:209
-#, python-format
-msgid "File '%s' already exists!"
-msgstr ""
-
-#: ../IDEFrame.py:353 ../dialogs/FindInPouDialog.py:30
+#: ../IDEFrame.py:358 ../dialogs/FindInPouDialog.py:30
 #: ../dialogs/FindInPouDialog.py:99
 msgid "Find"
 msgstr ""
 
-#: ../IDEFrame.py:355
+#: ../IDEFrame.py:360
 msgid "Find Next"
 msgstr ""
 
-#: ../IDEFrame.py:357
+#: ../IDEFrame.py:362
 msgid "Find Previous"
 msgstr ""
 
@@ -1565,11 +1603,11 @@
 msgid "Force runtime reload\n"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:295 ../editors/Viewer.py:1353
+#: ../controls/DebugVariablePanel.py:1934 ../editors/Viewer.py:1385
 msgid "Force value"
 msgstr ""
 
-#: ../dialogs/ForceVariableDialog.py:152
+#: ../dialogs/ForceVariableDialog.py:162
 msgid "Forcing Variable Value"
 msgstr ""
 
@@ -1580,7 +1618,7 @@
 msgid "Form isn't complete. %s must be filled!"
 msgstr ""
 
-#: ../dialogs/ConnectionDialog.py:142 ../dialogs/FBDBlockDialog.py:154
+#: ../dialogs/ConnectionDialog.py:149 ../dialogs/FBDBlockDialog.py:154
 msgid "Form isn't complete. Name must be filled!"
 msgstr ""
 
@@ -1600,15 +1638,15 @@
 msgid "Function"
 msgstr ""
 
-#: ../IDEFrame.py:329
+#: ../IDEFrame.py:334
 msgid "Function &Block"
 msgstr ""
 
-#: ../IDEFrame.py:1969 ../dialogs/SearchInProjectDialog.py:45
+#: ../IDEFrame.py:1845 ../dialogs/SearchInProjectDialog.py:45
 msgid "Function Block"
 msgstr ""
 
-#: ../controls/VariablePanel.py:741
+#: ../controls/VariablePanel.py:744
 msgid "Function Block Types"
 msgstr ""
 
@@ -1620,11 +1658,7 @@
 msgid "Function Blocks can't be used in Functions!"
 msgstr ""
 
-#: ../editors/Viewer.py:238
-msgid "Function Blocks can't be used in Transitions!"
-msgstr ""
-
-#: ../PLCControler.py:2055
+#: ../PLCControler.py:2180
 #, python-format
 msgid "FunctionBlock \"%s\" can't be pasted in a Function!!!"
 msgstr ""
@@ -1633,11 +1667,11 @@
 msgid "Functions"
 msgstr ""
 
-#: ../PLCOpenEditor.py:138
+#: ../PLCOpenEditor.py:119
 msgid "Generate Program"
 msgstr ""
 
-#: ../ProjectController.py:510
+#: ../ProjectController.py:524
 msgid "Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"
 msgstr ""
 
@@ -1645,7 +1679,7 @@
 msgid "Global"
 msgstr ""
 
-#: ../editors/GraphicViewer.py:131
+#: ../controls/DebugVariablePanel.py:1471 ../editors/GraphicViewer.py:131
 msgid "Go to current value"
 msgstr ""
 
@@ -1669,7 +1703,7 @@
 msgid "Height:"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:303
+#: ../editors/FileManagementPanel.py:85
 msgid "Home Directory:"
 msgstr ""
 
@@ -1681,13 +1715,13 @@
 msgid "Hours:"
 msgstr ""
 
-#: ../plcopen/structures.py:279
+#: ../plcopen/structures.py:278
 msgid ""
 "Hysteresis\n"
 "The hysteresis function block provides a hysteresis boolean output driven by the difference of two floating point (REAL) inputs XIN1 and XIN2."
 msgstr ""
 
-#: ../ProjectController.py:827
+#: ../ProjectController.py:851
 msgid "IEC-61131-3 code generation failed !\n"
 msgstr ""
 
@@ -1696,7 +1730,7 @@
 msgid "IL"
 msgstr ""
 
-#: ../Beremiz_service.py:356 ../Beremiz_service.py:357
+#: ../Beremiz_service.py:361 ../Beremiz_service.py:362
 msgid "IP is not valid!"
 msgstr ""
 
@@ -1704,11 +1738,16 @@
 msgid "Import SVG"
 msgstr ""
 
-#: ../controls/VariablePanel.py:76 ../dialogs/FBDVariableDialog.py:34
+#: ../controls/VariablePanel.py:76 ../editors/Viewer.py:1412
+#: ../dialogs/FBDVariableDialog.py:34
 msgid "InOut"
 msgstr ""
 
-#: ../controls/VariablePanel.py:263
+#: ../editors/Viewer.py:999
+msgid "Inactive"
+msgstr ""
+
+#: ../controls/VariablePanel.py:265
 #, python-format
 msgid "Incompatible data types between \"%s\" and \"%s\""
 msgstr ""
@@ -1727,17 +1766,17 @@
 msgid "Indicator"
 msgstr ""
 
-#: ../editors/Viewer.py:492
+#: ../editors/Viewer.py:491
 msgid "Initial Step"
 msgstr ""
 
 #: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48
+#: ../editors/DataTypeEditor.py:50
 msgid "Initial Value"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:178 ../editors/DataTypeEditor.py:209
-#: ../editors/DataTypeEditor.py:265 ../editors/DataTypeEditor.py:303
+#: ../editors/DataTypeEditor.py:184 ../editors/DataTypeEditor.py:215
+#: ../editors/DataTypeEditor.py:271 ../editors/DataTypeEditor.py:309
 msgid "Initial Value:"
 msgstr ""
 
@@ -1750,8 +1789,9 @@
 msgid "Inline"
 msgstr ""
 
-#: ../controls/VariablePanel.py:76 ../dialogs/BrowseLocationsDialog.py:36
-#: ../dialogs/FBDVariableDialog.py:33 ../dialogs/SFCStepDialog.py:61
+#: ../controls/VariablePanel.py:76 ../editors/Viewer.py:1410
+#: ../dialogs/BrowseLocationsDialog.py:35 ../dialogs/FBDVariableDialog.py:33
+#: ../dialogs/SFCStepDialog.py:61
 msgid "Input"
 msgstr ""
 
@@ -1763,16 +1803,16 @@
 msgid "Insertion (into)"
 msgstr ""
 
-#: ../plcopen/plcopen.py:1833
+#: ../plcopen/plcopen.py:1843
 #, python-format
 msgid "Instance with id %d doesn't exist!"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:247
+#: ../editors/ResourceEditor.py:255
 msgid "Instances:"
 msgstr ""
 
-#: ../plcopen/structures.py:259
+#: ../plcopen/structures.py:258
 msgid ""
 "Integral\n"
 "The integral function block integrates the value of input XIN over time."
@@ -1782,15 +1822,15 @@
 msgid "Interface"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:71
+#: ../editors/ResourceEditor.py:72
 msgid "Interrupt"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Interval"
 msgstr ""
 
-#: ../PLCControler.py:2032 ../PLCControler.py:2070
+#: ../PLCControler.py:2157 ../PLCControler.py:2195
 msgid "Invalid plcopen element(s)!!!"
 msgstr ""
 
@@ -1799,12 +1839,12 @@
 msgid "Invalid type \"%s\"-> %d != %d  for location\"%s\""
 msgstr ""
 
-#: ../dialogs/ForceVariableDialog.py:167
+#: ../dialogs/ForceVariableDialog.py:177
 #, python-format
 msgid "Invalid value \"%s\" for \"%s\" variable!"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:153 ../controls/DebugVariablePanel.py:156
+#: ../controls/DebugVariablePanel.py:319 ../controls/DebugVariablePanel.py:322
 #, python-format
 msgid "Invalid value \"%s\" for debug variable"
 msgstr ""
@@ -1825,7 +1865,7 @@
 "You must fill a numeric value."
 msgstr ""
 
-#: ../editors/Viewer.py:497
+#: ../editors/Viewer.py:496
 msgid "Jump"
 msgstr ""
 
@@ -1854,19 +1894,19 @@
 msgid "Language:"
 msgstr ""
 
-#: ../ProjectController.py:1451
+#: ../ProjectController.py:1486
 msgid "Latest build already matches current target. Transfering anyway...\n"
 msgstr ""
 
-#: ../Beremiz_service.py:324
+#: ../Beremiz_service.py:331
 msgid "Launch WX GUI inspector"
 msgstr ""
 
-#: ../Beremiz_service.py:323
+#: ../Beremiz_service.py:330
 msgid "Launch a live Python shell"
 msgstr ""
 
-#: ../editors/Viewer.py:428
+#: ../editors/Viewer.py:427
 msgid "Left"
 msgstr ""
 
@@ -1886,7 +1926,7 @@
 msgid "Less than or equal to"
 msgstr ""
 
-#: ../IDEFrame.py:600
+#: ../IDEFrame.py:604
 msgid "Library"
 msgstr ""
 
@@ -1902,7 +1942,11 @@
 msgid "Local"
 msgstr ""
 
-#: ../ProjectController.py:1353
+#: ../canfestival/canfestival.py:322
+msgid "Local entries"
+msgstr ""
+
+#: ../ProjectController.py:1391
 msgid "Local service discovery failed!\n"
 msgstr ""
 
@@ -1910,14 +1954,10 @@
 msgid "Location"
 msgstr ""
 
-#: ../dialogs/BrowseLocationsDialog.py:61
+#: ../dialogs/BrowseLocationsDialog.py:68
 msgid "Locations available:"
 msgstr ""
 
-#: ../Beremiz.py:393
-msgid "Log Console"
-msgstr ""
-
 #: ../plcopen/iec_std.csv:25
 msgid "Logarithm to base 10"
 msgstr ""
@@ -1927,19 +1967,19 @@
 msgid "MDNS resolution failure for '%s'\n"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:37 ../canfestival/NetworkEditor.py:67
+#: ../canfestival/SlaveEditor.py:41 ../canfestival/NetworkEditor.py:62
 msgid "Map Variable"
 msgstr ""
 
-#: ../features.py:6
+#: ../features.py:7
 msgid "Map located variables over CANopen"
 msgstr ""
 
-#: ../canfestival/NetworkEditor.py:89
+#: ../canfestival/NetworkEditor.py:83
 msgid "Master"
 msgstr ""
 
-#: ../ConfigTreeNode.py:480
+#: ../ConfigTreeNode.py:500
 #, python-format
 msgid "Max count (%d) reached for this confnode of type %s "
 msgstr ""
@@ -1948,15 +1988,15 @@
 msgid "Maximum"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:232
+#: ../editors/DataTypeEditor.py:238
 msgid "Maximum:"
 msgstr ""
 
-#: ../dialogs/BrowseLocationsDialog.py:38
+#: ../dialogs/BrowseLocationsDialog.py:37
 msgid "Memory"
 msgstr ""
 
-#: ../IDEFrame.py:568
+#: ../IDEFrame.py:572
 msgid "Menu ToolBar"
 msgstr ""
 
@@ -1964,7 +2004,7 @@
 msgid "Microseconds:"
 msgstr ""
 
-#: ../editors/Viewer.py:433
+#: ../editors/Viewer.py:432
 msgid "Middle"
 msgstr ""
 
@@ -1976,7 +2016,7 @@
 msgid "Minimum"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:219
+#: ../editors/DataTypeEditor.py:225
 msgid "Minimum:"
 msgstr ""
 
@@ -1992,7 +2032,7 @@
 msgid "Modifier:"
 msgstr ""
 
-#: ../PLCGenerator.py:703 ../PLCGenerator.py:936
+#: ../PLCGenerator.py:732 ../PLCGenerator.py:975
 #, python-format
 msgid "More than one connector found corresponding to \"%s\" continuation in \"%s\" POU"
 msgstr ""
@@ -2005,11 +2045,11 @@
 msgid "Move action up"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:185
+#: ../controls/DebugVariablePanel.py:1532
 msgid "Move debug variable down"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:184
+#: ../controls/DebugVariablePanel.py:1531
 msgid "Move debug variable up"
 msgstr ""
 
@@ -2017,31 +2057,31 @@
 msgid "Move down"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:348
+#: ../editors/DataTypeEditor.py:354
 msgid "Move element down"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:347
+#: ../editors/DataTypeEditor.py:353
 msgid "Move element up"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:254
+#: ../editors/ResourceEditor.py:262
 msgid "Move instance down"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:253
+#: ../editors/ResourceEditor.py:261
 msgid "Move instance up"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:225
+#: ../editors/ResourceEditor.py:233
 msgid "Move task down"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:224
+#: ../editors/ResourceEditor.py:232
 msgid "Move task up"
 msgstr ""
 
-#: ../IDEFrame.py:75 ../IDEFrame.py:90 ../IDEFrame.py:120 ../IDEFrame.py:161
+#: ../IDEFrame.py:82 ../IDEFrame.py:97 ../IDEFrame.py:127 ../IDEFrame.py:168
 msgid "Move the view"
 msgstr ""
 
@@ -2049,11 +2089,11 @@
 msgid "Move up"
 msgstr ""
 
-#: ../controls/VariablePanel.py:381
+#: ../controls/VariablePanel.py:383 ../c_ext/CFileEditor.py:520
 msgid "Move variable down"
 msgstr ""
 
-#: ../controls/VariablePanel.py:380
+#: ../controls/VariablePanel.py:382 ../c_ext/CFileEditor.py:519
 msgid "Move variable up"
 msgstr ""
 
@@ -2065,17 +2105,17 @@
 msgid "Multiplication"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:301
+#: ../editors/FileManagementPanel.py:83
 msgid "My Computer:"
 msgstr ""
 
 #: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48 ../editors/ResourceEditor.py:67
-#: ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50 ../editors/ResourceEditor.py:68
+#: ../editors/ResourceEditor.py:77
 msgid "Name"
 msgstr ""
 
-#: ../Beremiz_service.py:381
+#: ../Beremiz_service.py:386
 msgid "Name must not be null!"
 msgstr ""
 
@@ -2089,12 +2129,12 @@
 msgid "Natural logarithm"
 msgstr ""
 
-#: ../editors/Viewer.py:403 ../dialogs/LDElementDialog.py:67
+#: ../editors/Viewer.py:402 ../dialogs/LDElementDialog.py:67
 msgid "Negated"
 msgstr ""
 
-#: ../Beremiz.py:307 ../Beremiz.py:342 ../PLCOpenEditor.py:125
-#: ../PLCOpenEditor.py:167
+#: ../Beremiz.py:309 ../Beremiz.py:344 ../PLCOpenEditor.py:106
+#: ../PLCOpenEditor.py:148
 msgid "New"
 msgstr ""
 
@@ -2102,47 +2142,43 @@
 msgid "New item"
 msgstr ""
 
-#: ../editors/Viewer.py:402
+#: ../editors/Viewer.py:401
 msgid "No Modifier"
 msgstr ""
 
-#: ../PLCControler.py:2929
+#: ../PLCControler.py:3054
 msgid "No PLC project found"
 msgstr ""
 
-#: ../ProjectController.py:1478
+#: ../ProjectController.py:1513
 msgid "No PLC to transfer (did build succeed ?)\n"
 msgstr ""
 
-#: ../PLCGenerator.py:1321
+#: ../PLCGenerator.py:1360
 #, python-format
 msgid "No body defined in \"%s\" POU"
 msgstr ""
 
-#: ../PLCGenerator.py:722 ../PLCGenerator.py:945
+#: ../PLCGenerator.py:751 ../PLCGenerator.py:984
 #, python-format
 msgid "No connector found corresponding to \"%s\" continuation in \"%s\" POU"
 msgstr ""
 
-#: ../PLCOpenEditor.py:370
+#: ../PLCOpenEditor.py:349
 msgid ""
 "No documentation available.\n"
 "Coming soon."
 msgstr ""
 
-#: ../PLCGenerator.py:744
+#: ../PLCGenerator.py:773
 #, python-format
 msgid "No informations found for \"%s\" block"
 msgstr ""
 
-#: ../plcopen/structures.py:167
+#: ../plcopen/structures.py:166
 msgid "No output variable found"
 msgstr ""
 
-#: ../Beremiz_service.py:394
-msgid "No running PLC"
-msgstr ""
-
 #: ../controls/SearchResultPanel.py:169
 msgid "No search results available."
 msgstr ""
@@ -2166,15 +2202,11 @@
 msgid "No valid value selected!"
 msgstr ""
 
-#: ../PLCGenerator.py:1319
+#: ../PLCGenerator.py:1358
 #, python-format
 msgid "No variable defined in \"%s\" POU"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:49 ../canfestival/NetworkEditor.py:79
-msgid "Node infos"
-msgstr ""
-
 #: ../canfestival/config_utils.py:354
 #, python-format
 msgid "Non existing node ID : %d (variable %s)"
@@ -2205,13 +2237,13 @@
 msgid "Numerical"
 msgstr ""
 
-#: ../plcopen/structures.py:247
+#: ../plcopen/structures.py:246
 msgid ""
 "Off-delay timer\n"
 "The off-delay timer can be used to delay setting an output false, for fixed period after input goes false."
 msgstr ""
 
-#: ../plcopen/structures.py:242
+#: ../plcopen/structures.py:241
 msgid ""
 "On-delay timer\n"
 "The on-delay timer can be used to delay setting an output true, for fixed period after an input becomes true."
@@ -2221,8 +2253,8 @@
 msgid "Only Elements"
 msgstr ""
 
-#: ../Beremiz.py:309 ../Beremiz.py:343 ../PLCOpenEditor.py:127
-#: ../PLCOpenEditor.py:168
+#: ../Beremiz.py:311 ../Beremiz.py:345 ../PLCOpenEditor.py:108
+#: ../PLCOpenEditor.py:149
 msgid "Open"
 msgstr ""
 
@@ -2230,7 +2262,7 @@
 msgid "Open Inkscape"
 msgstr ""
 
-#: ../ProjectController.py:1530
+#: ../ProjectController.py:1565
 msgid "Open a file explorer to manage project files"
 msgstr ""
 
@@ -2250,24 +2282,25 @@
 msgid "Organization (optional):"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:47 ../canfestival/NetworkEditor.py:77
+#: ../canfestival/SlaveEditor.py:51 ../canfestival/NetworkEditor.py:72
 msgid "Other Profile"
 msgstr ""
 
-#: ../controls/VariablePanel.py:76 ../dialogs/BrowseLocationsDialog.py:37
-#: ../dialogs/FBDVariableDialog.py:35 ../dialogs/SFCStepDialog.py:65
+#: ../controls/VariablePanel.py:76 ../editors/Viewer.py:1411
+#: ../dialogs/BrowseLocationsDialog.py:36 ../dialogs/FBDVariableDialog.py:35
+#: ../dialogs/SFCStepDialog.py:65
 msgid "Output"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:36 ../canfestival/NetworkEditor.py:66
+#: ../canfestival/SlaveEditor.py:40 ../canfestival/NetworkEditor.py:61
 msgid "PDO Receive"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:35 ../canfestival/NetworkEditor.py:65
+#: ../canfestival/SlaveEditor.py:39 ../canfestival/NetworkEditor.py:60
 msgid "PDO Transmit"
 msgstr ""
 
-#: ../plcopen/structures.py:269
+#: ../plcopen/structures.py:268
 msgid ""
 "PID\n"
 "The PID (proportional, Integral, Derivative) function block provides the classical three term controller for closed loop control."
@@ -2277,16 +2310,15 @@
 msgid "PLC :\n"
 msgstr ""
 
-#: ../ProjectController.py:1096 ../ProjectController.py:1398
-#, python-format
-msgid "PLC is %s\n"
-msgstr ""
-
-#: ../PLCOpenEditor.py:313 ../PLCOpenEditor.py:391
+#: ../Beremiz.py:425
+msgid "PLC Log"
+msgstr ""
+
+#: ../PLCOpenEditor.py:294 ../PLCOpenEditor.py:370
 msgid "PLCOpen files (*.xml)|*.xml|All files|*.*"
 msgstr ""
 
-#: ../PLCOpenEditor.py:175 ../PLCOpenEditor.py:231
+#: ../PLCOpenEditor.py:156 ../PLCOpenEditor.py:212
 msgid "PLCOpenEditor"
 msgstr ""
 
@@ -2306,7 +2338,7 @@
 msgid "POU Type:"
 msgstr ""
 
-#: ../Beremiz.py:322 ../PLCOpenEditor.py:141
+#: ../Beremiz.py:324 ../PLCOpenEditor.py:122
 msgid "Page Setup"
 msgstr ""
 
@@ -2314,20 +2346,20 @@
 msgid "Page Size (optional):"
 msgstr ""
 
-#: ../PLCOpenEditor.py:476
+#: ../IDEFrame.py:2492
 #, python-format
 msgid "Page: %d"
 msgstr ""
 
-#: ../controls/PouInstanceVariablesPanel.py:41
+#: ../controls/PouInstanceVariablesPanel.py:48
 msgid "Parent instance"
 msgstr ""
 
-#: ../IDEFrame.py:350 ../IDEFrame.py:402 ../editors/Viewer.py:537
+#: ../IDEFrame.py:355 ../IDEFrame.py:407 ../editors/Viewer.py:537
 msgid "Paste"
 msgstr ""
 
-#: ../IDEFrame.py:1900
+#: ../IDEFrame.py:1776
 msgid "Paste POU"
 msgstr ""
 
@@ -2339,13 +2371,13 @@
 msgid "Pin number:"
 msgstr ""
 
-#: ../editors/Viewer.py:2289 ../editors/Viewer.py:2594
+#: ../editors/Viewer.py:2363 ../editors/Viewer.py:2670
 #: ../editors/SFCViewer.py:696
 msgid "Please choose a target"
 msgstr ""
 
-#: ../editors/Viewer.py:2112 ../editors/Viewer.py:2114
-#: ../editors/Viewer.py:2630 ../editors/Viewer.py:2632
+#: ../editors/Viewer.py:2186 ../editors/Viewer.py:2188
+#: ../editors/Viewer.py:2706 ../editors/Viewer.py:2708
 msgid "Please enter comment text"
 msgstr ""
 
@@ -2354,16 +2386,16 @@
 msgid "Please enter step name"
 msgstr ""
 
-#: ../dialogs/ForceVariableDialog.py:153
+#: ../dialogs/ForceVariableDialog.py:163
 #, python-format
 msgid "Please enter value for a \"%s\" variable:"
 msgstr ""
 
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
 msgid "Port number must be 0 <= port <= 65535!"
 msgstr ""
 
-#: ../Beremiz_service.py:366
+#: ../Beremiz_service.py:371
 msgid "Port number must be an integer!"
 msgstr ""
 
@@ -2371,7 +2403,7 @@
 msgid "Position:"
 msgstr ""
 
-#: ../editors/Viewer.py:476
+#: ../editors/Viewer.py:475
 msgid "Power Rail"
 msgstr ""
 
@@ -2379,7 +2411,7 @@
 msgid "Power Rail Properties"
 msgstr ""
 
-#: ../Beremiz.py:324 ../PLCOpenEditor.py:143
+#: ../Beremiz.py:326 ../PLCOpenEditor.py:124
 msgid "Preview"
 msgstr ""
 
@@ -2390,16 +2422,16 @@
 msgid "Preview:"
 msgstr ""
 
-#: ../Beremiz.py:326 ../Beremiz.py:346 ../PLCOpenEditor.py:145
-#: ../PLCOpenEditor.py:171
+#: ../Beremiz.py:328 ../Beremiz.py:348 ../PLCOpenEditor.py:126
+#: ../PLCOpenEditor.py:152
 msgid "Print"
 msgstr ""
 
-#: ../IDEFrame.py:1155
+#: ../IDEFrame.py:1038
 msgid "Print preview"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Priority"
 msgstr ""
 
@@ -2407,6 +2439,11 @@
 msgid "Priority:"
 msgstr ""
 
+#: ../runtime/PLCObject.py:318
+#, python-format
+msgid "Problem starting PLC : error %d"
+msgstr ""
+
 #: ../controls/ProjectPropertiesPanel.py:80
 msgid "Product Name (required):"
 msgstr ""
@@ -2419,11 +2456,11 @@
 msgid "Product Version (required):"
 msgstr ""
 
-#: ../IDEFrame.py:1972 ../dialogs/SearchInProjectDialog.py:46
+#: ../IDEFrame.py:1848 ../dialogs/SearchInProjectDialog.py:46
 msgid "Program"
 msgstr ""
 
-#: ../PLCOpenEditor.py:360
+#: ../PLCOpenEditor.py:339
 msgid "Program was successfully generated!"
 msgstr ""
 
@@ -2435,7 +2472,7 @@
 msgid "Programs can't be used by other POUs!"
 msgstr ""
 
-#: ../controls/ProjectPropertiesPanel.py:84 ../IDEFrame.py:553
+#: ../controls/ProjectPropertiesPanel.py:84 ../IDEFrame.py:557
 msgid "Project"
 msgstr ""
 
@@ -2444,7 +2481,7 @@
 msgid "Project '%s':"
 msgstr ""
 
-#: ../ProjectController.py:1529
+#: ../ProjectController.py:1564
 msgid "Project Files"
 msgstr ""
 
@@ -2456,32 +2493,40 @@
 msgid "Project Version (optional):"
 msgstr ""
 
-#: ../PLCControler.py:2916
+#: ../PLCControler.py:3041
 msgid ""
 "Project file syntax error:\n"
 "\n"
 msgstr ""
 
-#: ../dialogs/ProjectDialog.py:32
+#: ../editors/ProjectNodeEditor.py:14 ../dialogs/ProjectDialog.py:32
 msgid "Project properties"
 msgstr ""
 
-#: ../ConfigTreeNode.py:506
+#: ../ConfigTreeNode.py:526
 #, python-format
 msgid "Project tree layout do not match confnode.xml %s!=%s "
 msgstr ""
 
+#: ../dialogs/ConnectionDialog.py:96
+msgid "Propagate Name"
+msgstr ""
+
 #: ../PLCControler.py:96
 msgid "Properties"
 msgstr ""
 
-#: ../plcopen/structures.py:237
+#: ../plcopen/structures.py:236
 msgid ""
 "Pulse timer\n"
 "The pulse timer can be used to generate output pulses of a given time duration."
 msgstr ""
 
-#: ../features.py:8
+#: ../py_ext/PythonEditor.py:61
+msgid "Python code"
+msgstr ""
+
+#: ../features.py:9
 msgid "Python file"
 msgstr ""
 
@@ -2489,42 +2534,42 @@
 msgid "Qualifier"
 msgstr ""
 
-#: ../Beremiz_service.py:328 ../Beremiz.py:329 ../PLCOpenEditor.py:151
+#: ../Beremiz_service.py:333 ../Beremiz.py:331 ../PLCOpenEditor.py:132
 msgid "Quit"
 msgstr ""
 
-#: ../plcopen/structures.py:202
+#: ../plcopen/structures.py:201
 msgid ""
 "RS bistable\n"
 "The RS bistable is a latch where the Reset dominates."
 msgstr ""
 
-#: ../plcopen/structures.py:274
+#: ../plcopen/structures.py:273
 msgid ""
 "Ramp\n"
 "The RAMP function block is modelled on example given in the standard."
 msgstr ""
 
-#: ../editors/GraphicViewer.py:89
+#: ../controls/DebugVariablePanel.py:1462 ../editors/GraphicViewer.py:89
 msgid "Range:"
 msgstr ""
 
-#: ../ProjectController.py:1525
+#: ../ProjectController.py:1560
 msgid "Raw IEC code"
 msgstr ""
 
-#: ../plcopen/structures.py:254
+#: ../plcopen/structures.py:253
 msgid ""
 "Real time clock\n"
 "The real time clock has many uses including time stamping, setting dates and times of day in batch reports, in alarm messages and so on."
 msgstr ""
 
-#: ../Beremiz.py:1039
+#: ../Beremiz.py:1072
 #, python-format
 msgid "Really delete node '%s'?"
 msgstr ""
 
-#: ../IDEFrame.py:340 ../IDEFrame.py:398
+#: ../IDEFrame.py:345 ../IDEFrame.py:403
 msgid "Redo"
 msgstr ""
 
@@ -2532,7 +2577,7 @@
 msgid "Reference"
 msgstr ""
 
-#: ../IDEFrame.py:408 ../dialogs/DiscoveryDialog.py:105
+#: ../IDEFrame.py:413 ../dialogs/DiscoveryDialog.py:105
 msgid "Refresh"
 msgstr ""
 
@@ -2544,7 +2589,7 @@
 msgid "Regular expressions"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:299 ../editors/Viewer.py:1356
+#: ../controls/DebugVariablePanel.py:1938 ../editors/Viewer.py:1388
 msgid "Release value"
 msgstr ""
 
@@ -2552,7 +2597,7 @@
 msgid "Remainder (modulo)"
 msgstr ""
 
-#: ../Beremiz.py:1040
+#: ../Beremiz.py:1073
 #, python-format
 msgid "Remove %s node"
 msgstr ""
@@ -2561,39 +2606,39 @@
 msgid "Remove action"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:183
+#: ../controls/DebugVariablePanel.py:1530
 msgid "Remove debug variable"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:346
+#: ../editors/DataTypeEditor.py:352
 msgid "Remove element"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:281
+#: ../editors/FileManagementPanel.py:63
 msgid "Remove file from left folder"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:252
+#: ../editors/ResourceEditor.py:260
 msgid "Remove instance"
 msgstr ""
 
-#: ../canfestival/NetworkEditor.py:87
+#: ../canfestival/NetworkEditor.py:81
 msgid "Remove slave"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:223
+#: ../editors/ResourceEditor.py:231
 msgid "Remove task"
 msgstr ""
 
-#: ../controls/VariablePanel.py:379
+#: ../controls/VariablePanel.py:381 ../c_ext/CFileEditor.py:518
 msgid "Remove variable"
 msgstr ""
 
-#: ../IDEFrame.py:1976
+#: ../IDEFrame.py:1852
 msgid "Rename"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:399
+#: ../editors/FileManagementPanel.py:181
 msgid "Replace File"
 msgstr ""
 
@@ -2609,7 +2654,7 @@
 msgid "Reset Execution Order"
 msgstr ""
 
-#: ../IDEFrame.py:423
+#: ../IDEFrame.py:428
 msgid "Reset Perspective"
 msgstr ""
 
@@ -2629,11 +2674,11 @@
 msgid "Retain"
 msgstr ""
 
-#: ../controls/VariablePanel.py:352
+#: ../controls/VariablePanel.py:354
 msgid "Return Type:"
 msgstr ""
 
-#: ../editors/Viewer.py:430
+#: ../editors/Viewer.py:429
 msgid "Right"
 msgstr ""
 
@@ -2641,11 +2686,11 @@
 msgid "Right PowerRail"
 msgstr ""
 
-#: ../editors/Viewer.py:404 ../dialogs/LDElementDialog.py:80
+#: ../editors/Viewer.py:403 ../dialogs/LDElementDialog.py:80
 msgid "Rising Edge"
 msgstr ""
 
-#: ../plcopen/structures.py:212
+#: ../plcopen/structures.py:211
 msgid ""
 "Rising edge detector\n"
 "The output produces a single pulse when a rising edge is detected."
@@ -2663,19 +2708,19 @@
 msgid "Rounding up/down"
 msgstr ""
 
-#: ../ProjectController.py:1493
+#: ../ProjectController.py:1528
 msgid "Run"
 msgstr ""
 
-#: ../ProjectController.py:841 ../ProjectController.py:850
+#: ../ProjectController.py:865 ../ProjectController.py:874
 msgid "Runtime extensions C code generation failed !\n"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:34 ../canfestival/NetworkEditor.py:64
+#: ../canfestival/SlaveEditor.py:38 ../canfestival/NetworkEditor.py:59
 msgid "SDO Client"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:33 ../canfestival/NetworkEditor.py:63
+#: ../canfestival/SlaveEditor.py:37 ../canfestival/NetworkEditor.py:58
 msgid "SDO Server"
 msgstr ""
 
@@ -2683,7 +2728,7 @@
 msgid "SFC"
 msgstr ""
 
-#: ../plcopen/structures.py:197
+#: ../plcopen/structures.py:196
 msgid ""
 "SR bistable\n"
 "The SR bistable is a latch where the Set dominates."
@@ -2694,7 +2739,7 @@
 msgid "ST"
 msgstr ""
 
-#: ../PLCOpenEditor.py:347
+#: ../PLCOpenEditor.py:326
 msgid "ST files (*.st)|*.st|All files|*.*"
 msgstr ""
 
@@ -2702,20 +2747,20 @@
 msgid "SVG files (*.svg)|*.svg|All files|*.*"
 msgstr ""
 
-#: ../features.py:10
+#: ../features.py:11
 msgid "SVGUI"
 msgstr ""
 
-#: ../Beremiz.py:313 ../Beremiz.py:344 ../PLCOpenEditor.py:134
-#: ../PLCOpenEditor.py:169
+#: ../Beremiz.py:315 ../Beremiz.py:346 ../PLCOpenEditor.py:115
+#: ../PLCOpenEditor.py:150
 msgid "Save"
 msgstr ""
 
-#: ../Beremiz.py:345 ../PLCOpenEditor.py:136 ../PLCOpenEditor.py:170
+#: ../Beremiz.py:347 ../PLCOpenEditor.py:117 ../PLCOpenEditor.py:151
 msgid "Save As..."
 msgstr ""
 
-#: ../Beremiz.py:315
+#: ../Beremiz.py:317
 msgid "Save as"
 msgstr ""
 
@@ -2723,11 +2768,11 @@
 msgid "Scope"
 msgstr ""
 
-#: ../IDEFrame.py:592 ../dialogs/SearchInProjectDialog.py:105
+#: ../IDEFrame.py:596 ../dialogs/SearchInProjectDialog.py:105
 msgid "Search"
 msgstr ""
 
-#: ../IDEFrame.py:360 ../IDEFrame.py:404
+#: ../IDEFrame.py:365 ../IDEFrame.py:409
 #: ../dialogs/SearchInProjectDialog.py:52
 msgid "Search in Project"
 msgstr ""
@@ -2736,24 +2781,24 @@
 msgid "Seconds:"
 msgstr ""
 
-#: ../IDEFrame.py:366
+#: ../IDEFrame.py:371
 msgid "Select All"
 msgstr ""
 
-#: ../controls/VariablePanel.py:277 ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../controls/LocationCellEditor.py:97 ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:323 ../editors/Viewer.py:275
 msgid "Select a variable class:"
 msgstr ""
 
-#: ../ProjectController.py:1013
+#: ../ProjectController.py:1039
 msgid "Select an editor:"
 msgstr ""
 
-#: ../controls/PouInstanceVariablesPanel.py:197
+#: ../controls/PouInstanceVariablesPanel.py:209
 msgid "Select an instance"
 msgstr ""
 
-#: ../IDEFrame.py:576
+#: ../IDEFrame.py:580
 msgid "Select an object"
 msgstr ""
 
@@ -2769,7 +2814,7 @@
 msgid "Selection Divergence"
 msgstr ""
 
-#: ../plcopen/structures.py:207
+#: ../plcopen/structures.py:206
 msgid ""
 "Semaphore\n"
 "The semaphore provides a mechanism to allow software elements mutually exclusive access to certain ressources."
@@ -2791,19 +2836,19 @@
 msgid "Shift right"
 msgstr ""
 
-#: ../ProjectController.py:1519
+#: ../ProjectController.py:1554
 msgid "Show IEC code generated by PLCGenerator"
 msgstr ""
 
-#: ../canfestival/canfestival.py:288
+#: ../canfestival/canfestival.py:363
 msgid "Show Master"
 msgstr ""
 
-#: ../canfestival/canfestival.py:289
+#: ../canfestival/canfestival.py:364
 msgid "Show Master generated by config_utils"
 msgstr ""
 
-#: ../ProjectController.py:1517
+#: ../ProjectController.py:1552
 msgid "Show code"
 msgstr ""
 
@@ -2819,7 +2864,7 @@
 msgid "Sine"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Single"
 msgstr ""
 
@@ -2827,52 +2872,52 @@
 msgid "Square root (base 2)"
 msgstr ""
 
-#: ../plcopen/structures.py:193
+#: ../plcopen/structures.py:192
 msgid "Standard function blocks"
 msgstr ""
 
-#: ../Beremiz_service.py:319 ../ProjectController.py:1495
+#: ../Beremiz_service.py:321 ../ProjectController.py:1530
 msgid "Start PLC"
 msgstr ""
 
-#: ../ProjectController.py:819
+#: ../ProjectController.py:843
 #, python-format
 msgid "Start build in %s\n"
 msgstr ""
 
-#: ../ProjectController.py:1314
+#: ../ProjectController.py:1341
 msgid "Starting PLC\n"
 msgstr ""
 
-#: ../Beremiz.py:403
+#: ../Beremiz.py:435
 msgid "Status ToolBar"
 msgstr ""
 
-#: ../editors/Viewer.py:493
+#: ../editors/Viewer.py:492
 msgid "Step"
 msgstr ""
 
-#: ../ProjectController.py:1498
+#: ../ProjectController.py:1533
 msgid "Stop"
 msgstr ""
 
-#: ../Beremiz_service.py:320
+#: ../Beremiz_service.py:322
 msgid "Stop PLC"
 msgstr ""
 
-#: ../ProjectController.py:1500
+#: ../ProjectController.py:1535
 msgid "Stop Running PLC"
 msgstr ""
 
-#: ../ProjectController.py:1292
+#: ../ProjectController.py:1318
 msgid "Stopping debugger...\n"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Structure"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:52
+#: ../editors/DataTypeEditor.py:54
 msgid "Subrange"
 msgstr ""
 
@@ -2880,7 +2925,7 @@
 msgid "Subtraction"
 msgstr ""
 
-#: ../ProjectController.py:915
+#: ../ProjectController.py:942
 msgid "Successfully built.\n"
 msgstr ""
 
@@ -2892,11 +2937,11 @@
 msgid "Tangent"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:76
+#: ../editors/ResourceEditor.py:77
 msgid "Task"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:218
+#: ../editors/ResourceEditor.py:226
 msgid "Tasks:"
 msgstr ""
 
@@ -2904,33 +2949,33 @@
 msgid "Temp"
 msgstr ""
 
-#: ../editors/FileManagementPanel.py:398
+#: ../editors/FileManagementPanel.py:180
 #, python-format
 msgid ""
 "The file '%s' already exist.\n"
 "Do you want to replace it?"
 msgstr ""
 
-#: ../editors/LDViewer.py:879
+#: ../editors/LDViewer.py:882
 msgid "The group of block must be coherent!"
 msgstr ""
 
-#: ../IDEFrame.py:1091 ../Beremiz.py:555
+#: ../IDEFrame.py:974 ../Beremiz.py:590
 msgid "There are changes, do you want to save?"
 msgstr ""
 
-#: ../IDEFrame.py:1709 ../IDEFrame.py:1728
+#: ../IDEFrame.py:1590 ../IDEFrame.py:1609
 #, python-format
 msgid "There is a POU named \"%s\". This could cause a conflict. Do you wish to continue?"
 msgstr ""
 
-#: ../IDEFrame.py:1178
+#: ../IDEFrame.py:1061
 msgid ""
 "There was a problem printing.\n"
 "Perhaps your current printer is not set correctly?"
 msgstr ""
 
-#: ../editors/LDViewer.py:888
+#: ../editors/LDViewer.py:891
 msgid "This option isn't available yet!"
 msgstr ""
 
@@ -2971,31 +3016,31 @@
 msgid "Time-of-day subtraction"
 msgstr ""
 
-#: ../editors/Viewer.py:432
+#: ../editors/Viewer.py:431
 msgid "Top"
 msgstr ""
 
-#: ../ProjectController.py:1507
+#: ../ProjectController.py:1542
 msgid "Transfer"
 msgstr ""
 
+#: ../ProjectController.py:1544
+msgid "Transfer PLC"
+msgstr ""
+
 #: ../ProjectController.py:1509
-msgid "Transfer PLC"
-msgstr ""
-
-#: ../ProjectController.py:1474
 msgid "Transfer completed successfully.\n"
 msgstr ""
 
-#: ../ProjectController.py:1476
+#: ../ProjectController.py:1511
 msgid "Transfer failed\n"
 msgstr ""
 
-#: ../editors/Viewer.py:494
+#: ../editors/Viewer.py:493
 msgid "Transition"
 msgstr ""
 
-#: ../PLCGenerator.py:1212
+#: ../PLCGenerator.py:1252
 #, python-format
 msgid "Transition \"%s\" body must contain an output variable or coil referring to its name"
 msgstr ""
@@ -3008,17 +3053,17 @@
 msgid "Transition Name:"
 msgstr ""
 
-#: ../PLCGenerator.py:1301
+#: ../PLCGenerator.py:1340
 #, python-format
 msgid "Transition with content \"%s\" not connected to a next step in \"%s\" POU"
 msgstr ""
 
-#: ../PLCGenerator.py:1292
+#: ../PLCGenerator.py:1331
 #, python-format
 msgid "Transition with content \"%s\" not connected to a previous step in \"%s\" POU"
 msgstr ""
 
-#: ../plcopen/plcopen.py:1442
+#: ../plcopen/plcopen.py:1447
 #, python-format
 msgid "Transition with name %s doesn't exist!"
 msgstr ""
@@ -3027,16 +3072,20 @@
 msgid "Transitions"
 msgstr ""
 
-#: ../editors/ResourceEditor.py:67
+#: ../editors/ResourceEditor.py:68
 msgid "Triggering"
 msgstr ""
 
 #: ../controls/VariablePanel.py:58 ../controls/VariablePanel.py:59
-#: ../editors/DataTypeEditor.py:48 ../editors/ResourceEditor.py:76
+#: ../editors/DataTypeEditor.py:50 ../editors/ResourceEditor.py:77
 #: ../dialogs/ActionBlockDialog.py:37
 msgid "Type"
 msgstr ""
 
+#: ../dialogs/BrowseLocationsDialog.py:44
+msgid "Type and derivated"
+msgstr ""
+
 #: ../canfestival/config_utils.py:335 ../canfestival/config_utils.py:617
 #, python-format
 msgid "Type conflict for location \"%s\""
@@ -3046,13 +3095,17 @@
 msgid "Type conversion"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:155
+#: ../editors/DataTypeEditor.py:161
 msgid "Type infos:"
 msgstr ""
 
+#: ../dialogs/BrowseLocationsDialog.py:45
+msgid "Type strict"
+msgstr ""
+
 #: ../dialogs/SFCDivergenceDialog.py:51 ../dialogs/LDPowerRailDialog.py:51
-#: ../dialogs/ConnectionDialog.py:52 ../dialogs/SFCTransitionDialog.py:53
-#: ../dialogs/FBDBlockDialog.py:48
+#: ../dialogs/BrowseLocationsDialog.py:95 ../dialogs/ConnectionDialog.py:52
+#: ../dialogs/SFCTransitionDialog.py:53 ../dialogs/FBDBlockDialog.py:48
 msgid "Type:"
 msgstr ""
 
@@ -3066,30 +3119,30 @@
 msgid "Unable to get Xenomai's %s \n"
 msgstr ""
 
-#: ../PLCGenerator.py:865 ../PLCGenerator.py:924
+#: ../PLCGenerator.py:904 ../PLCGenerator.py:963
 #, python-format
 msgid "Undefined block type \"%s\" in \"%s\" POU"
 msgstr ""
 
-#: ../PLCGenerator.py:240
+#: ../PLCGenerator.py:252
 #, python-format
 msgid "Undefined pou type \"%s\""
 msgstr ""
 
-#: ../IDEFrame.py:338 ../IDEFrame.py:397
+#: ../IDEFrame.py:343 ../IDEFrame.py:402
 msgid "Undo"
 msgstr ""
 
-#: ../ProjectController.py:254
+#: ../ProjectController.py:262
 msgid "Unknown"
 msgstr ""
 
-#: ../editors/Viewer.py:336
+#: ../editors/Viewer.py:335
 #, python-format
 msgid "Unknown variable \"%s\" for this POU!"
 msgstr ""
 
-#: ../ProjectController.py:251 ../ProjectController.py:252
+#: ../ProjectController.py:259 ../ProjectController.py:260
 msgid "Unnamed"
 msgstr ""
 
@@ -3103,23 +3156,23 @@
 msgid "Unrecognized data size \"%s\""
 msgstr ""
 
-#: ../plcopen/structures.py:222
+#: ../plcopen/structures.py:221
 msgid ""
 "Up-counter\n"
 "The up-counter can be used to signal when a count has reached a maximum value."
 msgstr ""
 
-#: ../plcopen/structures.py:232
+#: ../plcopen/structures.py:231
 msgid ""
 "Up-down counter\n"
 "The up-down counter has two inputs CU and CD. It can be used to both count up on one input and down on the other."
 msgstr ""
 
-#: ../controls/VariablePanel.py:709 ../editors/DataTypeEditor.py:623
+#: ../controls/VariablePanel.py:712 ../editors/DataTypeEditor.py:631
 msgid "User Data Types"
 msgstr ""
 
-#: ../canfestival/SlaveEditor.py:38 ../canfestival/NetworkEditor.py:68
+#: ../canfestival/SlaveEditor.py:42 ../canfestival/NetworkEditor.py:63
 msgid "User Type"
 msgstr ""
 
@@ -3127,7 +3180,7 @@
 msgid "User-defined POUs"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:40 ../dialogs/ActionBlockDialog.py:37
+#: ../controls/DebugVariablePanel.py:58 ../dialogs/ActionBlockDialog.py:37
 msgid "Value"
 msgstr ""
 
@@ -3135,11 +3188,11 @@
 msgid "Values"
 msgstr ""
 
-#: ../editors/DataTypeEditor.py:252
+#: ../editors/DataTypeEditor.py:258
 msgid "Values:"
 msgstr ""
 
-#: ../controls/DebugVariablePanel.py:40 ../editors/Viewer.py:466
+#: ../controls/DebugVariablePanel.py:58 ../editors/Viewer.py:465
 #: ../dialogs/ActionBlockDialog.py:41
 msgid "Variable"
 msgstr ""
@@ -3148,12 +3201,12 @@
 msgid "Variable Properties"
 msgstr ""
 
-#: ../controls/VariablePanel.py:277 ../editors/TextViewer.py:330
-#: ../editors/Viewer.py:277
+#: ../controls/LocationCellEditor.py:97 ../controls/VariablePanel.py:277
+#: ../editors/TextViewer.py:323 ../editors/Viewer.py:275
 msgid "Variable class"
 msgstr ""
 
-#: ../editors/TextViewer.py:374 ../editors/Viewer.py:338
+#: ../editors/TextViewer.py:367 ../editors/Viewer.py:337
 msgid "Variable don't belong to this POU!"
 msgstr ""
 
@@ -3169,15 +3222,15 @@
 msgid "WXGLADE GUI"
 msgstr ""
 
-#: ../ProjectController.py:1276
+#: ../ProjectController.py:1302
 msgid "Waiting debugger to recover...\n"
 msgstr ""
 
-#: ../editors/LDViewer.py:888 ../dialogs/PouDialog.py:126
+#: ../editors/LDViewer.py:891 ../dialogs/PouDialog.py:126
 msgid "Warning"
 msgstr ""
 
-#: ../ProjectController.py:515
+#: ../ProjectController.py:529
 msgid "Warnings in ST/IL/SFC code generator :\n"
 msgstr ""
 
@@ -3193,7 +3246,7 @@
 msgid "Wrap search"
 msgstr ""
 
-#: ../features.py:9
+#: ../features.py:10
 msgid "WxGlade GUI"
 msgstr ""
 
@@ -3209,17 +3262,17 @@
 "Open wxGlade anyway ?"
 msgstr ""
 
-#: ../ProjectController.py:220
+#: ../ProjectController.py:224
 msgid ""
 "You must have permission to work on the project\n"
 "Work on a project copy ?"
 msgstr ""
 
-#: ../editors/LDViewer.py:883
+#: ../editors/LDViewer.py:886
 msgid "You must select the block or group of blocks around which a branch should be added!"
 msgstr ""
 
-#: ../editors/LDViewer.py:663
+#: ../editors/LDViewer.py:666
 msgid "You must select the wire where a contact should be added!"
 msgstr ""
 
@@ -3228,11 +3281,11 @@
 msgid "You must type a name!"
 msgstr ""
 
-#: ../dialogs/ForceVariableDialog.py:165
+#: ../dialogs/ForceVariableDialog.py:175
 msgid "You must type a value!"
 msgstr ""
 
-#: ../IDEFrame.py:414
+#: ../IDEFrame.py:419
 msgid "Zoom"
 msgstr ""
 
@@ -3240,7 +3293,7 @@
 msgid "Zoom:"
 msgstr ""
 
-#: ../PLCOpenEditor.py:356
+#: ../PLCOpenEditor.py:335
 #, python-format
 msgid "error: %s\n"
 msgstr ""
@@ -3250,7 +3303,7 @@
 msgid "exited with status %s (pid %s)\n"
 msgstr ""
 
-#: ../PLCOpenEditor.py:508 ../PLCOpenEditor.py:510
+#: ../PLCOpenEditor.py:393 ../PLCOpenEditor.py:395
 msgid "file : "
 msgstr ""
 
@@ -3258,7 +3311,7 @@
 msgid "function"
 msgstr ""
 
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
 msgid "function : "
 msgstr ""
 
@@ -3266,7 +3319,7 @@
 msgid "functionBlock"
 msgstr ""
 
-#: ../PLCOpenEditor.py:511
+#: ../PLCOpenEditor.py:396
 msgid "line : "
 msgstr ""
 
@@ -3286,7 +3339,7 @@
 msgid "string right of"
 msgstr ""
 
-#: ../PLCOpenEditor.py:354
+#: ../PLCOpenEditor.py:333
 #, python-format
 msgid "warning: %s\n"
 msgstr ""
@@ -3323,9 +3376,6 @@
 msgid "CAN_Driver"
 msgstr ""
 
-msgid "Debug_mode"
-msgstr ""
-
 msgid "CExtension"
 msgstr ""
 
Binary file images/LOG_CRITICAL.png has changed
Binary file images/LOG_DEBUG.png has changed
Binary file images/LOG_INFO.png has changed
Binary file images/LOG_WARNING.png has changed
Binary file images/fit_graph.png has changed
Binary file images/full_graph.png has changed
--- a/images/icons.svg	Wed Mar 13 12:34:55 2013 +0900
+++ b/images/icons.svg	Wed Jul 31 10:45:07 2013 +0900
@@ -16,7 +16,7 @@
    id="svg2"
    sodipodi:version="0.32"
    inkscape:version="0.48.3.1 r9886"
-   sodipodi:docname="icons.svg.2013_02_08_17_20_04.0.svg"
+   sodipodi:docname="icons.svg"
    inkscape:output_extension="org.inkscape.output.svg.inkscape">
   <metadata
      id="metadata13810">
@@ -26,7 +26,7 @@
         <dc:format>image/svg+xml</dc:format>
         <dc:type
            rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
+        <dc:title />
       </cc:Work>
     </rdf:RDF>
   </metadata>
@@ -34,19 +34,19 @@
      inkscape:window-height="1056"
      inkscape:window-width="1920"
      inkscape:pageshadow="2"
-     inkscape:pageopacity="1"
+     inkscape:pageopacity="0"
      guidetolerance="10.0"
      gridtolerance="10000"
      objecttolerance="10.0"
      borderopacity="1.0"
      bordercolor="#666666"
-     pagecolor="#f2f1f0"
+     pagecolor="#ffffff"
      id="base"
      showgrid="false"
-     inkscape:zoom="1"
-     inkscape:cx="590.30324"
-     inkscape:cy="563.15477"
-     inkscape:window-x="1920"
+     inkscape:zoom="4.0000001"
+     inkscape:cx="590.44587"
+     inkscape:cy="681.55725"
+     inkscape:window-x="0"
      inkscape:window-y="24"
      inkscape:current-layer="svg2"
      showguides="true"
@@ -90933,7 +90933,7 @@
      sodipodi:nodetypes="cccccccccccccccccccc" />
   <rect
      inkscape:label="#rect16270"
-     style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
      id="Debug"
      y="131.36218"
      x="670"
@@ -93515,4 +93515,132 @@
      id="path18458"
      inkscape:connector-curvature="0"
      sodipodi:nodetypes="ccccccccccccccccccccccccccccc" />
+  <text
+     style="font-size:20px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+     xml:space="preserve"
+     id="text18383-2-5"
+     y="379.81824"
+     x="170.02246"><tspan
+       id="tspan18385-7-6"
+       y="379.81824"
+       x="170.02246">Log levels icons</tspan></text>
+  <text
+     sodipodi:linespacing="125%"
+     style="font-size:12.76000023px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+     xml:space="preserve"
+     id="text60407-68-0"
+     y="360.20483"
+     x="365.61026"><tspan
+       y="360.20483"
+       x="365.61026"
+       id="tspan16195-3-3"
+       sodipodi:role="line">%% LOG_CRITICAL LOG_WARNING LOG_INFO LOG_DEBUG %%</tspan></text>
+  <rect
+     style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="LOG_CRITICAL"
+     y="366.36215"
+     x="431"
+     height="16"
+     width="16" />
+  <rect
+     style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="LOG_WARNING"
+     y="365.86215"
+     x="521"
+     height="16"
+     width="16" />
+  <rect
+     style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="LOG_INFO"
+     y="365.36215"
+     x="609.75"
+     height="16"
+     width="16" />
+  <rect
+     style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="LOG_DEBUG"
+     y="365.36215"
+     x="680"
+     height="16"
+     width="16" />
+  <path
+     sodipodi:type="arc"
+     style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="path18473"
+     sodipodi:cx="420.25"
+     sodipodi:cy="379.61218"
+     sodipodi:rx="6.25"
+     sodipodi:ry="6.25"
+     d="m 426.5,379.61218 a 6.25,6.25 0 1 1 -12.5,0 6.25,6.25 0 1 1 12.5,0 z"
+     transform="matrix(1.2040688,0,0,1.2040688,-67.009896,-82.717008)" />
+  <path
+     style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
+     d="m 436.51042,370.07049 -1.85417,1.91667 2.4375,2.375 -2.4375,2.375 1.85417,1.91667 2.47917,-2.4375 2.5,2.4375 1.85417,-1.91667 -2.43751,-2.375 2.43751,-2.375 -1.85417,-1.91667 -2.5,2.4375 -2.47917,-2.4375 z"
+     id="path18476"
+     inkscape:connector-curvature="0" />
+  <path
+     sodipodi:type="star"
+     style="color:#000000;fill:#ffcc00;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="path19290"
+     sodipodi:sides="3"
+     sodipodi:cx="485.4288"
+     sodipodi:cy="371.94867"
+     sodipodi:r1="12.432317"
+     sodipodi:r2="6.3169394"
+     sodipodi:arg1="0.52090978"
+     sodipodi:arg2="1.568032"
+     inkscape:flatsided="false"
+     inkscape:rounded="0"
+     inkscape:randomized="0"
+     d="m 496.21218,378.13585 -10.76592,0.12973 -10.76741,-0.0718 5.27061,-9.38843 5.44591,-9.28893 5.49531,9.25869 z"
+     inkscape:transform-center-x="-0.011147353"
+     inkscape:transform-center-y="-1.9795276"
+     transform="matrix(0.6666672,0,0,0.6666672,205.3694,127.93464)" />
+  <text
+     xml:space="preserve"
+     style="font-size:12.4041214px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+     x="441.56531"
+     y="451.09576"
+     id="text19292"
+     sodipodi:linespacing="125%"
+     transform="scale(1.191303,0.839417)"><tspan
+       sodipodi:role="line"
+       id="tspan19294"
+       x="441.56531"
+       y="451.09576">!</tspan></text>
+  <path
+     sodipodi:type="arc"
+     style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="path18473-1"
+     sodipodi:cx="420.25"
+     sodipodi:cy="379.61218"
+     sodipodi:rx="6.25"
+     sodipodi:ry="6.25"
+     d="m 426.5,379.61218 a 6.25,6.25 0 1 1 -12.5,0 6.25,6.25 0 1 1 12.5,0 z"
+     transform="matrix(1.2040688,0,0,1.2040688,111.7401,-83.717008)" />
+  <text
+     xml:space="preserve"
+     style="font-size:15.56442642px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+     x="475.99768"
+     y="488.26718"
+     id="text19314"
+     sodipodi:linespacing="125%"
+     transform="scale(1.2919212,0.77404101)"><tspan
+       sodipodi:role="line"
+       id="tspan19316"
+       x="475.99768"
+       y="488.26718">i</tspan></text>
+  <rect
+     style="color:#000000;fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:#c8c8c8;stroke-width:0.66666722;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     id="rect19318"
+     width="14.259998"
+     height="13.081486"
+     x="680.87"
+     y="366.82141" />
+  <path
+     style="fill:none;stroke:#ffffff;stroke-width:1.33333445;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+     d="m 681.20882,373.5854 4.27211,0 1.44678,-2.50589 2.63578,4.56531 1.3874,-2.40303 3.84029,0"
+     id="path19324"
+     inkscape:connector-curvature="0"
+     sodipodi:nodetypes="cccccc" />
 </svg>
--- a/images/plcopen_icons.svg	Wed Mar 13 12:34:55 2013 +0900
+++ b/images/plcopen_icons.svg	Wed Jul 31 10:45:07 2013 +0900
@@ -11114,6 +11114,318 @@
        id="radialGradient5955"
        xlink:href="#radialGradient4208-2"
        inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient5533-1"
+       y2="609.51001"
+       gradientUnits="userSpaceOnUse"
+       x2="302.85999"
+       gradientTransform="matrix(0.031048,0,0,0.013668,0.77854,15.669)"
+       y1="366.64999"
+       x1="302.85999">
+      <stop
+         id="stop5050-7-1"
+         style="stop-opacity:0"
+         offset="0" />
+      <stop
+         id="stop5056-19-2"
+         offset=".5" />
+      <stop
+         id="stop5052-4-7"
+         style="stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <radialGradient
+       id="radialGradient5535-3"
+       xlink:href="#linearGradient5060-1-4"
+       gradientUnits="userSpaceOnUse"
+       cy="486.64999"
+       cx="605.71002"
+       gradientTransform="matrix(0.031048,0,0,0.013668,0.78465,15.669)"
+       r="117.14" />
+    <linearGradient
+       id="linearGradient5060-1-4">
+      <stop
+         id="stop5062-7-0"
+         offset="0" />
+      <stop
+         id="stop5064-9-6"
+         style="stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <radialGradient
+       id="radialGradient5537-8"
+       xlink:href="#linearGradient5060-1-4"
+       gradientUnits="userSpaceOnUse"
+       cy="486.64999"
+       cx="605.71002"
+       gradientTransform="matrix(-0.031048,0,0,0.013668,23.215,15.669)"
+       r="117.14" />
+    <linearGradient
+       id="linearGradient5891">
+      <stop
+         id="stop5893"
+         offset="0" />
+      <stop
+         id="stop5895-3"
+         style="stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5565-0"
+       y2="39.924"
+       gradientUnits="userSpaceOnUse"
+       x2="21.780001"
+       gradientTransform="matrix(0.63636,0,0,0.62295,-3.9091,-3.1066)"
+       y1="8.5762997"
+       x1="21.865999">
+      <stop
+         id="stop2783-4"
+         style="stop-color:#505050"
+         offset="0" />
+      <stop
+         id="stop6301-2"
+         style="stop-color:#6e6e6e"
+         offset=".13216" />
+      <stop
+         id="stop2785-4"
+         style="stop-color:#8c8c8c"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5562-5-4"
+       y2="15.044"
+       gradientUnits="userSpaceOnUse"
+       x2="16.075001"
+       gradientTransform="matrix(0.61291,0,0,0.58621,-3.3226,-2.069)"
+       y1="9.0734997"
+       x1="16.034">
+      <stop
+         id="stop3692-0"
+         style="stop-color:#fff"
+         offset="0" />
+      <stop
+         id="stop3694-8"
+         style="stop-color:#fff;stop-opacity:.46875"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5559-2"
+       y2="40"
+       gradientUnits="userSpaceOnUse"
+       x2="24"
+       gradientTransform="matrix(0.52632,0,0,0.48148,-0.63158,1.7407)"
+       y1="13"
+       x1="24">
+      <stop
+         id="stop6459-5"
+         style="stop-color:#fff;stop-opacity:.94118"
+         offset="0" />
+      <stop
+         id="stop6461-67"
+         style="stop-color:#fff;stop-opacity:.70588"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient6639-4"
+       y2="22.839001"
+       xlink:href="#linearGradient6388-8"
+       gradientUnits="userSpaceOnUse"
+       x2="33.25"
+       gradientTransform="matrix(0,1,-1,0,25.121,-26.636)"
+       y1="16.121"
+       x1="39.879002" />
+    <linearGradient
+       id="linearGradient6388-8">
+      <stop
+         id="stop6390-9"
+         style="stop-color:#73a300"
+         offset="0" />
+      <stop
+         id="stop6392-8"
+         style="stop-color:#428300;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient6643-1"
+       y2="22.839001"
+       xlink:href="#linearGradient6388-8"
+       gradientUnits="userSpaceOnUse"
+       x2="33.25"
+       gradientTransform="matrix(0,-1,-1,0,25.121,55.879)"
+       y1="16.121"
+       x1="39.879002" />
+    <linearGradient
+       id="linearGradient5912">
+      <stop
+         id="stop5914"
+         style="stop-color:#73a300"
+         offset="0" />
+      <stop
+         id="stop5916"
+         style="stop-color:#428300;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient6647-7"
+       y2="22.839001"
+       xlink:href="#linearGradient6388-8"
+       gradientUnits="userSpaceOnUse"
+       x2="33.25"
+       gradientTransform="matrix(0,1,1,0,-1.1213,-26.879)"
+       y1="16.121"
+       x1="39.879002" />
+    <linearGradient
+       id="linearGradient5919">
+      <stop
+         id="stop5921"
+         style="stop-color:#73a300"
+         offset="0" />
+      <stop
+         id="stop5923"
+         style="stop-color:#428300;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       y2="22.839001"
+       x2="33.25"
+       y1="16.121"
+       x1="39.879002"
+       gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient3099-5-7"
+       xlink:href="#linearGradient6388-8"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient5926">
+      <stop
+         id="stop5928"
+         style="stop-color:#73a300"
+         offset="0" />
+      <stop
+         id="stop5930-6"
+         style="stop-color:#428300;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       y2="22.839001"
+       x2="33.25"
+       y1="16.121"
+       x1="39.879002"
+       gradientTransform="matrix(0,-1,1,0,-1.1213,55.879)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient5947"
+       xlink:href="#linearGradient6388-8"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient5528-2"
+       y2="39.924"
+       gradientUnits="userSpaceOnUse"
+       x2="21.780001"
+       gradientTransform="matrix(0.45455,0,0,0.45902,-3.3637,-2.6312)"
+       y1="8.5762997"
+       x1="21.865999">
+      <stop
+         id="stop2783-0"
+         style="stop-color:#505050"
+         offset="0" />
+      <stop
+         id="stop6301-1"
+         style="stop-color:#6e6e6e"
+         offset=".13216" />
+      <stop
+         id="stop2785-8"
+         style="stop-color:#8c8c8c"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5525-2"
+       y2="15.044"
+       gradientUnits="userSpaceOnUse"
+       x2="16.075001"
+       gradientTransform="matrix(0.41935,0,0,0.41379,-2.4838,-1.431)"
+       y1="9.0734997"
+       x1="16.034">
+      <stop
+         id="stop3692-5"
+         style="stop-color:#fff"
+         offset="0" />
+      <stop
+         id="stop3694-7"
+         style="stop-color:#fff;stop-opacity:.46875"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5522-2"
+       y2="40"
+       gradientUnits="userSpaceOnUse"
+       x2="24"
+       gradientTransform="matrix(0.36842,0,0,0.33333,-0.8421,1.6667)"
+       y1="13"
+       x1="24">
+      <stop
+         id="stop6459-3"
+         style="stop-color:#fff;stop-opacity:.94118"
+         offset="0" />
+      <stop
+         id="stop6461-4"
+         style="stop-color:#fff;stop-opacity:.70588"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5528-5"
+       y2="39.924"
+       gradientUnits="userSpaceOnUse"
+       x2="21.780001"
+       gradientTransform="matrix(0.45455,0,0,0.45902,-3.3637,-2.6312)"
+       y1="8.5762997"
+       x1="21.865999">
+      <stop
+         id="stop2783-8"
+         style="stop-color:#505050"
+         offset="0" />
+      <stop
+         id="stop6301-3"
+         style="stop-color:#6e6e6e"
+         offset=".13216" />
+      <stop
+         id="stop2785-3"
+         style="stop-color:#8c8c8c"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5525-31"
+       y2="15.044"
+       gradientUnits="userSpaceOnUse"
+       x2="16.075001"
+       gradientTransform="matrix(0.41935,0,0,0.41379,-2.4838,-1.431)"
+       y1="9.0734997"
+       x1="16.034">
+      <stop
+         id="stop3692-48"
+         style="stop-color:#fff"
+         offset="0" />
+      <stop
+         id="stop3694-10"
+         style="stop-color:#fff;stop-opacity:.46875"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5522-7"
+       y2="40"
+       gradientUnits="userSpaceOnUse"
+       x2="24"
+       gradientTransform="matrix(0.36842,0,0,0.33333,-0.8421,1.6667)"
+       y1="13"
+       x1="24">
+      <stop
+         id="stop6459-7"
+         style="stop-color:#fff;stop-opacity:.94118"
+         offset="0" />
+      <stop
+         id="stop6461-7"
+         style="stop-color:#fff;stop-opacity:.70588"
+         offset="1" />
+    </linearGradient>
   </defs>
   <sodipodi:namedview
      id="base"
@@ -11122,9 +11434,9 @@
      borderopacity="1.0"
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
-     inkscape:zoom="2.828427"
-     inkscape:cx="60.957367"
-     inkscape:cy="-299.13955"
+     inkscape:zoom="7.9999995"
+     inkscape:cx="103.60893"
+     inkscape:cy="-243.21864"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
      width="16px"
@@ -16155,5 +16467,161 @@
          d="M 18.531,8.7812 V 10 A 0.51754,0.51754 0 0 1 18,10.531 H 9.4375 l 0.03125,2.9375 h 8.5312 a 0.51754,0.51754 0 0 1 0.531,0.532 v 1.1562 l 3.469,-3.281 -3.469,-3.0938 z"
          transform="translate(0,0.99987)" />
     </g>
+    <text
+       xml:space="preserve"
+       style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+       x="70.198586"
+       y="255.80313"
+       id="text3638-3-3-2-0-9-8-5-5-2-6"><tspan
+         sodipodi:role="line"
+         id="tspan3640-1-8-0-6-8-4-3-9-5-4"
+         x="70.198586"
+         y="255.80313">%%fit_graph%%</tspan></text>
+    <rect
+       inkscape:label="#rect3636"
+       y="259.125"
+       x="80.25"
+       height="16"
+       width="16"
+       id="fit_graph"
+       style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <text
+       xml:space="preserve"
+       style="font-size:4.49727678px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
+       x="108.19859"
+       y="255.80313"
+       id="text3638-3-3-2-0-9-8-5-5-2-6-9"><tspan
+         sodipodi:role="line"
+         id="tspan3640-1-8-0-6-8-4-3-9-5-4-0"
+         x="108.19859"
+         y="255.80313">%%full_graph%%</tspan></text>
+    <rect
+       inkscape:label="#rect3636"
+       y="259.125"
+       x="119.84098"
+       height="16"
+       width="16"
+       id="full_graph"
+       style="opacity:0;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <g
+       transform="translate(119.73268,258.52291)"
+       id="g5512">
+      <rect
+         id="rect1887-08"
+         style="fill:url(#linearGradient5528-2);stroke:#565853;stroke-width:0.99993002;stroke-linejoin:round"
+         height="14"
+         width="15"
+         y="1.5"
+         x="0.49996999" />
+      <rect
+         id="rect2779-5"
+         style="opacity:0.2;fill:none;stroke:url(#linearGradient5525-2);stroke-width:1.00010002"
+         height="12"
+         width="13"
+         y="2.5000999"
+         x="1.5001" />
+      <rect
+         id="rect6287-2"
+         style="fill:url(#linearGradient5522-2)"
+         height="9"
+         width="14"
+         y="6"
+         x="1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6293-3"
+         style="fill:#ffc24c"
+         d="M 14,4.25 C 14,4.6642 13.664,5 13.25,5 12.836,5 12.5,4.6642 12.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="rect5590-5"
+         style="opacity:0.7;fill:#aa0000"
+         d="M 5.5355,13.293 2.7071,10.464 2,14 5.5355,13.293 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6302"
+         style="fill:#ffc24c"
+         d="M 12,4.25 C 12,4.6642 11.664,5 11.25,5 10.836,5 10.5,4.6642 10.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6304"
+         style="fill:#ffc24c"
+         d="M 10,4.25 C 10,4.6642 9.6643,5 9.25,5 8.8357,5 8.4998,4.6642 8.5,4.25 8.4998,3.8358 8.8357,3.5 9.25,3.5 9.6643,3.5 10,3.8358 10,4.25 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6312"
+         style="opacity:0.7;fill:#aa0000"
+         d="M 5.5355,7.7071 2.7071,10.536 2,7 5.5355,7.7071 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6316"
+         style="opacity:0.7;fill:#aa0000"
+         d="M 10.464,13.293 13.293,10.464 14,14 10.464,13.293 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6318"
+         style="opacity:0.7;fill:#aa0000"
+         d="M 10.464,7.7071 13.293,10.536 14,7 10.464,7.7071 z" />
+    </g>
+    <g
+       transform="translate(80.18127,258.41201)"
+       id="g5498-7">
+      <rect
+         id="rect1887-3"
+         style="fill:url(#linearGradient5528-5);stroke:#565853;stroke-width:0.99993002;stroke-linejoin:round"
+         height="14"
+         width="15"
+         y="1.5"
+         x="0.49996999" />
+      <rect
+         id="rect2779-33"
+         style="opacity:0.2;fill:none;stroke:url(#linearGradient5525-31);stroke-width:1.00010002"
+         height="12"
+         width="13"
+         y="2.5000999"
+         x="1.5001" />
+      <rect
+         id="rect6287-0"
+         style="fill:url(#linearGradient5522-7)"
+         height="9"
+         width="14"
+         y="6"
+         x="1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6293-8"
+         style="fill:#ffc24c"
+         d="M 14,4.25 C 14,4.6642 13.664,5 13.25,5 12.836,5 12.5,4.6642 12.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="rect5590-8"
+         style="fill:#73a300"
+         d="M 2.4645,11.707 5.2929,14.536 6,11 2.4645,11.707 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6302-0"
+         style="fill:#ffc24c"
+         d="M 12,4.25 C 12,4.6642 11.664,5 11.25,5 10.836,5 10.5,4.6642 10.5,4.25 c 0,-0.4142 0.336,-0.75 0.75,-0.75 0.41434,0 0.75018,0.33584 0.75,0.75 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6304-7"
+         style="fill:#ffc24c"
+         d="M 10,4.25 C 10,4.6642 9.6643,5 9.25,5 8.8357,5 8.4998,4.6642 8.5,4.25 8.4998,3.8358 8.8357,3.5 9.25,3.5 9.6643,3.5 10,3.8358 10,4.25 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6312-0"
+         style="fill:#73a300"
+         d="M 2.4645,9.2929 5.2929,6.4645 6,10 2.4645,9.2929 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6316-1"
+         style="fill:#73a300"
+         d="M 13.536,11.707 10.707,14.536 10,11 l 3.5355,0.70711 z" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path6318-5"
+         style="fill:#73a300"
+         d="M 13.536,9.2929 10.707,6.4645 10,10 13.536,9.2929 z" />
+    </g>
   </g>
 </svg>
Binary file locale/fr_FR/LC_MESSAGES/Beremiz.mo has changed
--- a/plcopen/plcopen.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/plcopen/plcopen.py	Wed Jul 31 10:45:07 2013 +0900
@@ -155,6 +155,17 @@
         self.text = text
     setattr(cls, "updateElementAddress", updateElementAddress)
     
+    def hasblock(self, block_type):
+        text = self.text.upper()
+        index = text.find(block_type.upper())
+        while index != -1:
+            if (not (index > 0 and (text[index - 1].isalnum() or text[index - 1] == "_")) and 
+                not (index < len(text) - len(block_type) and text[index + len(block_type)] != "(")):
+                return True
+            index = text.find(block_type.upper(), index + len(block_type))
+        return False
+    setattr(cls, "hasblock", hasblock)
+    
     def Search(self, criteria, parent_infos):
         return [(tuple(parent_infos),) + result for result in TestTextElement(self.gettext(), criteria)]
     setattr(cls, "Search", Search)
@@ -489,17 +500,19 @@
         self.CustomBlockTypes.append(block_infos)
     setattr(cls, "AddCustomBlockType", AddCustomBlockType)
 
+    def AddElementUsingTreeInstance(self, name, type_infos):
+        typename = type_infos.getname()
+        if not self.ElementUsingTree.has_key(typename):
+            self.ElementUsingTree[typename] = [name]
+        elif name not in self.ElementUsingTree[typename]:
+            self.ElementUsingTree[typename].append(name)
+    setattr(cls, "AddElementUsingTreeInstance", AddElementUsingTreeInstance)
+    
     def RefreshElementUsingTree(self):
         # Reset the tree of user-defined element cross-use
         self.ElementUsingTree = {}
         pous = self.getpous()
         datatypes = self.getdataTypes()
-        # Reference all the user-defined elementu names and initialize the tree of 
-        # user-defined elemnt cross-use
-        elementnames = [datatype.getname() for datatype in datatypes] + \
-                       [pou.getname() for pou in pous]
-        for name in elementnames:
-            self.ElementUsingTree[name] = []
         # Analyze each datatype
         for datatype in datatypes:
             name = datatype.getname()
@@ -511,16 +524,12 @@
             elif basetype_content["name"] in ["subrangeSigned", "subrangeUnsigned", "array"]:
                 base_type = basetype_content["value"].baseType.getcontent()
                 if base_type["name"] == "derived":
-                    typename = base_type["value"].getname()
-                    if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
-                        self.ElementUsingTree[typename].append(name)
+                    self.AddElementUsingTreeInstance(name, base_type["value"])
             elif basetype_content["name"] == "struct":
                 for element in basetype_content["value"].getvariable():
                     type_content = element.type.getcontent()
                     if type_content["name"] == "derived":
-                        typename = type_content["value"].getname()
-                        if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
-                            self.ElementUsingTree[typename].append(name)
+                        self.AddElementUsingTreeInstance(name, type_content["value"])
         # Analyze each pou
         for pou in pous:
             name = pou.getname()
@@ -530,9 +539,11 @@
                     for var in varlist.getvariable():
                         vartype_content = var.gettype().getcontent()
                         if vartype_content["name"] == "derived":
-                            typename = vartype_content["value"].getname()
-                            if self.ElementUsingTree.has_key(typename) and name not in self.ElementUsingTree[typename]:
-                                self.ElementUsingTree[typename].append(name)
+                            self.AddElementUsingTreeInstance(name, vartype_content["value"])
+            for typename in self.ElementUsingTree.iterkeys():
+                if typename != name and pou.hasblock(block_type=typename) and name not in self.ElementUsingTree[typename]:
+                    self.ElementUsingTree[typename].append(name)
+        
     setattr(cls, "RefreshElementUsingTree", RefreshElementUsingTree)
 
     def GetParentType(self, type):
@@ -840,6 +851,35 @@
 
 cls = PLCOpenClasses.get("configurations_configuration", None)
 if cls:
+    
+    def addglobalVar(self, type, name, location="", description=""):
+        globalvars = self.getglobalVars()
+        if len(globalvars) == 0:
+            globalvars.append(PLCOpenClasses["varList"]())
+        var = PLCOpenClasses["varListPlain_variable"]()
+        var.setname(name)
+        var_type = PLCOpenClasses["dataType"]()
+        if type in [x for x,y in TypeHierarchy_list if not x.startswith("ANY")]:
+            if type == "STRING":
+                var_type.setcontent({"name" : "string", "value" : PLCOpenClasses["elementaryTypes_string"]()})
+            elif type == "WSTRING":
+                var_type.setcontent({"name" : "wstring", "value" : PLCOpenClasses["elementaryTypes_wstring"]()})
+            else:
+                var_type.setcontent({"name" : type, "value" : None})
+        else:
+            derived_type = PLCOpenClasses["derivedTypes_derived"]()
+            derived_type.setname(type)
+            var_type.setcontent({"name" : "derived", "value" : derived_type})
+        var.settype(var_type)
+        if location != "":
+            var.setaddress(location)
+        if description != "":
+            ft = PLCOpenClasses["formattedText"]()
+            ft.settext(description)
+            var.setdocumentation(ft)
+        globalvars[-1].appendvariable(var)
+    setattr(cls, "addglobalVar", addglobalVar)
+    
     def updateElementName(self, old_name, new_name):
         _updateConfigurationResourceElementName(self, old_name, new_name)
         for resource in self.getresource():
@@ -1382,21 +1422,25 @@
                     break
     setattr(cls, "removepouVar", removepouVar)
     
-    def hasblock(self, name):
+    def hasblock(self, name=None, block_type=None):
         if self.getbodyType() in ["FBD", "LD", "SFC"]:
             for instance in self.getinstances():
-                if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
+                if (isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and 
+                    (name and instance.getinstanceName() == name or
+                     block_type and instance.gettypeName() == block_type)):
                     return True
             if self.transitions:
                 for transition in self.transitions.gettransition():
-                    result = transition.hasblock(name)
+                    result = transition.hasblock(name, block_type)
                     if result:
                         return result
             if self.actions:
                 for action in self.actions.getaction():
-                    result = action.hasblock(name)
+                    result = action.hasblock(name, block_type)
                     if result:
                         return result
+        elif block_type is not None and len(self.body) > 0:
+            return self.body[0].hasblock(block_type)
         return False
     setattr(cls, "hasblock", hasblock)
     
@@ -1626,6 +1670,24 @@
 def gettext(self):
     return self.body.gettext()
 
+def hasblock(self, name=None, block_type=None):
+    if self.getbodyType() in ["FBD", "LD", "SFC"]:
+        for instance in self.getinstances():
+            if (isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and 
+                (name and instance.getinstanceName() == name or
+                 block_type and instance.gettypeName() == block_type)):
+                return True
+    elif block_type is not None:
+        return self.body.hasblock(block_type)
+    return False
+
+def updateElementName(self, old_name, new_name):
+    self.body.updateElementName(old_name, new_name)
+
+def updateElementAddress(self, address_model, new_leading):
+    self.body.updateElementAddress(address_model, new_leading)
+    
+
 cls = PLCOpenClasses.get("transitions_transition", None)
 if cls:
     setattr(cls, "setbodyType", setbodyType)
@@ -1641,23 +1703,10 @@
     setattr(cls, "removeinstance", removeinstance)
     setattr(cls, "settext", settext)
     setattr(cls, "gettext", gettext)
-
-    def updateElementName(self, old_name, new_name):
-        self.body.updateElementName(old_name, new_name)
+    setattr(cls, "hasblock", hasblock)
     setattr(cls, "updateElementName", updateElementName)
-
-    def updateElementAddress(self, address_model, new_leading):
-        self.body.updateElementAddress(address_model, new_leading)
     setattr(cls, "updateElementAddress", updateElementAddress)
-
-    def hasblock(self, name):
-        if self.getbodyType() in ["FBD", "LD", "SFC"]:
-            for instance in self.getinstances():
-                if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
-                    return True
-        return False
-    setattr(cls, "hasblock", hasblock)
-
+    
     def Search(self, criteria, parent_infos):
         search_result = []
         parent_infos = parent_infos[:-1] + ["T::%s::%s" % (parent_infos[-1].split("::")[1], self.getname())]
@@ -1682,23 +1731,10 @@
     setattr(cls, "removeinstance", removeinstance)
     setattr(cls, "settext", settext)
     setattr(cls, "gettext", gettext)
-
-    def updateElementName(self, old_name, new_name):
-        self.body.updateElementName(old_name, new_name)
+    setattr(cls, "hasblock", hasblock)
     setattr(cls, "updateElementName", updateElementName)
-
-    def updateElementAddress(self, address_model, new_leading):
-        self.body.updateElementAddress(address_model, new_leading)
     setattr(cls, "updateElementAddress", updateElementAddress)
-
-    def hasblock(self, name):
-        if self.getbodyType() in ["FBD", "LD", "SFC"]:
-            for instance in self.getinstances():
-                if isinstance(instance, PLCOpenClasses["fbdObjects_block"]) and instance.getinstanceName() == name:
-                    return True
-        return False
-    setattr(cls, "hasblock", hasblock)
-
+    
     def Search(self, criteria, parent_infos):
         search_result = []
         parent_infos = parent_infos[:-1] + ["A::%s::%s" % (parent_infos[-1].split("::")[1], self.getname())]
@@ -1711,6 +1747,24 @@
 cls = PLCOpenClasses.get("body", None)
 if cls:
     cls.currentExecutionOrderId = 0
+    cls.instances_dict = {}
+    
+    setattr(cls, "_init_", getattr(cls, "__init__"))
+    
+    def __init__(self, *args, **kwargs):
+        self._init_(*args, **kwargs)
+        self.instances_dict = {}
+    setattr(cls, "__init__", __init__)
+    
+    setattr(cls, "_loadXMLTree", getattr(cls, "loadXMLTree"))
+    
+    def loadXMLTree(self, *args, **kwargs):
+        self._loadXMLTree(*args, **kwargs)
+        if self.content["name"] in ["LD","FBD","SFC"]:
+            self.instances_dict = dict(
+                [(element["value"].getlocalId(), element)
+                 for element in self.content["value"].getcontent()])
+    setattr(cls, "loadXMLTree", loadXMLTree)
     
     def resetcurrentExecutionOrderId(self):
         object.__setattr__(self, "currentExecutionOrderId", 0)
@@ -1785,7 +1839,9 @@
     
     def appendcontentInstance(self, name, instance):
         if self.content["name"] in ["LD","FBD","SFC"]:
-            self.content["value"].appendcontent({"name" : name, "value" : instance})
+            element = {"name" : name, "value" : instance}
+            self.content["value"].appendcontent(element)
+            self.instances_dict[instance.getlocalId()] = element
         else:
             raise TypeError, _("%s body don't have instances!")%self.content["name"]
     setattr(cls, "appendcontentInstance", appendcontentInstance)
@@ -1802,9 +1858,9 @@
 
     def getcontentInstance(self, id):
         if self.content["name"] in ["LD","FBD","SFC"]:
-            for element in self.content["value"].getcontent():
-                if element["value"].getlocalId() == id:
-                    return element["value"]
+            instance = self.instances_dict.get(id, None)
+            if instance is not None:
+                return instance["value"]
             return None
         else:
             raise TypeError, _("%s body don't have instances!")%self.content["name"]
@@ -1812,9 +1868,9 @@
     
     def getcontentRandomInstance(self, exclude):
         if self.content["name"] in ["LD","FBD","SFC"]:
-            for element in self.content["value"].getcontent():
-                if element["value"].getlocalId() not in exclude:
-                    return element["value"]
+            ids = self.instances_dict.viewkeys() - exclude
+            if len(ids) > 0:
+                return self.instances_dict[ids.pop()]["value"]
             return None
         else:
             raise TypeError, _("%s body don't have instances!")%self.content["name"]
@@ -1831,15 +1887,10 @@
     
     def removecontentInstance(self, id):
         if self.content["name"] in ["LD","FBD","SFC"]:
-            i = 0
-            removed = False
-            elements = self.content["value"].getcontent()
-            while i < len(elements) and not removed:
-                if elements[i]["value"].getlocalId() == id:
-                    self.content["value"].removecontent(i)
-                    removed = True
-                i += 1
-            if not removed:
+            element = self.instances_dict.pop(id, None)
+            if element is not None:
+                self.content["value"].getcontent().remove(element)
+            else:
                 raise ValueError, _("Instance with id %d doesn't exist!")%id
         else:
             raise TypeError, "%s body don't have instances!"%self.content["name"]
@@ -1859,6 +1910,13 @@
             raise TypeError, _("%s body don't have text!")%self.content["name"]
     setattr(cls, "gettext", gettext)
     
+    def hasblock(self, block_type):
+        if self.content["name"] in ["IL","ST"]:
+            return self.content["value"].hasblock(block_type)
+        else:
+            raise TypeError, _("%s body don't have text!")%self.content["name"]
+    setattr(cls, "hasblock", hasblock)
+    
     def updateElementName(self, old_name, new_name):
         if self.content["name"] in ["IL", "ST"]:
             self.content["value"].updateElementName(old_name, new_name)
--- a/plcopen/structures.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/plcopen/structures.py	Wed Jul 31 10:45:07 2013 +0900
@@ -46,38 +46,67 @@
     name = block.getinstanceName()
     type = block.gettypeName()
     executionOrderId = block.getexecutionOrderId()
+    input_variables = block.inputVariables.getvariable()
+    output_variables = block.outputVariables.getvariable()
     inout_variables = {}
-    for input_variable in block.inputVariables.getvariable():
-        for output_variable in block.outputVariables.getvariable():
+    for input_variable in input_variables:
+        for output_variable in output_variables:
             if input_variable.getformalParameter() == output_variable.getformalParameter():
                 inout_variables[input_variable.getformalParameter()] = ""
+    input_names = [input[0] for input in block_infos["inputs"]]
+    output_names = [output[0] for output in block_infos["outputs"]]
     if block_infos["type"] == "function":
-        output_variables = block.outputVariables.getvariable()
         if not generator.ComputedBlocks.get(block, False) and not order:
             generator.ComputedBlocks[block] = True
-            vars = []
+            connected_vars = []
+            if not block_infos["extensible"]:
+                input_connected = dict([("EN", None)] + 
+                                       [(input_name, None) for input_name in input_names])
+                for variable in input_variables:
+                    parameter = variable.getformalParameter()
+                    if input_connected.has_key(parameter):
+                        input_connected[parameter] = variable
+                if input_connected["EN"] is None:
+                    input_connected.pop("EN")
+                    input_parameters = input_names
+                else:
+                    input_parameters = ["EN"] + input_names
+            else:
+                input_connected = dict([(variable.getformalParameter(), variable)
+                                        for variable in input_variables])
+                input_parameters = [variable.getformalParameter()
+                                    for variable in input_variables]
             one_input_connected = False
-            for i, variable in enumerate(block.inputVariables.getvariable()):
-                input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
-                connections = variable.connectionPointIn.getconnections()
-                if connections is not None:
-                    parameter = variable.getformalParameter()
-                    if parameter != "EN":
-                        one_input_connected = True
-                    if inout_variables.has_key(parameter):
-                        value = generator.ComputeExpression(body, connections, executionOrderId > 0, True)
-                        inout_variables[parameter] = value
+            all_input_connected = True
+            for i, parameter in enumerate(input_parameters):
+                variable = input_connected.get(parameter)
+                if variable is not None:
+                    input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
+                    connections = variable.connectionPointIn.getconnections()
+                    if connections is not None:
+                        if parameter != "EN":
+                            one_input_connected = True
+                        if inout_variables.has_key(parameter):
+                            expression = generator.ComputeExpression(body, connections, executionOrderId > 0, True)
+                            if expression is not None:
+                                inout_variables[parameter] = value
+                        else:
+                            expression = generator.ComputeExpression(body, connections, executionOrderId > 0)
+                        if expression is not None:
+                            connected_vars.append(([(parameter, input_info), (" := ", ())],
+                                                   generator.ExtractModifier(variable, expression, input_info)))
                     else:
-                        value = generator.ComputeExpression(body, connections, executionOrderId > 0)
-                    if len(output_variables) > 1:
-                        vars.append([(parameter, input_info),
-                                     (" := ", ())] + generator.ExtractModifier(variable, value, input_info))
-                    else:
-                        vars.append(generator.ExtractModifier(variable, value, input_info))
+                        all_input_connected = False
+                else:
+                    all_input_connected = False
+            if len(output_variables) > 1 or not all_input_connected:
+                vars = [name + value for name, value in connected_vars]
+            else:
+                vars = [value for name, value in connected_vars]
             if one_input_connected:
                 for i, variable in enumerate(output_variables):
                     parameter = variable.getformalParameter()
-                    if not inout_variables.has_key(parameter):
+                    if not inout_variables.has_key(parameter) and parameter in output_names + ["", "ENO"]:
                         if variable.getformalParameter() == "":
                             variable_name = "%s%d"%(type, block.getlocalId())
                         else:
@@ -103,67 +132,99 @@
                 generator.Program += [(");\n", ())]
             else:
                 generator.Warnings.append(_("\"%s\" function cancelled in \"%s\" POU: No input connected")%(type, generator.TagName.split("::")[-1]))
-        if link:
-            connectionPoint = link.getposition()[-1]
-        else:
-            connectionPoint = None
-        for i, variable in enumerate(output_variables):
-            blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
-            if not connectionPoint or block.getx() + blockPointx == connectionPoint.getx() and block.gety() + blockPointy == connectionPoint.gety():
-                output_info = (generator.TagName, "block", block.getlocalId(), "output", i)
-                parameter = variable.getformalParameter()
-                if inout_variables.has_key(parameter):
-                    output_value = inout_variables[parameter]
-                else:
-                    if parameter == "":
-                        output_name = "%s%d"%(type, block.getlocalId())
-                    else:
-                        output_name = "%s%d_%s"%(type, block.getlocalId(), parameter)
-                    output_value = [(output_name, output_info)]
-                return generator.ExtractModifier(variable, output_value, output_info)
     elif block_infos["type"] == "functionBlock":
         if not generator.ComputedBlocks.get(block, False) and not order:
             generator.ComputedBlocks[block] = True
             vars = []
-            for i, variable in enumerate(block.inputVariables.getvariable()):
-                input_info = (generator.TagName, "block", block.getlocalId(), "input", i)
-                connections = variable.connectionPointIn.getconnections()
-                if connections is not None:
-                    parameter = variable.getformalParameter()
-                    value = generator.ComputeExpression(body, connections, executionOrderId > 0, inout_variables.has_key(parameter))
-                    vars.append([(parameter, input_info),
-                                 (" := ", ())] + generator.ExtractModifier(variable, value, input_info))
+            offset_idx = 0
+            for variable in input_variables:
+                parameter = variable.getformalParameter()
+                if parameter in input_names or parameter == "EN":
+                    if parameter == "EN":
+                        input_idx = 0
+                        offset_idx = 1
+                    else:
+                        input_idx = offset_idx + input_names.index(parameter)
+                    input_info = (generator.TagName, "block", block.getlocalId(), "input", input_idx)
+                    connections = variable.connectionPointIn.getconnections()
+                    if connections is not None:
+                        expression = generator.ComputeExpression(body, connections, executionOrderId > 0, inout_variables.has_key(parameter))
+                        if expression is not None:
+                            vars.append([(parameter, input_info),
+                                         (" := ", ())] + generator.ExtractModifier(variable, expression, input_info))
             generator.Program += [(generator.CurrentIndent, ()), 
                                   (name, (generator.TagName, "block", block.getlocalId(), "name")),
                                   ("(", ())]
             generator.Program += JoinList([(", ", ())], vars)
             generator.Program += [(");\n", ())]
-        if link:
-            connectionPoint = link.getposition()[-1]
+    
+    if link:
+        connectionPoint = link.getposition()[-1]
+        output_parameter = link.getformalParameter()
+    else:
+        connectionPoint = None
+        output_parameter = None
+    
+    output_variable = None
+    output_idx = 0
+    if output_parameter is not None:
+        if output_parameter in output_names or output_parameter == "ENO":
+            for variable in output_variables:
+                if variable.getformalParameter() == output_parameter:
+                    output_variable = variable
+                    if output_parameter != "ENO":
+                        output_idx = output_names.index(output_parameter)
+    else:
+        for i, variable in enumerate(output_variables):
+            blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
+            if (not connectionPoint or 
+                block.getx() + blockPointx == connectionPoint.getx() and 
+                block.gety() + blockPointy == connectionPoint.gety()):
+                output_variable = variable
+                output_parameter = variable.getformalParameter()
+                output_idx = i
+    
+    if output_variable is not None:
+        if block_infos["type"] == "function":
+            output_info = (generator.TagName, "block", block.getlocalId(), "output", output_idx)
+            if inout_variables.has_key(output_parameter):
+                output_value = inout_variables[output_parameter]
+            else:
+                if output_parameter == "":
+                    output_name = "%s%d"%(type, block.getlocalId())
+                else:
+                    output_name = "%s%d_%s"%(type, block.getlocalId(), output_parameter)
+                output_value = [(output_name, output_info)]
+            return generator.ExtractModifier(output_variable, output_value, output_info)
+        
+        if block_infos["type"] == "functionBlock":
+            output_info = (generator.TagName, "block", block.getlocalId(), "output", output_idx)
+            output_name = generator.ExtractModifier(output_variable, [("%s.%s"%(name, output_parameter), output_info)], output_info)
+            if to_inout:
+                variable_name = "%s_%s"%(name, output_parameter)
+                if not generator.IsAlreadyDefined(variable_name):
+                    if generator.Interface[-1][0] != "VAR" or generator.Interface[-1][1] is not None or generator.Interface[-1][2]:
+                        generator.Interface.append(("VAR", None, False, []))
+                    if variable.connectionPointOut in generator.ConnectionTypes:
+                        generator.Interface[-1][3].append(
+                            (generator.ConnectionTypes[output_variable.connectionPointOut], variable_name, None, None))
+                    else:
+                        generator.Interface[-1][3].append(("ANY", variable_name, None, None))
+                    generator.Program += [(generator.CurrentIndent, ()),
+                                          ("%s := "%variable_name, ())]
+                    generator.Program += output_name
+                    generator.Program += [(";\n", ())]
+                return [(variable_name, ())]
+            return output_name 
+    if link is not None:
+        if output_parameter is None:
+            output_parameter = ""
+        if name:
+            blockname = "%s(%s)" % (name, type)
         else:
-            connectionPoint = None
-        for i, variable in enumerate(block.outputVariables.getvariable()):
-            blockPointx, blockPointy = variable.connectionPointOut.getrelPositionXY()
-            if not connectionPoint or block.getx() + blockPointx == connectionPoint.getx() and block.gety() + blockPointy == connectionPoint.gety():
-                output_info = (generator.TagName, "block", block.getlocalId(), "output", i)
-                output_name = generator.ExtractModifier(variable, [("%s.%s"%(name, variable.getformalParameter()), output_info)], output_info)
-                if to_inout:
-                    variable_name = "%s_%s"%(name, variable.getformalParameter())
-                    if not generator.IsAlreadyDefined(variable_name):
-                        if generator.Interface[-1][0] != "VAR" or generator.Interface[-1][1] is not None or generator.Interface[-1][2]:
-                            generator.Interface.append(("VAR", None, False, []))
-                        if variable.connectionPointOut in generator.ConnectionTypes:
-                            generator.Interface[-1][3].append((generator.ConnectionTypes[variable.connectionPointOut], variable_name, None, None))
-                        else:
-                            generator.Interface[-1][3].append(("ANY", variable_name, None, None))
-                        generator.Program += [(generator.CurrentIndent, ()),
-                                              ("%s := "%variable_name, ())]
-                        generator.Program += output_name
-                        generator.Program += [(";\n", ())]
-                    return [(variable_name, ())]
-                return output_name 
-    if link is not None:
-        raise ValueError, _("No output variable found")
+            blockname = type
+        raise ValueError, _("No output %s variable found in block %s in POU %s. Connection must be broken")  % \
+                          (output_parameter, blockname, generator.Name)
 
 def initialise_block(type, name, block = None):
     return [(type, name, None, None)]
--- a/py_ext/PythonEditor.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/py_ext/PythonEditor.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,485 +1,59 @@
-import wx
-import wx.grid
-import wx.stc  as  stc
 import keyword
+import wx.stc as stc
 
-from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from controls.CustomStyledTextCtrl import faces
+from editors.CodeFileEditor import CodeFileEditor, CodeEditor
 
-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,
-             }
+class PythonCodeEditor(CodeEditor):
 
-[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
-    CONFNODEEDITOR_TABS = [
-        (_("Python code"), "_create_PythonCodeEditor")]
+    KEYWORDS = keyword.kwlist
+    COMMENT_HEADER = "#"
     
-    def _create_PythonCodeEditor(self, prnt):
-        self.PythonCodeEditor = stc.StyledTextCtrl(id=ID_PYTHONEDITOR, parent=prnt,
-                 name="TextViewer", pos=wx.DefaultPosition, 
-                 size=wx.DefaultSize, style=0)
-        self.PythonCodeEditor.ParentWindow = self
+    def SetCodeLexer(self):
+        self.SetLexer(stc.STC_LEX_PYTHON)
         
-        self.PythonCodeEditor.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
-        self.PythonCodeEditor.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
-
-        self.PythonCodeEditor.SetLexer(stc.STC_LEX_PYTHON)
-        self.PythonCodeEditor.SetKeyWords(0, " ".join(keyword.kwlist))
-
-        self.PythonCodeEditor.SetProperty("fold", "1")
-        self.PythonCodeEditor.SetProperty("tab.timmy.whinge.level", "1")
-        self.PythonCodeEditor.SetMargins(0,0)
-
-        self.PythonCodeEditor.SetViewWhiteSpace(False)
-        
-        self.PythonCodeEditor.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
-        self.PythonCodeEditor.SetEdgeColumn(78)
-
-        # Set up the numbers in the margin for margin #1
-        self.PythonCodeEditor.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
-        # Reasonable value for, say, 4-5 digits using a mono font (40 pix)
-        self.PythonCodeEditor.SetMarginWidth(1, 40)
-
-        # Setup a margin to hold fold markers
-        self.PythonCodeEditor.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
-        self.PythonCodeEditor.SetMarginMask(2, stc.STC_MASK_FOLDERS)
-        self.PythonCodeEditor.SetMarginSensitive(2, True)
-        self.PythonCodeEditor.SetMarginWidth(2, 12)
-
-        if self.fold_symbols == 0:
-            # Arrow pointing right for contracted folders, arrow pointing down for expanded
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_ARROWDOWN, "black", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_ARROW, "black", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_EMPTY, "black", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_EMPTY, "black", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_EMPTY,     "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY,     "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY,     "white", "black")
-            
-        elif self.fold_symbols == 1:
-            # Plus for contracted folders, minus for expanded
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_MINUS, "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_PLUS,  "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_EMPTY, "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_EMPTY, "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_EMPTY, "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
-
-        elif self.fold_symbols == 2:
-            # Like a flattened tree control using circular headers and curved joins
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_CIRCLEMINUS,          "white", "#404040")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_CIRCLEPLUS,           "white", "#404040")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,                "white", "#404040")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNERCURVE,         "white", "#404040")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_CIRCLEPLUSCONNECTED,  "white", "#404040")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE,         "white", "#404040")
-
-        elif self.fold_symbols == 3:
-            # Like a flattened tree control using square headers
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_BOXMINUS,          "white", "#808080")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_BOXPLUS,           "white", "#808080")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,             "white", "#808080")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNER,           "white", "#808080")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_BOXPLUSCONNECTED,  "white", "#808080")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
-            self.PythonCodeEditor.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,           "white", "#808080")
-
-
-        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')
-        elif wx.Platform == '__WXMAC__':
-            # TODO: if this looks fine on Linux too, remove the Mac-specific case 
-            # and use this whenever OS != MSW.
-            self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Monaco')
-        else:
-            defsize = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT).GetPointSize()
-            self.PythonCodeEditor.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Courier,size:%d'%defsize)
-
-        # Clear styles and revert to default.
-        self.PythonCodeEditor.StyleClearAll()
-
-        # Following style specs only indicate differences from default.
-        # The rest remains unchanged.
-
         # Line numbers in margin
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2')    
+        self.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.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.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.StyleSetSpec(stc.STC_STYLE_INDENTGUIDE, 'fore:#CDCDCD,size:%(size)d' % faces)
 
         # Python styles
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000')
+        self.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.StyleSetSpec(stc.STC_P_COMMENTLINE,  'fore:#008000,back:#F0FFF0,size:%(size)d' % faces)
+        self.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.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.StyleSetSpec(stc.STC_P_STRING, 'fore:#800080,size:%(size)d' % faces)
+        self.StyleSetSpec(stc.STC_P_CHARACTER, 'fore:#800080,size:%(size)d' % faces)
         # Keywords
-        self.PythonCodeEditor.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold')
+        self.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.StyleSetSpec(stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA,size:%(size)d' % faces)
+        self.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.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.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.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.StyleSetSpec(stc.STC_P_IDENTIFIER, 'fore:#000000,size:%(size)d' % faces)
+        
 
-        # Caret color
-        self.PythonCodeEditor.SetCaretForeground("BLUE")
-        # Selection background
-        self.PythonCodeEditor.SetSelBackground(1, '#66CCFF')
+#-------------------------------------------------------------------------------
+#                          CFileEditor Main Frame Class
+#-------------------------------------------------------------------------------
 
-        self.PythonCodeEditor.SetSelBackground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT))
-        self.PythonCodeEditor.SetSelForeground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
-        
-        # register some images for use in the AutoComplete box.
-        #self.RegisterImage(1, images.getSmilesBitmap())
-        self.PythonCodeEditor.RegisterImage(1, 
-            wx.ArtProvider.GetBitmap(wx.ART_DELETE, size=(16,16)))
-        self.PythonCodeEditor.RegisterImage(2, 
-            wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
-        self.PythonCodeEditor.RegisterImage(3, 
-            wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))
+class PythonEditor(CodeFileEditor):
+    
+    CONFNODEEDITOR_TABS = [
+        (_("Python code"), "_create_CodePanel")]
+    CODE_EDITOR = PythonCodeEditor
 
-        # Indentation and tab stuff
-        self.PythonCodeEditor.SetIndent(4)               # Proscribed indent size for wx
-        self.PythonCodeEditor.SetIndentationGuides(True) # Show indent guides
-        self.PythonCodeEditor.SetBackSpaceUnIndents(True)# Backspace unindents rather than delete 1 space
-        self.PythonCodeEditor.SetTabIndents(True)        # Tab key indents
-        self.PythonCodeEditor.SetTabWidth(4)             # Proscribed tab size for wx
-        self.PythonCodeEditor.SetUseTabs(False)          # Use spaces rather than tabs, or
-                                        # TabTimmy will complain!    
-        # White space
-        self.PythonCodeEditor.SetViewWhiteSpace(False)   # Don't view white space
-
-        # 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.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.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_PYTHONEDITOR)
-        self.PythonCodeEditor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
-        self.PythonCodeEditor.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModification, id=ID_PYTHONEDITOR)
-        
-        return self.PythonCodeEditor
-
-    def __init__(self, parent, controler, window):
-        ConfTreeNodeEditor.__init__(self, parent, controler, window)
-        
-        self.DisableEvents = False
-        self.CurrentAction = None
-    
-    def GetBufferState(self):
-        return self.Controler.GetBufferState()
-        
-    def Undo(self):
-        self.Controler.LoadPrevious()
-        self.RefreshView()
-            
-    def Redo(self):
-        self.Controler.LoadNext()
-        self.RefreshView()
-    
-    def OnModification(self, event):
-        if not self.DisableEvents:
-            mod_type = event.GetModificationType()
-            if not (mod_type&wx.stc.STC_PERFORMED_UNDO or mod_type&wx.stc.STC_PERFORMED_REDO):
-                if mod_type&wx.stc.STC_MOD_BEFOREINSERT:
-                    if self.CurrentAction is 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())
-                    wx.CallAfter(self.RefreshModel)
-                elif mod_type&wx.stc.STC_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())
-                    wx.CallAfter(self.RefreshModel)
-        event.Skip()
-    
-    def OnDoDrop(self, event):
-        self.ResetBuffer()
-        wx.CallAfter(self.RefreshModel)
-        event.Skip()
-
-    # Buffer the last model state
-    def RefreshBuffer(self):
-        self.Controler.BufferPython()
-        if self.ParentWindow is not None:
-            self.ParentWindow.RefreshTitle()
-            self.ParentWindow.RefreshFileMenu()
-            self.ParentWindow.RefreshEditMenu()
-            self.ParentWindow.RefreshPageTitles()
-    
-    def StartBuffering(self):
-        self.Controler.StartBuffering()
-        if self.ParentWindow is not None:
-            self.ParentWindow.RefreshTitle()
-            self.ParentWindow.RefreshFileMenu()
-            self.ParentWindow.RefreshEditMenu()
-            self.ParentWindow.RefreshPageTitles()
-    
-    def ResetBuffer(self):
-        if self.CurrentAction != None:
-            self.Controler.EndBuffering()
-            self.CurrentAction = None
-
-    def RefreshView(self):
-        ConfTreeNodeEditor.RefreshView(self)
-        
-        self.ResetBuffer()
-        self.DisableEvents = True
-        old_cursor_pos = self.PythonCodeEditor.GetCurrentPos()
-        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()
-        self.DisableEvents = False
-        
-        self.PythonCodeEditor.Colourise(0, -1)
-
-    def RefreshModel(self):
-        self.Controler.SetPythonCode(self.PythonCodeEditor.GetText())
-
-    def OnKeyPressed(self, event):
-        if self.PythonCodeEditor.CallTipActive():
-            self.PythonCodeEditor.CallTipCancel()
-        key = event.GetKeyCode()
-
-        if key == 32 and event.ControlDown():
-            pos = self.PythonCodeEditor.GetCurrentPos()
-
-            # Code completion
-            if not event.ShiftDown():
-                self.PythonCodeEditor.AutoCompSetIgnoreCase(False)  # so this needs to match
-
-                # Images are specified with a appended "?type"
-                self.PythonCodeEditor.AutoCompShow(0, " ".join([word + "?1" for word in keyword.kwlist]))
-        else:
-            event.Skip()
-
-    def OnKillFocus(self, event):
-        self.PythonCodeEditor.AutoCompCancel()
-        event.Skip()
-
-    def OnUpdateUI(self, evt):
-        # check for matching braces
-        braceAtCaret = -1
-        braceOpposite = -1
-        charBefore = None
-        caretPos = self.PythonCodeEditor.GetCurrentPos()
-
-        if caretPos > 0:
-            charBefore = self.PythonCodeEditor.GetCharAt(caretPos - 1)
-            styleBefore = self.PythonCodeEditor.GetStyleAt(caretPos - 1)
-
-        # check before
-        if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
-            braceAtCaret = caretPos - 1
-
-        # check after
-        if braceAtCaret < 0:
-            charAfter = self.PythonCodeEditor.GetCharAt(caretPos)
-            styleAfter = self.PythonCodeEditor.GetStyleAt(caretPos)
-
-            if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
-                braceAtCaret = caretPos
-
-        if braceAtCaret >= 0:
-            braceOpposite = self.PythonCodeEditor.BraceMatch(braceAtCaret)
-
-        if braceAtCaret != -1  and braceOpposite == -1:
-            self.PythonCodeEditor.BraceBadLight(braceAtCaret)
-        else:
-            self.PythonCodeEditor.BraceHighlight(braceAtCaret, braceOpposite)
-
-    def OnMarginClick(self, evt):
-        # fold and unfold as needed
-        if evt.GetMargin() == 2:
-            if evt.GetShift() and evt.GetControl():
-                self.FoldAll()
-            else:
-                lineClicked = self.PythonCodeEditor.LineFromPosition(evt.GetPosition())
-
-                if self.PythonCodeEditor.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
-                    if evt.GetShift():
-                        self.PythonCodeEditor.SetFoldExpanded(lineClicked, True)
-                        self.Expand(lineClicked, True, True, 1)
-                    elif evt.GetControl():
-                        if self.PythonCodeEditor.GetFoldExpanded(lineClicked):
-                            self.PythonCodeEditor.SetFoldExpanded(lineClicked, False)
-                            self.Expand(lineClicked, False, True, 0)
-                        else:
-                            self.PythonCodeEditor.SetFoldExpanded(lineClicked, True)
-                            self.Expand(lineClicked, True, True, 100)
-                    else:
-                        self.PythonCodeEditor.ToggleFold(lineClicked)
-
-
-    def FoldAll(self):
-        lineCount = self.PythonCodeEditor.GetLineCount()
-        expanding = True
-
-        # find out if we are folding or unfolding
-        for lineNum in range(lineCount):
-            if self.PythonCodeEditor.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
-                expanding = not self.PythonCodeEditor.GetFoldExpanded(lineNum)
-                break
-
-        lineNum = 0
-
-        while lineNum < lineCount:
-            level = self.PythonCodeEditor.GetFoldLevel(lineNum)
-            if level & stc.STC_FOLDLEVELHEADERFLAG and \
-               (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
-
-                if expanding:
-                    self.PythonCodeEditor.SetFoldExpanded(lineNum, True)
-                    lineNum = self.Expand(lineNum, True)
-                    lineNum = lineNum - 1
-                else:
-                    lastChild = self.PythonCodeEditor.GetLastChild(lineNum, -1)
-                    self.PythonCodeEditor.SetFoldExpanded(lineNum, False)
-
-                    if lastChild > lineNum:
-                        self.PythonCodeEditor.HideLines(lineNum+1, lastChild)
-
-            lineNum = lineNum + 1
-
-
-
-    def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
-        lastChild = self.PythonCodeEditor.GetLastChild(line, level)
-        line = line + 1
-
-        while line <= lastChild:
-            if force:
-                if visLevels > 0:
-                    self.PythonCodeEditor.ShowLines(line, line)
-                else:
-                    self.PythonCodeEditor.HideLines(line, line)
-            else:
-                if doExpand:
-                    self.PythonCodeEditor.ShowLines(line, line)
-
-            if level == -1:
-                level = self.PythonCodeEditor.GetFoldLevel(line)
-
-            if level & stc.STC_FOLDLEVELHEADERFLAG:
-                if force:
-                    if visLevels > 1:
-                        self.PythonCodeEditor.SetFoldExpanded(line, True)
-                    else:
-                        self.PythonCodeEditor.SetFoldExpanded(line, False)
-
-                    line = self.Expand(line, doExpand, force, visLevels-1)
-
-                else:
-                    if doExpand and self.PythonCodeEditor.GetFoldExpanded(line):
-                        line = self.Expand(line, True, force, visLevels-1)
-                    else:
-                        line = self.Expand(line, False, force, visLevels-1)
-            else:
-                line = line + 1
-
-        return line
-
-    def Cut(self):
-        self.ResetBuffer()
-        self.DisableEvents = True
-        self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_CUT)
-        self.DisableEvents = False
-        self.RefreshModel()
-        self.RefreshBuffer()
-    
-    def Copy(self):
-        self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_COPY)
-    
-    def Paste(self):
-        self.ResetBuffer()
-        self.DisableEvents = True
-        self.PythonCodeEditor.CmdKeyExecute(wx.stc.STC_CMD_PASTE)
-        self.DisableEvents = False
-        self.RefreshModel()
-        self.RefreshBuffer()
--- a/py_ext/PythonFileCTNMixin.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/py_ext/PythonFileCTNMixin.py	Wed Jul 31 10:45:07 2013 +0900
@@ -3,20 +3,30 @@
 from PythonEditor import PythonEditor
 
 from xml.dom import minidom
-from xmlclass import *
+from xmlclass import GenerateClassesFromXSD
 import cPickle
 
+from CodeFileTreeNode import CodeFile
+
 PythonClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "py_ext_xsd.xsd")) 
 
-class PythonFileCTNMixin:
-    
+class PythonFileCTNMixin(CodeFile):
+    
+    CODEFILE_NAME = "PyFile"
+    SECTIONS_NAMES = [
+        "globals",
+        "init",
+        "cleanup",
+        "start",
+        "stop"]
     EditorType = PythonEditor
     
     def __init__(self):
+        CodeFile.__init__(self)
         
         filepath = self.PythonFileName()
         
-        self.PythonCode = PythonClasses["Python"]()
+        python_code = PythonClasses["Python"]()
         if os.path.isfile(filepath):
             xmlfile = open(filepath, 'r')
             tree = minidom.parse(xmlfile)
@@ -24,87 +34,179 @@
             
             for child in tree.childNodes:
                 if child.nodeType == tree.ELEMENT_NODE and child.nodeName == "Python":
-                    self.PythonCode.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
-                    self.CreatePythonBuffer(True)
-        else:
-            self.CreatePythonBuffer(False)
-            self.OnCTNSave()
-
+                    python_code.loadXMLTree(child, ["xmlns", "xmlns:xsi", "xsi:schemaLocation"])
+                    self.CodeFile.globals.settext(python_code.gettext())
+                    os.remove(filepath)
+                    self.CreateCodeFileBuffer(False)
+                    self.OnCTNSave()
+    
+    def CodeFileName(self):
+        return os.path.join(self.CTNPath(), "pyfile.xml")
+    
     def PythonFileName(self):
         return os.path.join(self.CTNPath(), "py_ext.xml")
 
-    def GetFilename(self):
-        if self.PythonBuffer.IsCurrentSaved():
-            return "py_ext"
+    PreSectionsTexts = {}
+    PostSectionsTexts = {}
+    def GetSection(self,section):
+        return self.PreSectionsTexts.get(section,"") + "\n" + \
+               getattr(self.CodeFile, section).gettext() + "\n" + \
+               self.PostSectionsTexts.get(section,"")
+        
+
+    def CTNGenerate_C(self, buildpath, locations):
+        # location string for that CTN 
+        location_str = "_".join(map(lambda x:str(x), 
+                                self.GetCurrentLocation()))
+        configname = self.GetCTRoot().GetProjectConfigNames()[0]
+        
+        
+        # python side PLC global variables access stub
+        globalstubs = "\n".join(["""\
+_%(name)s_ctype, _%(name)s_unpack, _%(name)s_pack = \\
+    TypeTranslator["%(IECtype)s"]
+_PySafeGetPLCGlob_%(name)s = PLCBinary.__SafeGetPLCGlob_%(name)s
+_PySafeGetPLCGlob_%(name)s.restype = None
+_PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)] 
+_PySafeSetPLCGlob_%(name)s = PLCBinary.__SafeSetPLCGlob_%(name)s
+_PySafeSetPLCGlob_%(name)s.restype = None
+_PySafeSetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)]
+""" % { "name": variable.getname(),
+        "configname": configname.upper(),
+        "uppername": variable.getname().upper(),
+        "IECtype": variable.gettype()}
+            for variable in self.CodeFile.variables.variable])
+
+        # Runtime calls (start, stop, init, and cleanup)
+        rtcalls = ""
+        for section in self.SECTIONS_NAMES:
+            if section != "globals":
+                rtcalls += "def _runtime_%s_%s():\n" % (location_str, section)
+                sectiontext = self.GetSection(section).strip()
+                if sectiontext:
+                    rtcalls += '    ' + \
+                        sectiontext.replace('\n', '\n    ')+"\n\n"
+                else:
+                    rtcalls += "    pass\n\n"
+
+        globalsection = self.GetSection("globals")      
+
+        PyFileContent = """\
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+## Code generated by Beremiz python mixin confnode
+##        
+        
+## Code for PLC global variable access
+from targets.typemapping import TypeTranslator
+import ctypes 
+%(globalstubs)s
+        
+## User code in "global" scope
+%(globalsection)s
+
+## Beremiz python runtime calls
+%(rtcalls)s
+
+""" % locals()
+
+        # write generated content to python file
+        runtimefile_path = os.path.join(buildpath, 
+            "runtime_%s.py"%location_str)
+        runtimefile = open(runtimefile_path, 'w')
+        runtimefile.write(PyFileContent.encode('utf-8'))
+        runtimefile.close()
+
+        # C code for safe global variables access
+        
+        vardecfmt = """\
+extern  __IEC_%(IECtype)s_t %(configname)s__%(uppername)s;
+IEC_%(IECtype)s __%(name)s_rbuffer = __INIT_%(IECtype)s;
+IEC_%(IECtype)s __%(name)s_wbuffer;
+long __%(name)s_rlock = 0;
+long __%(name)s_wlock = 0;
+int __%(name)s_wbuffer_written = 0;
+void __SafeGetPLCGlob_%(name)s(IEC_%(IECtype)s *pvalue){
+    while(AtomicCompareExchange(&__%(name)s_rlock, 0, 1));
+    *pvalue = __%(name)s_rbuffer;
+    AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
+}
+void __SafeSetPLCGlob_%(name)s(IEC_%(IECtype)s *value){
+    while(AtomicCompareExchange(&__%(name)s_wlock, 0, 1));
+    __%(name)s_wbuffer = *value;
+    __%(name)s_wbuffer_written = 1;
+    AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
+}
+
+"""
+        varretfmt = """\
+    if(!AtomicCompareExchange(&__%(name)s_wlock, 0, 1)){
+        if(__%(name)s_wbuffer_written == 1){
+            %(configname)s__%(uppername)s.value = __%(name)s_wbuffer;
+            __%(name)s_wbuffer_written = 0;
+        }
+        AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
+    }
+""" 
+        varpubfmt = """\
+    if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
+        __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+        AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
+    }
+""" 
+
+        var_str = map("\n".join, zip(*[
+            map(lambda f : f % varinfo,
+                (vardecfmt, varretfmt, varpubfmt))
+                for varinfo in map(lambda variable : {
+                    "name": variable.getname(),
+                    "configname": configname.upper(),
+                    "uppername": variable.getname().upper(),
+                    "IECtype": variable.gettype()},
+                    self.CodeFile.variables.variable)]))
+        if len(var_str) > 0:
+            vardec, varret, varpub = var_str
         else:
-            return "~py_ext~"
-
-    def SetPythonCode(self, text):
-        self.PythonCode.settext(text)
-        
-    def GetPythonCode(self):
-        return self.PythonCode.gettext()
-    
-    def CTNTestModified(self):
-        return self.ChangesToSave or not self.PythonIsSaved()
-    
-    def OnCTNSave(self):
-        filepath = self.PythonFileName()
-        
-        text = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
-        extras = {"xmlns":"http://www.w3.org/2001/XMLSchema",
-                  "xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance",
-                  "xsi:schemaLocation" : "py_ext_xsd.xsd"}
-        text += self.PythonCode.generateXMLText("Python", 0, extras)
-
-        xmlfile = open(filepath,"w")
-        xmlfile.write(text.encode("utf-8"))
-        xmlfile.close()
-        
-        self.MarkPythonAsSaved()
-        return True
-        
-#-------------------------------------------------------------------------------
-#                      Current Buffering Management Functions
-#-------------------------------------------------------------------------------
-
-    """
-    Return a copy of the project
-    """
-    def Copy(self, model):
-        return cPickle.loads(cPickle.dumps(model))
-
-    def CreatePythonBuffer(self, saved):
-        self.Buffering = False
-        self.PythonBuffer = UndoBuffer(cPickle.dumps(self.PythonCode), saved)
-
-    def BufferPython(self):
-        self.PythonBuffer.Buffering(cPickle.dumps(self.PythonCode))
-    
-    def StartBuffering(self):
-        self.Buffering = True
-        
-    def EndBuffering(self):
-        if self.Buffering:
-            self.PythonBuffer.Buffering(cPickle.dumps(self.PythonCode))
-            self.Buffering = False
-    
-    def MarkPythonAsSaved(self):
-        self.EndBuffering()
-        self.PythonBuffer.CurrentSaved()
-    
-    def PythonIsSaved(self):
-        return self.PythonBuffer.IsCurrentSaved() and not self.Buffering
-        
-    def LoadPrevious(self):
-        self.EndBuffering()
-        self.PythonCode = cPickle.loads(self.PythonBuffer.Previous())
-    
-    def LoadNext(self):
-        self.PythonCode = cPickle.loads(self.PythonBuffer.Next())
-    
-    def GetBufferState(self):
-        first = self.PythonBuffer.IsFirst() and not self.Buffering
-        last = self.PythonBuffer.IsLast()
-        return not first, not last
-
+            vardec = varret = varpub = ""
+        
+        PyCFileContent = """\
+/* 
+ * Code generated by Beremiz py_ext confnode 
+ * for safe global variables access
+ */
+#include "iec_types_all.h"
+#include "beremiz.h"
+
+/* User variables reference */
+%(vardec)s
+
+/* Beremiz confnode functions */
+int __init_%(location_str)s(int argc,char **argv){
+    return 0;
+}
+
+void __cleanup_%(location_str)s(void){
+}
+
+void __retrieve_%(location_str)s(void){
+%(varret)s
+}
+
+void __publish_%(location_str)s(void){
+%(varpub)s
+}
+""" % locals()
+        
+        Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c"%location_str)
+        pycfile = open(Gen_PyCfile_path,'w')
+        pycfile.write(PyCFileContent)
+        pycfile.close()
+        
+        matiec_flags = '"-I%s"'%os.path.abspath(
+            self.GetCTRoot().GetIECLibPath())
+        
+        return ([(Gen_PyCfile_path, matiec_flags)],
+                "",
+                True,
+                ("runtime_%s.py"%location_str, file(runtimefile_path,"rb")))
+
--- a/py_ext/py_ext.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/py_ext/py_ext.py	Wed Jul 31 10:45:07 2013 +0900
@@ -8,18 +8,21 @@
 
     def Generate_C(self, buildpath, varlist, IECCFLAGS):
         
-        plc_python_filepath = os.path.join(os.path.split(__file__)[0], "plc_python.c")
+        plc_python_filepath = os.path.join(
+            os.path.split(__file__)[0], "plc_python.c")
         plc_python_file = open(plc_python_filepath, 'r')
         plc_python_code = plc_python_file.read()
         plc_python_file.close()
         python_eval_fb_list = []
         for v in varlist:
-            if v["vartype"] == "FB" and v["type"] in ["PYTHON_EVAL","PYTHON_POLL"]:
+            if v["vartype"] == "FB" and v["type"] in ["PYTHON_EVAL",
+                                                      "PYTHON_POLL"]:
                 python_eval_fb_list.append(v)
         python_eval_fb_count = max(1, len(python_eval_fb_list))
         
         # prepare python code
-        plc_python_code = plc_python_code % { "python_eval_fb_count": python_eval_fb_count }
+        plc_python_code = plc_python_code % {
+            "python_eval_fb_count": python_eval_fb_count }
         
         Gen_Pythonfile_path = os.path.join(buildpath, "py_ext.c")
         pythonfile = open(Gen_Pythonfile_path,'w')
@@ -33,15 +36,4 @@
     def GetIconName(self):
         return "Pyfile"
     
-    def CTNGenerate_C(self, buildpath, locations):
-        current_location = self.GetCurrentLocation()
-        # define a unique name for the generated C file
-        location_str = "_".join(map(lambda x:str(x), current_location))
-        
-        runtimefile_path = os.path.join(buildpath, "runtime_%s.py"%location_str)
-        runtimefile = open(runtimefile_path, 'w')
-        runtimefile.write(self.GetPythonCode())
-        runtimefile.close()
-        
-        return [], "", False, ("runtime_%s.py"%location_str, file(runtimefile_path,"rb"))
 
--- a/runtime/PLCObject.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/runtime/PLCObject.py	Wed Jul 31 10:45:07 2013 +0900
@@ -25,7 +25,7 @@
 import Pyro.core as pyro
 from threading import Timer, Thread, Lock, Semaphore
 import ctypes, os, commands, types, sys
-from targets.typemapping import LogLevelsDefault, LogLevelsCount, SameEndianessTypeTranslator as TypeTranslator
+from targets.typemapping import LogLevelsDefault, LogLevelsCount, TypeTranslator, UnpackDebugBuffer
 
 
 if os.name in ("nt", "ce"):
@@ -67,13 +67,14 @@
         self.hmi_frame = None
         self.website = website
         self._loading_error = None
-        self.python_threads_vars = None
+        self.python_runtime_vars = None
         
         # Get the last transfered PLC if connector must be restart
         try:
             self.CurrentPLCFilename=open(
                              self._GetMD5FileName(),
                              "r").read().strip() + lib_ext
+            self.LoadPLC()
         except Exception, e:
             self.PLCStatus = "Empty"
             self.CurrentPLCFilename=None
@@ -90,12 +91,15 @@
             msg, = args
         return self._LogMessage(level, msg, len(msg))
 
+    def ResetLogCount(self):
+        if self._ResetLogCount is not None:
+            self._ResetLogCount()
 
     def GetLogCount(self, level):
         if self._GetLogCount is not None :
             return int(self._GetLogCount(level))
         elif self._loading_error is not None and level==0:
-            return 1;
+            return 1
 
     def GetLogMessage(self, level, msgid):
         tick = ctypes.c_uint32()
@@ -122,7 +126,7 @@
         return os.path.join(self.workingdir,self.CurrentPLCFilename)
 
 
-    def _LoadNewPLC(self):
+    def LoadPLC(self):
         """
         Load PLC library
         Declare all functions, arguments and return values
@@ -182,6 +186,9 @@
             self._resumeDebug = self.PLClibraryHandle.resumeDebug
             self._resumeDebug.restype = None
 
+            self._ResetLogCount = self.PLClibraryHandle.ResetLogCount
+            self._ResetLogCount.restype = None
+
             self._GetLogCount = self.PLClibraryHandle.GetLogCount
             self._GetLogCount.restype = ctypes.c_uint32
             self._GetLogCount.argtypes = [ctypes.c_uint8]
@@ -189,19 +196,26 @@
             self._LogMessage = self.PLClibraryHandle.LogMessage
             self._LogMessage.restype = ctypes.c_int
             self._LogMessage.argtypes = [ctypes.c_uint8, ctypes.c_char_p, ctypes.c_uint32]
-            
+
             self._log_read_buffer = ctypes.create_string_buffer(1<<14) #16K
             self._GetLogMessage = self.PLClibraryHandle.GetLogMessage
             self._GetLogMessage.restype = ctypes.c_uint32
             self._GetLogMessage.argtypes = [ctypes.c_uint8, ctypes.c_uint32, ctypes.c_char_p, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32)]
 
             self._loading_error = None
+
+            self.PythonRuntimeInit()
+
             return True
         except:
             self._loading_error = traceback.format_exc()
             PLCprint(self._loading_error)
             return False
 
+    def UnLoadPLC(self):
+        self.PythonRuntimeCleanup()
+        self._FreePLC()
+
     def _FreePLC(self):
         """
         Unload PLC library.
@@ -209,7 +223,7 @@
         """
         self.PLClibraryLock.acquire()
         # Forget all refs to library
-        self._startPLC = lambda:None
+        self._startPLC = lambda x,y:None
         self._stopPLC = lambda:None
         self._ResetDebugVariables = lambda:None
         self._RegisterDebugVariable = lambda x, y:None
@@ -231,49 +245,75 @@
         self.PLClibraryLock.release()
         return False
 
-    def PrepareRuntimePy(self):
-        self.python_threads_vars = globals().copy()
-        self.python_threads_vars["WorkingDir"] = self.workingdir
-        self.python_threads_vars["website"] = self.website
-        self.python_threads_vars["_runtime_begin"] = []
-        self.python_threads_vars["_runtime_cleanup"] = []
-        self.python_threads_vars["PLCObject"] = self
-        self.python_threads_vars["PLCBinary"] = self.PLClibraryHandle
-        
+    def PythonRuntimeCall(self, methodname):
+        """ 
+        Calls init, start, stop or cleanup method provided by 
+        runtime python files, loaded when new PLC uploaded
+        """
+        for method in self.python_runtime_vars.get("_runtime_%s"%methodname, []):
+            res,exp = self.evaluator(method)
+            if exp is not None: 
+                self.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
+
+    def PythonRuntimeInit(self):
+        MethodNames = ["init", "start", "stop", "cleanup"]
+        self.python_runtime_vars = globals().copy()
+        self.python_runtime_vars["WorkingDir"] = self.workingdir
+        self.python_runtime_vars["website"] = self.website
+        for methodname in MethodNames :
+            self.python_runtime_vars["_runtime_%s"%methodname] = []
+        self.python_runtime_vars["PLCObject"] = self
+        self.python_runtime_vars["PLCBinary"] = self.PLClibraryHandle
+        class PLCSafeGlobals:
+            def __getattr__(_self, name):
+                try :
+                    t = self.python_runtime_vars["_"+name+"_ctype"]
+                except KeyError:
+                    raise KeyError("Try to get unknown shared global variable : %s"%name)
+                v = t()
+                r = self.python_runtime_vars["_PySafeGetPLCGlob_"+name](ctypes.byref(v))
+                return self.python_runtime_vars["_"+name+"_unpack"](v)
+            def __setattr__(_self, name, value):
+                try :
+                    t = self.python_runtime_vars["_"+name+"_ctype"]
+                except KeyError:
+                    raise KeyError("Try to set unknown shared global variable : %s"%name)
+                v = self.python_runtime_vars["_"+name+"_pack"](t,value)
+                self.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v))
+        self.python_runtime_vars["PLCGlobals"] = PLCSafeGlobals()
         try:
             for filename in os.listdir(self.workingdir):
                 name, ext = os.path.splitext(filename)
                 if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
-                    execfile(os.path.join(self.workingdir, filename), self.python_threads_vars)
-                    runtime_begin = self.python_threads_vars.get("_%s_begin" % name, None)
-                    if runtime_begin is not None:
-                        self.python_threads_vars["_runtime_begin"].append(runtime_begin)
-                    runtime_cleanup = self.python_threads_vars.get("_%s_cleanup" % name, None)
-                    if runtime_cleanup is not None:
-                        self.python_threads_vars["_runtime_cleanup"].append(runtime_cleanup)
-            
-            for runtime_begin in self.python_threads_vars.get("_runtime_begin", []):
-                runtime_begin()
+                    execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars)
+                    for methodname in MethodNames: 
+                        method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
+                        if method is not None:
+                            self.python_runtime_vars["_runtime_%s"%methodname].append(method)
         except:
             self.LogMessage(0,traceback.format_exc())
             raise
             
+        self.PythonRuntimeCall("init")
+
         if self.website is not None:
             self.website.PLCStarted()
 
-    def FinishRuntimePy(self):
-        for runtime_cleanup in self.python_threads_vars.get("_runtime_cleanup", []):
-            runtime_cleanup()    
+
+    def PythonRuntimeCleanup(self):
+        if self.python_runtime_vars is not None:
+            self.PythonRuntimeCall("cleanup")
+
         if self.website is not None:
             self.website.PLCStopped()
-        self.python_threads_vars = None
+
+        self.python_runtime_vars = None
 
     def PythonThreadProc(self):
         self.PLCStatus = "Started"
         self.StatusChange()
         self.StartSem.release()
-        res,exp = self.evaluator(self.PrepareRuntimePy)
-        if exp is not None: raise(exp)
+        self.PythonRuntimeCall("start")
         res,cmd,blkid = "None","None",ctypes.c_void_p()
         compile_cache={}
         while True:
@@ -284,24 +324,25 @@
             if cmd is None:
                 break
             try :
-                self.python_threads_vars["FBID"]=FBID
+                self.python_runtime_vars["FBID"]=FBID
                 ccmd,AST =compile_cache.get(FBID, (None,None))
                 if ccmd is None or ccmd!=cmd:
                     AST = compile(cmd, '<plc>', 'eval')
                     compile_cache[FBID]=(cmd,AST)
-                result,exp = self.evaluator(eval,cmd,self.python_threads_vars)
+                result,exp = self.evaluator(eval,cmd,self.python_runtime_vars)
                 if exp is not None: 
-                    raise(exp)
+                    res = "#EXCEPTION : "+str(exp[1])
+                    self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,
+                        '\n'.join(traceback.format_exception(*exp))))
                 else:
                     res=str(result)
-                self.python_threads_vars["FBID"]=None
+                self.python_runtime_vars["FBID"]=None
             except Exception,e:
                 res = "#EXCEPTION : "+str(e)
                 self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,str(e)))
         self.PLCStatus = "Stopped"
         self.StatusChange()
-        exp,res = self.evaluator(self.FinishRuntimePy)
-        if exp is not None: raise(exp)
+        self.PythonRuntimeCall("stop")
     
     def StartPLC(self):
         if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
@@ -343,12 +384,13 @@
         return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
     
     def NewPLC(self, md5sum, data, extrafiles):
-        self.LogMessage("NewPLC (%s)"%md5sum)
         if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
             NewFileName = md5sum + lib_ext
             extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
 
-            self._FreePLC()
+            self.UnLoadPLC()
+
+            self.LogMessage("NewPLC (%s)"%md5sum)
             self.PLCStatus = "Empty"
 
             try:
@@ -385,9 +427,10 @@
                 PLCprint(traceback.format_exc())
                 return False
 
-            if self._LoadNewPLC():
+            if self.LoadPLC():
                 self.PLCStatus = "Stopped"
             else:
+                self.PLCStatus = "Broken"
                 self._FreePLC()
             self.StatusChange()
 
@@ -431,39 +474,20 @@
         Return a list of variables, corresponding to the list of required idx
         """
         if self.PLCStatus == "Started":
-            res=[]
             tick = ctypes.c_uint32()
             size = ctypes.c_uint32()
-            buffer = ctypes.c_void_p()
-            offset = 0
+            buff = ctypes.c_void_p()
+            TraceVariables = None
             if self.PLClibraryLock.acquire(False):
                 if self._GetDebugData(ctypes.byref(tick),
                                       ctypes.byref(size),
-                                      ctypes.byref(buffer)) == 0:
+                                      ctypes.byref(buff)) == 0:
                     if size.value:
-                        for idx, iectype, forced in self._Idxs:
-                            cursor = ctypes.c_void_p(buffer.value + offset)
-                            c_type,unpack_func, pack_func = \
-                                TypeTranslator.get(iectype,
-                                                        (None,None,None))
-                            if c_type is not None and offset < size.value:
-                                res.append(unpack_func(
-                                            ctypes.cast(cursor,
-                                             ctypes.POINTER(c_type)).contents))
-                                offset += ctypes.sizeof(c_type)
-                            else:
-                                if c_type is None:
-                                    PLCprint("Debug error - " + iectype +
-                                             " not supported !")
-                                #if offset >= size.value:
-                                    #PLCprint("Debug error - buffer too small ! %d != %d"%(offset, size.value))
-                                break
+                        TraceVariables = UnpackDebugBuffer(buff, size.value, self._Idxs)
                     self._FreeDebugData()
                 self.PLClibraryLock.release()
-            if offset and offset == size.value:
-                return self.PLCStatus, tick.value, res
-            #elif size.value:
-                #PLCprint("Debug error - wrong buffer unpack ! %d != %d"%(offset, size.value))
+            if TraceVariables is not None:
+                return self.PLCStatus, tick.value, TraceVariables
         return self.PLCStatus, None, []
 
     def RemoteExec(self, script, **kwargs):
--- a/svgui/svgui.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/svgui/svgui.py	Wed Jul 31 10:45:07 2013 +0900
@@ -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 
@@ -72,10 +81,10 @@
         runtimefile = open(runtimefile_path, 'w')
         runtimefile.write(svguiservercode % {"svgfile" : "gui.svg"})
         runtimefile.write("""
-def _runtime_%(location)s_begin():
+def _runtime_%(location)s_start():
     website.LoadHMI(%(svgui_class)s, %(jsmodules)s)
     
-def _runtime_%(location)s_cleanup():
+def _runtime_%(location)s_stop():
     website.UnLoadHMI()
     
 """ % {"location": location_str,
--- a/targets/Linux/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Linux/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,6 +1,7 @@
 from ..toolchain_gcc import toolchain_gcc
 
 class Linux_target(toolchain_gcc):
+    dlopen_prefix = "./"
     extension = ".so"
     def getBuilderCFLAGS(self):
         return toolchain_gcc.getBuilderCFLAGS(self) + ["-fPIC"]
--- a/targets/Linux/plc_Linux_main.c	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Linux/plc_Linux_main.c	Wed Jul 31 10:45:07 2013 +0900
@@ -11,7 +11,6 @@
 #include <locale.h>
 #include <semaphore.h>
 
-extern unsigned long long common_ticktime__;
 static sem_t Run_PLC;
 
 long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
@@ -97,8 +96,6 @@
 {
     struct sigevent sigev;
     setlocale(LC_NUMERIC, "C");
-    /* Define Ttick to 1ms if common_ticktime not defined */
-    Ttick = common_ticktime__?common_ticktime__:1000000;
 
     PLC_shutdown = 0;
 
--- a/targets/Win32/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Win32/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,6 +1,7 @@
 from ..toolchain_gcc import toolchain_gcc
 
 class Win32_target(toolchain_gcc):
+    dlopen_prefix = ""
     extension = ".dll"
     def getBuilderLDFLAGS(self):
         return toolchain_gcc.getBuilderLDFLAGS(self) + ["-shared", "-lwinmm"]
--- a/targets/Win32/plc_Win32_main.c	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Win32/plc_Win32_main.c	Wed Jul 31 10:45:07 2013 +0900
@@ -8,8 +8,6 @@
 #include <windows.h>
 #include <locale.h>
 
-/* provided by POUS.C */
-extern unsigned long long common_ticktime__;
 
 long AtomicCompareExchange(long* atomicvar, long compared, long exchange)
 {
@@ -77,8 +75,6 @@
 	unsigned long thread_id = 0;
     BOOL tmp;
     setlocale(LC_NUMERIC, "C");
-	/* Define Ttick to 1ms if common_ticktime not defined */
-    Ttick = common_ticktime__?common_ticktime__:1000000;
 
     InitializeCriticalSection(&Atomic64CS);
 
--- a/targets/Xenomai/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Xenomai/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,6 +1,7 @@
 from ..toolchain_gcc import toolchain_gcc
 
 class Xenomai_target(toolchain_gcc):
+    dlopen_prefix = "./"
     extension = ".so"
     def getXenoConfig(self, flagsname):
         """ Get xeno-config from target parameters """
--- a/targets/Xenomai/plc_Xenomai_main.c	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/Xenomai/plc_Xenomai_main.c	Wed Jul 31 10:45:07 2013 +0900
@@ -38,8 +38,6 @@
 #define PYTHON_PIPE_MINOR            3
 #define PIPE_SIZE                    1 
 
-/* provided by POUS.C */
-extern unsigned long common_ticktime__;
 
 long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
 {
@@ -170,9 +168,6 @@
     /* no memory swapping for that process */
     mlockall(MCL_CURRENT | MCL_FUTURE);
 
-    /* Define Ttick to 1ms if common_ticktime not defined */
-    Ttick = common_ticktime__?common_ticktime__:1000000;
-
     PLC_shutdown = 0;
 
     /*** RT Pipes creation and opening ***/
--- a/targets/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -70,6 +70,10 @@
 def GetTargetCode(targetname):
     return open(targets[targetname]["code"]).read()
 
+def GetHeader():
+    filename = path.join(path.split(__file__)[0],"beremiz.h")
+    return open(filename).read()
+
 def GetCode(name):
     filename = path.join(path.split(__file__)[0],name + ".c")
     return open(filename).read()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/beremiz.h	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,14 @@
+/* Beremiz' header file for use by extensions */
+
+#include "iec_types.h"
+
+#define LOG_LEVELS 4
+#define LOG_CRITICAL 0
+#define LOG_WARNING 1
+#define LOG_INFO 2
+#define LOG_DEBUG 3
+
+extern unsigned long long common_ticktime__;
+int LogMessage(uint8_t level, char* buf, uint32_t size);
+long AtomicCompareExchange(long* atomicvar,long compared, long exchange);
+
--- a/targets/plc_common_main.c	Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/**
- * Code common to all C targets
- **/
-
-#include "iec_types.h"
-/*
- * Prototypes of functions provided by generated C softPLC
- **/
-void config_run__(unsigned long tick);
-void config_init__(void);
-
-/*
- * Prototypes of functions provided by generated target C code
- * */
-void __init_debug(void);
-void __cleanup_debug(void);
-/*void __retrieve_debug(void);*/
-void __publish_debug(void);
-
-/*
- *  Variables used by generated C softPLC and plugins
- **/
-IEC_TIME __CURRENT_TIME;
-IEC_BOOL __DEBUG = 0;
-unsigned long __tick = 0;
-
-/*
- *  Variable generated by C softPLC and plugins
- **/
-extern unsigned long greatest_tick_count__;
-
-/* Help to quit cleanly when init fail at a certain level */
-static int init_level = 0;
-
-/*
- * Prototypes of functions exported by plugins
- **/
-%(calls_prototypes)s
-
-/*
- * Retrieve input variables, run PLC and publish output variables
- **/
-void __run(void)
-{
-    __tick++;
-    if (greatest_tick_count__)
-        __tick %%= greatest_tick_count__;
-
-    %(retrieve_calls)s
-
-    /*__retrieve_debug();*/
-
-    config_run__(__tick);
-
-    __publish_debug();
-
-    %(publish_calls)s
-
-}
-
-/*
- * Initialize variables according to PLC's default values,
- * and then init plugins with that values
- **/
-int __init(int argc,char **argv)
-{
-    int res = 0;
-    init_level = 0;
-    config_init__();
-    __init_debug();
-    %(init_calls)s
-    return res;
-}
-/*
- * Calls plugin cleanup proc.
- **/
-void __cleanup(void)
-{
-    %(cleanup_calls)s
-    __cleanup_debug();
-}
-
-
-void PLC_GetTime(IEC_TIME *CURRENT_TIME);
-void PLC_SetTimer(unsigned long long next, unsigned long long period);
-
-#define CALIBRATED -2
-#define NOT_CALIBRATED -1
-static int calibration_count = NOT_CALIBRATED;
-static IEC_TIME cal_begin;
-static long long Tsync = 0;
-static long long FreqCorr = 0;
-static int Nticks = 0;
-static unsigned long last_tick = 0;
-static long long Ttick = 0;
-#define mod %%
-/*
- * Call this on each external sync,
- * @param sync_align_ratio 0->100 : align ratio, < 0 : no align, calibrate period
- **/
-void align_tick(int sync_align_ratio)
-{
-	/*
-	printf("align_tick(%%d)\n", calibrate);
-	*/
-	if(sync_align_ratio < 0){ /* Calibration */
-		if(calibration_count == CALIBRATED)
-			/* Re-calibration*/
-			calibration_count = NOT_CALIBRATED;
-		if(calibration_count == NOT_CALIBRATED)
-			/* Calibration start, get time*/
-			PLC_GetTime(&cal_begin);
-		calibration_count++;
-	}else{ /* do alignment (if possible) */
-		if(calibration_count >= 0){
-			/* End of calibration */
-			/* Get final time */
-			IEC_TIME cal_end;
-			PLC_GetTime(&cal_end);
-			/*adjust calibration_count*/
-			calibration_count++;
-			/* compute mean of Tsync, over calibration period */
-			Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
-					(cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
-			if( (Nticks = (Tsync / Ttick)) > 0){
-				FreqCorr = (Tsync mod Ttick); /* to be divided by Nticks */
-			}else{
-				FreqCorr = Tsync - (Ttick mod Tsync);
-			}
-			/*
-			printf("Tsync = %%ld\n", Tsync);
-			printf("calibration_count = %%d\n", calibration_count);
-			printf("Nticks = %%d\n", Nticks);
-			*/
-			calibration_count = CALIBRATED;
-		}
-		if(calibration_count == CALIBRATED){
-			/* Get Elapsed time since last PLC tick (__CURRENT_TIME) */
-			IEC_TIME now;
-			long long elapsed;
-			long long Tcorr;
-			long long PhaseCorr;
-			long long PeriodicTcorr;
-			PLC_GetTime(&now);
-			elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
-			if(Nticks > 0){
-				PhaseCorr = elapsed - (Ttick + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
-				Tcorr = Ttick + (PhaseCorr + FreqCorr) / Nticks;
-				if(Nticks < 2){
-					/* When Sync source period is near Tick time */
-					/* PhaseCorr may not be applied to Periodic time given to timer */
-					PeriodicTcorr = Ttick + FreqCorr / Nticks;
-				}else{
-					PeriodicTcorr = Tcorr;
-				}
-			}else if(__tick > last_tick){
-				last_tick = __tick;
-				PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
-				PeriodicTcorr = Tcorr = Ttick + PhaseCorr + FreqCorr;
-			}else{
-				/*PLC did not run meanwhile. Nothing to do*/
-				return;
-			}
-			/* DO ALIGNEMENT */
-			PLC_SetTimer(Tcorr - elapsed, PeriodicTcorr);
-		}
-	}
-}
-
-/**
- * Prototypes for function provided by arch-specific code (main)
- * is concatained hereafter
- **/
--- a/targets/plc_debug.c	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/plc_debug.c	Wed Jul 31 10:45:07 2013 +0900
@@ -152,6 +152,10 @@
                 /* compute next cursor positon.
                    No need to check overflow, as BUFFER_SIZE
                    is computed large enough */
+                if(vartype == STRING_ENUM){
+                    /* optimization for strings */
+                    size = ((STRING*)visible_value_p)->len + 1;
+                }
                 char* next_cursor = buffer_cursor + size;
                 /* copy data to the buffer */
                 memcpy(buffer_cursor, visible_value_p, size);
@@ -304,128 +308,3 @@
     return wait_error;
 }
 
-
-/* LOGGING
-*/
-
-#define LOG_LEVELS 4
-#define LOG_CRITICAL 0
-#define LOG_WARNING 1
-#define LOG_INFO 2
-#define LOG_DEBUG 3
-
-#ifndef LOG_BUFFER_SIZE
-#define LOG_BUFFER_SIZE (1<<14) /*16Ko*/
-#endif
-#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE-1)
-static char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE];
-void inline copy_to_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
-    if(buffpos + size < LOG_BUFFER_SIZE){
-        memcpy(&LogBuff[level][buffpos], buf, size);
-    }else{
-        uint32_t remaining = LOG_BUFFER_SIZE - buffpos - 1; 
-        memcpy(&LogBuff[level][buffpos], buf, remaining);
-        memcpy(LogBuff[level], buf + remaining, size - remaining);
-    }
-}
-void inline copy_from_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
-    if(buffpos + size < LOG_BUFFER_SIZE){
-        memcpy(buf, &LogBuff[level][buffpos], size);
-    }else{
-        uint32_t remaining = LOG_BUFFER_SIZE - buffpos; 
-        memcpy(buf, &LogBuff[level][buffpos], remaining);
-        memcpy(buf + remaining, LogBuff[level], size - remaining);
-    }
-}
-
-/* Log buffer structure
-
- |<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
- |  Message1 Body  |      Tail1      |   Message2 Body   |      Tail2      |
-
-*/
-typedef struct {
-    uint32_t msgidx;
-    uint32_t msgsize;
-    unsigned long tick;
-    IEC_TIME time;
-} mTail;
-
-/* Log cursor : 64b
-   |63 ... 32|31 ... 0|
-   | Message | Buffer |
-   | counter | Index  | */
-static uint64_t LogCursor[LOG_LEVELS] = {0x0,0x0,0x0,0x0};
-
-/* Store one log message of give size */
-int LogMessage(uint8_t level, uint8_t* buf, uint32_t size){
-    if(size < LOG_BUFFER_SIZE - sizeof(mTail)){
-        uint32_t buffpos;
-        uint64_t new_cursor, old_cursor;
-
-        mTail tail;
-        tail.msgsize = size;
-        tail.tick = __tick;
-        PLC_GetTime(&tail.time);
-
-        /* We cannot increment both msg index and string pointer 
-           in a single atomic operation but we can detect having been interrupted.
-           So we can try with atomic compare and swap in a loop until operation
-           succeeds non interrupted */
-        do{
-            old_cursor = LogCursor[level];
-            buffpos = (uint32_t)old_cursor;
-            tail.msgidx = (old_cursor >> 32); 
-            new_cursor = ((uint64_t)(tail.msgidx + 1)<<32) 
-                         | (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
-        }while(AtomicCompareExchange64(
-            (long long*)&LogCursor[level],
-            (long long)old_cursor,
-            (long long)new_cursor)!=old_cursor);
-
-        copy_to_log(level, buffpos, buf, size);
-        copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));
-
-        return 1; /* Success */
-    }else{
-    	uint8_t mstr[] = "Logging error : message too big";
-        LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
-    }
-    return 0;
-}
-
-uint32_t GetLogCount(uint8_t level){
-    return (uint64_t)LogCursor[level] >> 32;
-}
-
-/* Return message size and content */
-uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char* buf, uint32_t max_size, uint32_t* tick, uint32_t* tv_sec, uint32_t* tv_nsec){
-    uint64_t cursor = LogCursor[level];
-    if(cursor){
-        /* seach cursor */
-        uint32_t stailpos = (uint32_t)cursor; 
-        uint32_t smsgidx;
-        mTail tail;
-        tail.msgidx = cursor >> 32;
-        tail.msgsize = 0;
-
-        /* Message search loop */
-        do {
-            smsgidx = tail.msgidx;
-            stailpos = (stailpos - sizeof(mTail) - tail.msgsize ) & LOG_BUFFER_MASK;
-            copy_from_log(level, stailpos, &tail, sizeof(mTail));
-        }while((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));
-
-        if(tail.msgidx == msgidx){
-            uint32_t sbuffpos = (stailpos - tail.msgsize ) & LOG_BUFFER_MASK; 
-            uint32_t totalsize = tail.msgsize;
-            *tick = tail.tick; 
-            *tv_sec = tail.time.tv_sec; 
-            *tv_nsec = tail.time.tv_nsec; 
-            copy_from_log(level, sbuffpos, buf, 
-                          totalsize > max_size ? max_size : totalsize);
-            return totalsize;
-        }
-    }
-    return 0;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/plc_main_head.c	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,94 @@
+/**
+ * Head of code common to all C targets
+ **/
+
+#include "beremiz.h"
+/*
+ * Prototypes of functions provided by generated C softPLC
+ **/
+void config_run__(unsigned long tick);
+void config_init__(void);
+
+/*
+ * Prototypes of functions provided by generated target C code
+ * */
+void __init_debug(void);
+void __cleanup_debug(void);
+/*void __retrieve_debug(void);*/
+void __publish_debug(void);
+
+/*
+ *  Variables used by generated C softPLC and plugins
+ **/
+IEC_TIME __CURRENT_TIME;
+IEC_BOOL __DEBUG = 0;
+unsigned long __tick = 0;
+
+/*
+ *  Variable generated by C softPLC and plugins
+ **/
+extern unsigned long greatest_tick_count__;
+
+/* Effective tick time with 1ms default value */
+static long long Ttick = 1000000;
+
+/* Help to quit cleanly when init fail at a certain level */
+static int init_level = 0;
+
+/*
+ * Prototypes of functions exported by plugins
+ **/
+%(calls_prototypes)s
+
+/*
+ * Retrieve input variables, run PLC and publish output variables
+ **/
+void __run(void)
+{
+    __tick++;
+    if (greatest_tick_count__)
+        __tick %%= greatest_tick_count__;
+
+    %(retrieve_calls)s
+
+    /*__retrieve_debug();*/
+
+    config_run__(__tick);
+
+    __publish_debug();
+
+    %(publish_calls)s
+
+}
+
+/*
+ * Initialize variables according to PLC's default values,
+ * and then init plugins with that values
+ **/
+int __init(int argc,char **argv)
+{
+    int res = 0;
+    init_level = 0;
+    
+    if(common_ticktime__)
+        Ttick = common_ticktime__;
+
+    config_init__();
+    __init_debug();
+    %(init_calls)s
+    return res;
+}
+/*
+ * Calls plugin cleanup proc.
+ **/
+void __cleanup(void)
+{
+    %(cleanup_calls)s
+    __cleanup_debug();
+}
+
+void PLC_GetTime(IEC_TIME *CURRENT_TIME);
+void PLC_SetTimer(unsigned long long next, unsigned long long period);
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/plc_main_tail.c	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,221 @@
+/**
+ * Tail of code common to all C targets
+ **/
+
+/** 
+ * LOGGING
+ **/
+
+#ifndef LOG_BUFFER_SIZE
+#define LOG_BUFFER_SIZE (1<<14) /*16Ko*/
+#endif
+#ifndef LOG_BUFFER_ATTRS
+#define LOG_BUFFER_ATTRS
+#endif
+
+#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE-1)
+
+static char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE] LOG_BUFFER_ATTRS;
+void inline copy_to_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
+    if(buffpos + size < LOG_BUFFER_SIZE){
+        memcpy(&LogBuff[level][buffpos], buf, size);
+    }else{
+        uint32_t remaining = LOG_BUFFER_SIZE - buffpos; 
+        memcpy(&LogBuff[level][buffpos], buf, remaining);
+        memcpy(LogBuff[level], (char*)buf + remaining, size - remaining);
+    }
+}
+void inline copy_from_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
+    if(buffpos + size < LOG_BUFFER_SIZE){
+        memcpy(buf, &LogBuff[level][buffpos], size);
+    }else{
+        uint32_t remaining = LOG_BUFFER_SIZE - buffpos; 
+        memcpy(buf, &LogBuff[level][buffpos], remaining);
+        memcpy((char*)buf + remaining, LogBuff[level], size - remaining);
+    }
+}
+
+/* Log buffer structure
+
+ |<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
+ |  Message1 Body  |      Tail1      |   Message2 Body   |      Tail2      |
+
+*/
+typedef struct {
+    uint32_t msgidx;
+    uint32_t msgsize;
+    unsigned long tick;
+    IEC_TIME time;
+} mTail;
+
+/* Log cursor : 64b
+   |63 ... 32|31 ... 0|
+   | Message | Buffer |
+   | counter | Index  | */
+static uint64_t LogCursor[LOG_LEVELS] LOG_BUFFER_ATTRS = {0x0,0x0,0x0,0x0};
+
+void ResetLogCount(void) {
+	uint8_t level;
+	for(level=0;level<LOG_LEVELS;level++){
+		LogCursor[level] = 0;
+	}
+}
+
+/* Store one log message of give size */
+int LogMessage(uint8_t level, char* buf, uint32_t size){
+    if(size < LOG_BUFFER_SIZE - sizeof(mTail)){
+        uint32_t buffpos;
+        uint64_t new_cursor, old_cursor;
+
+        mTail tail;
+        tail.msgsize = size;
+        tail.tick = __tick;
+        PLC_GetTime(&tail.time);
+
+        /* We cannot increment both msg index and string pointer 
+           in a single atomic operation but we can detect having been interrupted.
+           So we can try with atomic compare and swap in a loop until operation
+           succeeds non interrupted */
+        do{
+            old_cursor = LogCursor[level];
+            buffpos = (uint32_t)old_cursor;
+            tail.msgidx = (old_cursor >> 32); 
+            new_cursor = ((uint64_t)(tail.msgidx + 1)<<32) 
+                         | (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
+        }while(AtomicCompareExchange64(
+            (long long*)&LogCursor[level],
+            (long long)old_cursor,
+            (long long)new_cursor)!=(long long)old_cursor);
+
+        copy_to_log(level, buffpos, buf, size);
+        copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));
+
+        return 1; /* Success */
+    }else{
+    	char mstr[] = "Logging error : message too big";
+        LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
+    }
+    return 0;
+}
+
+uint32_t GetLogCount(uint8_t level){
+    return (uint64_t)LogCursor[level] >> 32;
+}
+
+/* Return message size and content */
+uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char* buf, uint32_t max_size, uint32_t* tick, uint32_t* tv_sec, uint32_t* tv_nsec){
+    uint64_t cursor = LogCursor[level];
+    if(cursor){
+        /* seach cursor */
+        uint32_t stailpos = (uint32_t)cursor; 
+        uint32_t smsgidx;
+        mTail tail;
+        tail.msgidx = cursor >> 32;
+        tail.msgsize = 0;
+
+        /* Message search loop */
+        do {
+            smsgidx = tail.msgidx;
+            stailpos = (stailpos - sizeof(mTail) - tail.msgsize ) & LOG_BUFFER_MASK;
+            copy_from_log(level, stailpos, &tail, sizeof(mTail));
+        }while((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));
+
+        if(tail.msgidx == msgidx){
+            uint32_t sbuffpos = (stailpos - tail.msgsize ) & LOG_BUFFER_MASK; 
+            uint32_t totalsize = tail.msgsize;
+            *tick = tail.tick; 
+            *tv_sec = tail.time.tv_sec; 
+            *tv_nsec = tail.time.tv_nsec; 
+            copy_from_log(level, sbuffpos, buf, 
+                          totalsize > max_size ? max_size : totalsize);
+            return totalsize;
+        }
+    }
+    return 0;
+}
+
+#define CALIBRATED -2
+#define NOT_CALIBRATED -1
+static int calibration_count = NOT_CALIBRATED;
+static IEC_TIME cal_begin;
+static long long Tsync = 0;
+static long long FreqCorr = 0;
+static int Nticks = 0;
+static unsigned long last_tick = 0;
+
+/*
+ * Called on each external periodic sync event
+ * make PLC tick synchronous with external sync
+ * ratio defines when PLC tick occurs between two external sync
+ * @param sync_align_ratio 
+ *          0->100 : align ratio
+ *          < 0 : no align, calibrate period
+ **/
+void align_tick(int sync_align_ratio)
+{
+	/*
+	printf("align_tick(%d)\n", calibrate);
+	*/
+	if(sync_align_ratio < 0){ /* Calibration */
+		if(calibration_count == CALIBRATED)
+			/* Re-calibration*/
+			calibration_count = NOT_CALIBRATED;
+		if(calibration_count == NOT_CALIBRATED)
+			/* Calibration start, get time*/
+			PLC_GetTime(&cal_begin);
+		calibration_count++;
+	}else{ /* do alignment (if possible) */
+		if(calibration_count >= 0){
+			/* End of calibration */
+			/* Get final time */
+			IEC_TIME cal_end;
+			PLC_GetTime(&cal_end);
+			/*adjust calibration_count*/
+			calibration_count++;
+			/* compute mean of Tsync, over calibration period */
+			Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
+					(cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
+			if( (Nticks = (Tsync / Ttick)) > 0){
+				FreqCorr = (Tsync % Ttick); /* to be divided by Nticks */
+			}else{
+				FreqCorr = Tsync - (Ttick % Tsync);
+			}
+			/*
+			printf("Tsync = %ld\n", Tsync);
+			printf("calibration_count = %d\n", calibration_count);
+			printf("Nticks = %d\n", Nticks);
+			*/
+			calibration_count = CALIBRATED;
+		}
+		if(calibration_count == CALIBRATED){
+			/* Get Elapsed time since last PLC tick (__CURRENT_TIME) */
+			IEC_TIME now;
+			long long elapsed;
+			long long Tcorr;
+			long long PhaseCorr;
+			long long PeriodicTcorr;
+			PLC_GetTime(&now);
+			elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
+			if(Nticks > 0){
+				PhaseCorr = elapsed - (Ttick + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
+				Tcorr = Ttick + (PhaseCorr + FreqCorr) / Nticks;
+				if(Nticks < 2){
+					/* When Sync source period is near Tick time */
+					/* PhaseCorr may not be applied to Periodic time given to timer */
+					PeriodicTcorr = Ttick + FreqCorr / Nticks;
+				}else{
+					PeriodicTcorr = Tcorr;
+				}
+			}else if(__tick > last_tick){
+				last_tick = __tick;
+				PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
+				PeriodicTcorr = Tcorr = Ttick + PhaseCorr + FreqCorr;
+			}else{
+				/*PLC did not run meanwhile. Nothing to do*/
+				return;
+			}
+			/* DO ALIGNEMENT */
+			PLC_SetTimer(Tcorr - elapsed, PeriodicTcorr);
+		}
+	}
+}
--- a/targets/toolchain_gcc.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/toolchain_gcc.py	Wed Jul 31 10:45:07 2013 +0900
@@ -98,7 +98,7 @@
         ######### GENERATE OBJECT FILES ########################################
         obns = []
         objs = []
-        relink = False
+        relink = self.GetBinaryCode() is None
         for Location, CFilesAndCFLAGS, DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS:
             if CFilesAndCFLAGS:
                 if Location :
--- a/targets/typemapping.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/targets/typemapping.py	Wed Jul 31 10:45:07 2013 +0900
@@ -73,9 +73,37 @@
     #TODO
     } 
 
+TypeTranslator=SameEndianessTypeTranslator
+
 # Construct debugger natively supported types
 DebugTypesSize =  dict([(key,sizeof(t)) for key,(t,p,u) in SameEndianessTypeTranslator.iteritems() if t is not None])
 
+def UnpackDebugBuffer(buff, size, indexes):
+    res = []
+    offset = 0
+    for idx, iectype, forced in indexes:
+        cursor = c_void_p(buff.value + offset)
+        c_type,unpack_func, pack_func = \
+            TypeTranslator.get(iectype,
+                                    (None,None,None))
+        if c_type is not None and offset < size:
+            res.append(unpack_func(
+                        cast(cursor,
+                         POINTER(c_type)).contents))
+            offset += sizeof(c_type) if iectype != "STRING" else len(res[-1])+1
+        else:
+            #if c_type is None:
+            #    PLCprint("Debug error - " + iectype +
+            #             " not supported !")
+            #if offset >= size:
+            #    PLCprint("Debug error - buffer too small ! %d != %d"%(offset, size))
+            break
+    if offset and offset == size:
+        return res
+    return None
+
+
+
 LogLevels = ["CRITICAL","WARNING","INFO","DEBUG"]
 LogLevelsCount = len(LogLevels)
 LogLevelsDict = dict(zip(LogLevels,range(LogLevelsCount)))
--- a/tests/logging/plc.xml	Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/logging/plc.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -8,7 +8,7 @@
               productVersion="1"
               creationDateTime="2013-01-29T14:01:00"/>
   <contentHeader name="Unnamed"
-                 modificationDateTime="2013-02-26T16:22:11">
+                 modificationDateTime="2013-04-04T11:06:06">
     <coordinateInfo>
       <fbd>
         <scaling x="0" y="0"/>
--- a/tests/logging/py_ext_0@py_ext/py_ext.xml	Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/logging/py_ext_0@py_ext/py_ext.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <Python xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="py_ext_xsd.xsd">
-<![CDATA[import threading
+<![CDATA[import threading, time
 
 MyT = None
 Stop = False
 
+def StartLog():
+    global MyT
+    MyT=threading.Thread(target = DoLog)
+    MyT.start()
+
 def DoLog():
-    global MyT,Stop
-    MyT=threading.Timer(0.3, DoLog)
-    if not Stop : MyT.start()
-    Stop = False
-    PLCObject.LogMessage("Python side Logging")
+    global Stop
+    while not Stop:
+        PLCObject.LogMessage("Python side Logging (PLC is %s)"%PLCObject.PLCStatus)
+        time.sleep(0.3)
 
 def StopLog():
     global MyT,Stop
     Stop=True
-    if MyT is not None: MyT.cancel()
 
-_runtime_begin.append(DoLog)
+_runtime_init.append(StartLog)
 _runtime_cleanup.append(StopLog)
 ]]>
 </Python>
--- a/tests/python/c_code@c_ext/cfile.xml	Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/python/c_code@c_ext/cfile.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -1,16 +1,20 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<CFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="cext_xsd.xsd">
+<CFile>
   <includes>
-<![CDATA[#include "stdio.h"]]>
+<![CDATA[
+]]>
   </includes>
   <variables>
-    <variable name="TestInput" type="SINT" class="input"/>
-    <variable name="TestOutput" type="SINT" class="output"/>
+    <variable name="TestInput" type="SINT" initial="0"/>
+    <variable name="TestOutput" type="SINT"/>
   </variables>
   <globals>
-<![CDATA[volatile long Lock=0;
+<![CDATA[
+volatile long Lock=0;
 volatile char PtoC=1,CtoP=2;
 
+extern long AtomicCompareExchange(long*,long, long);
+
 int Simple_C_Call(int val){
   return val+1;
 }
@@ -23,10 +27,10 @@
      beremiz' runtime implementation */
 
   int res = 0;
-  if(!AtomicCompareExchange(&Lock, 0, 1)){
+  if(!AtomicCompareExchange((long*)&Lock, 0, 1)){
     PtoC=toC;
     *fromC=CtoP;
-    AtomicCompareExchange(&Lock, 1, 0);
+    AtomicCompareExchange((long*)&Lock, 1, 0);
     res=1;
   }
   printf("C code called by Python: toC %d fromC %d\n",toC,*fromC);
@@ -36,29 +40,34 @@
 int PLC_C_Call(char fromPLC, char *toPLC){
   /* PLC also have to be realy carefull not to 
      conflict with asynchronous python access */
-  int res;
-  if(!AtomicCompareExchange(&Lock, 0, 1)){
+  if(!AtomicCompareExchange((long*)&Lock, 0, 1)){
       CtoP = fromPLC;
       *toPLC = PtoC;
-      AtomicCompareExchange(&Lock, 1, 0);
+      AtomicCompareExchange((long*)&Lock, 1, 0);
       return 1;
   }
   return 0;
-}]]>
+}
+]]>
   </globals>
   <initFunction>
-<![CDATA[]]>
+<![CDATA[
+]]>
   </initFunction>
   <cleanUpFunction>
-<![CDATA[]]>
+<![CDATA[
+]]>
   </cleanUpFunction>
   <retrieveFunction>
-<![CDATA[]]>
+<![CDATA[
+]]>
   </retrieveFunction>
   <publishFunction>
-<![CDATA[if(!AtomicCompareExchange(&Lock, 0, 1)){
+<![CDATA[
+if(!AtomicCompareExchange((long*)&Lock, 0, 1)){
     TestInput = CtoP + PtoC + TestOutput;
-    AtomicCompareExchange(&Lock, 1, 0);
-}]]>
+    AtomicCompareExchange((long*)&Lock, 1, 0);
+}
+]]>
   </publishFunction>
 </CFile>
--- a/tests/python/plc.xml	Wed Mar 13 12:34:55 2013 +0900
+++ b/tests/python/plc.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -3,12 +3,12 @@
          xsi:schemaLocation="http://www.plcopen.org/xml/tc6.xsd"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xhtml="http://www.w3.org/1999/xhtml">
-  <fileHeader companyName="beremiz"
+  <fileHeader companyName=""
               productName="Beremiz"
               productVersion="0.0"
               creationDateTime="2008-12-14T16:21:19"/>
   <contentHeader name="Beremiz Python Support Tests"
-                 modificationDateTime="2013-02-23T23:37:47">
+                 modificationDateTime="2013-05-15T18:19:52">
     <coordinateInfo>
       <pageSize x="1024" y="1024"/>
       <fbd>
@@ -102,18 +102,18 @@
               </type>
             </variable>
           </localVars>
-          <localVars>
-            <variable name="TestInput" address="%IB1.0">
+          <externalVars>
+            <variable name="TestInput">
               <type>
                 <SINT/>
               </type>
             </variable>
-            <variable name="TestOutput" address="%QB1.0">
+            <variable name="TestOutput">
               <type>
                 <SINT/>
               </type>
             </variable>
-          </localVars>
+          </externalVars>
           <localVars>
             <variable name="FromInput">
               <type>
@@ -181,6 +181,26 @@
                 <derived name="RS"/>
               </type>
             </variable>
+            <variable name="TUTU">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="TOTO">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="Test_Python_Var">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="Second_Python_Var">
+              <type>
+                <INT/>
+              </type>
+            </variable>
           </externalVars>
         </interface>
         <body>
@@ -615,14 +635,14 @@
                 </variable>
               </outputVariables>
             </block>
-            <inVariable localId="27" height="30" width="75">
-              <position x="305" y="770"/>
-              <connectionPointOut>
-                <relPosition x="75" y="15"/>
+            <inVariable localId="27" height="30" width="90">
+              <position x="300" y="770"/>
+              <connectionPointOut>
+                <relPosition x="90" y="15"/>
               </connectionPointOut>
               <expression>TestInput</expression>
             </inVariable>
-            <outVariable localId="28" height="30" width="90">
+            <outVariable localId="28" height="30" width="105">
               <position x="395" y="705"/>
               <connectionPointIn>
                 <relPosition x="0" y="15"/>
@@ -639,7 +659,7 @@
                 <relPosition x="0" y="15"/>
                 <connection refLocalId="27">
                   <position x="415" y="785"/>
-                  <position x="380" y="785"/>
+                  <position x="390" y="785"/>
                 </connection>
               </connectionPointIn>
               <expression>FromInput</expression>
@@ -847,15 +867,15 @@
               </connectionPointOut>
               <expression>'True'</expression>
             </inVariable>
-            <block localId="46" width="130" height="45" typeName="BYTE_TO_STRING">
+            <block localId="46" width="130" height="45" typeName="INT_TO_STRING">
               <position x="900" y="970"/>
               <inputVariables>
                 <variable formalParameter="IN">
                   <connectionPointIn>
                     <relPosition x="0" y="30"/>
-                    <connection refLocalId="47">
+                    <connection refLocalId="58">
                       <position x="900" y="1000"/>
-                      <position x="850" y="1000"/>
+                      <position x="840" y="1000"/>
                     </connection>
                   </connectionPointIn>
                 </variable>
@@ -869,13 +889,6 @@
                 </variable>
               </outputVariables>
             </block>
-            <inVariable localId="47" height="30" width="80">
-              <position x="770" y="985"/>
-              <connectionPointOut>
-                <relPosition x="80" y="15"/>
-              </connectionPointOut>
-              <expression>BYTE#145</expression>
-            </inVariable>
             <inVariable localId="50" height="30" width="105">
               <position x="200" y="1085"/>
               <connectionPointOut>
@@ -1048,6 +1061,62 @@
               </connectionPointOut>
               <expression>Test_TOD</expression>
             </inOutVariable>
+            <inVariable localId="49" height="30" width="30">
+              <position x="765" y="1200"/>
+              <connectionPointOut>
+                <relPosition x="30" y="15"/>
+              </connectionPointOut>
+              <expression>42</expression>
+            </inVariable>
+            <outVariable localId="57" height="30" width="50">
+              <position x="845" y="1200"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="49">
+                  <position x="845" y="1215"/>
+                  <position x="795" y="1215"/>
+                </connection>
+              </connectionPointIn>
+              <expression>TOTO</expression>
+            </outVariable>
+            <outVariable localId="56" height="30" width="50">
+              <position x="845" y="1240"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="49">
+                  <position x="845" y="1255"/>
+                  <position x="820" y="1255"/>
+                  <position x="820" y="1215"/>
+                  <position x="795" y="1215"/>
+                </connection>
+              </connectionPointIn>
+              <expression>TUTU</expression>
+            </outVariable>
+            <inVariable localId="58" height="30" width="145">
+              <position x="715" y="985"/>
+              <connectionPointOut>
+                <relPosition x="145" y="15"/>
+              </connectionPointOut>
+              <expression>Second_Python_Var</expression>
+            </inVariable>
+            <outVariable localId="47" height="30" width="125">
+              <position x="400" y="975"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="59">
+                  <position x="400" y="990"/>
+                  <position x="330" y="990"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_Python_Var</expression>
+            </outVariable>
+            <inVariable localId="59" height="30" width="30">
+              <position x="300" y="975"/>
+              <connectionPointOut>
+                <relPosition x="30" y="15"/>
+              </connectionPointOut>
+              <expression>23</expression>
+            </inVariable>
           </FBD>
         </body>
       </pou>
@@ -1120,6 +1189,7 @@
 {{
   char toPLC;
   char fromPLC = GetFbVar(IN);
+  extern int PLC_C_Call(char, char *);
   if(PLC_C_Call(fromPLC, &toPLC)){
     SetFbVar(OUT, toPLC);
   }
@@ -1182,6 +1252,11 @@
               <derived name="RS"/>
             </type>
           </variable>
+          <variable name="TUTU">
+            <type>
+              <INT/>
+            </type>
+          </variable>
         </globalVars>
       </configuration>
     </configurations>
--- a/tests/python/python@py_ext/py_ext.xml	Wed Mar 13 12:34:55 2013 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<Python xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="py_ext_xsd.xsd">
-<![CDATA[import time,sys,ctypes
-Python_to_C_Call = PLCBinary.Python_to_C_Call
-Python_to_C_Call.restype = ctypes.c_int
-Python_to_C_Call.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
-
-def MyPythonFunc(arg):
-    i = ctypes.c_int()
-    if(Python_to_C_Call(arg, i)):
-        res = i.value
-        print "toC:", arg, "from C:", res, "FBID:", FBID
-    else:
-        print "Failed Python_to_C_Call failed"
-        res = None
-    sys.stdout.flush()
-    return res
-]]>
-</Python>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/python/python@py_ext/pyfile.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<PyFile>
+  <variables>
+    <variable name="Test_Python_Var" type="INT" initial="4"/>
+    <variable name="Second_Python_Var" type="INT" initial="5"/>
+  </variables>
+  <globals>
+<![CDATA[
+import time,sys,ctypes
+Python_to_C_Call = PLCBinary.Python_to_C_Call
+Python_to_C_Call.restype = ctypes.c_int
+Python_to_C_Call.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
+
+def MyPythonFunc(arg):
+    i = ctypes.c_int()
+    if(Python_to_C_Call(arg, i)):
+        res = i.value
+        print "toC:", arg, "from C:", res, "FBID:", FBID
+    else:
+        print "Failed Python_to_C_Call failed"
+        res = None
+    print "Python read PLC global :",PLCGlobals.Test_Python_Var
+    PLCGlobals.Second_Python_Var = 789
+    sys.stdout.flush()
+    return res
+
+async_error_test_code = """
+def badaboom():
+    tuple()[0]
+
+import wx
+def badaboomwx():
+    wx.CallAfter(badaboom)
+
+from threading import Timer
+a = Timer(3, badaboom)
+a.start()
+
+b = Timer(6, badaboomwx)
+b.start()
+"""
+]]>
+  </globals>
+  <init>
+<![CDATA[
+global x, y
+x = 2
+y = 5
+print "py_runtime init:", x, ",", y
+]]>
+  </init>
+  <cleanup>
+<![CDATA[
+print "py_runtime cleanup"
+]]>
+  </cleanup>
+  <start>
+<![CDATA[
+global x, y
+print "py_runtime start", x * x + y * y
+]]>
+  </start>
+  <stop>
+<![CDATA[
+print "py_runtime stop"
+]]>
+  </stop>
+</PyFile>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/beremiz.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BeremizRoot URI_location="LOCAL://">
+  <TargetType/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/plc.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.plcopen.org/xml/tc6.xsd"
+         xsi:schemaLocation="http://www.plcopen.org/xml/tc6.xsd"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns:xhtml="http://www.w3.org/1999/xhtml">
+  <fileHeader companyName="Unknown"
+              productName="Unnamed"
+              productVersion="1"
+              creationDateTime="2012-09-12T23:30:19"/>
+  <contentHeader name="Unnamed"
+                 modificationDateTime="2013-05-28T18:28:14">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="main" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="x">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="y">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="a">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="b">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="c">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="b1">
+              <type>
+                <WORD/>
+              </type>
+            </variable>
+            <variable name="b0">
+              <type>
+                <WORD/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="WiiNunchuckStickX">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="WiiNunchuckStickY">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="WiiNunchuckButtons">
+              <type>
+                <WORD/>
+              </type>
+            </variable>
+            <variable name="WiiButtons">
+              <type>
+                <WORD/>
+              </type>
+            </variable>
+            <variable name="WiiNunchuckAccX">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="WiiNunchuckAccY">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="WiiNunchuckAccZ">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+          </externalVars>
+        </interface>
+        <body>
+          <FBD>
+            <outVariable localId="1" height="30" width="20">
+              <position x="345" y="40"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="3">
+                  <position x="345" y="55"/>
+                  <position x="295" y="55"/>
+                </connection>
+              </connectionPointIn>
+              <expression>x</expression>
+            </outVariable>
+            <inVariable localId="3" height="30" width="150">
+              <position x="145" y="40"/>
+              <connectionPointOut>
+                <relPosition x="150" y="15"/>
+              </connectionPointOut>
+              <expression>WiiNunchuckStickX</expression>
+            </inVariable>
+            <inVariable localId="4" height="30" width="150">
+              <position x="145" y="100"/>
+              <connectionPointOut>
+                <relPosition x="150" y="15"/>
+              </connectionPointOut>
+              <expression>WiiNunchuckStickY</expression>
+            </inVariable>
+            <inVariable localId="5" height="30" width="155">
+              <position x="115" y="175"/>
+              <connectionPointOut>
+                <relPosition x="155" y="15"/>
+              </connectionPointOut>
+              <expression>WiiNunchuckButtons</expression>
+            </inVariable>
+            <inVariable localId="6" height="30" width="90">
+              <position x="180" y="225"/>
+              <connectionPointOut>
+                <relPosition x="90" y="15"/>
+              </connectionPointOut>
+              <expression>WiiButtons</expression>
+            </inVariable>
+            <outVariable localId="8" height="30" width="20">
+              <position x="345" y="100"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="4">
+                  <position x="345" y="115"/>
+                  <position x="295" y="115"/>
+                </connection>
+              </connectionPointIn>
+              <expression>y</expression>
+            </outVariable>
+            <outVariable localId="9" height="30" width="30">
+              <position x="345" y="175"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="5">
+                  <position x="345" y="190"/>
+                  <position x="270" y="190"/>
+                </connection>
+              </connectionPointIn>
+              <expression>b1</expression>
+            </outVariable>
+            <outVariable localId="10" height="30" width="30">
+              <position x="345" y="225"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="6">
+                  <position x="345" y="240"/>
+                  <position x="270" y="240"/>
+                </connection>
+              </connectionPointIn>
+              <expression>b0</expression>
+            </outVariable>
+            <inVariable localId="11" height="30" width="135">
+              <position x="135" y="295"/>
+              <connectionPointOut>
+                <relPosition x="135" y="15"/>
+              </connectionPointOut>
+              <expression>WiiNunchuckAccX</expression>
+            </inVariable>
+            <inVariable localId="12" height="30" width="135">
+              <position x="135" y="340"/>
+              <connectionPointOut>
+                <relPosition x="135" y="15"/>
+              </connectionPointOut>
+              <expression>WiiNunchuckAccY</expression>
+            </inVariable>
+            <inVariable localId="13" height="30" width="130">
+              <position x="140" y="385"/>
+              <connectionPointOut>
+                <relPosition x="130" y="15"/>
+              </connectionPointOut>
+              <expression>WiiNunchuckAccZ</expression>
+            </inVariable>
+            <outVariable localId="14" height="30" width="20">
+              <position x="345" y="295"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="11">
+                  <position x="345" y="310"/>
+                  <position x="270" y="310"/>
+                </connection>
+              </connectionPointIn>
+              <expression>a</expression>
+            </outVariable>
+            <outVariable localId="15" height="30" width="20">
+              <position x="345" y="340"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="12">
+                  <position x="345" y="355"/>
+                  <position x="270" y="355"/>
+                </connection>
+              </connectionPointIn>
+              <expression>b</expression>
+            </outVariable>
+            <outVariable localId="16" height="30" width="20">
+              <position x="345" y="385"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="13">
+                  <position x="345" y="400"/>
+                  <position x="270" y="400"/>
+                </connection>
+              </connectionPointIn>
+              <expression>c</expression>
+            </outVariable>
+          </FBD>
+        </body>
+        <documentation>
+<![CDATA[]]>
+        </documentation>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="tsk1" interval="T#1ms" priority="0">
+            <pouInstance name="inst1" typeName="main"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/py_ext_0@py_ext/baseconfnode.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BaseParams Name="py_ext_0" IEC_Channel="1"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/py_ext_0@py_ext/pyfile.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<PyFile>
+  <variables>
+    <variable name="WiiNunchuckStickX" type="INT"/>
+    <variable name="WiiNunchuckStickY" type="INT"/>
+    <variable name="WiiNunchuckAccX" type="INT"/>
+    <variable name="WiiNunchuckAccY" type="INT"/>
+    <variable name="WiiNunchuckAccZ" type="INT"/>
+    <variable name="WiiNunchuckButtons" type="WORD"/>
+    <variable name="WiiButtons" type="WORD"/>
+  </variables>
+  <globals>
+<![CDATA[
+import cwiid,commands,sys,re,os,time
+
+wiimote = None
+WIIMOTE_ADDR_MODEL = re.compile("((?:[0-9A-F]{2})(?::[0-9A-F]{2}){5})\s*Nintendo")
+nunchuckzero = None
+
+def Wiimote_cback(messages, time):
+    global nunchuckzero
+    state = dict(messages)
+    bts = state.get(cwiid.MESG_BTN, None)
+    if bts is not None:
+        PLCGlobals.WiiButtons = bts
+    nunchuck = state.get(cwiid.MESG_NUNCHUK, None)
+    if nunchuck is not None:
+        PLCGlobals.WiiNunchuckButtons = nunchuck['buttons']
+        X,Y = nunchuck['stick']
+        PLCGlobals.WiiNunchuckAccX = nunchuck['acc'][cwiid.X]
+        PLCGlobals.WiiNunchuckAccY = nunchuck['acc'][cwiid.Y]
+        PLCGlobals.WiiNunchuckAccZ = nunchuck['acc'][cwiid.Z]
+        if nunchuckzero is None:
+            nunchuckzero = X,Y
+        (PLCGlobals.WiiNunchuckStickX,
+         PLCGlobals.WiiNunchuckStickY) = X-nunchuckzero[0],Y-nunchuckzero[1]
+
+def Connect_Wiimote(connected_callback):
+    global wiimote,nunchuckzero
+    mac_addr = ''
+    try:
+        mac_addr = file("wiimac.txt","rt").read()
+    except:
+        PLCObject.LogMessage("Wiimote MAC unknown, scanning bluetooth")
+        output = commands.getoutput("hcitool scan")
+        result = WIIMOTE_ADDR_MODEL.search(output)
+        if result is not None:
+            mac_addr = result.group(1)
+            PLCObject.LogMessage("Found Wiimote with MAC %s"%mac_addr)
+            file("wiimac.txt","wt").write(mac_addr)
+ 
+    # Connect to wiimote
+    if not mac_addr:
+        PLCObject.LogMessage("Connection to unknown Wiimote...")
+        wiimote = cwiid.Wiimote()
+    else:
+        PLCObject.LogMessage("Connection to Wiimote %s..."%mac_addr)
+        wiimote = cwiid.Wiimote(mac_addr)
+
+    if wiimote is not None:
+        nunchuckzero = None
+        wiimote.rpt_mode = cwiid.RPT_BTN | cwiid.RPT_EXT
+        # use the callback interface
+        wiimote.mesg_callback = Wiimote_cback
+        wiimote.enable(cwiid.FLAG_MESG_IFC)
+        connected_callback(mac_addr)
+        PLCObject.LogMessage("Wiimote %s Connected"%mac_addr)
+    else:
+        PLCObject.LogMessage("Wiimote %s not found"%mac_addr)
+        os.remove("wiimac.txt")
+        connected_callback(None)
+
+def Disconnect_Wiimote():
+  global wiimote
+  if wiimote is not None:
+    wiimote.disable(cwiid.FLAG_MESG_IFC)
+    time.sleep(0.1)
+    wiimote.close()
+    wiimote = None
+    PLCObject.LogMessage("Wiimote disconnected")
+
+]]>
+  </globals>
+  <init>
+<![CDATA[
+]]>
+  </init>
+  <cleanup>
+<![CDATA[
+Disconnect_Wiimote()
+
+]]>
+  </cleanup>
+  <start>
+<![CDATA[
+]]>
+  </start>
+  <stop>
+<![CDATA[
+]]>
+  </stop>
+</PyFile>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/wxglade_hmi@wxglade_hmi/baseconfnode.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BaseParams Name="wxglade_hmi" IEC_Channel="0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/wxglade_hmi@wxglade_hmi/hmi.wxg	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!-- generated by wxGlade 0.6.4 on Thu May 16 13:24:16 2013 -->
+
+<application path="" name="" class="" option="0" language="python" top_window="wxglade_hmi" encoding="UTF-8" use_gettext="0" overwrite="0" use_new_namespace="1" for_version="2.8" is_template="0" indent_amount="4" indent_symbol="space" source_extension=".cpp" header_extension=".h">
+    <object class="Class_wxglade_hmi" name="wxglade_hmi" base="EditFrame">
+        <style>wxDEFAULT_FRAME_STYLE</style>
+        <title>frame_1</title>
+        <object class="wxStaticBoxSizer" name="sizer_1" base="EditStaticBoxSizer">
+            <orient>wxVERTICAL</orient>
+            <label>Wiimote Test</label>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxButton" name="button_1" base="EditButton">
+                    <label>Connect Wiimote</label>
+                    <events>
+                        <handler event="EVT_BUTTON">OnConnectButton</handler>
+                    </events>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxButton" name="button_2" base="EditButton">
+                    <label>Disconnect Wiimote</label>
+                    <events>
+                        <handler event="EVT_BUTTON">OnDisconnectButton</handler>
+                    </events>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxStaticText" name="label_1" base="EditStaticText">
+                    <attribute>1</attribute>
+                    <label>Status :</label>
+                </object>
+            </object>
+            <object class="sizeritem">
+                <flag>wxEXPAND</flag>
+                <border>0</border>
+                <option>0</option>
+                <object class="wxStaticText" name="label_2" base="EditStaticText">
+                    <attribute>1</attribute>
+                    <label>no status</label>
+                </object>
+            </object>
+        </object>
+    </object>
+</application>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wiimote/wxglade_hmi@wxglade_hmi/pyfile.xml	Wed Jul 31 10:45:07 2013 +0900
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<PyFile>
+  <variables/>
+  <globals>
+<![CDATA[
+from threading import Thread
+
+def OnConnectButton(self, event):
+  def OnWiiConnected(mac_addr):
+    self.label_2.SetLabel(
+      "Wiimote %s connected"%mac_addr 
+      if mac_addr else 
+      "Wiimote connection failed !")
+
+  def WiiConnected(mac_addr):
+    wx.CallAfter(OnWiiConnected,mac_addr)
+
+  Thread(target = Connect_Wiimote, args = (WiiConnected,)).start()
+  self.label_2.SetLabel("Press wiimote 1+2")
+  event.Skip()
+
+def OnDisconnectButton(self, event):
+  Disconnect_Wiimote()
+  self.label_2.SetLabel("Wiimote disconnected")
+  event.Skip()
+
+]]>
+  </globals>
+  <init>
+<![CDATA[
+]]>
+  </init>
+  <cleanup>
+<![CDATA[
+]]>
+  </cleanup>
+  <start>
+<![CDATA[
+]]>
+  </start>
+  <stop>
+<![CDATA[
+]]>
+  </stop>
+</PyFile>
--- a/wxglade_hmi/wxglade_hmi.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/wxglade_hmi/wxglade_hmi.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1,5 +1,5 @@
 import wx
-import os, sys
+import os, sys, shutil
 from xml.dom import minidom
 
 from py_ext import PythonFileCTNMixin
@@ -13,12 +13,17 @@
          "method" : "_editWXGLADE"},
     ]
 
+    def GetIconName(self):
+        return "wxGlade"
+
     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,23 +34,15 @@
         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):
-        """
-        Return C code generated by iec2c compiler 
-        when _generate_softPLC have been called
-        @param locations: ignored
-        @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
-        """
         
-        current_location = self.GetCurrentLocation()
-        # define a unique name for the generated C file
-        location_str = "_".join(map(lambda x:str(x), current_location))
-        
-        runtimefile_path = os.path.join(buildpath, "runtime_%s.py"%location_str)
-        runtimefile = open(runtimefile_path, 'w')
-        
-        hmi_frames = {}
+        hmi_frames = []
         
         wxgfile_path=self._getWXGLADEpath()
         if os.path.exists(wxgfile_path):
@@ -55,7 +52,12 @@
             
             for node in wxgtree.childNodes[1].childNodes:
                 if node.nodeType == wxgtree.ELEMENT_NODE:
-                    hmi_frames[node._attrs["name"].value] =  node._attrs["class"].value
+                    hmi_frames.append({
+                        "name" : node.getAttribute("name"),
+                        "class" : node.getAttribute("class"),
+                        "handlers" : [
+                            hnode.firstChild.data for hnode in 
+                            node.getElementsByTagName("handler")]})
                     
             hmipyfile_path=os.path.join(self._getBuildPath(), "hmi.py")
             if wx.Platform == '__WXMSW__':
@@ -66,39 +68,42 @@
             self.launch_wxglade(['-o', wxghmipyfile_path, '-g', 'python', wxgfile_path], wait=True)
             
             hmipyfile = open(hmipyfile_path, 'r')
-            runtimefile.write(hmipyfile.read())
+            define_hmi = hmipyfile.read().decode('utf-8')
             hmipyfile.close()
         
-        runtimefile.write(self.GetPythonCode())
-        runtimefile.write("""
-%(declare)s
+        else:
+            define_hmi = ""
+        
+        declare_hmi = "\n".join(["%(name)s = None\n" % x + 
+                          "\n".join(["%(class)s.%(h)s = %(h)s"%
+                            dict(x,h=h) for h in x['handlers']])
+                                for x in hmi_frames])
+        global_hmi = ("global %s\n"%",".join(
+                         [x["name"] for x in hmi_frames]) 
+                      if len(hmi_frames) > 0 else "")
+        init_hmi = "\n".join(["""\
+def OnCloseFrame(evt):
+    wx.MessageBox(_("Please stop PLC to close"))
 
-def _runtime_%(location)s_begin():
-    global %(global)s
-    
-    def OnCloseFrame(evt):
-        wx.MessageBox(_("Please stop PLC to close"))
-    
-    %(init)s
-    
-def _runtime_%(location)s_cleanup():
-    global %(global)s
-    
-    %(cleanup)s
+%(name)s = %(class)s(None)
+%(name)s.Bind(wx.EVT_CLOSE, OnCloseFrame)
+%(name)s.Show()
+""" % x for x in hmi_frames])
+        cleanup_hmi = "\n".join(
+            ["if %(name)s is not None: %(name)s.Destroy()" % x 
+                for x in hmi_frames])
+        
+        self.PreSectionsTexts = {
+            "globals":define_hmi,
+            "start":global_hmi,
+            "stop":global_hmi + cleanup_hmi
+        }
+        self.PostSectionsTexts = {
+            "globals":declare_hmi,
+            "start":init_hmi,
+        }
 
-""" % {"location": location_str,
-       "declare": "\n".join(map(lambda x:"%s = None" % x, hmi_frames.keys())),
-       "global": ",".join(hmi_frames.keys()),
-       "init": "\n".join(map(lambda x: """
-    %(name)s = %(class)s(None)
-    %(name)s.Bind(wx.EVT_CLOSE, OnCloseFrame)
-    %(name)s.Show()
-""" % {"name": x[0], "class": x[1]},
-                             hmi_frames.items())),
-       "cleanup": "\n    ".join(map(lambda x:"%s.Destroy()" % x, hmi_frames.keys()))})
-        runtimefile.close()
-        
-        return [], "", False, ("runtime_%s.py"%location_str, file(runtimefile_path,"rb"))
+        return PythonFileCTNMixin.CTNGenerate_C(self, buildpath, locations)
 
     def _editWXGLADE(self):
         wxg_filename = self._getWXGLADEpath()
@@ -128,3 +133,4 @@
             if wx.Platform == '__WXMSW__':
                 wxg_filename = "\"%s\""%wxg_filename
             self.launch_wxglade([wxg_filename])
+
--- a/xmlclass/__init__.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/xmlclass/__init__.py	Wed Jul 31 10:45:07 2013 +0900
@@ -24,5 +24,5 @@
 
 # Package initialisation
 
-from xmlclass import ClassFactory, GenerateClasses, GetAttributeValue, time_model, CreateNode, NodeSetAttr, NodeRenameAttr
+from xmlclass import ClassFactory, GenerateClasses, GetAttributeValue, time_model, CreateNode, NodeSetAttr, NodeRenameAttr, UpdateXMLClassGlobals
 from xsdschema import XSDClassFactory, GenerateClassesFromXSD, GenerateClassesFromXSDstring
--- a/xmlclass/xmlclass.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/xmlclass/xmlclass.py	Wed Jul 31 10:45:07 2013 +0900
@@ -1598,14 +1598,14 @@
         if path is not None:
             parts = path.split(".", 1)
             if attributes.has_key(parts[0]):
-                if len(parts) != 0:
+                if len(parts) != 1:
                     raise ValueError("Wrong path!")
                 attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"], 
                                          attributes[parts[0]]["attr_type"]["facets"])
                 value = getattr(self, parts[0], "")
             elif elements.has_key(parts[0]):
                 if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
-                    if len(parts) != 0:
+                    if len(parts) != 1:
                         raise ValueError("Wrong path!")
                     attr_type = gettypeinfos(elements[parts[0]]["elmt_type"]["basename"], 
                                              elements[parts[0]]["elmt_type"]["facets"])
@@ -1620,6 +1620,11 @@
                         return attr.getElementInfos(parts[0])
                     else:
                         return attr.getElementInfos(parts[0], parts[1])
+            elif elements.has_key("content"):
+                if len(parts) > 0:
+                    return self.content["value"].getElementInfos(name, path)
+            elif classinfos.has_key("base"):
+                classinfos["base"].getElementInfos(name, path)
             else:
                 raise ValueError("Wrong path!")
         else:
@@ -1669,6 +1674,13 @@
                     raise ValueError("Wrong path!")
                 if attributes[parts[0]]["attr_type"]["basename"] == "boolean":
                     setattr(self, parts[0], value)
+                elif attributes[parts[0]]["use"] == "optional" and value == "":
+                    if attributes[parts[0]].has_key("default"):
+                        setattr(self, parts[0], 
+                            attributes[parts[0]]["attr_type"]["extract"](
+                                attributes[parts[0]]["default"], False))
+                    else:
+                        setattr(self, parts[0], None)
                 else:
                     setattr(self, parts[0], attributes[parts[0]]["attr_type"]["extract"](value, False))
             elif elements.has_key(parts[0]):
@@ -1677,6 +1689,8 @@
                         raise ValueError("Wrong path!")
                     if elements[parts[0]]["elmt_type"]["basename"] == "boolean":
                         setattr(self, parts[0], value)
+                    elif attributes[parts[0]]["minOccurs"] == 0 and value == "":
+                        setattr(self, parts[0], None)
                     else:
                         setattr(self, parts[0], elements[parts[0]]["elmt_type"]["extract"](value, False))
                 else:
@@ -1848,9 +1862,11 @@
 def GenerateClasses(factory):
     ComputedClasses = factory.CreateClasses()
     if factory.FileName is not None and len(ComputedClasses) == 1:
-        globals().update(ComputedClasses[factory.FileName])
+        UpdateXMLClassGlobals(ComputedClasses[factory.FileName])
         return ComputedClasses[factory.FileName]
     else:
-        globals().update(ComputedClasses)
+        UpdateXMLClassGlobals(ComputedClasses)
         return ComputedClasses
 
+def UpdateXMLClassGlobals(classes):
+    globals().update(classes)
--- a/xmlclass/xsdschema.py	Wed Mar 13 12:34:55 2013 +0900
+++ b/xmlclass/xsdschema.py	Wed Jul 31 10:45:07 2013 +0900
@@ -2142,7 +2142,7 @@
         "extract": GenerateIntegerExtraction(minExclusive=0),
         "facets": DECIMAL_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
-        "initial": lambda: 0,
+        "initial": lambda: 1,
         "check": lambda x: isinstance(x, IntType)
     },
     
@@ -2152,7 +2152,7 @@
         "extract": GenerateIntegerExtraction(maxExclusive=0),
         "facets": DECIMAL_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
-        "initial": lambda: 0,
+        "initial": lambda: -1,
         "check": lambda x: isinstance(x, IntType)
     },