Merge from default branch + fix traffic light example SVG missing background + update generated XSLT
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Sun, 17 Jul 2022 22:53:35 +0200
changeset 3558 f5850ce25caf
parent 3552 ffe4e46a3163 (diff)
parent 3557 59158e360b8c (current diff)
child 3559 f247fb5b7756
Merge from default branch + fix traffic light example SVG missing background + update generated XSLT
exemples/svghmi_traffic_light/svghmi_0@svghmi/svghmi.svg
svghmi/gen_index_xhtml.xslt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.github/workflows/run_tests_in_docker.yml	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,68 @@
+name: Docker Image CI
+
+on:
+  push:
+    branches: [ wxPython4 ]
+
+jobs:
+
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+          path: beremiz
+
+    - uses: actions/checkout@v3
+      with:
+          repository: beremiz/matiec
+          ref: 2a25f4dbf4e2b1e017a3a583db7dede4771fe523
+          path: matiec
+
+    - uses: actions/checkout@v3
+      with:
+          repository: open62541/open62541
+          ref: v3.0.2
+          path: open62541
+          submodules: recursive
+
+    - name: Cache docker image
+      id: cache-docker
+      uses: actions/cache@v3
+      env:
+        cache-name: cache-docker
+      with:
+        path: /tmp/latest.tar
+        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('beremiz/tests/tools/Docker/beremiz-sikuli') }}
+
+    - if: ${{ steps.cache-docker.outputs.cache-hit == false }}
+      name: Create docker image
+      run: |
+        cd beremiz/tests/tools/Docker/beremiz-sikuli
+        ./build_docker_image.sh
+        docker image save --output="/tmp/latest.tar" beremiz_sikuli
+
+    - if: ${{ steps.cache-docker.outputs.cache-hit != false }}
+      name: Re-use docker image
+      run: |
+        docker image load --input="/tmp/latest.tar" 
+
+    - name: Create docker container
+      run: |
+        cd beremiz/tests/tools/Docker/beremiz-sikuli
+        ./create_docker_container.sh ${{ github.workspace }}/test
+        
+    - name: Run tests in docker
+      run: |
+        cd beremiz/tests/tools/Docker/beremiz-sikuli
+        ./do_test_in_docker.sh
+
+    - name: Upload test resuts artifact
+      uses: actions/upload-artifact@v3
+      if: failure()
+      with:
+        name: test_results
+        path: ${{ github.workspace }}/test
+        retention-days: 5
--- a/.hgignore	Thu Jul 14 11:40:27 2022 +0200
+++ b/.hgignore	Sun Jul 17 22:53:35 2022 +0200
@@ -24,3 +24,5 @@
 
 doc/_build
 doc/locale
+
+^.*\$py.class$
--- a/Beremiz.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/Beremiz.py	Sun Jul 17 22:53:35 2022 +0200
@@ -50,6 +50,7 @@
         self.modules = ["BeremizIDE"]
         self.debug = os.path.exists("BEREMIZ_DEBUG")
         self.handle_exception = None
+        self.logf = None
 
     def Bpath(self, *args):
         return os.path.join(self.app_dir, *args)
@@ -62,12 +63,13 @@
         print("-h --help                    Print this help")
         print("-u --updatecheck URL         Retrieve update information by checking URL")
         print("-e --extend PathToExtension  Extend IDE functionality by loading at start additional extensions")
+        print("-l --log path                write content of console tab to given file")
         print("")
         print("")
 
     def SetCmdOptions(self):
-        self.shortCmdOpts = "hu:e:"
-        self.longCmdOpts = ["help", "updatecheck=", "extend="]
+        self.shortCmdOpts = "hu:e:l:"
+        self.longCmdOpts = ["help", "updatecheck=", "extend=", "log="]
 
     def ProcessOption(self, o, a):
         if o in ("-h", "--help"):
@@ -77,6 +79,8 @@
             self.updateinfo_url = a
         if o in ("-e", "--extend"):
             self.extensions.append(a)
+        if o in ("-l", "--log"):
+            self.logf = open(a, 'a')
 
     def ProcessCommandLineArgs(self):
         self.SetCmdOptions()
@@ -182,10 +186,10 @@
     def InstallExceptionHandler(self):
         import version
         import util.ExceptionHandler
-        self.handle_exception = util.ExceptionHandler.AddExceptHook(version.app_version)
+        self.handle_exception = util.ExceptionHandler.AddExceptHook(version.app_version, logf=self.logf)
 
     def CreateUI(self):
-        self.frame = self.BeremizIDE.Beremiz(None, self.projectOpen, self.buildpath)
+        self.frame = self.BeremizIDE.Beremiz(None, self.projectOpen, self.buildpath, logf=self.logf)
 
     def CloseSplash(self):
         if self.splash:
--- a/BeremizIDE.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/BeremizIDE.py	Sun Jul 17 22:53:35 2022 +0200
@@ -28,9 +28,7 @@
 from __future__ import print_function
 import os
 import sys
-import tempfile
 import shutil
-import random
 import time
 from time import time as gettime
 from threading import Lock, Timer, currentThread
@@ -47,9 +45,8 @@
 from editors.TextViewer import TextViewer
 from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
 from editors.DataTypeEditor import DataTypeEditor
-from util import paths as paths
+from util.paths import Bpath
 from util.MiniTextControler import MiniTextControler
-from util.ProcessLogger import ProcessLogger
 from util.BitmapLibrary import GetBitmap
 from controls.LogViewer import LogViewer
 from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
@@ -84,15 +81,11 @@
     EncodeFileSystemPath, \
     DecodeFileSystemPath
 
-
-beremiz_dir = paths.AbsDir(__file__)
-
-
-def Bpath(*args):
-    return os.path.join(beremiz_dir, *args)
+from LocalRuntimeMixin import LocalRuntimeMixin
+
 
 def AppendMenu(parent, help, id, kind, text):
-    return parent.Append(help=help, id=id, kind=kind, text=text)
+    return parent.Append(wx.MenuItem(helpString=help, id=id, kind=kind, text=text))
 
 MAX_RECENT_PROJECTS = 9
 
@@ -115,7 +108,7 @@
 
 class LogPseudoFile(object):
     """ Base class for file like objects to facilitate StdOut for the Shell."""
-    def __init__(self, output, risecall):
+    def __init__(self, output, risecall, logf):
         self.red_white = 1
         self.red_yellow = 2
         self.black_white = wx.stc.STC_STYLE_DEFAULT
@@ -131,8 +124,12 @@
         self.LastRefreshTime = gettime()
         self.LastRefreshTimer = None
         self.refreshPending = False
+        self.logf = logf
 
     def write(self, s, style=None):
+        if self.logf is not None:
+            self.logf.write(s)
+            self.logf.flush()
         self.StackLock.acquire()
         self.stack.append((s, style))
         self.StackLock.release()
@@ -179,7 +176,7 @@
                     if style is None:
                         style = self.black_white
                     if style != self.black_white:
-                        self.output.StartStyling(self.output.GetLength(), 0xff)
+                        self.output.StartStyling(self.output.GetLength())
 
                     # Temporary deactivate read only mode on StyledTextCtrl for
                     # adding text. It seems that text modifications, even
@@ -236,7 +233,7 @@
 ID_FILEMENURECENTPROJECTS = wx.NewId()
 
 
-class Beremiz(IDEFrame):
+class Beremiz(IDEFrame, LocalRuntimeMixin):
 
     def _init_utils(self):
         self.ConfNodeMenu = wx.Menu(title='')
@@ -250,9 +247,9 @@
                    kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N')
         AppendMenu(parent, help='', id=wx.ID_OPEN,
                    kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
-        parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu)
+        parent.Append(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu)
         parent.AppendSeparator()
-        parent.AppendMenu(wx.ID_ANY, _("&Tutorials and Examples"), self.TutorialsProjectsMenu)
+        parent.Append(wx.ID_ANY, _("&Tutorials and Examples"), self.TutorialsProjectsMenu)
 
         exemples_dir = Bpath("exemples")
         project_list = sorted(os.listdir(exemples_dir))
@@ -314,10 +311,10 @@
         for name, text, helpstr, children in items:
             if len(children) > 0:
                 new_menu = wx.Menu(title='')
-                menu.AppendMenu(wx.ID_ANY, text, new_menu)
+                menu.AppendSubMenu(new_menu, text)
                 self._RecursiveAddMenuItems(new_menu, children)
             else:
-                item = menu.Append(wx.ID_ANY, text, helpstr)
+                item = menu.Append(wx.MenuItem(text=text, helpString=helpstr, kind=wx.ITEM_NORMAL, id=wx.ID_ANY))
                 self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name), item)
 
     def _init_coll_AddMenu_Items(self, parent):
@@ -334,16 +331,16 @@
         item = parent.Append(wx.ID_ANY, _(u'Community support'), '')
         self.Bind(wx.EVT_MENU, handler, item)
 
-        parent.Append(help='', id=wx.ID_ABOUT,
-                      kind=wx.ITEM_NORMAL, text=_(u'About'))
+        parent.Append(wx.MenuItem(helpString='', id=wx.ID_ABOUT,
+                      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.SetStatusText(i=0, text='')
+        parent.SetStatusText(i=1, text='')
+        parent.SetStatusText(i=2, text='')
 
         parent.SetStatusWidths([-1, 300, 200])
 
@@ -356,6 +353,8 @@
         self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=inspectorID)
         accels = [wx.AcceleratorEntry(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord('I'), inspectorID)]
 
+        self.methodLock = Lock()
+
         for method, shortcut in [("Stop",     wx.WXK_F4),
                                  ("Run",      wx.WXK_F5),
                                  ("Transfer", wx.WXK_F6),
@@ -365,8 +364,15 @@
             def OnMethodGen(obj, meth):
                 def OnMethod(evt):
                     if obj.CTR is not None:
-                        obj.CTR.CallMethod('_'+meth)
-                    wx.CallAfter(self.RefreshStatusToolBar)
+                        if obj.methodLock.acquire(False):
+                            obj.CTR.CallMethod('_'+meth)
+                            obj.methodLock.release()
+                            wx.CallAfter(obj.RefreshStatusToolBar)
+                        else:
+                            # Postpone call if one of method already running
+                            # can happen because of long method using log, 
+                            # itself calling wx.Yield
+                            wx.CallLater(50, OnMethod, evt)
                 return OnMethod
             newid = wx.NewId()
             self.Bind(wx.EVT_MENU, OnMethodGen(self, method), id=newid)
@@ -395,6 +401,7 @@
         self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED")
 
         self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT)
+        self.LogConsole.SetCaretPeriod(0)
 
         self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick)
         self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified)
@@ -420,7 +427,7 @@
 
         self.AUIManager.Update()
 
-        self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.ST_SIZEGRIP)
+        self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.STB_SIZEGRIP)
         self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar)
         self.ProgressStatusBar = wx.Gauge(self.ConnectionStatusBar, -1, range=100)
         self.ConnectionStatusBar.AddWidget(self.ProgressStatusBar, esb.ESB_EXACT_FIT, esb.ESB_EXACT_FIT, 2)
@@ -436,17 +443,16 @@
             # found here.
             os.environ["PATH"] = os.getcwd()+';'+os.environ["PATH"]
 
-    def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True):
+    def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True, logf=None):
+
         # Add beremiz's icon in top left corner of the frame
         self.icon = wx.Icon(Bpath("images", "brz.ico"), wx.BITMAP_TYPE_ICO)
         self.__init_execute_path()
 
         IDEFrame.__init__(self, parent, debug)
-        self.Log = LogPseudoFile(self.LogConsole, self.SelectTab)
-
-        self.local_runtime = None
-        self.runtime_port = None
-        self.local_runtime_tmpdir = None
+        self.Log = LogPseudoFile(self.LogConsole, self.SelectTab, logf)
+
+        LocalRuntimeMixin.__init__(self, self.Log)
 
         self.LastPanelSelected = None
 
@@ -511,37 +517,6 @@
         else:
             self.SetTitle(name)
 
-    def StartLocalRuntime(self, taskbaricon=True):
-        if (self.local_runtime is None) or (self.local_runtime.exitcode is not None):
-            # create temporary directory for runtime working directory
-            self.local_runtime_tmpdir = tempfile.mkdtemp()
-            # choose an arbitrary random port for runtime
-            self.runtime_port = int(random.random() * 1000) + 61131
-            self.Log.write(_("Starting local runtime...\n"))
-            # launch local runtime
-            self.local_runtime = ProcessLogger(
-                self.Log,
-                "\"%s\" \"%s\" -p %s -i localhost %s %s" % (
-                    sys.executable,
-                    Bpath("Beremiz_service.py"),
-                    self.runtime_port,
-                    {False: "-x 0", True: "-x 1"}[taskbaricon],
-                    self.local_runtime_tmpdir),
-                no_gui=False,
-                timeout=500, keyword=self.local_runtime_tmpdir,
-                cwd=self.local_runtime_tmpdir)
-            self.local_runtime.spin()
-        return self.runtime_port
-
-    def KillLocalRuntime(self):
-        if self.local_runtime is not None:
-            # shutdown local runtime
-            self.local_runtime.kill(gently=False)
-            # clear temp dir
-            shutil.rmtree(self.local_runtime_tmpdir)
-
-            self.local_runtime = None
-
     def OnOpenWidgetInspector(self, evt):
         # Activate the widget inspection tool
         from wx.lib.inspection import InspectionTool
@@ -722,7 +697,7 @@
 
         while self.RecentProjectsMenu.GetMenuItemCount() > 0:
             item = self.RecentProjectsMenu.FindItemByPosition(0)
-            self.RecentProjectsMenu.RemoveItem(item)
+            self.RecentProjectsMenu.Remove(item)
 
         self.FileMenu.Enable(ID_FILEMENURECENTPROJECTS, len(recent_projects) > 0)
         for idx, projectpath in enumerate(recent_projects):
@@ -769,9 +744,9 @@
 
             for confnode_method in self.CTR.StatusMethods:
                 if "method" in confnode_method and confnode_method.get("shown", True):
-                    tool = StatusToolBar.AddSimpleTool(
-                        wx.ID_ANY, GetBitmap(confnode_method.get("bitmap", "Unknown")),
-                        confnode_method["tooltip"])
+                    tool = StatusToolBar.AddTool(
+                        wx.ID_ANY, confnode_method["tooltip"],
+                        GetBitmap(confnode_method.get("bitmap", "Unknown")))
                     self.Bind(wx.EVT_MENU, self.GetMenuCallBackFunction(confnode_method["method"]), tool)
 
             StatusToolBar.Realize()
@@ -821,7 +796,7 @@
                     else:
                         self.EditMenu.Delete(item.GetId())
             self.LastPanelSelected = None
-        self.MenuBar.UpdateMenus()
+        self.MenuBar.Refresh()
 
     def RefreshAll(self):
         self.RefreshStatusToolBar()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Beremiz_cli.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import posixpath
+import sys
+import time
+from functools import wraps
+
+import click
+from importlib import import_module
+
+
+class CLISession(object):
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+        self.controller = None
+
+pass_session = click.make_pass_decorator(CLISession)
+
+
+@click.group(chain=True)
+@click.option(
+    "--project-home",
+    envvar="PROJECT_HOME",
+    default=".",
+    metavar="PATH",
+    help="Changes the project folder location.",
+)
+@click.option(
+    "--config",
+    nargs=2,
+    multiple=True,
+    metavar="KEY VALUE",
+    help="Overrides a config key/value pair.",
+)
+@click.option(
+    "--keep", "-k", is_flag=True,
+    help="Keep local runtime, do not kill it after executing commands.",
+)
+@click.option("--verbose", "-v", is_flag=True, help="Enables verbose mode.")
+@click.option(
+    "--buildpath", "-b", help="Where to store files created during build."
+)
+@click.option(
+    "--uri", "-u", help="URI to reach remote PLC."
+)
+@click.version_option("0.1")
+@click.pass_context
+def cli(ctx, **kwargs):
+    """Beremiz CLI manipulates beremiz projects and runtimes. """
+
+    ctx.obj = CLISession(**kwargs)
+
+def ensure_controller(func):
+    @wraps(func)
+    def func_wrapper(session, *args, **kwargs):
+        if session.controller is None:
+            session.controller = import_module("CLIController").CLIController(session)
+        ret = func(session, *args, **kwargs)
+        return ret
+
+    return func_wrapper
+
+@cli.command()
+@click.option(
+    "--target", "-t", help="Target system triplet."
+)
+@pass_session
+@ensure_controller
+def build(session, target):
+    """Builds project. """
+    def processor():
+        return session.controller.build_project(target)
+    return processor
+
+@cli.command()
+@pass_session
+@ensure_controller
+def transfer(session):
+    """Transfer program to PLC runtim."""
+    def processor():
+        return session.controller.transfer_project()
+    return processor
+
+@cli.command()
+@pass_session
+@ensure_controller
+def run(session):
+    """Run program already present in PLC. """
+    def processor():
+        return session.controller.run_project()
+    return processor
+
+
+@cli.resultcallback()
+@pass_session
+def process_pipeline(session, processors, **kwargs):
+    ret = 0
+    for processor in processors:
+        ret = processor()
+        if ret != 0:
+            if len(processors) > 1 :
+                click.echo("Command sequence aborted")
+            break
+
+    session.controller.finish()
+
+    if session.keep:
+        click.echo("Press Ctrl+C to quit")
+        while True:
+            time.sleep(1)
+
+    return ret
+
+if __name__ == '__main__':
+    cli()
+
--- a/Beremiz_service.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/Beremiz_service.py	Sun Jul 17 22:53:35 2022 +0200
@@ -233,6 +233,7 @@
 
     if havewx:
         import re
+        import wx.adv
 
         if wx.VERSION >= (3, 0, 0):
             app = wx.App(redirect=False)
@@ -274,7 +275,7 @@
             def SetTests(self, tests):
                 self.Tests = tests
 
-        class BeremizTaskBarIcon(wx.TaskBarIcon):
+        class BeremizTaskBarIcon(wx.adv.TaskBarIcon):
             TBMENU_START = wx.NewId()
             TBMENU_STOP = wx.NewId()
             TBMENU_CHANGE_NAME = wx.NewId()
@@ -286,7 +287,7 @@
             TBMENU_QUIT = wx.NewId()
 
             def __init__(self, pyroserver):
-                wx.TaskBarIcon.__init__(self)
+                wx.adv.TaskBarIcon.__init__(self)
                 self.pyroserver = pyroserver
                 # Set the image
                 self.UpdateIcon(None)
@@ -334,7 +335,7 @@
                 elif "wxGTK" in wx.PlatformInfo:
                     img = img.Scale(22, 22)
                 # wxMac can be any size upto 128x128, so leave the source img alone....
-                icon = wx.IconFromBitmap(img.ConvertToBitmap())
+                icon = wx.Icon(img.ConvertToBitmap())
                 return icon
 
             def OnTaskBarStartPLC(self, evt):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CLIController.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+from functools import wraps
+
+import click
+
+import fake_wx
+
+from ProjectController import ProjectController
+from LocalRuntimeMixin import LocalRuntimeMixin
+
+class Log:
+
+    def __init__(self):
+        self.crlfpending = False
+
+    def write(self, s):
+        if s:
+            if self.crlfpending:
+                sys.stdout.write("\n")
+            sys.stdout.write(s)
+            sys.stdout.flush()
+            self.crlfpending = 0
+
+    def write_error(self, s):
+        if s:
+            self.write("Error: "+s)
+
+    def write_warning(self, s):
+        if s:
+            self.write("Warning: "+s)
+
+    def flush(self):
+        sys.stdout.flush()
+        
+    def isatty(self):
+        return False
+
+    def progress(self, s):
+        if s:
+            sys.stdout.write(s+"\r")
+            self.crlfpending = True
+
+
+def with_project_loaded(func):
+    @wraps(func)
+    def func_wrapper(self, *args, **kwargs):
+        if not self.HasOpenedProject():
+            if self.check_and_load_project():
+                return 1 
+            self.apply_config()
+        return func(self, *args, **kwargs)
+
+    return func_wrapper
+
+def connected(func):
+    @wraps(func)
+    def func_wrapper(self, *args, **kwargs):
+        if self._connector is None:
+            if self.session.uri:
+                self.BeremizRoot.setURI_location(self.session.uri)
+            if not self._Connect():
+                return 1
+        return func(self, *args, **kwargs)
+
+    return func_wrapper
+
+class CLIController(LocalRuntimeMixin, ProjectController):
+    def __init__(self, session):
+        self.session = session
+        log = Log()
+        LocalRuntimeMixin.__init__(self, log, use_gui=False)
+        ProjectController.__init__(self, None, log)
+
+    def check_and_load_project(self):
+        if not os.path.isdir(self.session.project_home):
+            self.logger.write_error(
+                _("\"%s\" is not a valid Beremiz project\n") % self.session.project_home)
+            return True
+
+        errmsg, error = self.LoadProject(self.session.project_home, self.session.buildpath)
+        if error:
+            self.logger.write_error(errmsg)
+            return True
+
+    def apply_config(self):
+        for k,v in self.session.config:
+            self.SetParamsAttribute("BeremizRoot."+k, v)
+
+    @with_project_loaded
+    def build_project(self, target):
+
+        if target:
+            self.SetParamsAttribute("BeremizRoot.TargetType", target)
+            
+        return 0 if self._Build() else 1
+
+    @with_project_loaded
+    @connected
+    def transfer_project(self):
+
+        return 0 if self._Transfer() else 1
+
+    @with_project_loaded
+    @connected
+    def run_project(self):
+
+        return 0 if self._Run() else 1
+        
+
+    def finish(self):
+
+        self._Disconnect()
+
+        if not self.session.keep:
+            self.KillLocalRuntime()
+
+
--- a/IDEFrame.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/IDEFrame.py	Sun Jul 17 22:53:35 2022 +0200
@@ -115,7 +115,7 @@
 
 
 def AppendMenu(parent, help, kind, text, id=wx.ID_ANY):
-    return parent.Append(help=help, kind=kind, text=text, id=id)
+    return parent.Append(wx.MenuItem(helpString=help, kind=kind, text=text, id=id))
 
 
 [
@@ -391,7 +391,7 @@
         parent.AppendSeparator()
         add_menu = wx.Menu(title='')
         self._init_coll_AddMenu_Items(add_menu)
-        parent.AppendMenu(wx.ID_ADD, _(u"&Add Element"), add_menu)
+        parent.Append(wx.ID_ADD, _(u"&Add Element"), add_menu)
         AppendMenu(parent, help='', id=wx.ID_SELECTALL,
                    kind=wx.ITEM_NORMAL, text=_(u'Select All') + '\tCTRL+A')
         AppendMenu(parent, help='', id=wx.ID_DELETE,
@@ -442,7 +442,7 @@
                        kind=wx.ITEM_NORMAL, text=_(u'Clear Errors') + '\tCTRL+K')
         parent.AppendSeparator()
         zoommenu = wx.Menu(title='')
-        parent.AppendMenu(wx.ID_ZOOM_FIT, _("Zoom"), zoommenu)
+        parent.Append(wx.ID_ZOOM_FIT, _("Zoom"), zoommenu)
         for idx, value in enumerate(ZOOM_FACTORS):
             new_item = AppendMenu(zoommenu, help='',
                        kind=wx.ITEM_RADIO, text=str(int(round(value * 100))) + "%")
@@ -569,8 +569,8 @@
 
         self.ProjectPanel = wx.SplitterWindow(
             id=ID_PLCOPENEDITORPROJECTPANEL,
-            name='ProjectPanel', parent=self.LeftNoteBook, point=wx.Point(0, 0),
-            size=wx.Size(0, 0), style=wx.SP_3D)
+            name='ProjectPanel', parent=self.LeftNoteBook,
+            size=wx.Size(0, 0))
 
         self.ProjectTree = CustomTree(id=ID_PLCOPENEDITORPROJECTTREE,
                                       name='ProjectTree',
@@ -631,9 +631,9 @@
                                    wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
         EditorToolBar.SetToolBitmapSize(wx.Size(25, 25))
         EditorToolBar.AddRadioTool(ID_PLCOPENEDITOREDITORTOOLBARSELECTION,
+                                   _("Select an object"),
                                    GetBitmap("select"),
-                                   wx.NullBitmap,
-                                   _("Select an object"))
+                                   wx.NullBitmap)
         EditorToolBar.Realize()
         self.Panes["EditorToolBar"] = EditorToolBar
         self.AUIManager.AddPane(EditorToolBar, wx.aui.AuiPaneInfo().
@@ -921,12 +921,8 @@
 
         :param elements: List of elements to refresh.
         """
-        try:
-            for element in elements:
-                self.RefreshFunctions[element]()
-        except wx.PyDeadObjectError:
-            # ignore exceptions caused by refresh while quitting
-            pass
+        for element in elements:
+            self.RefreshFunctions[element]()
 
     def OnPageClose(self, event):
         """Callback function when AUINotebook Page closed with CloseButton
@@ -1418,7 +1414,8 @@
         self.AuiTabCtrl = auitabctrl
         if self.TabsOpened.GetPageCount() == 0:
             pane = self.AUIManager.GetPane(self.TabsOpened)
-            if pane.IsMaximized():
+            # on wxPython 4.1.0, AuiPaneInfo has no "IsMaximized" attribute...
+            if (not hasattr(pane, "IsMaximized")) or pane.IsMaximized():
                 self.AUIManager.RestorePane(pane)
             self.AUIManager.Update()
 
@@ -1499,17 +1496,23 @@
     def GetTabsOpenedDClickFunction(self, tabctrl):
         def OnTabsOpenedDClick(event):
             pos = event.GetPosition()
-            if tabctrl.TabHitTest(pos.x, pos.y, None):
+            if tabctrl.TabHitTest(pos.x, pos.y):
                 self.SwitchPerspective(event)
             event.Skip()
         return OnTabsOpenedDClick
 
     def SwitchPerspective(self, evt):
         pane = self.AUIManager.GetPane(self.TabsOpened)
-        if pane.IsMaximized():
+        # on wxPython 4.1.0, AuiPaneInfo has no "IsMaximized" attribute...
+        IsMaximized = pane.IsMaximized() if hasattr(pane, "IsMaximized") \
+            else (self.TabBookIsMaximized if hasattr(self, "TabBookIsMaximized") \
+                else False)
+        if IsMaximized:
             self.AUIManager.RestorePane(pane)
+            self.TabBookIsMaximized = False
         else:
             self.AUIManager.MaximizePane(pane)
+            self.TabBookIsMaximized = True
         self.AUIManager.Update()
 
     def SwitchFullScrMode(self, evt):
@@ -1808,7 +1811,7 @@
                     else:
                         block_type = "Action"
                     self.LastToolTipItem = item
-                    wx.CallAfter(self.ProjectTree.SetToolTipString,
+                    wx.CallAfter(self.ProjectTree.SetToolTip,
                                  "%s : %s : %s" % (
                                      block_type, bodytype, item_infos["name"]))
             elif self.LastToolTipItem is not None:
@@ -2118,7 +2121,7 @@
                 MenuToolBar.AddSeparator()
             else:
                 id, bitmap, help, callback = toolbar_item
-                MenuToolBar.AddSimpleTool(id=id, shortHelpString=help, bitmap=GetBitmap(bitmap))
+                MenuToolBar.AddTool(id, help, GetBitmap(bitmap))
                 if callback is not None:
                     self.Bind(wx.EVT_TOOL, callback, id=id)
         MenuToolBar.Realize()
@@ -2158,9 +2161,9 @@
                 for radio, modes, id, method, picture, help in self.EditorToolBarItems[menu]:
                     if modes & self.DrawingMode:
                         if radio or self.DrawingMode == FREEDRAWING_MODE:
-                            EditorToolBar.AddRadioTool(id, GetBitmap(picture), wx.NullBitmap, help)
+                            EditorToolBar.AddRadioTool(id, help, GetBitmap(picture), wx.NullBitmap)
                         else:
-                            EditorToolBar.AddSimpleTool(id, GetBitmap(picture), help)
+                            EditorToolBar.AddTool(id, help, GetBitmap(picture))
                         self.Bind(wx.EVT_MENU, getattr(self, method), id=id)
                         self.CurrentEditorToolBar.append(id)
                 EditorToolBar.Realize()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LocalRuntimeMixin.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import sys
+import tempfile
+import random
+import shutil
+from util.ProcessLogger import ProcessLogger
+from util.paths import Bpath
+
+class LocalRuntimeMixin():
+
+    def __init__(self, log, use_gui=True):
+        self.local_runtime_log = log
+        self.local_runtime = None
+        self.runtime_port = None
+        self.local_runtime_tmpdir = None
+        self.use_gui = use_gui
+
+    def StartLocalRuntime(self):
+        if (self.local_runtime is None) or (self.local_runtime.exitcode is not None):
+            # create temporary directory for runtime working directory
+            self.local_runtime_tmpdir = tempfile.mkdtemp()
+            # choose an arbitrary random port for runtime
+            self.runtime_port = int(random.random() * 1000) + 61131
+            self.local_runtime_log.write(_("Starting local runtime...\n"))
+            # launch local runtime
+            self.local_runtime = ProcessLogger(
+                self.local_runtime_log,
+                "\"%s\" \"%s\" -p %s -i localhost %s %s" % (
+                    sys.executable,
+                    Bpath("Beremiz_service.py"),
+                    self.runtime_port,
+                    {False: "-x 0", True: "-x 1"}[self.use_gui],
+                    self.local_runtime_tmpdir),
+                no_gui=False,
+                timeout=500, keyword=self.local_runtime_tmpdir,
+                cwd=self.local_runtime_tmpdir)
+            self.local_runtime.spin()
+        return self.runtime_port
+
+    def KillLocalRuntime(self):
+        if self.local_runtime is not None:
+            # shutdown local runtime
+            self.local_runtime.kill(gently=False)
+            # clear temp dir
+            shutil.rmtree(self.local_runtime_tmpdir)
+
+            self.local_runtime = None
+
--- a/ProjectController.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/ProjectController.py	Sun Jul 17 22:53:35 2022 +0200
@@ -1526,7 +1526,8 @@
         # clear previous_plcstate to restore status
         # in UpdateMethodsFromPLCStatus()
         self.previous_plcstate = ""
-        self.AppFrame.ProgressStatusBar.Hide()
+        if self.AppFrame is not None:
+            self.AppFrame.ProgressStatusBar.Hide()
         self.UpdateMethodsFromPLCStatus()
 
     def PullPLCStatusProc(self, event):
@@ -1785,13 +1786,16 @@
         """
         Start PLC
         """
+        success = False
         if self.GetIECProgramsAndVariables():
             self._connector.StartPLC()
             self.logger.write(_("Starting PLC\n"))
             self._connect_debug()
+            success = True
         else:
             self.logger.write_error(_("Couldn't start PLC !\n"))
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+        return success
 
     def _Stop(self):
         """
@@ -1805,6 +1809,10 @@
 
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
+    def StartLocalRuntime(self):
+        if self.AppFrame:
+            return self.AppFrame.StartLocalRuntime()
+
     def _SetConnector(self, connector, update_status=True):
         self._connector = connector
         if self.AppFrame is not None:
@@ -1821,6 +1829,7 @@
                 wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
     def _Connect(self):
+        success = False
         # don't accept re-connetion if already connected
         if self._connector is not None:
             self.logger.write_error(
@@ -1882,6 +1891,8 @@
                 else:
                     self.logger.write_warning(
                         _("Debug does not match PLC - stop/transfert/start to re-enable\n"))
+            success = True
+        return success
 
     def CompareLocalAndRemotePLC(self):
         if self._connector is None:
@@ -1905,6 +1916,7 @@
         self._SetConnector(None)
 
     def _Transfer(self):
+        success = False
         if self.IsPLCStarted():
             dialog = wx.MessageDialog(
                 self.AppFrame,
@@ -1967,16 +1979,19 @@
                 if self.GetIECProgramsAndVariables():
                     self.UnsubscribeAllDebugIECVariable()
                     self.ProgramTransferred()
-                    self.AppFrame.CloseObsoleteDebugTabs()
-                    self.AppFrame.RefreshPouInstanceVariablesPanel()
-                    self.AppFrame.LogViewer.ResetLogCounters()
+                    if self.AppFrame is not None:
+                        self.AppFrame.CloseObsoleteDebugTabs()
+                        self.AppFrame.RefreshPouInstanceVariablesPanel()
+                        self.AppFrame.LogViewer.ResetLogCounters()
                     self.logger.write(_("PLC installed successfully.\n"))
+                    success = True
                 else:
                     self.logger.write_error(_("Missing debug data\n"))
             else:
                 self.logger.write_error(_("PLC couldn't be installed\n"))
 
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+        return success
 
     def _Repair(self):
         dialog = wx.MessageDialog(
--- a/README.md	Thu Jul 14 11:40:27 2022 +0200
+++ b/README.md	Sun Jul 17 22:53:35 2022 +0200
@@ -10,8 +10,8 @@
 
 Beremiz consists of two components:
 
-* Integrated Development Environment (IDE), [Beremiz.py](https://bitbucket.org/automforge/beremiz/src/tip/Beremiz.py?at=default). It's running on user's computer and is used to write/compile/debug PLC programs and control PLC runtime.
-* Reference runtime implementation in python, [Beremiz_service.py](https://bitbucket.org/automforge/beremiz/src/tip/Beremiz_service.py?at=default). It's running on target platform, communicates with I/O and executes PLC program.
+* Integrated Development Environment (IDE), Beremiz.py. It is running on user's computer and is used to write/compile/debug PLC programs and control PLC runtime.
+* Reference runtime implementation in python, Beremiz_service.py. It's running on target platform, communicates with I/O and executes PLC program.
 
 See official [Beremiz website](http://www.beremiz.org/) for more information.
 
@@ -77,6 +77,52 @@
 		cd ~/Beremiz/beremiz
 		python Beremiz.py
 
+## Build documentation
+
+Source code for Beremiz user manual is stored in
+[doc](tree/default/doc)
+directory in project's source tree.
+It's written in reStructuredText (ReST) and uses Sphinx to build documentation in different formats.
+
+
+To build documentation you need following packages on Ubuntu/Debian:
+
+```
+sudo apt-get install build-essential python-sphynx
+```
+
+### Documentation in HTML
+
+Build documentation
+
+```
+cd ~/Beremiz/doc
+make all
+
+```
+
+Result documentation is stored in directories 'doc/\_build/dirhtml\*'.
+
+### Documentation in PDF
+
+To build pdf documentation you have to install additional packages on Ubuntu/Debian:
+
+```
+sudo apt-get install textlive-latex-base texlive-latex-recommended \
+     texlive-fonts-recommended texlive-latex-extra
+
+```
+
+Build documentation
+
+```
+cd ~/Beremiz/doc
+make latexpdf
+
+```
+
+Result documentation is stored in 'doc/\_build/latex/Beremiz.pdf'.
+
 ## Run standalone Beremiz runtime ##
 
 Runtime implementation can be different on different platforms.
@@ -89,14 +135,11 @@
 
 * Start standalone Beremiz service
 
-		cd ~/Beremiz
-		mkdir beremiz_workdir
-		cd ~/beremiz
-		python Beremiz_service.py -p 61194 -i localhost -x 0 -a 1 ~/Beremiz/beremiz_workdir
+		mkdir ~/beremiz_workdir
+		python Beremiz_service.py -p 61194 -i localhost -x 0 -a 1 ~/beremiz_workdir
 
 * Launch Beremiz IDE
 
-		cd ~/Beremiz/beremiz
 		python Beremiz.py
 
 * Open/Create PLC project in Beremiz IDE.  
@@ -105,7 +148,8 @@
 
 ## Examples ##
 
-Almost for all functionality exists example in ['tests'](https://bitbucket.org/automforge/beremiz/src/tip/tests/?at=default) directory.
+Almost for all functionality exists example in ['tests'](tree/default/tests/projects) and ['exemples'](tree/default/tests/projects) directories.
+
 Most of examples are shown on [Beremiz youtube channel](https://www.youtube.com/channel/UCcE4KYI0p1f6CmSwtzyg-ZA).
 
 ## Documentation ##
--- a/bacnet/BacnetSlaveEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/bacnet/BacnetSlaveEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -367,8 +367,7 @@
         "Engineering Units": {"GridCellEditor": wx.grid.GridCellChoiceEditor,
                               # use string renderer with choice editor!
                               "GridCellRenderer": wx.grid.GridCellStringRenderer,
-                              # syntax for GridCellChoiceEditor -> comma separated values
-                              "GridCellEditorParam": ','.join([x[0] for x in BACnetEngineeringUnits])}
+                              "GridCellEditorConstructorArgs": [x[0] for x in BACnetEngineeringUnits]}
     }
 
     # obj_properties should be a dictionary, with keys "Object Identifier",
@@ -576,7 +575,10 @@
                 PropertyName = self.BACnetObjectType.PropertyNames[col]
                 PropertyConfig = self.BACnetObjectType.PropertyConfig[PropertyName]
                 grid.SetReadOnly(row, col, False)
-                grid.SetCellEditor(row, col, PropertyConfig["GridCellEditor"]())
+                GridCellEditorConstructorArgs = \
+                    PropertyConfig["GridCellEditorConstructorArgs"]
+                    if "GridCellEditorConstructorArgs" in PropertyConfig else []
+                grid.SetCellEditor(row, col, PropertyConfig["GridCellEditor"](*GridCellEditorConstructorArgs))
                 grid.SetCellRenderer(row, col, PropertyConfig["GridCellRenderer"]())
                 grid.SetCellBackgroundColour(row, col, wx.WHITE)
                 grid.SetCellTextColour(row, col, wx.BLACK)
@@ -816,7 +818,7 @@
                 self, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28),
                 style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
             controls_sizer.Add(button)
 
@@ -826,7 +828,7 @@
         # use only to enable drag'n'drop
         # self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
         self.VariablesGrid.Bind(
-            wx.grid.EVT_GRID_CELL_CHANGE,     self.OnVariablesGridCellChange)
+            wx.grid.EVT_GRID_CELL_CHANGING,     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)
         self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW)
--- a/canfestival/NetworkEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/canfestival/NetworkEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -68,7 +68,7 @@
         main_sizer.AddGrowableCol(0)
         main_sizer.AddGrowableRow(0)
 
-        main_sizer.AddWindow(self.NetworkNodes, 0, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(self.NetworkNodes, 0, border=5, flag=wx.GROW | wx.ALL)
 
         self.NetworkEditor.SetSizer(main_sizer)
 
--- a/connectors/SchemeEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/connectors/SchemeEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -28,19 +28,19 @@
                     (wx.StaticText(self, label=label),
                      wx.ALIGN_CENTER_VERTICAL),
                     (txtctrl, wx.GROW)]:
-                self.fieldsizer.AddWindow(win, flag=flag)
+                self.fieldsizer.Add(win, flag=flag)
 
         self.fieldsizer.AddSpacer(20)
 
         if self.EnableIDSelector:
             self.mainsizer = wx.FlexGridSizer(cols=2, hgap=10, vgap=10)
-            self.mainsizer.AddSizer(self.fieldsizer)
+            self.mainsizer.Add(self.fieldsizer)
             self.idselector = IDBrowser(
                 self, parent.ctr,
                 # use a callafter, as editor can be deleted by calling SetURI
                 partial(wx.CallAfter, parent.SetURI),
                 self.txtctrls["ID"].SetValue)
-            self.mainsizer.AddWindow(self.idselector)
+            self.mainsizer.Add(self.idselector)
             self.SetSizer(self.mainsizer)
         else:
             self.SetSizer(self.fieldsizer)
--- a/connectors/__init__.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/connectors/__init__.py	Sun Jul 17 22:53:35 2022 +0200
@@ -76,8 +76,7 @@
         # pyro connection to local runtime
         # started on demand, listening on random port
         scheme = "PYRO"
-        runtime_port = confnodesroot.AppFrame.StartLocalRuntime(
-            taskbaricon=True)
+        runtime_port = confnodesroot.StartLocalRuntime()
         uri = "PYROLOC://127.0.0.1:" + str(runtime_port)
 
     # commented code to enable for MDNS:// support
--- a/controls/CustomEditableListBox.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/CustomEditableListBox.py	Sun Jul 17 22:53:35 2022 +0200
@@ -25,13 +25,13 @@
 
 from __future__ import absolute_import
 import wx
-import wx.gizmos
+import wx.adv
 
 
-class CustomEditableListBox(wx.gizmos.EditableListBox):
+class CustomEditableListBox(wx.adv.EditableListBox):
 
     def __init__(self, *args, **kwargs):
-        wx.gizmos.EditableListBox.__init__(self, *args, **kwargs)
+        wx.adv.EditableListBox.__init__(self, *args, **kwargs)
 
         listbox = self.GetListCtrl()
         listbox.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
@@ -44,7 +44,7 @@
                 (self.GetDelButton(), _("Delete item"), "_OnDelButton"),
                 (self.GetUpButton(), _("Move up"), "_OnUpButton"),
                 (self.GetDownButton(), _("Move down"), "_OnDownButton")]:
-            button.SetToolTipString(tooltip)
+            button.SetToolTip(tooltip)
             button.Bind(wx.EVT_BUTTON, self.GetButtonPressedFunction(call_function))
 
         self.Editing = False
--- a/controls/CustomStyledTextCtrl.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/CustomStyledTextCtrl.py	Sun Jul 17 22:53:35 2022 +0200
@@ -105,9 +105,9 @@
                     [self.GetMarginWidth(i) for i in xrange(3)],
                     0)
                 if x <= margin_width:
-                    self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
+                    self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
                 else:
-                    self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM))
+                    self.SetCursor(wx.Cursor(wx.CURSOR_IBEAM))
             else:
                 event.Skip()
         else:
--- a/controls/CustomTable.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/CustomTable.py	Sun Jul 17 22:53:35 2022 +0200
@@ -40,7 +40,7 @@
     """
     def __init__(self, parent, data, colnames):
         # The base class must be initialized *first*
-        wx.grid.PyGridTableBase.__init__(self)
+        wx.grid.GridTableBase.__init__(self)
         self.data = data
         self.colnames = colnames
         self.Highlights = {}
@@ -64,7 +64,7 @@
             return self.colnames[col]
 
     def GetRowLabelValue(self, row, translate=True):
-        return row
+        return str(row)
 
     def GetValue(self, row, col):
         if row < self.GetNumberRows():
--- a/controls/CustomToolTip.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/CustomToolTip.py	Sun Jul 17 22:53:35 2022 +0200
@@ -137,7 +137,7 @@
         max_width = max_height = 0
 
         # Create a memory DC for calculating text extent
-        dc = wx.MemoryDC(wx.EmptyBitmap(1, 1))
+        dc = wx.MemoryDC(wx.Bitmap(1, 1))
         dc.SetFont(self.Font)
 
         # Compute max tip text size
@@ -175,7 +175,6 @@
         dc.SetFont(self.Font)
 
         # Draw Tool tip
-        dc.BeginDrawing()
         tip_width, tip_height = self.GetToolTipSize()
 
         # Draw background rectangle
@@ -188,6 +187,5 @@
             _line_width, line_height = dc.GetTextExtent(line)
             line_offset += line_height
 
-        dc.EndDrawing()
 
         event.Skip()
--- a/controls/CustomTree.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/CustomTree.py	Sun Jul 17 22:53:35 2022 +0200
@@ -120,9 +120,9 @@
             _item, flags = self.HitTest(pos)
 
             bitmap_rect = self.GetBitmapRect()
-            if ((bitmap_rect.InsideXY(pos.x, pos.y) or
+            if ((bitmap_rect.Contains(pos.x, pos.y) or
                  flags & wx.TREE_HITTEST_NOWHERE) and self.AddMenu is not None):
-                wx.CallAfter(self.PopupMenuXY, self.AddMenu, pos.x, pos.y)
+                wx.CallAfter(self.PopupMenu, self.AddMenu, pos.x, pos.y)
         event.Skip()
 
     def OnEraseBackground(self, event):
--- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py	Sun Jul 17 22:53:35 2022 +0200
@@ -174,7 +174,7 @@
             # 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):
+            if not self.ParentControl.Is3DCanvas() and rect.Contains(x, y):
                 # Default merge type is parallel
                 merge_type = GRAPH_PARALLEL
 
@@ -182,7 +182,7 @@
                 # wall be merged orthogonally
                 merge_rect = wx.Rect(rect.x, rect.y,
                                      rect.width / 2., rect.height)
-                if merge_rect.InsideXY(x, y):
+                if merge_rect.Contains(x, y):
                     merge_type = GRAPH_ORTHOGONAL
 
                 # Merge graphs
@@ -625,7 +625,7 @@
                 (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):
+                if rect.Contains(x, y):
                     item_idx = i
                     break
 
@@ -736,7 +736,7 @@
                 (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):
+                if rect.Contains(event.x, height - event.y):
                     item_idx = i
                     menu_direction = dir
                     break
@@ -756,7 +756,7 @@
             # Update resize highlight
             if event.y <= 5:
                 if self.SetHighlight(HIGHLIGHT_RESIZE):
-                    self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
+                    self.SetCursor(wx.Cursor(wx.CURSOR_SIZENS))
                     self.ParentWindow.ForceRefresh()
             else:
                 if self.SetHighlight(HIGHLIGHT_NONE):
@@ -832,7 +832,7 @@
         # Check that double click was done inside figure
         pos = event.GetPosition()
         rect = self.GetAxesBoundingBox()
-        if rect.InsideXY(pos.x, pos.y):
+        if rect.Contains(pos.x, pos.y):
             # Reset Cursor tick to value before starting clicking
             self.ParentWindow.SetCursorTick(self.StartCursorTick)
             # Toggle to text Viewer(s)
@@ -926,10 +926,10 @@
 
         # Mouse is over Viewer figure and graph is not 3D
         bbox = self.GetAxesBoundingBox()
-        if bbox.InsideXY(x, y) and not self.Is3DCanvas():
+        if bbox.Contains(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):
+            if rect.Contains(x, y):
                 self.SetHighlight(HIGHLIGHT_LEFT)
 
             # Mouse is over Viewer right part of figure
@@ -1381,8 +1381,6 @@
         # rendering
         destGC = wx.GCDC(destDC)
 
-        destGC.BeginDrawing()
-
         # Get canvas size and figure bounding box in canvas
         width, height = self.GetSize()
         bbox = self.GetAxesBoundingBox()
@@ -1409,7 +1407,5 @@
         # Draw other Viewer common elements
         self.DrawCommonElements(destGC, self.GetButtons())
 
-        destGC.EndDrawing()
-
         self._isDrawn = True
         self.gui_repaint(drawDC=drawDC)
--- a/controls/DebugVariablePanel/DebugVariablePanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/DebugVariablePanel/DebugVariablePanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -221,14 +221,14 @@
         self.GraphicPanels = []
 
         graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(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)
+        graphics_button_sizer.Add(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,
+        graphics_button_sizer.Add(self.CanvasRange, 1,
                                         border=5,
                                         flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
 
@@ -246,10 +246,10 @@
             button = wx.lib.buttons.GenBitmapButton(
                 self, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(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)
+            graphics_button_sizer.Add(button, border=5, flag=wx.LEFT)
 
         self.CanvasPosition = wx.ScrollBar(
             self, size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
@@ -263,19 +263,19 @@
                                  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)
+        main_sizer.Add(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)
+        main_sizer.Add(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.TickSizer.Add(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.TickSizer.Add(self.MaskLabel, 1, border=5, flag=wx.RIGHT | wx.GROW)
 
         self.TickTimeLabel = wx.StaticText(self)
-        self.TickSizer.AddWindow(self.TickTimeLabel)
+        self.TickSizer.Add(self.TickTimeLabel)
 
         self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL | wx.VSCROLL)
         self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
@@ -284,7 +284,7 @@
         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)
+        main_sizer.Add(self.GraphicsWindow, 1, flag=wx.GROW)
 
         self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
         self.GraphicsWindow.SetSizer(self.GraphicsSizer)
@@ -441,7 +441,7 @@
             x, y = panel.GetPosition()
             width, height = panel.GetSize()
             rect = wx.Rect(x, y, width, height)
-            if rect.InsideXY(x_mouse, y_mouse) or \
+            if rect.Contains(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)
@@ -488,7 +488,7 @@
             xw, yw = panel.GetPosition()
             width, height = panel.GetSize()
             bbox = wx.Rect(xw, yw, width, height)
-            if bbox.InsideXY(x_mouse, y_mouse):
+            if bbox.Contains(x_mouse, y_mouse):
                 panel.ShowButtons(True)
                 merge_type = GRAPH_PARALLEL
                 if isinstance(panel, DebugVariableTextViewer) or panel.Is3DCanvas():
@@ -497,9 +497,9 @@
                     wx.CallAfter(self.MoveValue, variable, idx, True)
                 else:
                     rect = panel.GetAxesBoundingBox(True)
-                    if rect.InsideXY(x_mouse, y_mouse):
+                    if rect.Contains(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):
+                        if merge_rect.Contains(x_mouse, y_mouse):
                             merge_type = GRAPH_ORTHOGONAL
                         wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
                     else:
@@ -510,7 +510,7 @@
                 return
         width, height = self.GraphicsWindow.GetVirtualSize()
         rect = wx.Rect(0, 0, width, height)
-        if rect.InsideXY(x_mouse, y_mouse):
+        if rect.Contains(x_mouse, y_mouse):
             wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels), True)
         self.ForceRefresh()
 
@@ -518,7 +518,7 @@
         self.GraphicsSizer.Clear()
 
         for panel in self.GraphicPanels:
-            self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
+            self.GraphicsSizer.Add(panel, flag=wx.GROW)
 
         self.GraphicsSizer.Layout()
         self.RefreshGraphicsWindowScrollbars()
--- a/controls/DebugVariablePanel/DebugVariableTextViewer.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/DebugVariablePanel/DebugVariableTextViewer.py	Sun Jul 17 22:53:35 2022 +0200
@@ -195,7 +195,7 @@
 
         # Create buffered DC for drawing in panel
         width, height = self.GetSize()
-        bitmap = wx.EmptyBitmap(width, height)
+        bitmap = wx.Bitmap(width, height)
         dc = wx.BufferedDC(wx.PaintDC(self), bitmap)
         dc.Clear()
 
@@ -203,8 +203,6 @@
         # rendering
         gc = wx.GCDC(dc)
 
-        gc.BeginDrawing()
-
         # Get first item
         item = self.ItemsDict.values()[0]
 
@@ -232,8 +230,6 @@
         # Draw other Viewer common elements
         self.DrawCommonElements(gc)
 
-        gc.EndDrawing()
-
     def OnLeftDown(self, event):
         """
         Function called when mouse left button is pressed
@@ -252,7 +248,7 @@
         # 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):
+        if item_path_bbox.Contains(x, y):
             self.ShowButtons(False)
             data = wx.TextDataObject(str((item.GetVariable(), "debug", "move")))
             dragSource = wx.DropSource(self)
--- a/controls/DebugVariablePanel/GraphButton.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/DebugVariablePanel/GraphButton.py	Sun Jul 17 22:53:35 2022 +0200
@@ -146,7 +146,7 @@
         # 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)
+        return rect.Contains(x, y)
 
     def ProcessCallback(self):
         """
--- a/controls/DiscoveryPanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/DiscoveryPanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -44,17 +44,17 @@
 class DiscoveryPanel(wx.Panel, listmix.ColumnSorterMixin):
 
     def _init_coll_MainSizer_Items(self, parent):
-        parent.AddWindow(self.staticText1,    0, border=20, flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
-        parent.AddWindow(self.ServicesList,   0, border=20, flag=wx.LEFT | wx.RIGHT | wx.GROW)
-        parent.AddSizer(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
+        parent.Add(self.staticText1,    0, border=20, flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
+        parent.Add(self.ServicesList,   0, border=20, flag=wx.LEFT | wx.RIGHT | wx.GROW)
+        parent.Add(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
     def _init_coll_MainSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
         parent.AddGrowableRow(1)
 
     def _init_coll_ButtonGridSizer_Items(self, parent):
-        parent.AddWindow(self.RefreshButton, 0, border=0, flag=0)
-        # parent.AddWindow(self.ByIPCheck, 0, border=0, flag=0)
+        parent.Add(self.RefreshButton, 0, border=0, flag=0)
+        # parent.Add(self.ByIPCheck, 0, border=0, flag=0)
 
     def _init_coll_ButtonGridSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
--- a/controls/DurationCellEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/DurationCellEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -46,12 +46,12 @@
         self.Duration = wx.TextCtrl(self, size=wx.Size(0, -1),
                                     style=wx.TE_PROCESS_ENTER)
         self.Duration.Bind(wx.EVT_KEY_DOWN, self.OnDurationChar)
-        main_sizer.AddWindow(self.Duration, flag=wx.GROW)
+        main_sizer.Add(self.Duration, flag=wx.GROW)
 
         # create browse button
         self.EditButton = wx.Button(self, label='...', size=wx.Size(30, -1))
         self.Bind(wx.EVT_BUTTON, self.OnEditButtonClick, self.EditButton)
-        main_sizer.AddWindow(self.EditButton, flag=wx.GROW)
+        main_sizer.Add(self.EditButton, flag=wx.GROW)
 
         self.Bind(wx.EVT_SIZE, self.OnSize)
 
@@ -98,12 +98,12 @@
         self.Duration.SetFocus()
 
 
-class DurationCellEditor(wx.grid.PyGridCellEditor):
+class DurationCellEditor(wx.grid.GridCellEditor):
     '''
     Grid cell editor that uses DurationCellControl to display an edit button.
     '''
     def __init__(self, table, colname):
-        wx.grid.PyGridCellEditor.__init__(self)
+        wx.grid.GridCellEditor.__init__(self)
 
         self.Table = table
         self.Colname = colname
--- a/controls/EnhancedStatusBar.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/EnhancedStatusBar.py	Sun Jul 17 22:53:35 2022 +0200
@@ -85,12 +85,12 @@
 
 class EnhancedStatusBar(wx.StatusBar):
 
-    def __init__(self, parent, id=wx.ID_ANY, style=wx.ST_SIZEGRIP,
+    def __init__(self, parent, id=wx.ID_ANY, style=wx.STB_SIZEGRIP,
                  name="EnhancedStatusBar"):
         """Default Class Constructor.
 
         EnhancedStatusBar.__init__(self, parent, id=wx.ID_ANY,
-                                   style=wx.ST_SIZEGRIP,
+                                   style=wx.STB_SIZEGRIP,
                                    name="EnhancedStatusBar")
         """
 
@@ -100,7 +100,7 @@
         self._curPos = 0
         self._parent = parent
 
-        wx.EVT_SIZE(self, self.OnSize)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
         wx.CallAfter(self.OnSize, None)
 
     def OnSize(self, event):
--- a/controls/FolderTree.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/FolderTree.py	Sun Jul 17 22:53:35 2022 +0200
@@ -74,12 +74,12 @@
         self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnTreeItemCollapsed, self.Tree)
         self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnTreeBeginLabelEdit, self.Tree)
         self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnTreeEndLabelEdit, self.Tree)
-        main_sizer.AddWindow(self.Tree, 1, flag=wx.GROW)
+        main_sizer.Add(self.Tree, 1, flag=wx.GROW)
 
         if filter is not None:
             self.Filter = wx.ComboBox(self, style=wx.CB_READONLY)
             self.Bind(wx.EVT_COMBOBOX, self.OnFilterChanged, self.Filter)
-            main_sizer.AddWindow(self.Filter, flag=wx.GROW)
+            main_sizer.Add(self.Filter, flag=wx.GROW)
         else:
             self.Filter = None
 
--- a/controls/LibraryPanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/LibraryPanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -77,12 +77,12 @@
             search_textctrl = self.SearchCtrl.GetChildren()[0]
             search_textctrl.Bind(wx.EVT_CHAR, self.OnKeyDown)
 
-        main_sizer.AddWindow(self.SearchCtrl, flag=wx.GROW)
+        main_sizer.Add(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)
+        main_sizer.Add(splitter_window, flag=wx.GROW)
 
         # Add TreeCtrl for functions and function blocks library in splitter
         # window
@@ -216,7 +216,7 @@
 
                 # Set data associated to tree item (only save that item is a
                 # category)
-                self.Tree.SetPyData(category_item, {"type": CATEGORY})
+                self.Tree.SetItemData(category_item, {"type": CATEGORY})
 
                 # Iterate over functions and function blocks defined in library
                 # category add a tree item to category tree item for each of
@@ -253,7 +253,7 @@
                                        if blocktype["extensible"] else None),
                         "comment":    _(comment) + blocktype.get("usage", "")
                     }
-                    self.Tree.SetPyData(blocktype_item, block_data)
+                    self.Tree.SetItemData(blocktype_item, block_data)
 
                     # Select block tree item in tree if it corresponds to
                     # previously selected one
--- a/controls/LocationCellEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/LocationCellEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -46,12 +46,12 @@
         self.Location = wx.TextCtrl(self, size=wx.Size(0, -1),
                                     style=wx.TE_PROCESS_ENTER)
         self.Location.Bind(wx.EVT_KEY_DOWN, self.OnLocationChar)
-        main_sizer.AddWindow(self.Location, flag=wx.GROW)
+        main_sizer.Add(self.Location, flag=wx.GROW)
 
         # create browse button
         self.BrowseButton = wx.Button(self, label='...', size=wx.Size(30, -1))
         self.BrowseButton.Bind(wx.EVT_BUTTON, self.OnBrowseButtonClick)
-        main_sizer.AddWindow(self.BrowseButton, flag=wx.GROW)
+        main_sizer.Add(self.BrowseButton, flag=wx.GROW)
 
         self.Bind(wx.EVT_SIZE, self.OnSize)
 
@@ -150,12 +150,12 @@
         self.Location.SetFocus()
 
 
-class LocationCellEditor(wx.grid.PyGridCellEditor):
+class LocationCellEditor(wx.grid.GridCellEditor):
     '''
     Grid cell editor that uses LocationCellControl to display a browse button.
     '''
     def __init__(self, table, controller):
-        wx.grid.PyGridCellEditor.__init__(self)
+        wx.grid.GridCellEditor.__init__(self)
 
         self.Table = table
         self.Controller = controller
@@ -178,7 +178,7 @@
             self.CellControl.SetVarType(self.Table.GetValueByName(row, 'Type'))
         self.CellControl.SetFocus()
 
-    def EndEditInternal(self, row, col, grid, old_loc):
+    def EndEdit(self, row, col, grid, old_loc):
         loc = self.CellControl.GetValue()
         changed = loc != old_loc
         if changed:
@@ -201,13 +201,8 @@
         self.CellControl.Disable()
         return changed
 
-    if wx.VERSION >= (3, 0, 0):
-        def EndEdit(self, row, col, grid, oldval):
-            return self.EndEditInternal(row, col, grid, oldval)
-    else:
-        def EndEdit(self, row, col, grid):
-            old_loc = self.Table.GetValueByName(row, 'Location')
-            return self.EndEditInternal(row, col, grid, old_loc)
+    def ApplyEdit(self, row, col, grid):
+        pass
 
     def SetSize(self, rect):
         self.CellControl.SetDimensions(rect.x + 1, rect.y,
--- a/controls/LogViewer.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/LogViewer.py	Sun Jul 17 22:53:35 2022 +0200
@@ -99,8 +99,8 @@
         width, height = self.GetClientSize()
         range_rect = self.GetRangeRect()
         thumb_rect = self.GetThumbRect()
-        if range_rect.InsideXY(posx, posy):
-            if thumb_rect.InsideXY(posx, posy):
+        if range_rect.Contains(posx, posy):
+            if thumb_rect.Contains(posx, posy):
                 self.ThumbScrollingStartPos = wx.Point(posx, posy)
             elif posy < thumb_rect.y:
                 self.Parent.ScrollToLast()
@@ -139,7 +139,6 @@
     def OnPaint(self, event):
         dc = wx.BufferedPaintDC(self)
         dc.Clear()
-        dc.BeginDrawing()
 
         gc = wx.GCDC(dc)
 
@@ -179,7 +178,6 @@
         gc.DrawRectangle(thumb_rect.x, thumb_rect.y,
                          thumb_rect.width, thumb_rect.height)
 
-        dc.EndDrawing()
         event.Skip()
 
 
@@ -207,7 +205,7 @@
     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):
+        if rect.Contains(x, y):
             return True
         return False
 
@@ -303,7 +301,7 @@
         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)
+        main_sizer.Add(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"))
@@ -312,7 +310,7 @@
         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)
+        filter_sizer.Add(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)
@@ -322,18 +320,18 @@
                   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)
+        filter_sizer.Add(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.CleanButton.SetToolTip(_("Clean log messages"))
         self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
-        filter_sizer.AddWindow(self.CleanButton)
+        filter_sizer.Add(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)
+        main_sizer.Add(message_panel_sizer, border=5, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
         self.MessagePanel = wx.Panel(self)
         if wx.Platform == '__WXMSW__':
@@ -349,10 +347,10 @@
         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)
+        message_panel_sizer.Add(self.MessagePanel, flag=wx.GROW)
 
         self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
-        message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
+        message_panel_sizer.Add(self.MessageScrollBar, flag=wx.GROW)
 
         self.SetSizer(main_sizer)
 
@@ -534,10 +532,9 @@
 
     def RefreshView(self):
         width, height = self.MessagePanel.GetClientSize()
-        bitmap = wx.EmptyBitmap(width, height)
+        bitmap = wx.Bitmap(width, height)
         dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
         dc.Clear()
-        dc.BeginDrawing()
 
         if self.CurrentMessage is not None:
 
@@ -559,8 +556,6 @@
                     draw_date = message.Date != previous_message.Date
                 message = previous_message
 
-        dc.EndDrawing()
-
         self.MessageScrollBar.RefreshThumbPosition()
 
     def IsPLCLogEmpty(self):
--- a/controls/PouInstanceVariablesPanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/PouInstanceVariablesPanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#.!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 # This file is part of Beremiz, a Integrated Development Environment for
@@ -93,7 +93,7 @@
             rect = wx.Rect(images_bbx.x + 4, images_bbx.y + 4,
                            r_image_w, r_image_h)
             for r_image in rightimages:
-                if rect.Inside(point):
+                if rect.Contains(point):
                     return r_image
                 rect.x += r_image_w + 4
 
@@ -132,7 +132,7 @@
 
         self.ParentButton = wx.lib.buttons.GenBitmapButton(
             self, bitmap=GetBitmap("top"), size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.ParentButton.SetToolTipString(_("Parent instance"))
+        self.ParentButton.SetToolTip(_("Parent instance"))
         self.Bind(wx.EVT_BUTTON, self.OnParentButtonClick,
                   self.ParentButton)
 
@@ -142,7 +142,7 @@
 
         self.DebugButton = wx.lib.buttons.GenBitmapButton(
             self, bitmap=GetBitmap("debug_instance"), size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.DebugButton.SetToolTipString(_("Debug instance"))
+        self.DebugButton.SetToolTip(_("Debug instance"))
         self.Bind(wx.EVT_BUTTON, self.OnDebugButtonClick,
                   self.DebugButton)
 
@@ -175,15 +175,15 @@
                 self.DebugButtonCallback, self.DebugButtonDClickCallback)}
 
         buttons_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
-        buttons_sizer.AddWindow(self.ParentButton)
-        buttons_sizer.AddWindow(self.InstanceChoice, flag=wx.GROW)
-        buttons_sizer.AddWindow(self.DebugButton)
+        buttons_sizer.Add(self.ParentButton)
+        buttons_sizer.Add(self.InstanceChoice, flag=wx.GROW)
+        buttons_sizer.Add(self.DebugButton)
         buttons_sizer.AddGrowableCol(1)
         buttons_sizer.AddGrowableRow(0)
 
         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
-        main_sizer.AddSizer(buttons_sizer, flag=wx.GROW)
-        main_sizer.AddWindow(self.VariablesList, flag=wx.GROW)
+        main_sizer.Add(buttons_sizer, flag=wx.GROW)
+        main_sizer.Add(self.VariablesList, flag=wx.GROW)
         main_sizer.AddGrowableCol(0)
         main_sizer.AddGrowableRow(1)
 
--- a/controls/ProjectPropertiesPanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/ProjectPropertiesPanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -57,7 +57,7 @@
                 border |= wx.BOTTOM
 
             st = wx.StaticText(parent, label=label)
-            sizer.AddWindow(st, border=10,
+            sizer.Add(st, border=10,
                             flag=wx.ALIGN_CENTER_VERTICAL | border | wx.LEFT)
 
             tc = wx.TextCtrl(parent, style=wx.TE_PROCESS_ENTER)
@@ -65,7 +65,7 @@
             callback = self.GetTextCtrlChangedFunction(tc, name)
             self.Bind(wx.EVT_TEXT_ENTER, callback, tc)
             tc.Bind(wx.EVT_KILL_FOCUS, callback)
-            sizer.AddWindow(tc, border=10,
+            sizer.Add(tc, border=10,
                             flag=wx.GROW | border | wx.RIGHT)
 
     def __init__(self, parent, controller=None, window=None, enable_required=True, scrolling=True):
@@ -125,19 +125,19 @@
 
         pageSize_st = wx.StaticText(self.GraphicsPanel,
                                     label=_('Page Size (optional):'))
-        graphicpanel_sizer.AddWindow(
+        graphicpanel_sizer.Add(
             pageSize_st, border=10,
             flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT | wx.RIGHT)
 
         pageSize_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
         pageSize_sizer.AddGrowableCol(1)
-        graphicpanel_sizer.AddSizer(pageSize_sizer, border=10,
+        graphicpanel_sizer.Add(pageSize_sizer, border=10,
                                     flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         for name, label in [('PageWidth', _('Width:')),
                             ('PageHeight', _('Height:'))]:
             st = wx.StaticText(self.GraphicsPanel, label=label)
-            pageSize_sizer.AddWindow(st, border=12,
+            pageSize_sizer.Add(st, border=12,
                                      flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT)
 
             sp = wx.SpinCtrl(self.GraphicsPanel,
@@ -146,15 +146,15 @@
             callback = self.GetPageSizeChangedFunction(sp, name)
             self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
             sp.Bind(wx.EVT_KILL_FOCUS, callback)
-            pageSize_sizer.AddWindow(sp, flag=wx.GROW)
+            pageSize_sizer.Add(sp, flag=wx.GROW)
 
         scaling_st = wx.StaticText(self.GraphicsPanel,
                                    label=_('Grid Resolution:'))
-        graphicpanel_sizer.AddWindow(scaling_st, border=10,
+        graphicpanel_sizer.Add(scaling_st, border=10,
                                      flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         scaling_nb = wx.Notebook(self.GraphicsPanel)
-        graphicpanel_sizer.AddWindow(scaling_nb, border=10,
+        graphicpanel_sizer.Add(scaling_nb, border=10,
                                      flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.Scalings = {}
@@ -173,7 +173,7 @@
                     border = wx.BOTTOM
 
                 st = wx.StaticText(scaling_panel, label=label)
-                scalingpanel_sizer.AddWindow(
+                scalingpanel_sizer.Add(
                     st, border=10,
                     flag=wx.ALIGN_CENTER_VERTICAL | border | wx.LEFT)
 
@@ -183,7 +183,7 @@
                 callback = self.GetScalingChangedFunction(sp, language, name)
                 self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
                 sp.Bind(wx.EVT_KILL_FOCUS, callback)
-                scalingpanel_sizer.AddWindow(sp, border=10,
+                scalingpanel_sizer.Add(sp, border=10,
                                              flag=wx.GROW | border | wx.RIGHT)
 
             self.Scalings[language] = scaling_controls
@@ -206,18 +206,18 @@
 
         language_label = wx.StaticText(self.MiscellaneousPanel,
                                        label=_('Language (optional):'))
-        miscellaneouspanel_sizer.AddWindow(language_label, border=10,
+        miscellaneouspanel_sizer.Add(language_label, border=10,
                                            flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT)
 
         self.Language = wx.ComboBox(self.MiscellaneousPanel,
                                     style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnLanguageChanged, self.Language)
-        miscellaneouspanel_sizer.AddWindow(self.Language, border=10,
+        miscellaneouspanel_sizer.Add(self.Language, border=10,
                                            flag=wx.GROW | wx.TOP | wx.RIGHT)
 
         description_label = wx.StaticText(
             self.MiscellaneousPanel, label=_('Content Description (optional):'))
-        miscellaneouspanel_sizer.AddWindow(description_label, border=10,
+        miscellaneouspanel_sizer.Add(description_label, border=10,
                                            flag=wx.BOTTOM | wx.LEFT)
 
         self.ContentDescription = wx.TextCtrl(
@@ -227,7 +227,7 @@
                   self.ContentDescription)
         self.ContentDescription.Bind(wx.EVT_KILL_FOCUS,
                                      self.OnContentDescriptionChanged)
-        miscellaneouspanel_sizer.AddWindow(self.ContentDescription, border=10,
+        miscellaneouspanel_sizer.Add(self.ContentDescription, border=10,
                                            flag=wx.GROW | wx.BOTTOM | wx.RIGHT)
 
         self.AddPage(self.MiscellaneousPanel, _("Miscellaneous"))
--- a/controls/SearchResultPanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/SearchResultPanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -52,16 +52,16 @@
 class SearchResultPanel(wx.Panel):
 
     def _init_coll_MainSizer_Items(self, parent):
-        parent.AddSizer(self.HeaderSizer, 0, border=0, flag=wx.GROW)
-        parent.AddWindow(self.SearchResultsTree, 1, border=0, flag=wx.GROW)
+        parent.Add(self.HeaderSizer, 0, border=0, flag=wx.GROW)
+        parent.Add(self.SearchResultsTree, 1, border=0, flag=wx.GROW)
 
     def _init_coll_MainSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
         parent.AddGrowableRow(1)
 
     def _init_coll_HeaderSizer_Items(self, parent):
-        parent.AddWindow(self.HeaderLabel, 1, border=5, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
-        parent.AddWindow(self.ResetButton, 0, border=0, flag=0)
+        parent.Add(self.HeaderLabel, 1, border=5, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
+        parent.Add(self.ResetButton, 0, border=0, flag=0)
 
     def _init_coll_HeaderSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
@@ -90,7 +90,7 @@
         self.ResetButton = wx.lib.buttons.GenBitmapButton(
             self, bitmap=GetBitmap("reset"),
             size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.ResetButton.SetToolTipString(_("Reset search result"))
+        self.ResetButton.SetToolTip(_("Reset search result"))
         self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton)
 
         self._init_sizers()
--- a/controls/TextCtrlAutoComplete.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/TextCtrlAutoComplete.py	Sun Jul 17 22:53:35 2022 +0200
@@ -97,7 +97,7 @@
         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))
-        elif parent_rect.InsideXY(event.GetX(), event.GetY()):
+        elif parent_rect.Contains(event.GetX(), event.GetY()):
             result, x, y = self.Parent.HitTest(wx.Point(event.GetX(), event.GetY() + parent_size[1]))
             if result != wx.TE_HT_UNKNOWN:
                 self.Parent.SetInsertionPoint(self.Parent.XYToPosition(x, y))
--- a/controls/VariablePanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/controls/VariablePanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -200,8 +200,7 @@
                                              retain=self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"],
                                              non_retain=self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"])
                         if len(options) > 1:
-                            editor = wx.grid.GridCellChoiceEditor()
-                            editor.SetParameters(",".join(map(_, options)))
+                            editor = wx.grid.GridCellChoiceEditor(map(_, options))
                         else:
                             grid.SetReadOnly(row, col, True)
                     elif col != 0 and self._GetRowEdit(row):
@@ -212,8 +211,7 @@
                         elif colname == "Initial Value":
                             if var_class not in ["External", "InOut"]:
                                 if self.Parent.Controler.IsEnumeratedType(var_type):
-                                    editor = wx.grid.GridCellChoiceEditor()
-                                    editor.SetParameters(",".join([""] + self.Parent.Controler.GetEnumeratedDataValues(var_type)))
+                                    editor = wx.grid.GridCellChoiceEditor([""] + self.Parent.Controler.GetEnumeratedDataValues(var_type))
                                 else:
                                     editor = wx.grid.GridCellTextEditor()
                                 renderer = wx.grid.GridCellStringRenderer()
@@ -229,11 +227,10 @@
                             if len(self.Parent.ClassList) == 1:
                                 grid.SetReadOnly(row, col, True)
                             else:
-                                editor = wx.grid.GridCellChoiceEditor()
                                 excluded = []
                                 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]))
+                                editor = wx.grid.GridCellChoiceEditor([_(choice) for choice in self.Parent.ClassList if choice not in excluded])
                     elif colname != "Documentation":
                         grid.SetReadOnly(row, col, True)
 
@@ -456,32 +453,32 @@
         controls_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5)
         controls_sizer.AddGrowableCol(5)
         controls_sizer.AddGrowableRow(0)
-        self.MainSizer.AddSizer(controls_sizer, border=5, flag=wx.GROW | wx.ALL)
+        self.MainSizer.Add(controls_sizer, border=5, flag=wx.GROW | wx.ALL)
 
         self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:'))
-        controls_sizer.AddWindow(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.ReturnType = wx.ComboBox(self,
                                       size=wx.Size(145, -1), style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType)
-        controls_sizer.AddWindow(self.ReturnType)
+        controls_sizer.Add(self.ReturnType)
 
         self.DescriptionLabel = wx.StaticText(self, label=_('Description:'))
-        controls_sizer.AddWindow(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.Description = wx.TextCtrl(self,
                                        size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description)
         self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged)
-        controls_sizer.AddWindow(self.Description)
+        controls_sizer.Add(self.Description)
 
         class_filter_label = wx.StaticText(self, label=_('Class Filter:'))
-        controls_sizer.AddWindow(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.ClassFilter = wx.ComboBox(self,
                                        size=wx.Size(145, -1), style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter)
-        controls_sizer.AddWindow(self.ClassFilter)
+        controls_sizer.Add(self.ClassFilter)
 
         for name, bitmap, help in [
                 ("AddButton", "add_element", _("Add variable")),
@@ -490,19 +487,19 @@
                 ("DownButton", "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)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            controls_sizer.AddWindow(button)
+            controls_sizer.Add(button)
 
         self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL | wx.HSCROLL)
         self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
-        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED,
                                 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)
-        self.MainSizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+        self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW)
 
         self.SetSizer(self.MainSizer)
 
@@ -848,7 +845,7 @@
         # build a submenu containing standard IEC types
         base_menu = wx.Menu(title='')
         for base_type in self.Controler.GetBaseTypes():
-            item = base_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=base_type)
+            item = base_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=base_type)
             self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), item)
 
         type_menu.AppendMenu(wx.ID_ANY, _("Base Types"), base_menu)
@@ -858,7 +855,7 @@
         datatype_menu = wx.Menu(title='')
         datatypes = self.Controler.GetDataTypes(basetypes=False, confnodetypes=False)
         for datatype in datatypes:
-            item = datatype_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=datatype)
+            item = datatype_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=datatype)
             self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), item)
 
         type_menu.AppendMenu(wx.ID_ANY, _("User Data Types"), datatype_menu)
@@ -869,7 +866,7 @@
                 # build a submenu containing confnode types
                 confnode_datatype_menu = wx.Menu(title='')
                 for datatype in category["list"]:
-                    item = confnode_datatype_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=datatype)
+                    item = confnode_datatype_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=datatype)
                     self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), item)
 
                 type_menu.AppendMenu(wx.ID_ANY, category["name"], confnode_datatype_menu)
@@ -883,13 +880,13 @@
             functionblock_menu = wx.Menu(title='')
             fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
             for functionblock_type in fbtypes:
-                item = functionblock_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=functionblock_type)
+                item = functionblock_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=functionblock_type)
                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), item)
 
             type_menu.AppendMenu(wx.ID_ANY, _("Function Block Types"), functionblock_menu)
 
     def BuildArrayTypesMenu(self, type_menu):
-        item = type_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=_("Array"))
+        item = type_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=_("Array"))
         self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, item)
 
     def OnVariablesGridEditorShown(self, event):
@@ -916,7 +913,7 @@
             corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
 
             # pop up this new menu
-            self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
+            self.VariablesGrid.PopupMenu(type_menu, corner_x, corner_y)
             type_menu.Destroy()
             event.Veto()
             value = self.Values[row].Type
--- a/dialogs/ActionBlockDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/ActionBlockDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -85,29 +85,24 @@
                 readonly = False
                 colname = self.GetColLabelValue(col, False)
                 if colname == "Qualifier":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.QualifierList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.QualifierList)
                 if colname == "Duration":
                     editor = wx.grid.GridCellTextEditor()
                     renderer = wx.grid.GridCellStringRenderer()
                     readonly = not self.Parent.DurationList[self.data[row].qualifier]
                 elif colname == "Type":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.TypeList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.TypeList)
                 elif colname == "Value":
                     value_type = self.data[row].type
                     if value_type == "Action":
-                        editor = wx.grid.GridCellChoiceEditor()
-                        editor.SetParameters(self.Parent.ActionList)
+                        editor = wx.grid.GridCellChoiceEditor(self.Parent.ActionList)
                     elif value_type == "Variable":
-                        editor = wx.grid.GridCellChoiceEditor()
-                        editor.SetParameters(self.Parent.VariableList)
+                        editor = wx.grid.GridCellChoiceEditor(self.Parent.VariableList)
                     elif value_type == "Inline":
                         editor = wx.grid.GridCellTextEditor()
                         renderer = wx.grid.GridCellStringRenderer()
                 elif colname == "Indicator":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.VariableList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.VariableList)
 
                 grid.SetCellEditor(row, col, editor)
                 grid.SetCellRenderer(row, col, renderer)
@@ -133,11 +128,11 @@
         top_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         top_sizer.AddGrowableCol(0)
         top_sizer.AddGrowableRow(0)
-        main_sizer.AddSizer(top_sizer, border=20,
+        main_sizer.Add(top_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         actions_label = wx.StaticText(self, label=_('Actions:'))
-        top_sizer.AddWindow(actions_label, flag=wx.ALIGN_BOTTOM)
+        top_sizer.Add(actions_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("AddButton", "add_element", _("Add action")),
@@ -147,21 +142,21 @@
             button = wx.lib.buttons.GenBitmapButton(
                 self, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            top_sizer.AddWindow(button)
+            top_sizer.Add(button)
 
         self.ActionsGrid = CustomGrid(self, size=wx.Size(-1, 250), style=wx.VSCROLL)
         self.ActionsGrid.DisableDragGridSize()
         self.ActionsGrid.EnableScrolling(False, True)
-        self.ActionsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.ActionsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING,
                               self.OnActionsGridCellChange)
-        main_sizer.AddSizer(self.ActionsGrid, border=20,
+        main_sizer.Add(self.ActionsGrid, 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,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/ArrayTypeDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/ArrayTypeDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -50,31 +50,31 @@
         main_sizer.AddGrowableRow(1)
 
         top_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(top_sizer, border=20,
+        main_sizer.Add(top_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         basetype_label = wx.StaticText(self, label=_('Base Type:'))
-        top_sizer.AddWindow(basetype_label, 1, flag=wx.ALIGN_BOTTOM)
+        top_sizer.Add(basetype_label, 1, flag=wx.ALIGN_BOTTOM)
 
         self.BaseType = wx.ComboBox(self, style=wx.CB_READONLY)
-        top_sizer.AddWindow(self.BaseType, 1, flag=wx.GROW)
+        top_sizer.Add(self.BaseType, 1, flag=wx.GROW)
 
         self.Dimensions = CustomEditableListBox(self, label=_("Dimensions:"),
-                                                style=(wx.gizmos.EL_ALLOW_NEW |
-                                                       wx.gizmos.EL_ALLOW_EDIT |
-                                                       wx.gizmos.EL_ALLOW_DELETE))
+                                                style=(wx.adv.EL_ALLOW_NEW |
+                                                       wx.adv.EL_ALLOW_EDIT |
+                                                       wx.adv.EL_ALLOW_DELETE))
         for func in ["_OnLabelEndEdit",
                      "_OnAddButton",
                      "_OnDelButton",
                      "_OnUpButton",
                      "_OnDownButton"]:
             setattr(self.Dimensions, func, self.OnDimensionsChanged)
-        main_sizer.AddSizer(self.Dimensions, border=20,
+        main_sizer.Add(self.Dimensions, 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,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/BlockPreviewDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/BlockPreviewDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -75,8 +75,7 @@
 
         # 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.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
         self.Element = None            # Graphic element to display in preview
         self.MinElementSize = None     # Graphic element minimal size
@@ -120,7 +119,7 @@
 
         # Create a sizer for dividing parameters in two columns
         self.ColumnSizer = wx.BoxSizer(wx.HORIZONTAL)
-        self.MainSizer.AddSizer(self.ColumnSizer, border=20,
+        self.MainSizer.Add(self.ColumnSizer, border=20,
                                 flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         # Create a sizer for left column
@@ -129,7 +128,7 @@
         self.LeftGridSizer.AddGrowableCol(0)
         if left_growable_row is not None:
             self.LeftGridSizer.AddGrowableRow(left_growable_row)
-        self.ColumnSizer.AddSizer(self.LeftGridSizer, 1, border=5,
+        self.ColumnSizer.Add(self.LeftGridSizer, 1, border=5,
                                   flag=wx.GROW | wx.RIGHT | wx.EXPAND)
 
         # Create a sizer for right column
@@ -138,7 +137,7 @@
         self.RightGridSizer.AddGrowableCol(0)
         if right_growable_row is not None:
             self.RightGridSizer.AddGrowableRow(right_growable_row)
-        self.ColumnSizer.AddSizer(self.RightGridSizer, 1, border=5,
+        self.ColumnSizer.Add(self.RightGridSizer, 1, border=5,
                                   flag=wx.GROW | wx.LEFT)
 
         self.SetSizer(self.MainSizer)
--- a/dialogs/BrowseLocationsDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/BrowseLocationsDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -76,7 +76,7 @@
         main_sizer.AddGrowableRow(1)
 
         locations_label = wx.StaticText(self, label=_('Locations available:'))
-        main_sizer.AddWindow(locations_label, border=20,
+        main_sizer.Add(locations_label, border=20,
                              flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
 
         self.LocationsTree = wx.TreeCtrl(self,
@@ -88,7 +88,7 @@
         self.LocationsTree.SetInitialSize(wx.Size(-1, 300))
         self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnLocationsTreeItemActivated,
                   self.LocationsTree)
-        main_sizer.AddWindow(self.LocationsTree, border=20,
+        main_sizer.Add(self.LocationsTree, border=20,
                              flag=wx.LEFT | wx.RIGHT | wx.GROW)
 
         self.RenameCheckBox = wx.CheckBox(self, label=_("Rename variable to signal name"))
@@ -97,37 +97,37 @@
         self.RenameCheckBox.SetValue(default_checked)
         self.do_rename = default_checked
 
-        main_sizer.AddWindow(self.RenameCheckBox, border=20,
+        main_sizer.Add(self.RenameCheckBox, border=20,
                              flag=wx.LEFT | wx.RIGHT | wx.GROW)
 
         button_gridsizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         button_gridsizer.AddGrowableCol(1)
         button_gridsizer.AddGrowableCol(3)
         button_gridsizer.AddGrowableRow(0)
-        main_sizer.AddSizer(button_gridsizer, border=20,
+        main_sizer.Add(button_gridsizer, border=20,
                             flag=wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.GROW)
 
         direction_label = wx.StaticText(self, label=_('Direction:'))
-        button_gridsizer.AddWindow(direction_label,
+        button_gridsizer.Add(direction_label,
                                    flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.DirFilterChoice = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnFilterChoice, self.DirFilterChoice)
-        button_gridsizer.AddWindow(self.DirFilterChoice,
+        button_gridsizer.Add(self.DirFilterChoice,
                                    flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL)
 
         filter_label = wx.StaticText(self, label=_('Type:'))
-        button_gridsizer.AddWindow(filter_label,
+        button_gridsizer.Add(filter_label,
                                    flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.TypeFilterChoice = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnFilterChoice, self.TypeFilterChoice)
-        button_gridsizer.AddWindow(self.TypeFilterChoice,
+        button_gridsizer.Add(self.TypeFilterChoice,
                                    flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        button_gridsizer.AddSizer(button_sizer, flag=wx.ALIGN_RIGHT)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        button_gridsizer.Add(button_sizer, flag=wx.ALIGN_RIGHT)
 
         self.SetSizer(main_sizer)
 
--- a/dialogs/BrowseValuesLibraryDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/BrowseValuesLibraryDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -51,13 +51,13 @@
 
         self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
 
-        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetAffirmativeButton().GetId())
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
         self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
 
-        self.flexGridSizer1.AddWindow(self.staticText1,   0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
-        self.flexGridSizer1.AddWindow(self.ValuesLibrary, 0, border=20, flag=wx.GROW | wx.LEFT | wx.RIGHT)
-        self.flexGridSizer1.AddSizer(self.ButtonSizer,    0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
+        self.flexGridSizer1.Add(self.staticText1,   0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
+        self.flexGridSizer1.Add(self.ValuesLibrary, 0, border=20, flag=wx.GROW | wx.LEFT | wx.RIGHT)
+        self.flexGridSizer1.Add(self.ButtonSizer,    0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.flexGridSizer1.AddGrowableCol(0)
         self.flexGridSizer1.AddGrowableRow(1)
--- a/dialogs/ConnectionDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/ConnectionDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -59,7 +59,7 @@
 
         # Create label for connection type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create radio buttons for selecting connection type
         self.TypeRadioButtons = {}
@@ -70,39 +70,39 @@
                                           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.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.TypeRadioButtons[type] = radio_button
             first = False
 
         # Create label for connection name
         name_label = wx.StaticText(self, label=_('Name:'))
-        self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(name_label, flag=wx.GROW)
 
         # Create text control for defining connection name
         self.ConnectionName = wx.TextCtrl(self)
         self.ConnectionName.SetMinSize(wx.Size(200, -1))
         self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.ConnectionName)
-        self.LeftGridSizer.AddWindow(self.ConnectionName, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.ConnectionName, flag=wx.GROW)
 
         # Add preview panel and associated label to sizers
         self.Preview.SetMinSize(wx.Size(-1, 100))
-        self.LeftGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.LeftGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
-        self.ColumnSizer.RemoveSizer(self.RightGridSizer)
+        self.ColumnSizer.Remove(self.RightGridSizer)
 
         # 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(
+            self.ApplyToAllButton.SetToolTip(
                 _("Apply name modification to all continuations with the same name"))
             self.Bind(wx.EVT_BUTTON, self.OnApplyToAll, self.ApplyToAllButton)
-            self.ButtonSizer.AddWindow(self.ApplyToAllButton, flag=wx.LEFT)
+            self.ButtonSizer.Add(self.ApplyToAllButton, flag=wx.LEFT)
         else:
             self.ConnectionName.ChangeValue(
                 controller.GenerateNewName(
--- a/dialogs/DurationEditorDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/DurationEditorDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -68,7 +68,7 @@
         main_sizer.AddGrowableRow(0)
 
         controls_sizer = wx.FlexGridSizer(cols=len(CONTROLS), hgap=10, rows=2, vgap=10)
-        main_sizer.AddSizer(controls_sizer, border=20,
+        main_sizer.Add(controls_sizer, border=20,
                             flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
 
         controls = []
@@ -85,14 +85,14 @@
             controls.append((st, txtctrl))
 
         for st, txtctrl in controls:
-            controls_sizer.AddWindow(st, flag=wx.GROW)
+            controls_sizer.Add(st, flag=wx.GROW)
 
         for st, txtctrl in controls:
-            controls_sizer.AddWindow(txtctrl, flag=wx.GROW)
+            controls_sizer.Add(txtctrl, 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.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
@@ -153,6 +153,7 @@
         return duration
 
     def OnOK(self, event):
+        event.Skip()
         self.OnCloseDialog()
 
     def OnCloseDialog(self):
--- a/dialogs/FBDBlockDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/FBDBlockDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -68,7 +68,7 @@
         # Create static box around library panel
         type_staticbox = wx.StaticBox(self, label=_('Type:'))
         left_staticboxsizer = wx.StaticBoxSizer(type_staticbox, wx.VERTICAL)
-        self.LeftGridSizer.AddSizer(left_staticboxsizer, border=5, flag=wx.GROW)
+        self.LeftGridSizer.Add(left_staticboxsizer, border=5, flag=wx.GROW)
 
         # Create Library panel and add it to static box
         self.LibraryPanel = LibraryPanel(self)
@@ -77,65 +77,65 @@
         # Set function to call when selection in Library panel changed
         setattr(self.LibraryPanel, "_OnTreeItemSelected",
                 self.OnLibraryTreeItemSelected)
-        left_staticboxsizer.AddWindow(self.LibraryPanel, 1, border=5,
+        left_staticboxsizer.Add(self.LibraryPanel, 1, border=5,
                                       flag=wx.GROW | wx.TOP)
 
         # Create sizer for other block parameters
         top_right_gridsizer = wx.FlexGridSizer(cols=2, hgap=0, rows=4, vgap=5)
         top_right_gridsizer.AddGrowableCol(1)
-        self.RightGridSizer.AddSizer(top_right_gridsizer, flag=wx.GROW)
+        self.RightGridSizer.Add(top_right_gridsizer, flag=wx.GROW)
 
         # Create label for block name
         name_label = wx.StaticText(self, label=_('Name:'))
-        top_right_gridsizer.AddWindow(name_label,
+        top_right_gridsizer.Add(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)
+        top_right_gridsizer.Add(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,
+        top_right_gridsizer.Add(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)
+        top_right_gridsizer.Add(self.Inputs, flag=wx.GROW)
 
         # Create label for block execution order
         execution_order_label = wx.StaticText(self,
                                               label=_('Execution Order:'))
-        top_right_gridsizer.AddWindow(execution_order_label,
+        top_right_gridsizer.Add(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)
-        top_right_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
+        top_right_gridsizer.Add(self.ExecutionOrder, flag=wx.GROW)
 
         # Create label for block execution control
         execution_control_label = wx.StaticText(self,
                                                 label=_('Execution Control:'))
-        top_right_gridsizer.AddWindow(execution_control_label,
+        top_right_gridsizer.Add(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)
-        top_right_gridsizer.AddWindow(self.ExecutionControl, flag=wx.GROW)
+        top_right_gridsizer.Add(self.ExecutionControl, 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.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(self.ButtonSizer, border=20,
+        self.MainSizer.Add(self.ButtonSizer, border=20,
                                 flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         # Dictionary containing correspondence between parameter exchanged and
--- a/dialogs/FBDVariableDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/FBDVariableDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -73,49 +73,49 @@
 
         # Create label for variable class
         class_label = wx.StaticText(self, label=_('Class:'))
-        self.LeftGridSizer.AddWindow(class_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(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)
-        self.LeftGridSizer.AddWindow(self.Class, flag=wx.GROW)
+        self.LeftGridSizer.Add(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)
+        self.LeftGridSizer.Add(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)
+        self.LeftGridSizer.Add(self.ExecutionOrder, flag=wx.GROW)
 
         # Create label for variable expression
         name_label = wx.StaticText(self, label=_('Expression:'))
-        self.RightGridSizer.AddWindow(name_label, border=5,
+        self.RightGridSizer.Add(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)
-        self.RightGridSizer.AddWindow(self.Expression, flag=wx.GROW)
+        self.RightGridSizer.Add(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(-1, 120),
                                        style=wx.LB_SINGLE | wx.LB_SORT)
         self.Bind(wx.EVT_LISTBOX, self.OnNameChanged, self.VariableName)
-        self.RightGridSizer.AddWindow(self.VariableName, border=4, flag=wx.GROW | wx.TOP)
+        self.RightGridSizer.Add(self.VariableName, border=4, flag=wx.GROW | wx.TOP)
 
         # Add preview panel and associated label to sizers
-        self.MainSizer.AddWindow(self.PreviewLabel, border=20,
+        self.MainSizer.Add(self.PreviewLabel, border=20,
                                  flag=wx.GROW | wx.LEFT | wx.RIGHT)
-        self.MainSizer.AddWindow(self.Preview, border=20,
+        self.MainSizer.Add(self.Preview, border=20,
                                  flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
--- a/dialogs/FindInPouDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/FindInPouDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -48,76 +48,76 @@
         main_sizer.AddGrowableRow(0)
 
         controls_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(controls_sizer, border=20,
+        main_sizer.Add(controls_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         patterns_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5)
         patterns_sizer.AddGrowableCol(1)
-        controls_sizer.AddSizer(patterns_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
+        controls_sizer.Add(patterns_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
 
         find_label = wx.StaticText(panel, label=_("Find:"))
-        patterns_sizer.AddWindow(find_label, flag=wx.ALIGN_CENTER_VERTICAL)
+        patterns_sizer.Add(find_label, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.FindPattern = wx.TextCtrl(panel)
         self.Bind(wx.EVT_TEXT, self.OnFindPatternChanged, self.FindPattern)
         self.Bind(wx.EVT_CHAR_HOOK, self.OnEscapeKey)
-        patterns_sizer.AddWindow(self.FindPattern, flag=wx.GROW)
+        patterns_sizer.Add(self.FindPattern, flag=wx.GROW)
 
         params_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        controls_sizer.AddSizer(params_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
+        controls_sizer.Add(params_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
 
         direction_staticbox = wx.StaticBox(panel, label=_("Direction"))
         direction_staticboxsizer = wx.StaticBoxSizer(
             direction_staticbox, wx.VERTICAL)
-        params_sizer.AddSizer(direction_staticboxsizer, 1, border=5,
+        params_sizer.Add(direction_staticboxsizer, 1, border=5,
                               flag=wx.GROW | wx.RIGHT)
 
         self.Forward = wx.RadioButton(panel, label=_("Forward"),
                                       style=wx.RB_GROUP)
-        direction_staticboxsizer.AddWindow(self.Forward, border=5,
+        direction_staticboxsizer.Add(self.Forward, border=5,
                                            flag=wx.ALL | wx.GROW)
 
         self.Backward = wx.RadioButton(panel, label=_("Backward"))
-        direction_staticboxsizer.AddWindow(self.Backward, border=5,
+        direction_staticboxsizer.Add(self.Backward, border=5,
                                            flag=wx.ALL | wx.GROW)
 
         options_staticbox = wx.StaticBox(panel, label=_("Options"))
         options_staticboxsizer = wx.StaticBoxSizer(
             options_staticbox, wx.VERTICAL)
-        params_sizer.AddSizer(options_staticboxsizer, 1, flag=wx.GROW)
+        params_sizer.Add(options_staticboxsizer, 1, flag=wx.GROW)
 
         self.CaseSensitive = wx.CheckBox(panel, label=_("Case sensitive"))
         self.CaseSensitive.SetValue(True)
-        options_staticboxsizer.AddWindow(self.CaseSensitive, border=5,
+        options_staticboxsizer.Add(self.CaseSensitive, border=5,
                                          flag=wx.ALL | wx.GROW)
 
         self.WrapSearch = wx.CheckBox(panel, label=_("Wrap search"))
         self.WrapSearch.SetValue(True)
-        options_staticboxsizer.AddWindow(self.WrapSearch, border=5,
+        options_staticboxsizer.Add(self.WrapSearch, border=5,
                                          flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
         self.RegularExpressions = wx.CheckBox(panel, label=_("Regular expressions"))
-        options_staticboxsizer.AddWindow(self.RegularExpressions, border=5,
+        options_staticboxsizer.Add(self.RegularExpressions, border=5,
                                          flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
         buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(buttons_sizer, border=20,
+        main_sizer.Add(buttons_sizer, border=20,
                             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)
+        buttons_sizer.Add(self.FindButton, border=5, flag=wx.RIGHT)
 
         self.CloseButton = wx.Button(panel, label=_("Close"))
         self.Bind(wx.EVT_BUTTON, self.OnCloseButton, self.CloseButton)
-        buttons_sizer.AddWindow(self.CloseButton)
+        buttons_sizer.Add(self.CloseButton)
 
         # set the longest message here, to use it length to calculate
         # optimal size of dialog window
         self.RegExpSyntaxErrMsg = _("Syntax error in regular expression of pattern to search!")
         self.StatusLabel = wx.StaticText(panel, label=self.RegExpSyntaxErrMsg)
-        controls_sizer.AddWindow(self.StatusLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(self.StatusLabel)
 
         panel.SetSizer(main_sizer)
         main_sizer.Fit(self)
--- a/dialogs/ForceVariableDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/ForceVariableDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -189,7 +189,7 @@
         info_sizer = wx.BoxSizer(wx.VERTICAL)
 
         message_label = wx.StaticText(self, label=_("Forcing Variable Value"))
-        info_sizer.AddWindow(message_label, border=10,
+        info_sizer.Add(message_label, border=10,
                              flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         if GetTypeValue[self.IEC_Type] in [getinteger, getfloat]:
@@ -201,8 +201,8 @@
         self.GetEnteredValue = self.GetValueDefault
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        info_sizer.AddSizer(button_sizer, border=10, flag=wx.ALIGN_RIGHT | wx.ALL)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        info_sizer.Add(button_sizer, border=10, flag=wx.ALIGN_RIGHT | wx.ALL)
 
         self.SetSizer(info_sizer)
         self.Fit()
@@ -216,7 +216,7 @@
         """Add simple text control to change variable of any type"""
         self.ValueCtrl = wx.TextCtrl(self)
         self.ValueCtrl.SetValue(defaultValue)
-        info_sizer.AddWindow(self.ValueCtrl, border=10, proportion=1,
+        info_sizer.Add(self.ValueCtrl, border=10, proportion=1,
                              flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
     def GetValueDefault(self):
@@ -235,11 +235,11 @@
         sizer = wx.BoxSizer(wx.HORIZONTAL)
         self.InitCtrlDefault(sizer, defaultValue)
         self.SpinButtonCtrl = wx.SpinButton(self, style=wx.HORIZONTAL | wx.SP_WRAP)
-        sizer.AddWindow(self.SpinButtonCtrl, border=10,
+        sizer.Add(self.SpinButtonCtrl, border=10,
                         flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND)
         self.Bind(wx.EVT_SPIN_UP, self.SpinButtonChanged)
         self.Bind(wx.EVT_SPIN_DOWN, self.SpinButtonChanged)
-        info_sizer.AddWindow(sizer, proportion=1, flag=wx.EXPAND)
+        info_sizer.Add(sizer, proportion=1, flag=wx.EXPAND)
 
     def SpinButtonChanged(self, evt):
         """Increment/decrement variable value"""
@@ -261,7 +261,7 @@
         if value is not None:
             self.ValueCtrl.SetValue(value)
 
-        info_sizer.AddWindow(self.ValueCtrl, border=10,
+        info_sizer.Add(self.ValueCtrl, border=10,
                              flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
 
     def OnOK(self, event):
--- a/dialogs/IDMergeDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/IDMergeDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -15,11 +15,11 @@
         main_sizer = wx.BoxSizer(wx.VERTICAL)
 
         message = wx.StaticText(self, label=question)
-        main_sizer.AddWindow(message, border=20,
+        main_sizer.Add(message, border=20,
                              flag=wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.LEFT | wx.RIGHT)
 
         self.check = wx.CheckBox(self, label=optiontext)
-        main_sizer.AddWindow(self.check, border=20,
+        main_sizer.Add(self.check, border=20,
                              flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL)
 
         buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -30,9 +30,9 @@
                 return lambda event: self.EndModal(_wxID)
 
             self.Bind(wx.EVT_BUTTON, OnButtonFactory(wxID), Button)
-            buttons_sizer.AddWindow(Button)
+            buttons_sizer.Add(Button)
 
-        main_sizer.AddSizer(buttons_sizer, border=20,
+        main_sizer.Add(buttons_sizer, border=20,
                             flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/LDElementDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/LDElementDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -63,7 +63,7 @@
 
         # Create label for LD element modifier
         modifier_label = wx.StaticText(self, label=_('Modifier:'))
-        self.LeftGridSizer.AddWindow(modifier_label, border=5,
+        self.LeftGridSizer.Add(modifier_label, border=5,
                                      flag=wx.GROW | wx.BOTTOM)
 
         # Create radio buttons for selecting LD element modifier
@@ -84,13 +84,13 @@
                                           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.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.ModifierRadioButtons[modifier] = radio_button
             first = False
 
         # Create label for LD element variable
         element_variable_label = wx.StaticText(self, label=_('Variable:'))
-        self.LeftGridSizer.AddWindow(element_variable_label, border=5,
+        self.LeftGridSizer.Add(element_variable_label, border=5,
                                      flag=wx.GROW | wx.TOP)
 
         # Create a combo box for defining LD element variable
@@ -99,15 +99,15 @@
                   self.ElementVariable)
         self.Bind(wx.EVT_TEXT, self.OnVariableChanged,
                   self.ElementVariable)
-        self.LeftGridSizer.AddWindow(self.ElementVariable, border=5,
+        self.LeftGridSizer.Add(self.ElementVariable, border=5,
                                      flag=wx.GROW | wx.TOP)
 
         # 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.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(self.ButtonSizer, border=20,
+        self.MainSizer.Add(self.ButtonSizer, border=20,
                                 flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         # Save LD element class
@@ -198,8 +198,9 @@
             self.GetElementModifier(),
             value)
 
-        button = self.ButtonSizer.GetAffirmativeButton()
-        button.Enable(value != "")
+        # FIXME : how to disable OK button when content is not valid
+        # button = self.ButtonSizer.GetAffirmativeButton()
+        # button.Enable(value != "")
 
         # Call BlockPreviewDialog function
         BlockPreviewDialog.DrawPreview(self)
--- a/dialogs/LDPowerRailDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/LDPowerRailDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -56,7 +56,7 @@
 
         # Create label for connection type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create radio buttons for selecting power rail type
         self.TypeRadioButtons = {}
@@ -67,27 +67,27 @@
                                           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.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.TypeRadioButtons[type] = radio_button
             first = False
 
         # 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.LeftGridSizer.Add(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.PinNumber.SetValue(1)
         self.Bind(wx.EVT_SPINCTRL, self.OnPinNumberChanged, self.PinNumber)
-        self.LeftGridSizer.AddWindow(self.PinNumber, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.PinNumber, 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.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
         self.Fit()
--- a/dialogs/PouActionDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/PouActionDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -50,27 +50,26 @@
 
         infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=15)
         infos_sizer.AddGrowableCol(1)
-        main_sizer.AddSizer(infos_sizer, border=20,
+        main_sizer.Add(infos_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         actionname_label = wx.StaticText(self, label=_('Action Name:'))
-        infos_sizer.AddWindow(actionname_label, border=4,
+        infos_sizer.Add(actionname_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.ActionName = wx.TextCtrl(self, size=wx.Size(180, -1))
-        infos_sizer.AddWindow(self.ActionName, flag=wx.GROW)
+        infos_sizer.Add(self.ActionName, flag=wx.GROW)
 
         language_label = wx.StaticText(self, label=_('Language:'))
-        infos_sizer.AddWindow(language_label, border=4,
+        infos_sizer.Add(language_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
-        infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+        infos_sizer.Add(self.Language, 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.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/PouDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/PouDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -58,34 +58,34 @@
 
         infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=15)
         infos_sizer.AddGrowableCol(1)
-        main_sizer.AddSizer(infos_sizer, border=20,
+        main_sizer.Add(infos_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         pouname_label = wx.StaticText(self, label=_('POU Name:'))
-        infos_sizer.AddWindow(pouname_label, border=4,
+        infos_sizer.Add(pouname_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.PouName = wx.TextCtrl(self)
-        infos_sizer.AddWindow(self.PouName, flag=wx.GROW)
+        infos_sizer.Add(self.PouName, flag=wx.GROW)
 
         poutype_label = wx.StaticText(self, label=_('POU Type:'))
-        infos_sizer.AddWindow(poutype_label, border=4,
+        infos_sizer.Add(poutype_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.PouType = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnTypeChanged, self.PouType)
-        infos_sizer.AddWindow(self.PouType, flag=wx.GROW)
+        infos_sizer.Add(self.PouType, flag=wx.GROW)
 
         language_label = wx.StaticText(self, label=_('Language:'))
-        infos_sizer.AddWindow(language_label, border=4,
+        infos_sizer.Add(language_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
-        infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+        infos_sizer.Add(self.Language, 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.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/PouNameDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/PouNameDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -40,8 +40,7 @@
 
         self.PouNames = []
 
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  self.GetSizer().GetItem(2).GetSizer().GetItem(1).GetSizer().GetAffirmativeButton())
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
     def OnOK(self, event):
         message = None
--- a/dialogs/PouTransitionDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/PouTransitionDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -53,26 +53,26 @@
 
         infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=10)
         infos_sizer.AddGrowableCol(1)
-        main_sizer.AddSizer(infos_sizer, border=20,
+        main_sizer.Add(infos_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         transitionname_label = wx.StaticText(self, label=_('Transition Name:'))
-        infos_sizer.AddWindow(transitionname_label, border=4,
+        infos_sizer.Add(transitionname_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.TransitionName = wx.TextCtrl(self, size=wx.Size(180, -1))
-        infos_sizer.AddWindow(self.TransitionName, flag=wx.GROW)
+        infos_sizer.Add(self.TransitionName, flag=wx.GROW)
 
         language_label = wx.StaticText(self, label=_('Language:'))
-        infos_sizer.AddWindow(language_label, border=4,
+        infos_sizer.Add(language_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
-        infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+        infos_sizer.Add(self.Language, 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, flag=wx.ALIGN_RIGHT | wx.BOTTOM)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM)
 
         self.SetSizer(main_sizer)
 
--- a/dialogs/ProjectDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/ProjectDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -42,12 +42,11 @@
 
         self.ProjectProperties = ProjectPropertiesPanel(
             self, enable_required=enable_required, scrolling=False)
-        main_sizer.AddWindow(self.ProjectProperties, flag=wx.GROW)
+        main_sizer.Add(self.ProjectProperties, flag=wx.GROW)
 
         self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  self.ButtonSizer.GetAffirmativeButton())
-        main_sizer.AddSizer(self.ButtonSizer, border=20,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(self.ButtonSizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/SFCDivergenceDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/SFCDivergenceDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -58,7 +58,7 @@
 
         # Create label for divergence type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create radio buttons for selecting divergence type
         divergence_buttons = [
@@ -80,7 +80,7 @@
                                           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.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.TypeRadioButtons[type] = radio_button
             if first:
                 focusbtn = type
@@ -89,19 +89,19 @@
         # Create label for number of divergence sequences
         sequences_label = wx.StaticText(self,
                                         label=_('Number of sequences:'))
-        self.LeftGridSizer.AddWindow(sequences_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(sequences_label, flag=wx.GROW)
 
         # Create spin control for defining number of divergence sequences
         self.Sequences = wx.SpinCtrl(self, min=2, max=20, initial=2)
         self.Bind(wx.EVT_SPINCTRL, self.OnSequencesChanged, self.Sequences)
-        self.LeftGridSizer.AddWindow(self.Sequences, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.Sequences, 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.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
--- a/dialogs/SFCStepDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/SFCStepDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -57,16 +57,16 @@
 
         # Create label for SFC step name
         name_label = wx.StaticText(self, label=_('Name:'))
-        self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(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)
-        self.LeftGridSizer.AddWindow(self.StepName, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.StepName, flag=wx.GROW)
 
         # Create label for SFC step connectors
         connectors_label = wx.StaticText(self, label=_('Connectors:'))
-        self.LeftGridSizer.AddWindow(connectors_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(connectors_label, flag=wx.GROW)
 
         # Create check boxes for defining connectors available on SFC step
         self.ConnectorsCheckBox = {}
@@ -77,15 +77,15 @@
             if name == "output" or (name == "input" and not initial):
                 check_box.SetValue(True)
             self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, check_box)
-            self.LeftGridSizer.AddWindow(check_box, flag=wx.GROW)
+            self.LeftGridSizer.Add(check_box, flag=wx.GROW)
             self.ConnectorsCheckBox[name] = check_box
 
         # 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.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
--- a/dialogs/SFCStepNameDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/SFCStepNameDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -42,8 +42,7 @@
         self.Variables = []
         self.StepNames = []
 
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  self.GetSizer().GetItem(2).GetSizer().GetItem(1).GetSizer().GetAffirmativeButton())
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
     def OnOK(self, event):
         message = None
--- a/dialogs/SFCTransitionDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/SFCTransitionDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -57,7 +57,7 @@
 
         # Create label for transition type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create combo box for selecting reference value
         reference = wx.ComboBox(self, style=wx.CB_READONLY)
@@ -80,28 +80,28 @@
                                           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.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             if control is not None:
                 control.Enable(first)
-                self.LeftGridSizer.AddWindow(control, flag=wx.GROW)
+                self.LeftGridSizer.Add(control, flag=wx.GROW)
             self.TypeRadioButtons[type] = (radio_button, control)
             first = False
 
         # Create label for transition priority
         priority_label = wx.StaticText(self, label=_('Priority:'))
-        self.LeftGridSizer.AddWindow(priority_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(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)
-        self.LeftGridSizer.AddWindow(self.Priority, flag=wx.GROW)
+        self.LeftGridSizer.Add(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)
+        self.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
--- a/dialogs/SearchInProjectDialog.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/dialogs/SearchInProjectDialog.py	Sun Jul 17 22:53:35 2022 +0200
@@ -54,59 +54,59 @@
 
         pattern_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
         pattern_sizer.AddGrowableCol(0)
-        main_sizer.AddSizer(pattern_sizer, border=20,
+        main_sizer.Add(pattern_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         pattern_label = wx.StaticText(self, label=_('Pattern to search:'))
-        pattern_sizer.AddWindow(pattern_label, flag=wx.ALIGN_BOTTOM)
+        pattern_sizer.Add(pattern_label, flag=wx.ALIGN_BOTTOM)
 
         self.CaseSensitive = wx.CheckBox(self, label=_('Case sensitive'))
-        pattern_sizer.AddWindow(self.CaseSensitive, flag=wx.GROW)
+        pattern_sizer.Add(self.CaseSensitive, flag=wx.GROW)
 
         self.Pattern = wx.TextCtrl(self, size=wx.Size(250, -1))
         self.Bind(wx.EVT_TEXT, self.FindPatternChanged, self.Pattern)
-        pattern_sizer.AddWindow(self.Pattern, flag=wx.GROW)
+        pattern_sizer.Add(self.Pattern, flag=wx.GROW)
         self.Bind(wx.EVT_CHAR_HOOK, self.OnEscapeKey)
         self.RegularExpression = wx.CheckBox(self, label=_('Regular expression'))
-        pattern_sizer.AddWindow(self.RegularExpression, flag=wx.GROW)
+        pattern_sizer.Add(self.RegularExpression, flag=wx.GROW)
 
         scope_staticbox = wx.StaticBox(self, label=_('Scope'))
         scope_sizer = wx.StaticBoxSizer(scope_staticbox, wx.HORIZONTAL)
-        main_sizer.AddSizer(scope_sizer, border=20,
+        main_sizer.Add(scope_sizer, border=20,
                             flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         scope_selection_sizer = wx.BoxSizer(wx.VERTICAL)
-        scope_sizer.AddSizer(scope_selection_sizer, 1, border=5,
+        scope_sizer.Add(scope_selection_sizer, 1, border=5,
                              flag=wx.GROW | wx.TOP | wx.LEFT | wx.BOTTOM)
 
         self.WholeProject = wx.RadioButton(self, label=_('Whole Project'), style=wx.RB_GROUP)
         self.WholeProject.SetValue(True)
         self.Bind(wx.EVT_RADIOBUTTON, self.OnScopeChanged, self.WholeProject)
-        scope_selection_sizer.AddWindow(self.WholeProject, border=5,
+        scope_selection_sizer.Add(self.WholeProject, border=5,
                                         flag=wx.GROW | wx.BOTTOM)
 
         self.OnlyElements = wx.RadioButton(self, label=_('Only Elements'))
         self.Bind(wx.EVT_RADIOBUTTON, self.OnScopeChanged, self.OnlyElements)
         self.OnlyElements.SetValue(False)
-        scope_selection_sizer.AddWindow(self.OnlyElements, flag=wx.GROW)
+        scope_selection_sizer.Add(self.OnlyElements, flag=wx.GROW)
 
         self.ElementsList = wx.CheckListBox(self)
         self.ElementsList.Enable(False)
-        scope_sizer.AddWindow(self.ElementsList, 1, border=5,
+        scope_sizer.Add(self.ElementsList, 1, border=5,
                               flag=wx.GROW | wx.TOP | wx.RIGHT | wx.BOTTOM)
 
         buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(buttons_sizer, border=20,
+        main_sizer.Add(buttons_sizer, border=20,
                             flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT)
 
         self.FindButton = wx.Button(self, label=_("Find"))
         self.FindButton.SetDefault()
         self.Bind(wx.EVT_BUTTON, self.OnFindButton, self.FindButton)
-        buttons_sizer.AddWindow(self.FindButton, border=5, flag=wx.RIGHT)
+        buttons_sizer.Add(self.FindButton, border=5, flag=wx.RIGHT)
 
         self.CloseButton = wx.Button(self, label=_("Close"))
         self.Bind(wx.EVT_BUTTON, self.OnCloseButton, self.CloseButton)
-        buttons_sizer.AddWindow(self.CloseButton)
+        buttons_sizer.Add(self.CloseButton)
 
         self.SetSizer(main_sizer)
 
--- a/editors/CodeFileEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/CodeFileEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -291,17 +291,16 @@
         doc_end_pos = self.GetLength()
         for section in self.Controler.SECTIONS_NAMES:
             section_comments = self.SectionsComments[section]
-            start_pos = self.FindText(0, doc_end_pos, section_comments["comment"])
-            end_pos = start_pos + len(section_comments["comment"])
-            self.StartStyling(start_pos, 0xff)
+            start_pos, end_pos = self.FindText(0, doc_end_pos, section_comments["comment"])
+            self.StartStyling(start_pos)
             self.SetStyling(end_pos - start_pos, STC_CODE_SECTION)
             self.SetLineState(self.LineFromPosition(start_pos), 1)
 
-        self.StartStyling(end_pos, 0x00)
+        self.StartStyling(end_pos)
         self.SetStyling(doc_end_pos - end_pos, stc.STC_STYLE_DEFAULT)
 
     def DoGetBestSize(self):
-        return self.ParentWindow.GetPanelBestSize()
+        return self.ParentWindow.GetBestSize()
 
     def RefreshModel(self):
         text = self.GetText()
@@ -597,9 +596,9 @@
                 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.StartStyling(highlight_start_pos)
             self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
-            self.StartStyling(highlight_end_pos, 0x00)
+            self.StartStyling(highlight_end_pos)
             self.SetStyling(len(self.GetText()) - highlight_end_pos, stc.STC_STYLE_DEFAULT)
 
 
@@ -614,8 +613,7 @@
 
 class ClassGridCellEditor(wx.grid.GridCellChoiceEditor):
     def __init__(self, table, row, col):
-        wx.grid.GridCellChoiceEditor.__init__(self)
-        self.SetParameters("input,memory,output")
+        wx.grid.GridCellChoiceEditor.__init__(self,["input","memory","output"])
 
 
 class VariablesTable(CustomTable):
@@ -678,7 +676,7 @@
         main_sizer.AddGrowableRow(0)
 
         controls_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(controls_sizer, border=5, flag=wx.ALL)
+        main_sizer.Add(controls_sizer, border=5, flag=wx.ALL)
 
         for name, bitmap, help in [
                 ("AddVariableButton", "add_element", _("Add variable")),
@@ -687,15 +685,15 @@
                 ("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)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            controls_sizer.AddWindow(button, border=5, flag=wx.BOTTOM)
+            controls_sizer.Add(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_CHANGING, 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)
+        main_sizer.Add(self.VariablesGrid, flag=wx.GROW)
 
         self.SetSizer(main_sizer)
 
@@ -785,7 +783,7 @@
         self.VariablesGrid.RefreshButtons()
 
     def DoGetBestSize(self):
-        return self.ParentWindow.GetPanelBestSize()
+        return self.ParentWindow.GetBestSize()
 
     def ShowErrorMessage(self, message):
         dialog = wx.MessageDialog(self, message, _("Error"), wx.OK | wx.ICON_ERROR)
@@ -836,7 +834,7 @@
             type_menu.AppendMenu(wx.ID_ANY, "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())
+            self.VariablesGrid.PopupMenu(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
             type_menu.Destroy()
             event.Veto()
         else:
--- a/editors/ConfTreeNodeEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/ConfTreeNodeEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -120,7 +120,7 @@
 
         bitmap = GetBitmap(bitmapname)
         if bitmap is None:
-            bitmap = wx.EmptyBitmap(0, 0)
+            bitmap = wx.Bitmap()
 
         wx.StaticBitmap.__init__(self, parent, ID,
                                  bitmap,
@@ -148,18 +148,18 @@
 
             if self.SHOW_BASE_PARAMS:
                 baseparamseditor_sizer = wx.BoxSizer(wx.HORIZONTAL)
-                self.MainSizer.AddSizer(baseparamseditor_sizer, border=5,
+                self.MainSizer.Add(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"]))
-                baseparamseditor_sizer.AddWindow(self.FullIECChannel,
+                baseparamseditor_sizer.Add(self.FullIECChannel,
                                                  flag=wx.ALIGN_CENTER_VERTICAL)
 
                 updownsizer = wx.BoxSizer(wx.VERTICAL)
-                baseparamseditor_sizer.AddSizer(updownsizer, border=5,
+                baseparamseditor_sizer.Add(updownsizer, border=5,
                                                 flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
 
                 self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(
@@ -169,14 +169,14 @@
                     style=wx.NO_BORDER)
                 self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1),
                                        self.IECCUpButton)
-                updownsizer.AddWindow(self.IECCUpButton, flag=wx.ALIGN_LEFT)
+                updownsizer.Add(self.IECCUpButton, flag=wx.ALIGN_LEFT)
 
                 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)
+                updownsizer.Add(self.IECCDownButton, flag=wx.ALIGN_LEFT)
 
                 self.ConfNodeName = wx.TextCtrl(self.Editor,
                                                 size=wx.Size(150, 25))
@@ -187,17 +187,17 @@
                     wx.EVT_TEXT,
                     self.GetTextCtrlCallBackFunction(self.ConfNodeName, "BaseParams.Name", True),
                     self.ConfNodeName)
-                baseparamseditor_sizer.AddWindow(
+                baseparamseditor_sizer.Add(
                     self.ConfNodeName, border=5,
                     flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
 
                 buttons_sizer = self.GenerateMethodButtonSizer()
-                baseparamseditor_sizer.AddSizer(buttons_sizer, flag=wx.ALIGN_CENTER)
+                baseparamseditor_sizer.Add(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)
+                self.MainSizer.Add(self.ConfNodeNoteBook, 1, flag=wx.GROW)
             else:
                 parent = self.Editor
                 self.ConfNodeNoteBook = None
@@ -212,7 +212,7 @@
             if self.ConfNodeNoteBook is not None:
                 self.ConfNodeNoteBook.AddPage(editor, title)
             elif self.SHOW_BASE_PARAMS:
-                self.MainSizer.AddWindow(editor, 1, flag=wx.GROW)
+                self.MainSizer.Add(editor, 1, flag=wx.GROW)
             else:
                 self.Editor = editor
 
@@ -232,7 +232,7 @@
             self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
 
             self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
-            self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5,
+            self.ParamsEditorSizer.Add(self.ConfNodeParamsSizer, border=5,
                                             flag=wx.LEFT | wx.RIGHT | wx.BOTTOM)
 
             self.RefreshConfNodeParamsSizer()
@@ -240,7 +240,7 @@
             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)
+                self.MainSizer.Add(self.ParamsEditor, 1, flag=wx.GROW)
             else:
                 self.Editor = self.ParamsEditor
         else:
@@ -317,7 +317,7 @@
                                              label=confnode_method["name"],
                                              style=wx.NO_BORDER)
                 button.SetFont(normal_bt_font)
-                button.SetToolTipString(confnode_method["tooltip"])
+                button.SetToolTip(confnode_method["tooltip"])
                 if confnode_method.get("push", False):
                     button.Bind(wx.EVT_LEFT_DOWN, self.GetButtonCallBackFunction(confnode_method["method"], True))
                 else:
@@ -335,7 +335,7 @@
                 # hack to force size to mini
                 if not confnode_method.get("enabled", True):
                     button.Disable()
-                msizer.AddWindow(button, flag=wx.ALIGN_CENTER)
+                msizer.Add(button, flag=wx.ALIGN_CENTER)
         return msizer
 
     def UriOptions(self, event):
@@ -380,32 +380,32 @@
                 flags = (wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
                 if first:
                     flags |= wx.TOP
-                sizer.AddSizer(staticboxsizer, border=5, flag=flags)
+                sizer.Add(staticboxsizer, border=5, flag=flags)
                 self.GenerateSizerElements(staticboxsizer,
                                            element_infos["children"],
                                            element_path)
             else:
-                boxsizer = wx.FlexGridSizer(cols=4, rows=1)
+                boxsizer = wx.FlexGridSizer(cols=4, rows=1, gap=wx.Size(0,0))
                 boxsizer.AddGrowableCol(1)
                 flags = (wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
                 if first:
                     flags |= wx.TOP
-                sizer.AddSizer(boxsizer, border=5, flag=flags)
+                sizer.Add(boxsizer, border=5, flag=flags)
                 staticbitmap = GenStaticBitmap(
                     ID=-1, bitmapname=element_infos["name"],
                     name="%s_bitmap" % element_infos["name"], parent=self.ParamsEditor,
                     pos=wx.Point(0, 0), size=wx.Size(24, 24), style=0)
-                boxsizer.AddWindow(staticbitmap, border=5, flag=wx.RIGHT)
+                boxsizer.Add(staticbitmap, border=5, flag=wx.RIGHT)
 
                 statictext = wx.StaticText(self.ParamsEditor,
                                            label="%s:" % _(element_infos["name"]))
-                boxsizer.AddWindow(statictext, border=5,
+                boxsizer.Add(statictext, border=5,
                                    flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT)
 
                 if isinstance(element_infos["type"], list):
                     if isinstance(element_infos["value"], tuple):
                         browse_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
-                        boxsizer.AddSizer(browse_boxsizer)
+                        boxsizer.Add(browse_boxsizer)
 
                         textctrl = wx.TextCtrl(self.ParamsEditor,
                                                size=wx.Size(275, -1), style=wx.TE_READONLY)
@@ -414,10 +414,10 @@
                             value_infos = element_infos["value"][1]
                         else:
                             value_infos = None
-                        browse_boxsizer.AddWindow(textctrl)
+                        browse_boxsizer.Add(textctrl)
 
                         button = wx.Button(self.ParamsEditor, label="...")
-                        browse_boxsizer.AddWindow(button)
+                        browse_boxsizer.Add(button)
                         button.Bind(wx.EVT_BUTTON,
                                     self.GetBrowseCallBackFunction(element_infos["name"], textctrl, element_infos["type"],
                                                                    value_infos, element_path),
@@ -425,7 +425,7 @@
                     else:
                         combobox = wx.ComboBox(self.ParamsEditor,
                                                size=wx.Size(300, -1), style=wx.CB_READONLY)
-                        boxsizer.AddWindow(combobox)
+                        boxsizer.Add(combobox)
 
                         if element_infos["use"] == "optional":
                             combobox.Append("")
@@ -439,7 +439,7 @@
                                                      label="%s - %s" % (_(name), _(value)),
                                                      size=wx.Size(10, 0))
                             staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
-                            sizer.AddSizer(staticboxsizer, border=5, flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
+                            sizer.Add(staticboxsizer, border=5, flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
                             self.GenerateSizerElements(staticboxsizer, element_infos["children"], element_path)
                             callback = self.GetChoiceContentCallBackFunction(combobox, staticboxsizer, element_path)
                         else:
@@ -463,7 +463,7 @@
                                            size=wx.Size(300, -1),
                                            style=wx.SP_ARROW_KEYS | wx.ALIGN_RIGHT)
                     spinctrl.SetRange(scmin, scmax)
-                    boxsizer.AddWindow(spinctrl)
+                    boxsizer.Add(spinctrl)
                     if element_infos["value"] is not None:
                         spinctrl.SetValue(element_infos["value"])
                     spinctrl.Bind(wx.EVT_SPINCTRL,
@@ -473,7 +473,7 @@
                 else:
                     if element_infos["type"] == "boolean":
                         checkbox = wx.CheckBox(self.ParamsEditor)
-                        boxsizer.AddWindow(checkbox, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT)
+                        boxsizer.Add(checkbox, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT)
                         if element_infos["value"] is not None:
                             checkbox.SetValue(element_infos["value"])
                         checkbox.Bind(wx.EVT_CHECKBOX,
@@ -490,7 +490,7 @@
                                                size=wx.Size(300, -1),
                                                style=wx.SP_ARROW_KEYS | wx.ALIGN_RIGHT)
                         spinctrl.SetRange(scmin, scmax)
-                        boxsizer.AddWindow(spinctrl)
+                        boxsizer.Add(spinctrl)
                         if element_infos["value"] is not None:
                             spinctrl.SetValue(element_infos["value"])
                         spinctrl.Bind(wx.EVT_SPINCTRL,
@@ -513,12 +513,12 @@
                             self.EditButton = wx.Button(self.ParamsEditor, label='...', size=wx.Size(30, -1))
                             self.Bind(wx.EVT_BUTTON, self.UriOptions, self.EditButton)
 
-                            uriSizer.AddWindow(textctrl, flag=wx.GROW)
-                            uriSizer.AddWindow(self.EditButton, flag=wx.GROW)
-
-                            boxsizer.AddWindow(uriSizer)
+                            uriSizer.Add(textctrl, flag=wx.GROW)
+                            uriSizer.Add(self.EditButton, flag=wx.GROW)
+
+                            boxsizer.Add(uriSizer)
                         else:
-                            boxsizer.AddWindow(textctrl)
+                            boxsizer.Add(textctrl)
 
                         if element_infos["value"] is not None:
                             textctrl.ChangeValue(str(element_infos["value"]))
@@ -535,7 +535,7 @@
                               self.GetResetFunction(element_path),
                               bt)
 
-                    boxsizer.AddWindow(bt, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT)
+                    boxsizer.Add(bt, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT)
             first = False
         sizer.Layout()
         self.RefreshScrollbars()
--- a/editors/DataTypeEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/DataTypeEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -47,7 +47,7 @@
 
 
 def AppendMenu(parent, help, kind, text):
-    return parent.Append(help=help, id=wx.ID_ANY, kind=kind, text=text)
+    return parent.Append(wx.MenuItem(helpString=help, id=wx.ID_ANY, kind=kind, text=text))
 
 
 def GetElementsTableColnames():
@@ -155,49 +155,49 @@
         self.MainSizer.AddGrowableRow(1)
 
         top_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        self.MainSizer.AddSizer(top_sizer, border=5,
+        self.MainSizer.Add(top_sizer, border=5,
                                 flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         derivation_type_label = wx.StaticText(self.Editor, label=_('Derivation Type:'))
-        top_sizer.AddWindow(derivation_type_label, border=5,
+        top_sizer.Add(derivation_type_label, border=5,
                             flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT)
 
         self.DerivationType = wx.ComboBox(self.Editor,
                                           size=wx.Size(200, -1), style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnDerivationTypeChanged, self.DerivationType)
-        top_sizer.AddWindow(self.DerivationType, border=5, flag=wx.GROW | wx.RIGHT)
+        top_sizer.Add(self.DerivationType, border=5, flag=wx.GROW | wx.RIGHT)
 
         typeinfos_staticbox = wx.StaticBox(self.Editor, label=_('Type infos:'))
         typeinfos_sizer = wx.StaticBoxSizer(typeinfos_staticbox, wx.HORIZONTAL)
-        self.MainSizer.AddSizer(typeinfos_sizer, border=5,
+        self.MainSizer.Add(typeinfos_sizer, border=5,
                                 flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         # Panel for Directly derived data types
 
         self.DirectlyPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.DirectlyPanel, 1)
+        typeinfos_sizer.Add(self.DirectlyPanel, 1)
 
         directly_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
 
         directly_basetype_label = wx.StaticText(self.DirectlyPanel,
                                                 label=_('Base Type:'))
-        directly_panel_sizer.AddWindow(directly_basetype_label, 1, border=5,
+        directly_panel_sizer.Add(directly_basetype_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.DirectlyBaseType = wx.ComboBox(self.DirectlyPanel, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.DirectlyBaseType)
-        directly_panel_sizer.AddWindow(self.DirectlyBaseType, 1, border=5,
+        directly_panel_sizer.Add(self.DirectlyBaseType, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         directly_initialvalue_label = wx.StaticText(self.DirectlyPanel,
                                                     label=_('Initial Value:'))
-        directly_panel_sizer.AddWindow(directly_initialvalue_label, 1, border=5,
+        directly_panel_sizer.Add(directly_initialvalue_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.DirectlyInitialValue = wx.TextCtrl(self.DirectlyPanel,
                                                 style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.DirectlyInitialValue)
-        directly_panel_sizer.AddWindow(self.DirectlyInitialValue, 1, border=5,
+        directly_panel_sizer.Add(self.DirectlyInitialValue, 1, border=5,
                                        flag=wx.ALL)
 
         self.DirectlyPanel.SetSizer(directly_panel_sizer)
@@ -205,52 +205,52 @@
         # Panel for Subrange data types
 
         self.SubrangePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.SubrangePanel, 1)
+        typeinfos_sizer.Add(self.SubrangePanel, 1)
 
         subrange_panel_sizer = wx.GridSizer(cols=4, hgap=5, rows=3, vgap=0)
 
         subrange_basetype_label = wx.StaticText(self.SubrangePanel,
                                                 label=_('Base Type:'))
-        subrange_panel_sizer.AddWindow(subrange_basetype_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_basetype_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeBaseType = wx.ComboBox(self.SubrangePanel, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnSubrangeBaseTypeChanged,
                   self.SubrangeBaseType)
-        subrange_panel_sizer.AddWindow(self.SubrangeBaseType, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeBaseType, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         subrange_initialvalue_label = wx.StaticText(self.SubrangePanel,
                                                     label=_('Initial Value:'))
-        subrange_panel_sizer.AddWindow(subrange_initialvalue_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_initialvalue_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeInitialValue = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
         self.SubrangeInitialValue.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnInfosChanged)
-        subrange_panel_sizer.AddWindow(self.SubrangeInitialValue, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeInitialValue, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         subrange_minimum_label = wx.StaticText(self.SubrangePanel, label=_('Minimum:'))
-        subrange_panel_sizer.AddWindow(subrange_minimum_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_minimum_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeMinimum = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
         self.SubrangeMinimum.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnSubrangeMinimumChanged)
-        subrange_panel_sizer.AddWindow(self.SubrangeMinimum, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeMinimum, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         for dummy in xrange(2):
-            subrange_panel_sizer.AddWindow(wx.Size(0, 0), 1)
+            subrange_panel_sizer.Add(wx.Size(0, 0), 1)
 
         subrange_maximum_label = wx.StaticText(self.SubrangePanel,
                                                label=_('Maximum:'))
-        subrange_panel_sizer.AddWindow(subrange_maximum_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_maximum_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeMaximum = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
         self.SubrangeMaximum.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnSubrangeMaximumChanged)
 
-        subrange_panel_sizer.AddWindow(self.SubrangeMaximum, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeMaximum, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         self.SubrangePanel.SetSizer(subrange_panel_sizer)
@@ -258,35 +258,35 @@
         # Panel for Enumerated data types
 
         self.EnumeratedPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.EnumeratedPanel, 1)
+        typeinfos_sizer.Add(self.EnumeratedPanel, 1)
 
         enumerated_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
 
         self.EnumeratedValues = CustomEditableListBox(
             self.EnumeratedPanel,
             label=_("Values:"),
-            style=(wx.gizmos.EL_ALLOW_NEW |
-                   wx.gizmos.EL_ALLOW_EDIT |
-                   wx.gizmos.EL_ALLOW_DELETE))
+            style=(wx.adv.EL_ALLOW_NEW |
+                   wx.adv.EL_ALLOW_EDIT |
+                   wx.adv.EL_ALLOW_DELETE))
         setattr(self.EnumeratedValues, "_OnLabelEndEdit", self.OnEnumeratedValueEndEdit)
         for func in ["_OnAddButton", "_OnDelButton", "_OnUpButton", "_OnDownButton"]:
             setattr(self.EnumeratedValues, func, self.OnEnumeratedValuesChanged)
-        enumerated_panel_sizer.AddWindow(self.EnumeratedValues, 1, border=5,
+        enumerated_panel_sizer.Add(self.EnumeratedValues, 1, border=5,
                                          flag=wx.GROW | wx.ALL)
 
         enumerated_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
-        enumerated_panel_sizer.AddSizer(enumerated_panel_rightsizer, 1)
+        enumerated_panel_sizer.Add(enumerated_panel_rightsizer, 1)
 
         enumerated_initialvalue_label = wx.StaticText(self.EnumeratedPanel,
                                                       label=_('Initial Value:'))
-        enumerated_panel_rightsizer.AddWindow(enumerated_initialvalue_label, 1,
+        enumerated_panel_rightsizer.Add(enumerated_initialvalue_label, 1,
                                               border=5,
                                               flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.EnumeratedInitialValue = wx.ComboBox(self.EnumeratedPanel,
                                                   style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.EnumeratedInitialValue)
-        enumerated_panel_rightsizer.AddWindow(self.EnumeratedInitialValue, 1,
+        enumerated_panel_rightsizer.Add(self.EnumeratedInitialValue, 1,
                                               border=5, flag=wx.ALL)
 
         self.EnumeratedPanel.SetSizer(enumerated_panel_sizer)
@@ -294,7 +294,7 @@
         # Panel for Array data types
 
         self.ArrayPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.ArrayPanel, 1)
+        typeinfos_sizer.Add(self.ArrayPanel, 1)
 
         array_panel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=0)
         array_panel_sizer.AddGrowableCol(0)
@@ -302,41 +302,41 @@
         array_panel_sizer.AddGrowableRow(1)
 
         array_panel_leftSizer = wx.BoxSizer(wx.HORIZONTAL)
-        array_panel_sizer.AddSizer(array_panel_leftSizer, flag=wx.GROW)
+        array_panel_sizer.Add(array_panel_leftSizer, flag=wx.GROW)
 
         array_basetype_label = wx.StaticText(self.ArrayPanel, label=_('Base Type:'))
-        array_panel_leftSizer.AddWindow(array_basetype_label, 1, border=5,
+        array_panel_leftSizer.Add(array_basetype_label, 1, border=5,
                                         flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.ArrayBaseType = wx.ComboBox(self.ArrayPanel, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.ArrayBaseType)
-        array_panel_leftSizer.AddWindow(self.ArrayBaseType, 1, border=5,
+        array_panel_leftSizer.Add(self.ArrayBaseType, 1, border=5,
                                         flag=wx.GROW | wx.ALL)
 
         array_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
-        array_panel_sizer.AddSizer(array_panel_rightsizer, flag=wx.GROW)
+        array_panel_sizer.Add(array_panel_rightsizer, flag=wx.GROW)
 
         array_initialvalue_label = wx.StaticText(self.ArrayPanel,
                                                  label=_('Initial Value:'))
-        array_panel_rightsizer.AddWindow(array_initialvalue_label, 1, border=5,
+        array_panel_rightsizer.Add(array_initialvalue_label, 1, border=5,
                                          flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.ArrayInitialValue = wx.TextCtrl(self.ArrayPanel,
                                              style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.ArrayInitialValue)
-        array_panel_rightsizer.AddWindow(self.ArrayInitialValue, 1, border=5,
+        array_panel_rightsizer.Add(self.ArrayInitialValue, 1, border=5,
                                          flag=wx.ALL)
 
         self.ArrayDimensions = CustomEditableListBox(
             self.ArrayPanel,
             label=_("Dimensions:"),
-            style=(wx.gizmos.EL_ALLOW_NEW |
-                   wx.gizmos.EL_ALLOW_EDIT |
-                   wx.gizmos.EL_ALLOW_DELETE))
+            style=(wx.adv.EL_ALLOW_NEW |
+                   wx.adv.EL_ALLOW_EDIT |
+                   wx.adv.EL_ALLOW_DELETE))
         for func in ["_OnLabelEndEdit", "_OnAddButton", "_OnDelButton",
                      "_OnUpButton", "_OnDownButton"]:
             setattr(self.ArrayDimensions, func, self.OnDimensionsChanged)
-        array_panel_sizer.AddWindow(self.ArrayDimensions, 0, border=5,
+        array_panel_sizer.Add(self.ArrayDimensions, 0, border=5,
                                     flag=wx.GROW | wx.ALL)
 
         self.ArrayPanel.SetSizer(array_panel_sizer)
@@ -344,7 +344,7 @@
         # Panel for Structure data types
 
         self.StructurePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.StructurePanel, 1)
+        typeinfos_sizer.Add(self.StructurePanel, 1)
 
         structure_panel_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
         structure_panel_sizer.AddGrowableCol(0)
@@ -353,12 +353,12 @@
         structure_button_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         structure_button_sizer.AddGrowableCol(0)
         structure_button_sizer.AddGrowableRow(0)
-        structure_panel_sizer.AddSizer(structure_button_sizer, 0, border=5,
+        structure_panel_sizer.Add(structure_button_sizer, 0, border=5,
                                        flag=wx.ALL | wx.GROW)
 
         structure_elements_label = wx.StaticText(self.StructurePanel,
                                                  label=_('Elements :'))
-        structure_button_sizer.AddWindow(structure_elements_label, flag=wx.ALIGN_BOTTOM)
+        structure_button_sizer.Add(structure_elements_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("StructureAddButton", "add_element", _("Add element")),
@@ -369,17 +369,17 @@
                                                     bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28),
                                                     style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            structure_button_sizer.AddWindow(button)
+            structure_button_sizer.Add(button)
 
         self.StructureElementsGrid = CustomGrid(self.StructurePanel,
                                                 size=wx.Size(0, 150), style=wx.VSCROLL)
-        self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED,
                                         self.OnStructureElementsGridCellChange)
         self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
                                         self.OnStructureElementsGridEditorShown)
-        structure_panel_sizer.AddWindow(self.StructureElementsGrid, flag=wx.GROW)
+        structure_panel_sizer.Add(self.StructureElementsGrid, flag=wx.GROW)
 
         self.StructurePanel.SetSizer(structure_panel_sizer)
 
@@ -647,7 +647,7 @@
             self.Bind(wx.EVT_MENU, self.ElementArrayTypeFunction, new_entry)
 
             rect = self.StructureElementsGrid.BlockToDeviceRect((row, col), (row, col))
-            self.StructureElementsGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.StructureElementsGrid.GetColLabelSize())
+            self.StructureElementsGrid.PopupMenu(type_menu, rect.x + rect.width, rect.y + self.StructureElementsGrid.GetColLabelSize())
             type_menu.Destroy()
             event.Veto()
         else:
@@ -786,7 +786,7 @@
                 value = control.GetValueStr() if isinstance(control, CustomIntCtrl) else \
                         control.GetValue()
                 control.SetStyle(0, len(value), wx.TextAttr(wx.NullColour))
-            elif isinstance(control, wx.gizmos.EditableListBox):
+            elif isinstance(control, wx.adv.EditableListBox):
                 listctrl = control.GetListCtrl()
                 for i in xrange(listctrl.GetItemCount()):
                     listctrl.SetItemBackgroundColour(i, wx.NullColour)
@@ -811,7 +811,7 @@
                         control.SetForegroundColour(highlight_type[1])
                     elif isinstance(control, wx.TextCtrl):
                         control.SetStyle(start[1], end[1] + 1, wx.TextAttr(highlight_type[1], highlight_type[0]))
-                    elif isinstance(control, wx.gizmos.EditableListBox):
+                    elif isinstance(control, wx.adv.EditableListBox):
                         listctrl = control.GetListCtrl()
                         listctrl.SetItemBackgroundColour(infos[1], highlight_type[0])
                         listctrl.SetItemTextColour(infos[1], highlight_type[1])
--- a/editors/FileManagementPanel.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/FileManagementPanel.py	Sun Jul 17 22:53:35 2022 +0200
@@ -43,14 +43,14 @@
         main_sizer = wx.BoxSizer(wx.HORIZONTAL)
 
         left_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(left_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(left_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
 
         managed_dir_label = wx.StaticText(self.Editor, label=_(self.TagName) + ":")
-        left_sizer.AddWindow(managed_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
+        left_sizer.Add(managed_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
 
         FILTER = _("All files (*.*)|*.*|CSV files (*.csv)|*.csv")
         self.ManagedDir = FolderTree(self.Editor, self.Folder, FILTER)
-        left_sizer.AddWindow(self.ManagedDir, 1, flag=wx.GROW)
+        left_sizer.Add(self.ManagedDir, 1, flag=wx.GROW)
 
         managed_treectrl = self.ManagedDir.GetTreeCtrl()
         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, managed_treectrl)
@@ -58,7 +58,7 @@
             self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, managed_treectrl)
 
         button_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(button_sizer, border=5,
+        main_sizer.Add(button_sizer, border=5,
                             flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
 
         for idx, (name, bitmap, help) in enumerate([
@@ -70,26 +70,26 @@
                 self.Editor,
                 bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
             if idx > 0:
                 flag = wx.TOP
             else:
                 flag = 0
             self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
-            button_sizer.AddWindow(button, border=20, flag=flag)
+            button_sizer.Add(button, border=20, flag=flag)
 
         right_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(right_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(right_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
 
         if wx.Platform == '__WXMSW__':
             system_dir_label = wx.StaticText(self.Editor, label=_("My Computer:"))
         else:
             system_dir_label = wx.StaticText(self.Editor, label=_("Home Directory:"))
-        right_sizer.AddWindow(system_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
+        right_sizer.Add(system_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
 
         self.SystemDir = FolderTree(self.Editor, self.HomeDirectory, FILTER, False)
-        right_sizer.AddWindow(self.SystemDir, 1, flag=wx.GROW)
+        right_sizer.Add(self.SystemDir, 1, flag=wx.GROW)
 
         system_treectrl = self.SystemDir.GetTreeCtrl()
         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, system_treectrl)
--- a/editors/ProjectNodeEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/ProjectNodeEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -60,7 +60,7 @@
         ConfTreeNodeEditor.__init__(self, parent, controler, window, tagname)
 
         buttons_sizer = self.GenerateMethodButtonSizer()
-        self.MainSizer.InsertSizer(0, buttons_sizer, 0, border=5, flag=wx.ALL)
+        self.MainSizer.Insert(0, buttons_sizer, 0, border=5, flag=wx.ALL)
         self.MainSizer.Layout()
 
         self.VariableEditor = self.VariableEditorPanel
--- a/editors/ResourceEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/ResourceEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -76,10 +76,6 @@
     return [_("Interrupt"), _("Cyclic")]
 
 
-def SingleCellEditor(*x):
-    return wx.grid.GridCellChoiceEditor()
-
-
 def CheckSingle(single, varlist):
     return single in varlist
 
@@ -162,25 +158,21 @@
                     if interval != "" and IEC_TIME_MODEL.match(interval.upper()) is None:
                         error = True
                 elif colname == "Single":
-                    editor = SingleCellEditor(self, colname)
-                    editor.SetParameters(self.Parent.VariableList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.VariableList)
                     if self.GetValueByName(row, "Triggering") != "Interrupt":
                         grid.SetReadOnly(row, col, True)
                     single = self.GetValueByName(row, colname)
                     if single != "" and not CheckSingle(single, self.Parent.VariableList):
                         error = True
                 elif colname == "Triggering":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(",".join(map(_, GetTaskTriggeringOptions())))
+                    editor = wx.grid.GridCellChoiceEditor(map(_, GetTaskTriggeringOptions()))
                 elif colname == "Type":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.TypeList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.TypeList)
                 elif colname == "Priority":
                     editor = wx.grid.GridCellNumberEditor()
                     editor.SetParameters("0,65535")
                 elif colname == "Task":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.TaskList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.TaskList)
 
                 grid.SetCellEditor(row, col, editor)
                 grid.SetCellRenderer(row, col, renderer)
@@ -230,16 +222,16 @@
         tasks_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
         tasks_sizer.AddGrowableCol(0)
         tasks_sizer.AddGrowableRow(1)
-        main_sizer.AddSizer(tasks_sizer, border=5,
+        main_sizer.Add(tasks_sizer, border=5,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         tasks_buttons_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         tasks_buttons_sizer.AddGrowableCol(0)
         tasks_buttons_sizer.AddGrowableRow(0)
-        tasks_sizer.AddSizer(tasks_buttons_sizer, flag=wx.GROW)
+        tasks_sizer.Add(tasks_buttons_sizer, flag=wx.GROW)
 
         tasks_label = wx.StaticText(self.Editor, label=_(u'Tasks:'))
-        tasks_buttons_sizer.AddWindow(tasks_label, flag=wx.ALIGN_BOTTOM)
+        tasks_buttons_sizer.Add(tasks_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("AddTaskButton", "add_element", _("Add task")),
@@ -250,27 +242,27 @@
                                                     bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28),
                                                     style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            tasks_buttons_sizer.AddWindow(button)
+            tasks_buttons_sizer.Add(button)
 
         self.TasksGrid = CustomGrid(self.Editor, style=wx.VSCROLL)
-        self.TasksGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnTasksGridCellChange)
-        tasks_sizer.AddWindow(self.TasksGrid, flag=wx.GROW)
+        self.TasksGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.OnTasksGridCellChange)
+        tasks_sizer.Add(self.TasksGrid, flag=wx.GROW)
 
         instances_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
         instances_sizer.AddGrowableCol(0)
         instances_sizer.AddGrowableRow(1)
-        main_sizer.AddSizer(instances_sizer, border=5,
+        main_sizer.Add(instances_sizer, border=5,
                             flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         instances_buttons_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         instances_buttons_sizer.AddGrowableCol(0)
         instances_buttons_sizer.AddGrowableRow(0)
-        instances_sizer.AddSizer(instances_buttons_sizer, flag=wx.GROW)
+        instances_sizer.Add(instances_buttons_sizer, flag=wx.GROW)
 
         instances_label = wx.StaticText(self.Editor, label=_(u'Instances:'))
-        instances_buttons_sizer.AddWindow(instances_label, flag=wx.ALIGN_BOTTOM)
+        instances_buttons_sizer.Add(instances_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("AddInstanceButton", "add_element", _("Add instance")),
@@ -280,13 +272,13 @@
             button = wx.lib.buttons.GenBitmapButton(
                 self.Editor, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            instances_buttons_sizer.AddWindow(button)
+            instances_buttons_sizer.Add(button)
 
         self.InstancesGrid = CustomGrid(self.Editor, style=wx.VSCROLL)
-        self.InstancesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnInstancesGridCellChange)
-        instances_sizer.AddWindow(self.InstancesGrid, flag=wx.GROW)
+        self.InstancesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.OnInstancesGridCellChange)
+        instances_sizer.Add(self.InstancesGrid, flag=wx.GROW)
 
         self.Editor.SetSizer(main_sizer)
 
@@ -405,20 +397,20 @@
         self.RefreshHighlightsTimer.Stop()
 
     def RefreshTypeList(self):
-        self.TypeList = ""
+        self.TypeList = []
         blocktypes = self.Controler.GetBlockResource()
         for blocktype in blocktypes:
-            self.TypeList += ",%s" % blocktype
+            self.TypeList.append(blocktype)
 
     def RefreshTaskList(self):
-        self.TaskList = ""
+        self.TaskList = []
         for row in xrange(self.TasksTable.GetNumberRows()):
-            self.TaskList += ",%s" % self.TasksTable.GetValueByName(row, "Name")
+            self.TaskList.append(self.TasksTable.GetValueByName(row, "Name"))
 
     def RefreshVariableList(self):
-        self.VariableList = ""
+        self.VariableList = []
         for variable in self.Controler.GetEditedResourceVariables(self.TagName):
-            self.VariableList += ",%s" % variable
+            self.VariableList.append(variable)
 
     def RefreshModel(self):
         self.Controler.SetEditedResourceInfos(self.TagName, self.TasksTable.GetData(), self.InstancesTable.GetData())
@@ -481,7 +473,7 @@
                 wx.CallAfter(self.ShowErrorMessage, message)
                 return
 
-            tasklist = [name for name in self.TaskList.split(",") if name != ""]
+            tasklist = [name for name in self.TaskList if name != ""]
             for i in xrange(self.TasksTable.GetNumberRows()):
                 task = self.TasksTable.GetValueByName(i, "Name")
                 if task in tasklist:
--- a/editors/TextViewer.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/TextViewer.py	Sun Jul 17 22:53:35 2022 +0200
@@ -198,12 +198,20 @@
     def Colourise(self, start, end):
         self.Editor.Colourise(start, end)
 
-    def StartStyling(self, pos, mask):
-        self.Editor.StartStyling(pos, mask)
+    def StartStyling(self, pos):
+        self.Editor.StartStyling(pos)
+
+    INDIC0 = 0
+    INDIC1 = 1
+    INDIC2 = 2
 
     def SetStyling(self, length, style):
         self.Editor.SetStyling(length, style)
 
+    def SetIndicatorCurrentFillRange(self, start, length, indic):
+        self.Editor.SetIndicatorCurrent(indic)
+        self.Editor.IndicatorFillRange(start, length)
+
     def GetCurrentPos(self):
         return self.Editor.GetCurrentPos()
 
@@ -560,7 +568,7 @@
             start_pos = last_styled_pos = self.Editor.GetLineEndPosition(line_number - 1) + 1
         self.RefreshLineFolding(line_number)
         end_pos = event.GetPosition()
-        self.StartStyling(start_pos, 0xff)
+        self.StartStyling(start_pos)
 
         current_context = self.Variables
         current_call = None
@@ -595,9 +603,8 @@
                     else:
                         self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY)
                         if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos):
-                            self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK)
-                            self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK)
-                            self.StartStyling(current_pos, 0xff)
+                            self.SetIndicatorCurrentFillRange(last_styled_pos, current_pos - last_styled_pos, self.INDIC0)
+                            self.StartStyling(current_pos)
                 else:
                     self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY)
                 last_styled_pos = current_pos
@@ -698,9 +705,8 @@
                     else:
                         self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY)
                         if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos):
-                            self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK)
-                            self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK)
-                            self.StartStyling(current_pos, 0xff)
+                            self.SetIndicatorCurrentFillRange(last_styled_pos, current_pos - last_styled_pos, self.INDIC0)
+                            self.StartStyling(current_pos)
                     if char == '.':
                         if word != "]":
                             if current_context is not None:
@@ -956,8 +962,8 @@
             else:
                 highlight_end_pos = self.Editor.GetLineEndPosition(end[0] - 1) + end[1] - indent + 2
             if highlight_start_pos < end_pos and highlight_end_pos > start_pos:
-                self.StartStyling(highlight_start_pos, 0xff)
+                self.StartStyling(highlight_start_pos)
                 self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
-                self.StartStyling(highlight_start_pos, 0x00)
+                self.StartStyling(highlight_start_pos)
                 until_end = max(0, len(self.Editor.GetText()) - highlight_end_pos)
                 self.SetStyling(until_end, wx.stc.STC_STYLE_DEFAULT)
--- a/editors/Viewer.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/editors/Viewer.py	Sun Jul 17 22:53:35 2022 +0200
@@ -60,11 +60,11 @@
     global CURSORS
     if CURSORS is None:
         CURSORS = [wx.NullCursor,
-                   wx.StockCursor(wx.CURSOR_HAND),
-                   wx.StockCursor(wx.CURSOR_SIZENWSE),
-                   wx.StockCursor(wx.CURSOR_SIZENESW),
-                   wx.StockCursor(wx.CURSOR_SIZEWE),
-                   wx.StockCursor(wx.CURSOR_SIZENS)]
+                   wx.Cursor(wx.CURSOR_HAND),
+                   wx.Cursor(wx.CURSOR_SIZENWSE),
+                   wx.Cursor(wx.CURSOR_SIZENESW),
+                   wx.Cursor(wx.CURSOR_SIZEWE),
+                   wx.Cursor(wx.CURSOR_SIZENS)]
 
 
 if wx.Platform == '__WXMSW__':
@@ -317,7 +317,7 @@
                             selected = None
                         dialog.Destroy()
                         if selected is None:
-                            return
+                            return False
                         if selected == 0:
                             location = "%I" + location
                         elif selected == 1:
@@ -333,7 +333,7 @@
                     var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                     dlg.Destroy()
                     if var_name is None:
-                        return
+                        return False
                     elif 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)]:
@@ -363,7 +363,7 @@
                     var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                     dlg.Destroy()
                     if var_name is None:
-                        return
+                        return False
                     elif 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)]:
@@ -385,7 +385,7 @@
                 var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                 dlg.Destroy()
                 if var_name is None:
-                    return
+                    return False
                 elif 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)]:
@@ -410,7 +410,7 @@
                     if len(tree[0]) > 0:
                         menu = wx.Menu(title='')
                         self.GenerateTreeMenu(x, y, scaling, menu, "", var_class, [(values[0], values[2], tree)])
-                        self.ParentWindow.PopupMenuXY(menu)
+                        self.ParentWindow.PopupMenu(menu)
                     else:
                         self.ParentWindow.AddVariableBlock(x, y, scaling, var_class, values[0], values[2])
                 else:
@@ -419,6 +419,8 @@
                 message = _("Variable don't belong to this POU!")
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
+        return True
 
     def GenerateTreeMenu(self, x, y, scaling, menu, base_path, var_class, tree):
         for child_name, child_type, (child_tree, child_dimensions) in tree:
@@ -429,8 +431,10 @@
             if len(child_dimensions) > 0:
                 child_path += "[%s]" % ",".join([str(dimension[0]) for dimension in child_dimensions])
                 child_name += "[]"
-            item = menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=child_name)
-            self.ParentWindow.Bind(wx.EVT_MENU, self.GetAddVariableBlockFunction(x, y, scaling, var_class, child_path, child_type), item)
+
+            item = self.AppendItem(menu,
+                child_name,
+                self.GetAddVariableBlockFunction(x, y, scaling, var_class, child_path, child_type))
             if len(child_tree) > 0:
                 child_menu = wx.Menu(title='')
                 self.GenerateTreeMenu(x, y, scaling, child_menu, child_path, var_class, child_tree)
@@ -712,7 +716,7 @@
                 break
             faces["size"] -= 1
         self.Editor.SetFont(font)
-        self.MiniTextDC = wx.MemoryDC(wx.EmptyBitmap(1, 1))
+        self.MiniTextDC = wx.MemoryDC(wx.Bitmap(1, 1))
         self.MiniTextDC.SetFont(wx.Font(faces["size"] * 0.75, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName=faces["helv"]))
 
         self.CurrentScale = None
@@ -825,15 +829,14 @@
     def GetViewScale(self):
         return self.ViewScale
 
-    def GetLogicalDC(self, buffered=False):
-        if buffered:
-            bitmap = wx.EmptyBitmap(*self.Editor.GetClientSize())
-            dc = wx.MemoryDC(bitmap)
-        else:
-            dc = wx.ClientDC(self.Editor)
+    def PrepareDC(self, dc):
         dc.SetFont(self.GetFont())
         self.Editor.DoPrepareDC(dc)
         dc.SetUserScale(self.ViewScale[0], self.ViewScale[1])
+
+    def GetLogicalDC(self):
+        dc = wx.ClientDC(self.Editor)
+        self.PrepareDC(dc)
         return dc
 
     def RefreshRect(self, rect, eraseBackground=True):
@@ -1058,7 +1061,7 @@
             self.SelectedElement.SetSelected(False)
             self.SelectedElement = None
         if self.Mode == MODE_MOTION:
-            wx.CallAfter(self.Editor.SetCursor, wx.StockCursor(wx.CURSOR_HAND))
+            wx.CallAfter(self.Editor.SetCursor, wx.Cursor(wx.CURSOR_HAND))
             self.SavedMode = True
 
     # Return current drawing mode
@@ -1116,13 +1119,13 @@
             if self.DrawGrid:
                 width = max(2, int(scaling[0] * self.ViewScale[0]))
                 height = max(2, int(scaling[1] * self.ViewScale[1]))
-                bitmap = wx.EmptyBitmap(width, height)
+                bitmap = wx.Bitmap(width, height)
                 dc = wx.MemoryDC(bitmap)
                 dc.SetBackground(wx.Brush(self.Editor.GetBackgroundColour()))
                 dc.Clear()
                 dc.SetPen(MiterPen(wx.Colour(180, 180, 180)))
                 dc.DrawPoint(0, 0)
-                self.GridBrush = wx.BrushFromBitmap(bitmap)
+                self.GridBrush = wx.Brush(bitmap)
             else:
                 self.GridBrush = wx.TRANSPARENT_BRUSH
         else:
@@ -1572,10 +1575,15 @@
         iec_path = self.GetElementIECPath(self.SelectedElement)
         if iec_path is not None:
             menu = wx.Menu(title='')
-            item = menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=_("Force value"))
-            self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(iec_path.upper(), self.SelectedElement), item)
-            ritem = menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=_("Release value"))
-            self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(iec_path.upper()), ritem)
+            item = self.AppendItem(menu,
+                _("Force value"),
+                self.GetForceVariableMenuFunction(
+                    iec_path.upper(),
+                    self.SelectedElement))
+
+            ritem = self.AppendItem(menu,
+                _("Release value"),
+                self.GetReleaseVariableMenuFunction(iec_path.upper()))
             if self.SelectedElement.IsForced():
                 ritem.Enable(True)
             else:
@@ -1903,9 +1911,9 @@
 
     def OnViewerMouseEvent(self, event):
         self.ResetBuffer()
-        if event.Leaving() and self.ToolTipElement is not None:
+        if (event.Leaving() or event.RightUp()) and self.ToolTipElement is not None:
             self.ToolTipElement.DestroyToolTip()
-        elif (not event.Entering() and
+        elif (not event.Entering() and not event.RightUp() and
               gettime() - self.LastToolTipCheckTime > REFRESH_PERIOD):
             self.LastToolTipCheckTime = gettime()
             element = None
@@ -3379,7 +3387,7 @@
             element = self.ParentWindow.GetCopyBuffer()
             if bbx is None:
                 mouse_pos = self.Editor.ScreenToClient(wx.GetMousePosition())
-                middle = wx.Rect(0, 0, *self.Editor.GetClientSize()).InsideXY(mouse_pos.x, mouse_pos.y)
+                middle = wx.Rect(0, 0, *self.Editor.GetClientSize()).Contains(mouse_pos.x, mouse_pos.y)
                 if middle:
                     x, y = self.CalcUnscrolledPosition(mouse_pos.x, mouse_pos.y)
                 else:
@@ -3633,7 +3641,6 @@
         else:
             dc.SetBackground(wx.Brush(self.Editor.GetBackgroundColour()))
             dc.Clear()
-            dc.BeginDrawing()
         if self.Scaling is not None and self.DrawGrid and not printing:
             dc.SetPen(wx.TRANSPARENT_PEN)
             dc.SetBrush(self.GridBrush)
@@ -3680,12 +3687,15 @@
                 self.InstanceName.Draw(dc)
             if self.rubberBand.IsShown():
                 self.rubberBand.Draw(dc)
-            dc.EndDrawing()
 
     def OnPaint(self, event):
-        dc = self.GetLogicalDC(True)
+        event.Skip()
+        sx,sy = self.Editor.GetClientSize()
+        if sx <= 0 or sy <= 0 :
+            return
+        dc = wx.MemoryDC(wx.Bitmap(sx,sy))
+        self.PrepareDC(dc)
         self.DoDrawing(dc)
         wx.BufferedPaintDC(self.Editor, dc.GetAsBitmap())
         if self.Debug:
             DebugViewer.RefreshNewData(self)
-        event.Skip()
--- a/etherlab/ConfigEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/etherlab/ConfigEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -16,7 +16,7 @@
 
 import wx
 import wx.grid
-import wx.gizmos
+import wx.adv
 import wx.lib.buttons
 
 from plcopen.structures import IEC_KEYWORDS, TestIdentifier
@@ -81,9 +81,9 @@
         self.VariablesFilter.Bind(wx.EVT_COMBOBOX, self.OnVariablesFilterChanged)
         self.VariablesFilter.Bind(wx.EVT_TEXT_ENTER, self.OnVariablesFilterChanged)
         self.VariablesFilter.Bind(wx.EVT_CHAR, self.OnVariablesFilterKeyDown)
-        self.AddWindow(self.VariablesFilter, flag=wx.GROW)
-
-        self.VariablesGrid = wx.gizmos.TreeListCtrl(parent,
+        self.Add(self.VariablesFilter, flag=wx.GROW)
+
+        self.VariablesGrid = wx.adv.TreeListCtrl(parent,
                                                     style=wx.TR_DEFAULT_STYLE |
                                                     wx.TR_ROW_LINES |
                                                     wx.TR_COLUMN_LINES |
@@ -91,7 +91,7 @@
                                                     wx.TR_FULL_ROW_HIGHLIGHT)
         self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN,
                                                 self.OnVariablesGridLeftClick)
-        self.AddWindow(self.VariablesGrid, flag=wx.GROW)
+        self.Add(self.VariablesGrid, flag=wx.GROW)
 
         self.Filters = []
         for desc, value in VARIABLES_FILTERS:
@@ -274,10 +274,10 @@
 
         variables_label = wx.StaticText(self.EthercatNodeEditor,
                                         label=_('Variable entries:'))
-        main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP | wx.LEFT | wx.RIGHT)
+        main_sizer.Add(variables_label, border=10, flag=wx.TOP | wx.LEFT | wx.RIGHT)
 
         self.NodeVariables = NodeVariablesSizer(self.EthercatNodeEditor, self.Controler)
-        main_sizer.AddSizer(self.NodeVariables, border=10,
+        main_sizer.Add(self.NodeVariables, border=10,
                             flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.EthercatNodeEditor.SetSizer(main_sizer)
@@ -310,7 +310,7 @@
 
         self.EtherCATManagementTreebook = EtherCATManagementTreebook(self.EtherCATManagementEditor, self.Controler, self)
 
-        self.EtherCATManagermentEditor_Main_Sizer.AddSizer(self.EtherCATManagementTreebook, border=10, flag=wx.GROW)
+        self.EtherCATManagermentEditor_Main_Sizer.Add(self.EtherCATManagementTreebook, border=10, flag=wx.GROW)
 
         self.EtherCATManagementEditor.SetSizer(self.EtherCATManagermentEditor_Main_Sizer)
         return self.EtherCATManagementEditor
@@ -617,7 +617,7 @@
 
         self.MasterStateEditor_Panel = MasterStatePanelClass(self.MasterStateEditor, self.Controler)
 
-        self.MasterStateEditor_Panel_Main_Sizer.AddSizer(self.MasterStateEditor_Panel, border=10, flag=wx.GROW)
+        self.MasterStateEditor_Panel_Main_Sizer.Add(self.MasterStateEditor_Panel, border=10, flag=wx.GROW)
 
         self.MasterStateEditor.SetSizer(self.MasterStateEditor_Panel_Main_Sizer)
         return self.MasterStateEditor
@@ -651,7 +651,7 @@
 
         process_variables_label = wx.StaticText(self.EthercatMasterEditor,
                                                 label=_("Process variables mapped between nodes:"))
-        process_variables_header.AddWindow(process_variables_label, 1,
+        process_variables_header.Add(process_variables_label, 1,
                                            flag=wx.ALIGN_CENTER_VERTICAL)
 
         for name, bitmap, help in [
@@ -661,14 +661,14 @@
                 ("DownVariableButton", "down", _("Move process variable down"))]:
             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            process_variables_header.AddWindow(button, border=5, flag=wx.LEFT)
+            process_variables_header.Add(button, border=5, flag=wx.LEFT)
 
         self.ProcessVariablesGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
         self.ProcessVariablesGrid.SetMinSize(wx.Size(0, 150))
         self.ProcessVariablesGrid.SetDropTarget(ProcessVariableDropTarget(self))
-        self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING,
                                        self.OnProcessVariablesGridCellChange)
         self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
                                        self.OnProcessVariablesGridCellLeftClick)
@@ -678,7 +678,7 @@
 
         startup_commands_label = wx.StaticText(self.EthercatMasterEditor,
                                                label=_("Startup service variables assignments:"))
-        startup_commands_header.AddWindow(startup_commands_label, 1,
+        startup_commands_header.Add(startup_commands_label, 1,
                                           flag=wx.ALIGN_CENTER_VERTICAL)
 
         for name, bitmap, help in [
@@ -686,14 +686,14 @@
                 ("DeleteCommandButton", "remove_element", _("Remove startup service variable"))]:
             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            startup_commands_header.AddWindow(button, border=5, flag=wx.LEFT)
+            startup_commands_header.Add(button, border=5, flag=wx.LEFT)
 
         self.StartupCommandsGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
         self.StartupCommandsGrid.SetDropTarget(StartupCommandDropTarget(self))
         self.StartupCommandsGrid.SetMinSize(wx.Size(0, 150))
-        self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING,
                                       self.OnStartupCommandsGridCellChange)
         self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
                                       self.OnStartupCommandsGridEditorShow)
@@ -702,29 +702,29 @@
 
         main_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Node filter:"))
         staticbox_sizer = wx.StaticBoxSizer(main_staticbox, wx.VERTICAL)
-        self.EthercatMasterEditorSizer.AddSizer(staticbox_sizer, 0, border=10, flag=wx.GROW | wx.ALL)
+        self.EthercatMasterEditorSizer.Add(staticbox_sizer, 0, border=10, flag=wx.GROW | wx.ALL)
 
         main_staticbox_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=0)
         main_staticbox_sizer.AddGrowableCol(0)
         main_staticbox_sizer.AddGrowableRow(2)
         main_staticbox_sizer.AddGrowableRow(4)
         main_staticbox_sizer.AddGrowableRow(5)
-        staticbox_sizer.AddSizer(main_staticbox_sizer, 1, flag=wx.GROW)
-        main_staticbox_sizer.AddWindow(self.NodesFilter, border=5, flag=wx.GROW | wx.ALL)
-        main_staticbox_sizer.AddSizer(process_variables_header, border=5,
+        staticbox_sizer.Add(main_staticbox_sizer, 1, flag=wx.GROW)
+        main_staticbox_sizer.Add(self.NodesFilter, border=5, flag=wx.GROW | wx.ALL)
+        main_staticbox_sizer.Add(process_variables_header, border=5,
                                       flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
-        main_staticbox_sizer.AddWindow(self.ProcessVariablesGrid, 1,
+        main_staticbox_sizer.Add(self.ProcessVariablesGrid, 1,
                                        border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
-        main_staticbox_sizer.AddSizer(startup_commands_header,
+        main_staticbox_sizer.Add(startup_commands_header,
                                       border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
-        main_staticbox_sizer.AddWindow(self.StartupCommandsGrid, 1,
+        main_staticbox_sizer.Add(self.StartupCommandsGrid, 1,
                                        border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
 
         second_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Nodes variables filter:"))
         second_staticbox_sizer = wx.StaticBoxSizer(second_staticbox, wx.VERTICAL)
-        second_staticbox_sizer.AddSizer(self.NodesVariables, 1, border=5, flag=wx.GROW | wx.ALL)
-
-        main_staticbox_sizer.AddSizer(second_staticbox_sizer, 1,
+        second_staticbox_sizer.Add(self.NodesVariables, 1, border=5, flag=wx.GROW | wx.ALL)
+
+        main_staticbox_sizer.Add(second_staticbox_sizer, 1,
                                       border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
 
         self.EthercatMasterEditor.SetSizer(self.EthercatMasterEditorSizer)
@@ -1113,21 +1113,21 @@
 
         ESI_files_label = wx.StaticText(parent,
                                         label=_("ESI Files:"))
-        self.AddWindow(ESI_files_label, border=10,
+        self.Add(ESI_files_label, border=10,
                        flag=wx.TOP | wx.LEFT | wx.RIGHT)
 
         folder_tree_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=0)
         folder_tree_sizer.AddGrowableCol(0)
         folder_tree_sizer.AddGrowableRow(0)
-        self.AddSizer(folder_tree_sizer, border=10,
+        self.Add(folder_tree_sizer, border=10,
                       flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         self.ESIFiles = FolderTree(parent, self.GetPath(), editable=False)
         self.ESIFiles.SetFilter(".xml")
-        folder_tree_sizer.AddWindow(self.ESIFiles, flag=wx.GROW)
+        folder_tree_sizer.Add(self.ESIFiles, flag=wx.GROW)
 
         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
-        folder_tree_sizer.AddSizer(buttons_sizer,
+        folder_tree_sizer.Add(buttons_sizer,
                                    flag=wx.ALIGN_CENTER_VERTICAL)
 
         for idx, (name, bitmap, help, callback) in enumerate(buttons):
@@ -1135,7 +1135,7 @@
                                                     bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28),
                                                     style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
             if idx > 0:
                 flag = wx.TOP
@@ -1145,14 +1145,14 @@
                 callback = getattr(self, "On" + name, None)
             if callback is not None:
                 parent.Bind(wx.EVT_BUTTON, callback, button)
-            buttons_sizer.AddWindow(button, border=10, flag=flag)
+            buttons_sizer.Add(button, border=10, flag=flag)
 
         modules_label = wx.StaticText(parent,
                                       label=_("Modules library:"))
-        self.AddSizer(modules_label, border=10,
+        self.Add(modules_label, border=10,
                       flag=wx.LEFT | wx.RIGHT)
 
-        self.ModulesGrid = wx.gizmos.TreeListCtrl(parent,
+        self.ModulesGrid = wx.adv.TreeListCtrl(parent,
                                                   style=wx.TR_DEFAULT_STYLE |
                                                   wx.TR_ROW_LINES |
                                                   wx.TR_COLUMN_LINES |
@@ -1166,7 +1166,7 @@
                               self.OnModulesGridEndLabelEdit)
         self.ModulesGrid.GetHeaderWindow().Bind(wx.EVT_MOTION,
                                                 self.OnModulesGridHeaderMotion)
-        self.AddWindow(self.ModulesGrid, border=10,
+        self.Add(self.ModulesGrid, border=10,
                        flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         for colname, colsize, colalign in zip(
@@ -1335,7 +1335,7 @@
         if col > 0 and self.LastToolTipCol != col:
             self.LastToolTipCol = col
             _param, param_infos = self.ModuleLibrary.MODULES_EXTRA_PARAMS[col - 1]
-            wx.CallAfter(self.ModulesGrid.GetHeaderWindow().SetToolTipString,
+            wx.CallAfter(self.ModulesGrid.GetHeaderWindow().SetToolTip,
                          param_infos["description"])
         event.Skip()
 
@@ -1359,13 +1359,14 @@
                 ("DeleteButton", "remove_element", _("Remove file from database"), None)
             ])
         self.DatabaseSizer.SetControlMinSize(wx.Size(0, 0))
-        main_sizer.AddSizer(self.DatabaseSizer, border=10,
+        main_sizer.Add(self.DatabaseSizer, border=10,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
-        button_sizer.GetCancelButton().SetLabel(_("Close"))
-        main_sizer.AddSizer(button_sizer, border=10,
+        # FIXME: find a way to change buttons label compatible with wxPython 4.x
+        # button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
+        # button_sizer.GetCancelButton().SetLabel(_("Close"))
+        main_sizer.Add(button_sizer, border=10,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/etherlab/EtherCATManagementEditor.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/etherlab/EtherCATManagementEditor.py	Sun Jul 17 22:53:35 2022 +0200
@@ -15,7 +15,7 @@
 
 import wx
 import wx.grid
-import wx.gizmos
+import wx.adv
 import wx.lib.buttons
 
 # --------------------------------------------------------------------
@@ -135,7 +135,7 @@
             self.SizerDic["SlaveInfosDetailsInnerSizer"].AddMany([self.StaticTextDic[statictext_name],
                                                                   self.TextCtrlDic[textctrl_name]])
 
-        self.SizerDic["SlaveInfosDetailsBox"].AddSizer(self.SizerDic["SlaveInfosDetailsInnerSizer"])
+        self.SizerDic["SlaveInfosDetailsBox"].Add(self.SizerDic["SlaveInfosDetailsInnerSizer"])
 
         self.SyncManagersGrid = CustomGrid(self, size=wx.Size(605, 155), style=wx.VSCROLL)
 
@@ -153,7 +153,7 @@
         for button_name, button_id, button_label, button_tooltipstring, event_method, sub_item in buttons:
             self.ButtonDic[button_name] = wx.Button(self, id=button_id, label=_(button_label))
             self.ButtonDic[button_name].Bind(wx.EVT_BUTTON, event_method)
-            self.ButtonDic[button_name].SetToolTipString(button_tooltipstring)
+            self.ButtonDic[button_name].SetToolTip(button_tooltipstring)
             self.SizerDic["SlaveState_up_sizer"].Add(self.ButtonDic[button_name])
             for statictext_name, statictext_label, textctrl_name in sub_item:
                 self.StaticTextDic[statictext_name] = wx.StaticText(self, label=_(statictext_label))
@@ -166,7 +166,7 @@
                 ("StopTimerButton", "Stop State Monitoring", "Slave State Update Stop", self.CurrentStateThreadStop)]:
             self.ButtonDic[button_name] = wx.Button(self, label=_(button_label))
             self.ButtonDic[button_name].Bind(wx.EVT_BUTTON, event_method)
-            self.ButtonDic[button_name].SetToolTipString(button_tooltipstring)
+            self.ButtonDic[button_name].SetToolTip(button_tooltipstring)
             self.SizerDic["SlaveState_down_sizer"].Add(self.ButtonDic[button_name])
 
         self.SizerDic["SlaveState_sizer"].AddMany([self.SizerDic["SlaveState_up_sizer"],
@@ -1729,7 +1729,7 @@
 
         wx.Panel.__init__(self, parent, -1, size=(350, 500))
 
-        self.Tree = wx.gizmos.TreeListCtrl(self, -1, size=(350, 500),
+        self.Tree = wx.adv.TreeListCtrl(self, -1, size=(350, 500),
                                            style=(wx.TR_DEFAULT_STYLE |
                                                   wx.TR_FULL_ROW_HIGHLIGHT |
                                                   wx.TR_HIDE_ROOT |
@@ -2692,7 +2692,7 @@
             self.TextCtrl[key] = wx.TextCtrl(self, size=wx.Size(130, 24), style=wx.TE_READONLY)
             self.MasterStateSizer['innerMasterState'].AddMany([self.StaticText[key], self.TextCtrl[key]])
 
-        self.MasterStateSizer['masterState'].AddSizer(self.MasterStateSizer['innerMasterState'])
+        self.MasterStateSizer['masterState'].Add(self.MasterStateSizer['innerMasterState'])
 
         # ----------------------- Ethernet Network Card Information ---------------------------------------
         for key, label in [
@@ -2705,7 +2705,7 @@
             self.TextCtrl[key] = wx.TextCtrl(self, size=wx.Size(130, 24), style=wx.TE_READONLY)
             self.MasterStateSizer['innerDeviceInfo'].AddMany([self.StaticText[key], self.TextCtrl[key]])
 
-        self.MasterStateSizer['deviceInfo'].AddSizer(self.MasterStateSizer['innerDeviceInfo'])
+        self.MasterStateSizer['deviceInfo'].Add(self.MasterStateSizer['innerDeviceInfo'])
 
         # ----------------------- Network Frame Information -----------------------------------------------
         for key, label in [
@@ -2722,13 +2722,13 @@
                 self.TextCtrl[key][index] = wx.TextCtrl(self, size=wx.Size(130, 24), style=wx.TE_READONLY)
                 self.MasterStateSizer['innerFrameInfo'].Add(self.TextCtrl[key][index])
 
-        self.MasterStateSizer['frameInfo'].AddSizer(self.MasterStateSizer['innerFrameInfo'])
+        self.MasterStateSizer['frameInfo'].Add(self.MasterStateSizer['innerFrameInfo'])
 
         # ------------------------------- Slave Information  -----------------------------------------------
         self.SITreeListCtrl = SITreeListCtrl(self, self.Controler) 
         self.MasterStateSizer["innerSlaveInfo"].AddMany([self.SIUpdateButton,
                                                                self.SITreeListCtrl])
-        self.MasterStateSizer["slaveInfo"].AddSizer(
+        self.MasterStateSizer["slaveInfo"].Add(
                 self.MasterStateSizer["innerSlaveInfo"]) 
 
         # --------------------------------- Main Sizer ----------------------------------------------------
@@ -2743,7 +2743,7 @@
             ("main", [
                     "innerTop", "innerMiddle", "innerBottom"])]:
             for key2 in sub:
-                self.MasterStateSizer[key].AddSizer(self.MasterStateSizer[key2])
+                self.MasterStateSizer[key].Add(self.MasterStateSizer[key2])
 
         self.SetSizer(self.MasterStateSizer["main"])
     
@@ -2798,7 +2798,7 @@
 
         self.Controler=controler
         
-        self.Tree = wx.gizmos.TreeListCtrl(self, -1, size=wx.Size(750,350), 
+        self.Tree = wx.adv.TreeListCtrl(self, -1, size=wx.Size(750,350), 
                                                             style=wx.TR_HAS_BUTTONS
                                                             |wx.TR_HIDE_ROOT
                                                             |wx.TR_ROW_LINES
--- a/exemples/python/plc.xml	Thu Jul 14 11:40:27 2022 +0200
+++ b/exemples/python/plc.xml	Sun Jul 17 22:53:35 2022 +0200
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:21:19" contentDescription="This example shows many features in Beremiz:&#10;&#10;   1. How to implement python extensions.&#10;   2. How to implement basic C extension.&#10;   3. How to use C code in IEC POUs.&#10;   4. How to call C functions from python code.&#10;   5. How to avoid race conditions between IEC, C and python code.&#10;   6. How to convert betweet different IEC types.&#10;"/>
-  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2020-10-19T23:53:08">
+  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2022-07-03T16:04:31">
     <coordinateInfo>
       <pageSize x="1024" y="1024"/>
       <fbd>
@@ -269,12 +269,12 @@
         </interface>
         <body>
           <FBD>
-            <inVariable localId="4" height="30" width="160" executionOrderId="0" negated="false">
-              <position x="295" y="450"/>
-              <connectionPointOut>
-                <relPosition x="160" y="15"/>
-              </connectionPointOut>
-              <expression>'666'</expression>
+            <inVariable localId="4" height="30" width="315" executionOrderId="0" negated="false">
+              <position x="200" y="390"/>
+              <connectionPointOut>
+                <relPosition x="315" y="15"/>
+              </connectionPointOut>
+              <expression>'sys.stdout.write("Hello world\n")'</expression>
             </inVariable>
             <block localId="5" width="125" height="80" typeName="python_eval" instanceName="py1" executionOrderId="0">
               <position x="686" y="400"/>
@@ -293,9 +293,11 @@
                 <variable formalParameter="CODE">
                   <connectionPointIn>
                     <relPosition x="0" y="65"/>
-                    <connection refLocalId="4">
+                    <connection refLocalId="80" formalParameter="OUT">
                       <position x="686" y="465"/>
-                      <position x="455" y="465"/>
+                      <position x="653" y="465"/>
+                      <position x="653" y="485"/>
+                      <position x="630" y="485"/>
                     </connection>
                   </connectionPointIn>
                 </variable>
@@ -739,12 +741,6 @@
 Happy hacking! ]]></xhtml:p>
               </content>
             </comment>
-            <comment localId="31" height="90" width="345">
-              <position x="295" y="485"/>
-              <content>
-                <xhtml:p><![CDATA[Sleep here is bad. It blocks other py_eval instances. Whith a wxGlade GUI, GUI freeze for a second.]]></xhtml:p>
-              </content>
-            </comment>
             <comment localId="6" height="80" width="345">
               <position x="295" y="630"/>
               <content>
@@ -1443,10 +1439,10 @@
               </connectionPointIn>
               <expression>Test_Python_Var</expression>
             </outVariable>
-            <inVariable localId="79" executionOrderId="0" height="25" width="30" negated="false">
+            <inVariable localId="79" executionOrderId="0" height="27" width="30" negated="false">
               <position x="560" y="1340"/>
               <connectionPointOut>
-                <relPosition x="30" y="10"/>
+                <relPosition x="30" y="15"/>
               </connectionPointOut>
               <expression>23</expression>
             </inVariable>
@@ -1464,6 +1460,44 @@
               </connectionPointOut>
               <expression>SomeVarName</expression>
             </inOutVariable>
+            <block localId="80" typeName="MOVE" executionOrderId="0" height="40" width="60">
+              <position x="570" y="455"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="82">
+                      <position x="578" y="485"/>
+                      <position x="532" y="485"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="60" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <connector name="Connection0" localId="81" height="30" width="130">
+              <position x="545" y="390"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="4">
+                  <position x="545" y="405"/>
+                  <position x="515" y="405"/>
+                </connection>
+              </connectionPointIn>
+            </connector>
+            <continuation name="Connection0" localId="82" height="30" width="130">
+              <position x="410" y="470"/>
+              <connectionPointOut>
+                <relPosition x="130" y="15"/>
+              </connectionPointOut>
+            </continuation>
           </FBD>
         </body>
       </pou>
--- a/exemples/svghmi_traffic_light/svghmi_0@svghmi/svghmi.svg	Thu Jul 14 11:40:27 2022 +0200
+++ b/exemples/svghmi_traffic_light/svghmi_0@svghmi/svghmi.svg	Sun Jul 17 22:53:35 2022 +0200
@@ -1251,14 +1251,14 @@
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
      inkscape:zoom="1.979899"
-     inkscape:cx="205.65994"
-     inkscape:cy="103.00174"
+     inkscape:cx="52.116754"
+     inkscape:cy="96.940825"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
      showgrid="false"
      units="px"
-     inkscape:window-width="1600"
-     inkscape:window-height="836"
+     inkscape:window-width="3840"
+     inkscape:window-height="2096"
      inkscape:window-x="0"
      inkscape:window-y="27"
      inkscape:window-maximized="1"
@@ -1274,7 +1274,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>
@@ -1283,6 +1283,14 @@
      inkscape:groupmode="layer"
      id="layer1"
      transform="translate(37.474617,-760.93329)">
+    <rect
+       style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+       id="rect250"
+       width="320"
+       height="240"
+       x="-37.474617"
+       y="760.93329"
+       inkscape:label="HMI:Page:Home" />
     <path
        style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#282828;fill-opacity:1;stroke:none;stroke-width:2.04116011;marker:none;enable-background:accumulate"
        d="m 114.28125,14.28125 v 130 h 18.9375 v 93.5625 h 5.71875 V 176.4375 h 8.90625 v 15.71875 h 36.4375 v -32.5 h -36.4375 v 12.125 h -8.90625 v -27.5 h 21.78125 v -130 z"
@@ -1529,13 +1537,5 @@
          x="62.818459"
          y="812.17749"
          style="font-size:6.17188501px;line-height:1.25;font-family:sans-serif">ON</tspan></text>
-    <rect
-       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
-       id="rect250"
-       width="320"
-       height="240"
-       x="-37.474617"
-       y="760.93329"
-       inkscape:label="HMI:Page:Home" />
   </g>
 </svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fake_wx.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+import new
+from types import ModuleType
+
+# TODO use gettext instead
+def get_translation(txt):
+    return txt
+
+
+class FakeObject:
+    def __init__(self, *args, **kwargs):
+        self.__classname__ = kwargs["__classname__"]
+
+    def __getattr__(self,name):
+        if name.startswith('__'):
+            raise AttributeError, name
+        return FakeObject(__classname__=self.__classname__+"."+name)
+
+    def __call__(self, *args, **kwargs):
+        return FakeObject(__classname__=self.__classname__+"()")
+
+    def __getitem__(self, key):
+        raise IndexError, key
+
+    def __str__(self):
+        return self.__classname__
+
+    def __or__(self, other):
+        return FakeObject(__classname__=self.__classname__+"|"+other.__classname__)
+
+
+class FakeClass:
+    def __init__(self, *args, **kwargs):
+        print("DUMMY Class __init__ !",self.__name__,args,kwargs)
+
+
+class FakeModule(ModuleType):
+    def __init__(self, name, classes):
+        self.__modname__ = name
+        self.__objects__ = dict(map(lambda desc: 
+            (desc, new.classobj(desc, (FakeClass,), {}))
+            if type(desc)==str else desc, classes))
+        ModuleType(name)
+
+    def __getattr__(self,name):
+        if name.startswith('__'):
+            raise AttributeError, name
+  
+        if self.__objects__.has_key(name):
+            return self.__objects__[name]
+  
+        obj = FakeObject(__classname__=self.__modname__+"."+name)
+        self.__objects__[name] = obj
+        return obj
+
+
+# Keep track of already faked modules to catch those
+# that are already present in sys.modules from start
+# (i.e. mpl_toolkits for exemple)
+already_patched = {}
+
+for name, classes in [
+    # list given for each module name contains name string for a FakeClass,
+    # otherwise a tuple (name, object) for arbitrary object/function/class
+    ('wx',[
+        'Panel', 'PyCommandEvent', 'Dialog', 'PopupWindow', 'TextEntryDialog',
+        'Notebook', 'ListCtrl', 'TextDropTarget', 'PyControl', 'TextCtrl', 
+        'SplitterWindow', 'Frame', 'Printout', 'StaticBitmap', 'DropTarget',
+        ('GetTranslation', get_translation)]),
+    ('wx.lib.agw.advancedsplash',[]),
+    ('wx.dataview',['DataViewIndexListModel', 'PyDataViewIndexListModel']),
+    ('wx.lib.buttons',['GenBitmapTextButton']),
+    ('wx.adv',['EditableListBox']),
+    ('wx.grid',[
+        'Grid', 'PyGridTableBase', 'GridCellEditor', 'GridCellTextEditor',
+        'GridCellChoiceEditor']),
+    ('wx.lib.agw.customtreectrl',['CustomTreeCtrl']),
+    ('wx.lib.gizmos',[]),
+    ('wx.lib.intctrl',['IntCtrl']),
+    ('matplotlib.pyplot',[]),
+    ('matplotlib.backends.backend_wxagg',['FigureCanvasWxAgg']),
+    ('wx.stc',['StyledTextCtrl']),
+    ('wx.lib.scrolledpanel',[]),
+    ('wx.lib.mixins.listctrl',['ColumnSorterMixin', 'ListCtrlAutoWidthMixin']),
+    ('wx.dataview',['PyDataViewIndexListModel']),
+    ('matplotlib.backends.backend_agg',[]),
+    ('wx.aui',[]),
+    ('mpl_toolkits.mplot3d',[])]:
+    modpath = None
+    parentmod = None
+    for identifier in name.split("."):
+        modpath = (modpath + "." + identifier) if modpath else identifier
+        mod = sys.modules.get(modpath, None)
+
+        if mod is None or modpath not in already_patched:
+            mod = FakeModule(modpath, classes)
+            sys.modules[modpath] = mod
+            already_patched[modpath] = True
+
+        if parentmod is not None:
+            parentmod.__objects__[identifier] = mod
+
+        parentmod = mod
+
+from six.moves import builtins
+
+builtins.__dict__['_'] = get_translation
+
--- a/graphics/FBD_Objects.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/graphics/FBD_Objects.py	Sun Jul 17 22:53:35 2022 +0200
@@ -122,10 +122,10 @@
     # Returns if the point given is in the bounding box
     def HitTest(self, pt, connectors=True):
         if self.Name != "":
-            test_text = self.GetTextBoundingBox().InsideXY(pt.x, pt.y)
+            test_text = self.GetTextBoundingBox().Contains(pt.x, pt.y)
         else:
             test_text = False
-        test_block = self.GetBlockBoundingBox(connectors).InsideXY(pt.x, pt.y)
+        test_block = self.GetBlockBoundingBox(connectors).Contains(pt.x, pt.y)
         return test_text or test_block
 
     # Returns the bounding box of the name outside the block
@@ -392,7 +392,7 @@
 #            pos = event.GetLogicalPosition(dc)
 #            for input in self.Inputs:
 #                rect = input.GetRedrawRect()
-#                if rect.InsideXY(pos.x, pos.y):
+#                if rect.Contains(pos.x, pos.y):
 #                    print "Find input"
 #                    tip = wx.TipWindow(self.Parent, "Test")
 #                    tip.SetBoundingRect(rect)
@@ -771,6 +771,7 @@
         Graphic_Element.Draw(self, dc)
         dc.SetPen(MiterPen(wx.BLACK))
         dc.SetBrush(wx.WHITE_BRUSH)
+        dc.SetTextForeground(wx.BLACK)
 
         if getattr(dc, "printing", False):
             name_size = dc.GetTextExtent(self.Name)
@@ -1011,6 +1012,7 @@
         Graphic_Element.Draw(self, dc)
         dc.SetPen(MiterPen(wx.BLACK))
         dc.SetBrush(wx.WHITE_BRUSH)
+        dc.SetTextForeground(wx.BLACK)
 
         if getattr(dc, "printing", False):
             name_size = dc.GetTextExtent(self.Name)
--- a/graphics/GraphicCommons.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/graphics/GraphicCommons.py	Sun Jul 17 22:53:35 2022 +0200
@@ -388,11 +388,11 @@
             rect = self.BoundingBox
         else:
             rect = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
-        return rect.InsideXY(pt.x, pt.y)
+        return rect.Contains(pt.x, pt.y)
 
     # Returns if the point given is in the bounding box
     def IsInSelection(self, rect):
-        return rect.InsideXY(self.BoundingBox.x, self.BoundingBox.y) and rect.InsideXY(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
+        return rect.Contains(self.BoundingBox.x, self.BoundingBox.y) and rect.Contains(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
 
     # Override this method for refreshing the bounding box
     def RefreshBoundingBox(self):
@@ -448,7 +448,7 @@
         intern_rect = wx.Rect(left + HANDLE_SIZE, top + HANDLE_SIZE, right - left - HANDLE_SIZE, bottom - top - HANDLE_SIZE)
 
         # Verify that this element is selected
-        if self.Selected and extern_rect.InsideXY(pt.x, pt.y) and not intern_rect.InsideXY(pt.x, pt.y):
+        if self.Selected and extern_rect.Contains(pt.x, pt.y) and not intern_rect.Contains(pt.x, pt.y):
             # Find if point is on a handle horizontally
             if left <= pt.x < left + HANDLE_SIZE:
                 handle_x = 1
@@ -1401,7 +1401,7 @@
             width = ANCHOR_DISTANCE * 2 + abs(self.Direction[0]) * CONNECTOR_SIZE
             height = ANCHOR_DISTANCE * 2 + abs(self.Direction[1]) * CONNECTOR_SIZE
             rect = wx.Rect(x, y, width, height)
-            inside = rect.InsideXY(pt.x, pt.y)
+            inside = rect.Contains(pt.x, pt.y)
 
         return inside
 
@@ -1933,7 +1933,7 @@
             # Calculate a rectangle around the segment
             rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
                            abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
-            test |= rect.InsideXY(pt.x, pt.y)
+            test |= rect.Contains(pt.x, pt.y)
         return test
 
     # Returns the wire start or end point if the point given is on one of them
@@ -1941,13 +1941,13 @@
         # Test the wire start point
         rect = wx.Rect(self.Points[0].x - ANCHOR_DISTANCE, self.Points[0].y - ANCHOR_DISTANCE,
                        2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
-        if rect.InsideXY(pt.x, pt.y):
+        if rect.Contains(pt.x, pt.y):
             return 0
         # Test the wire end point
         if len(self.Points) > 1:
             rect = wx.Rect(self.Points[-1].x - ANCHOR_DISTANCE, self.Points[-1].y - ANCHOR_DISTANCE,
                            2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
-            if rect.InsideXY(pt.x, pt.y):
+            if rect.Contains(pt.x, pt.y):
                 return -1
         return None
 
@@ -1961,7 +1961,7 @@
                 # Calculate a rectangle around the segment
                 rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
                                abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
-                if rect.InsideXY(pt.x, pt.y):
+                if rect.Contains(pt.x, pt.y):
                     return i, self.Segments[i]
         return None
 
--- a/graphics/RubberBand.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/graphics/RubberBand.py	Sun Jul 17 22:53:35 2022 +0200
@@ -94,7 +94,7 @@
 
         # Change viewer mouse cursor to reflect a rubberband bounding box is
         # edited
-        self.DrawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
+        self.DrawingSurface.SetCursor(wx.Cursor(wx.CURSOR_CROSS))
 
         self.Redraw()
 
@@ -195,3 +195,25 @@
         """
         # Erase last bbox and draw current bbox
         self.DrawBoundingBoxes([self.CurrentBBox], dc)
+
+
+def PatchRubberBandForGTK3():
+    """
+    GTK3 implementation of DC doesn't support SetLogicalFuntion(XOR)
+    Then Rubberband can't be erased by just redrawing it on the same place
+    So this is a complete refresh instead, eating a lot of CPU.
+    """
+    def Redraw(self, dc=None):
+        self.Viewer.Refresh()
+        self.Draw()
+
+    RubberBand.Redraw = Redraw
+
+    def Erase(self, dc=None):
+        self.Viewer.Refresh()
+
+    RubberBand.Erase = Erase
+
+
+if "gtk3" in wx.PlatformInfo:
+    PatchRubberBandForGTK3()
--- a/graphics/SFC_Objects.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/graphics/SFC_Objects.py	Sun Jul 17 22:53:35 2022 +0200
@@ -719,7 +719,7 @@
                                self.Pos.y + (self.Size[1] - text_height) // 2,
                                text_width,
                                text_height)
-            test_text = text_bbx.InsideXY(pt.x, pt.y)
+            test_text = text_bbx.Contains(pt.x, pt.y)
         else:
             test_text = False
         return test_text or Graphic_Element.HitTest(self, pt, connectors)
@@ -1204,7 +1204,7 @@
 
     # Returns if the point given is in the bounding box
     def HitTest(self, pt, connectors=True):
-        return self.BoundingBox.InsideXY(pt.x, pt.y) or self.TestConnector(pt, exclude=False) is not None
+        return self.BoundingBox.Contains(pt.x, pt.y) or self.TestConnector(pt, exclude=False) is not None
 
     # Refresh the divergence bounding box
     def RefreshBoundingBox(self):
@@ -1592,7 +1592,7 @@
                            self.Pos.y + (self.Size[1] - text_height) // 2,
                            text_width,
                            text_height)
-        return text_bbx.InsideXY(pt.x, pt.y) or Graphic_Element.HitTest(self, pt, connectors)
+        return text_bbx.Contains(pt.x, pt.y) or Graphic_Element.HitTest(self, pt, connectors)
 
     # Refresh the jump bounding box
     def RefreshBoundingBox(self):
--- a/opc_ua/opcua_client_maker.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/opc_ua/opcua_client_maker.py	Sun Jul 17 22:53:35 2022 +0200
@@ -7,7 +7,7 @@
 from opcua import ua
 
 import wx
-from wx.lib.agw.hypertreelist import HyperTreeList as TreeListCtrl
+import wx.lib.gizmos as gizmos  # Formerly wx.gizmos in Classic
 import wx.dataview as dv
 
 
@@ -38,9 +38,9 @@
 
 directions = ["input", "output"]
 
-class OPCUASubListModel(dv.PyDataViewIndexListModel):
+class OPCUASubListModel(dv.DataViewIndexListModel):
     def __init__(self, data, log):
-        dv.PyDataViewIndexListModel.__init__(self, len(data))
+        dv.DataViewIndexListModel.__init__(self, len(data))
         self.data = data
         self.log = log
 
@@ -283,10 +283,10 @@
             self.tree_sizer.AddGrowableCol(0)
             self.tree_sizer.AddGrowableRow(0)
 
-            self.tree = TreeListCtrl(self.tree_panel, -1, style=0, agwStyle=
-                                            wx.TR_DEFAULT_STYLE
-                                            | wx.TR_MULTIPLE
-                                            | wx.TR_FULL_ROW_HIGHLIGHT
+            self.tree = gizmos.TreeListCtrl(self.tree_panel, -1, style=0, agwStyle=
+                                            gizmos.TR_DEFAULT_STYLE
+                                            | gizmos.TR_MULTIPLE
+                                            | gizmos.TR_FULL_ROW_HIGHLIGHT
                                        )
 
             prepare_image_list()
--- a/svghmi/gen_index_xhtml.xslt	Thu Jul 14 11:40:27 2022 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Sun Jul 17 22:53:35 2022 +0200
@@ -5600,12 +5600,6 @@
 </xsl:text>
     <xsl:text>            this.disabled = !Number(value);
 </xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            // TODO : use RequestAnimate and animate()
-</xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>            this.update_state();
 </xsl:text>
     <xsl:text>        }
@@ -6353,8 +6347,6 @@
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>        // TODO : use RequestAnimate and animate()
-</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>
--- a/svghmi/ui.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/svghmi/ui.py	Sun Jul 17 22:53:35 2022 +0200
@@ -58,13 +58,13 @@
                 display_name = ('{} (class={})'.format(c.name, c.hmiclass)) \
                                if c.hmiclass is not None else c.name
                 tc_child = self.AppendItem(current_tc_root, display_name)
-                self.SetPyData(tc_child, c)
+                self.SetItemData(tc_child, c)
 
                 self._recurseTree(c,tc_child)
             else:
                 display_name = '{} {}'.format(c.nodetype[4:], c.name)
                 tc_child = self.AppendItem(current_tc_root, display_name)
-                self.SetPyData(tc_child, c)
+                self.SetItemData(tc_child, c)
 
     def OnTreeNodeSelection(self, event):
         items = self.GetSelections()
@@ -106,7 +106,7 @@
         root_display_name = _("Please build to see HMI Tree") \
             if hmi_tree_root is None else "HMI"
         self.root = self.AddRoot(root_display_name)
-        self.SetPyData(self.root, hmi_tree_root)
+        self.SetItemData(self.root, hmi_tree_root)
 
         if hmi_tree_root is not None:
             self._recurseTree(hmi_tree_root, self.root)
@@ -146,11 +146,11 @@
                     for d in dirlist:
                         current_tc_root = self.AppendItem(current_tc_root, d)
                         res.append(current_tc_root)
-                        self.SetPyData(current_tc_root, None)
+                        self.SetItemData(current_tc_root, None)
                     dirlist = []
                     res.pop()
                 tc_child = self.AppendItem(current_tc_root, f)
-                self.SetPyData(tc_child, p)
+                self.SetItemData(tc_child, p)
         return res
 
     def MakeTree(self, lib_dir = None):
@@ -163,7 +163,7 @@
         root_display_name = _("Please select widget library directory") \
             if lib_dir is None else os.path.basename(lib_dir)
         self.root = self.AddRoot(root_display_name)
-        self.SetPyData(self.root, None)
+        self.SetItemData(self.root, None)
 
         if lib_dir is not None and os.path.exists(lib_dir):
             self._recurseTree(lib_dir, self.root, [])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/Makefile	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,227 @@
+#! gmake
+
+# beremiz/tests/Makefile :
+#
+#   Makefile to prepare and run Beremiz tests.
+#
+#   For developper to:
+#       - quickly run a test (TDD) on current code 
+#       - write new tests, debug existing tests 
+#
+#     Use cases :
+#
+#       run given tests
+#           $ make run_python_exemple.sikuli
+#
+#       run tests from particular test classes
+#           $ make ide_tests
+#
+#       run one particular test in a Xnest window
+#           $ make xnest_run_python_exemple.sikuli
+#
+#       run Xnest window with just xterm
+#           $ make xnest_xterm
+#
+#       run Xnest window with sikuli IDE and xterm
+#           $ make xnest_sikuli
+#
+#       build minimal beremiz and matiec to run tests
+#           $ make built_apps
+#
+#   For CI/CD scripts to catch and report all failures. Use cases :
+#
+#       run all tests
+#           $ make
+#
+#   
+#   Test results, and other test byproducts are in $(test_dir), 
+#   $(test_dir) defaults to $(HOME)/test and can be overloaded:
+#       $ make test_dir=${HOME}/other_test_dir
+#
+#   Makefile attemps to use xvfb-run to run each test individually with its own
+#   X server instance. This behavior can be overloaded
+#       $ DISPLAY=:42 make xserver_command='echo "Using $DISPLAY X Server !";'
+#
+#   Matiec and Beremiz code are expected to be clean, ready to build
+#   Any change in Matiec directory triggers rebuild of matiec.
+#   Any change in Matiec and Beremiz directory triggers copy of source code
+#   to $(test_dir)/build.
+#
+#   BEREMIZPYTHONPATH is expected to be absolute path to python interpreter
+#
+#   Please note:
+#       In order to run asside a freshly build Matiec, tested beremiz instance
+#       needs to run on code from $(test_dir)/build/beremiz, a fresh copy
+#       of the Beremiz directory $(src)/beremiz, where we run tests from.
+#   
+
+all: source_check cli_tests ide_tests runtime_tests 
+
+# Variable $(src) is directory such that executed 
+# $(src)/Makefile is this file.
+src := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
+# $(workspace) is directory containing this project
+workspace ?= $(abspath $(src)/../..)
+
+test_dir ?= $(HOME)/test
+build_dir = $(test_dir)/build
+
+#
+# SOURCE and BUILD
+#
+
+BUILT_PROJECTS=beremiz matiec open62541
+
+tar_opts=--absolute-names --exclude=.hg --exclude=.git --exclude=.*.pyc --exclude=.*.swp
+
+# sha1 checksum of source is used to force copy/compile on each change
+
+define make_checksum_assign
+$(1)_checksum = $(shell tar $(tar_opts) -c $(workspace)/$(1) | sha1sum | cut -d ' ' -f 1)
+endef
+$(foreach project,$(BUILT_PROJECTS),$(eval $(call make_checksum_assign,$(project))))
+
+$(build_dir):
+	mkdir -p $(build_dir)
+
+define make_src_rule
+$(build_dir)/$(1)/$($(1)_checksum).sha1: | $(build_dir) $(workspace)/$(1)
+	rm -rf $(build_dir)/$(1)
+	tar -C $(workspace) $(tar_opts) -c $(1) | tar -C $(build_dir) -x
+	touch $$@
+endef
+$(foreach project,$(BUILT_PROJECTS),$(eval $(call make_src_rule,$(project))))
+
+$(build_dir)/matiec/iec2c: $(build_dir)/matiec/$(matiec_checksum).sha1
+	cd $(build_dir)/matiec && \
+    autoreconf -i && \
+    ./configure && \
+    make
+
+$(build_dir)/open62541/build/bin/libopen62541.a: $(build_dir)/open62541/$(open62541_checksum).sha1
+	cd $(build_dir)/open62541 && \
+    rm -rf build && mkdir build && cd build && \
+	cmake .. && \
+	make
+
+built_apps: $(build_dir)/matiec/iec2c $(build_dir)/beremiz/$(beremiz_checksum).sha1 $(build_dir)/open62541/build/bin/libopen62541.a
+	touch $@
+
+define log_command
+	$(call $(1),$(2)) | tee test_stdout.txt; exit $$$${PIPESTATUS[0]}
+endef
+
+define prep_test
+	rm -rf $(test_dir)/$(1)_results
+	mkdir $(test_dir)/$(1)_results
+	cd $(test_dir)/$(1)_results
+endef
+
+#
+# IDE TESTS
+#
+
+ide_test_dir = $(src)/ide_tests
+sikuli_ide_tests = $(subst $(ide_test_dir)/,,$(wildcard $(ide_test_dir)/*.sikuli))
+pytest_ide_tests = $(subst $(ide_test_dir)/,,$(wildcard $(ide_test_dir)/*.pytest))
+
+define sikuli_idetest_command
+	(fluxbox >/dev/null 2>&1 &); BEREMIZPATH=$(build_dir)/beremiz sikulix -r $(src)/ide_tests/$(1)
+endef
+
+
+DELAY=400
+KILL_DELAY=430
+PYTEST=$(dir $(BEREMIZPYTHONPATH))/pytest
+define pytest_idetest_command
+	(fluxbox >/dev/null 2>&1 &); PYTHONPATH=$(ide_test_dir) timeout -k $(KILL_DELAY) $(DELAY) $(PYTEST) --maxfail=1 --timeout=100  $(src)/ide_tests/$(1)
+endef
+
+# Xnest based interactive sessions for tests edit and debug. 
+# Would be nice with something equivalent to xvfb-run, waiting for USR1.
+# Arbitrary "sleep 1" is probably enough for interactive use
+define xnest_run
+	Xnest :42 -geometry 1920x1080+0+0 & export xnestpid=$$!; sleep 1; DISPLAY=:42 $(1); export res=$$?; kill $${xnestpid} 2>/dev/null; exit $${res}
+endef
+
+xserver_command ?= xvfb-run -s '-screen 0 1920x1080x24'
+
+define make_idetest_rule
+$(test_dir)/$(1)_results/.passed: built_apps
+	$(call prep_test,$(1)); $(xserver_command) bash -c '$(call log_command,$(2),$(1))'
+	touch $$@
+
+# Manually invoked rule {testname}.sikuli
+$(1): $(test_dir)/$(1)_results/.passed
+
+# Manually invoked rule xnest_{testname}.sikuli
+# runs test in xnest so that one can see what happens
+xnest_$(1): built_apps
+	$(call prep_test,$(1)); $$(call xnest_run, bash -c '$(call log_command,$(2),$(1))')
+
+ide_tests_targets += $(test_dir)/$(1)_results/.passed
+endef
+$(foreach idetest,$(sikuli_ide_tests),$(eval $(call make_idetest_rule,$(idetest),sikuli_idetest_command)))
+$(foreach idetest,$(pytest_ide_tests),$(eval $(call make_idetest_rule,$(idetest),pytest_idetest_command)))
+
+ide_tests : $(ide_tests_targets)
+	echo "$(ide_tests_targets) : Passed"
+
+xnest_xterm: built_apps
+	$(call xnest_run, bash -c '(fluxbox &);xterm')
+
+xnest_sikuli: built_apps
+	$(call xnest_run, bash -c '(fluxbox &);(BEREMIZPATH=$(build_dir)/beremiz xterm -e sikulix &);xterm')
+
+xvfb_sikuli: built_apps
+	echo "******************************************"
+	echo "On host, run 'xvncviewer 127.0.0.1:5900' to see sikuli X session"
+	echo "Docker container must be created with TESTDEBUG=YES. For example :"
+	echo "./clean_docker_container.sh && ./build_docker_image.sh && TESTDEBUG=YES ./create_docker_container.sh && ./do_test_in_docker.sh xvfb_sikuli"
+	echo "******************************************"
+	$(xserver_command) bash -c '(fluxbox &);(x11vnc &);(BEREMIZPATH=$(build_dir)/beremiz xterm -e sikulix &);xterm'
+
+#
+# CLI TESTS
+#
+
+cli_test_dir = $(src)/cli_tests
+cli_tests = $(subst $(cli_test_dir)/,,$(wildcard $(cli_test_dir)/*.bash))
+
+define clitest_command
+	BEREMIZPATH=$(build_dir)/beremiz source $(src)/cli_tests/$(1)
+endef
+
+define make_clitest_rule
+$(test_dir)/$(1)_results/.passed: built_apps
+	$(call prep_test,$(1)); bash -c '$(call log_command,$(2),$(1))'
+	touch $$@
+
+# Manually invoked rule
+$(1): $(test_dir)/$(1)_results/.passed
+
+cli_tests_targets += $(test_dir)/$(1)_results/.passed
+endef
+$(foreach clitest,$(cli_tests),$(eval $(call make_clitest_rule,$(clitest),clitest_command)))
+
+cli_tests: $(cli_tests_targets)
+	echo "$(cli_tests_targets) : Passed"
+
+clean_results:
+	rm -rf $(test_dir)/*_results
+
+clean: clean_results
+	rm -rf $(build_dir)
+
+
+# TODOs 
+
+source_check:
+	echo TODO $@
+
+runtime_tests:
+	echo TODO $@
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cli_tests/opcua_test.bash	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,84 @@
+#!/bin/bash
+
+rm -f ./SRVOK ./PLCOK
+
+# Run server
+$BEREMIZPYTHONPATH - > >(
+    echo "Start SRV loop"
+    while read line; do 
+        # Wait for server to print modified value
+        echo "SRV>> $line"
+        if [[ "$line" == 3.4 ]]; then
+            echo "PLC could write value"
+            touch ./SRVOK
+        fi
+    done
+    echo "End SRV loop"
+) << EOF &
+
+import sys
+import time
+
+from opcua import ua, Server
+
+server = Server()
+server.set_endpoint("opc.tcp://127.0.0.1:4840/freeopcua/server/")
+
+uri = "http://beremiz.github.io"
+idx = server.register_namespace(uri)
+
+objects = server.get_objects_node()
+
+testobj = objects.add_object(idx, "TestObject")
+testvarout = testobj.add_variable(idx, "TestOut", 1.2)
+testvar = testobj.add_variable(idx, "TestIn", 5.6)
+testvar.set_writable()
+
+server.start()
+
+try:
+    while True:
+        time.sleep(1)
+        print testvar.get_value()
+        sys.stdout.flush()
+finally:
+    server.stop()
+EOF
+SERVER_PID=$!
+
+# Start PLC with opcua test
+setsid $BEREMIZPYTHONPATH $BEREMIZPATH/Beremiz_cli.py -k \
+     --project-home $BEREMIZPATH/tests/projects/opcua_client build transfer run > >(
+echo "Start PLC loop"
+while read line; do 
+    # Wait for PLC runtime to output expected value on stdout
+    echo "PLC>> $line"
+    if [[ "$line" == 1.2 ]]; then
+        echo "PLC could read value"
+        touch ./PLCOK
+    fi
+done
+echo "End PLC loop"
+) &
+PLC_PID=$!
+
+echo all subprocess started, start polling results
+res=110  # default to ETIMEDOUT
+c=30
+while ((c--)); do
+    if [[ -a ./SRVOK && -a ./PLCOK ]]; then
+        echo got results.
+        res=0  # OK success
+        break
+    else
+        echo waiting.... $c
+        sleep 1
+    fi
+done
+
+# Kill PLC and subprocess
+echo will kill PLC:$PLC_PID and SERVER:$SERVER_PID
+pkill -s $PLC_PID 
+kill $SERVER_PID
+
+exit $res
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cli_tests/run_python_example.bash	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Run python example throug command line, and check usual output
+
+coproc setsid $BEREMIZPYTHONPATH $BEREMIZPATH/Beremiz_cli.py -k --project-home ~/src/beremiz/exemples/python build transfer run;
+
+while read -u ${COPROC[0]} line; do 
+    echo "$line"
+    if [[ "$line" == *Grumpf* ]]; then
+        pkill -9 -s $COPROC_PID 
+        exit 0
+    fi
+done
+
+exit 42
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/conftest.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+# Copyright (C) 2017: Andrey Skvortsov
+#
+# See COPYING file for copyrights details.
+#
+# This program 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
+# of the License, or (at your option) any later version.
+#
+# This program 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 program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+
+from __future__ import absolute_import
+import os
+import sys
+
+# import pytest
+# import xvfbwrapper
+
+
+def init_environment():
+    """Append module root directory to sys.path"""
+    try:
+        import Beremiz as _Beremiz
+    except ImportError:
+        sys.path.append(
+            os.path.abspath(
+                os.path.join(
+                    os.path.dirname(__file__), '..', '..')
+            )
+        )
+
+
+init_environment()
+
+#
+# Something seems to be broken in Beremiz application,
+# because after tests in test_application.py during Xvfb shutdown
+# pytest returns error message:
+# pytest: Fatal IO error 11 (Die Ressource ist zur Zeit nicht verfügbar) on X server :2821.
+#
+# As a result of that pytest returns code 1 as some tests were failed,
+# but they aren't.
+#
+# To avoid this Xvfb is launched and killed not by pytest.
+# $ Xvfb :42 -screen 0 1280x1024x24 &
+# $ export DISPLAY=:42
+# $ pytest --timeout=10 ./tests/tools
+# $ pkill -9 Xvfb
+#
+# TODO: find root of this problem.
+
+
+# vdisplay = None
+#
+# @pytest.fixture(scope="session", autouse=True)
+# def start_xvfb_server(request):
+#     global vdisplay
+#     vdisplay = xvfbwrapper.Xvfb(width=1280, height=720)
+#     vdisplay.start()
+#     request.addfinalizer(stop_xvfb_server)
+#
+# def stop_xvfb_server():
+#     if vdisplay is not None:
+#         vdisplay.stop()
Binary file tests/ide_tests/debug_project.sikuli/1646062660770.png has changed
Binary file tests/ide_tests/debug_project.sikuli/1646066996789.png has changed
Binary file tests/ide_tests/debug_project.sikuli/1646066996790.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/debug_project.sikuli/debug_project.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,68 @@
+""" This test opens, modifies, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import run_test
+
+def test(app):
+
+    app.k.Clean()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Build()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Connect()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Transfer()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.click("1646062660770.png")
+
+    app.WaitIdleUI()
+    
+    app.click("1646066996789.png")
+
+    app.WaitIdleUI()
+
+    app.click("example")
+
+    app.WaitIdleUI()
+
+    app.type(Key.DOWN * 10, Key.CTRL)
+
+    app.WaitIdleUI()
+
+    app.k.Run()
+
+    # wait up to 10 seconds for 10 Grumpfs
+    app.waitPatternInStdout("Grumpf", 10, 10)
+
+    app.rightClick("1646066996790.png")
+
+    app.click("Force value")
+
+    #app.type("a", Key.CTRL)
+
+    app.type(Key.BACKSPACE)
+
+    app.type("'sys.stdout.write(\"DEBUG TEST OK\\n\")'")
+
+    app.type(Key.ENTER)
+    
+    # wait 10 seconds for 10 patterns
+    return app.waitPatternInStdout("DEBUG TEST OK", 10)
+
+run_test(test, exemple="python")
Binary file tests/ide_tests/edit_project.sikuli/1646062660770.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/edit_project.sikuli/edit_project.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,61 @@
+""" This test opens, modifies, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import run_test
+
+def test(app):
+
+    app.doubleClick("1646062660770.png")
+
+    app.WaitIdleUI()
+
+    app.click("example")
+
+    app.WaitIdleUI()
+
+    app.type(Key.DOWN * 10, Key.CTRL)
+
+    app.WaitIdleUI()
+
+    app.doubleClick("Hello world")
+
+    app.WaitIdleUI()
+
+    app.type(Key.TAB*3)  # select text content
+
+    app.type("'sys.stdout.write(\"EDIT TEST OK\\n\")'")
+
+    app.type(Key.ENTER)
+
+    app.WaitIdleUI()
+
+    app.k.Clean()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Build()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Connect()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Transfer()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Run()
+
+    # wait 10 seconds for 10 patterns
+    return app.waitPatternInStdout("EDIT TEST OK", 10)
+
+run_test(test, exemple="python")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/load_and_build_tests.pytest/test_application.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,232 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+# Copyright (C) 2017: Andrey Skvortsov
+#
+# See COPYING file for copyrights details.
+#
+# This program 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
+# of the License, or (at your option) any later version.
+#
+# This program 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 program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+
+from __future__ import absolute_import
+from __future__ import print_function
+import os
+import sys
+import unittest
+import time
+
+import six
+import pytest
+import wx
+import ddt
+
+import conftest
+import Beremiz
+import PLCOpenEditor
+
+
+class UserApplicationTest(unittest.TestCase):
+    def InstallExceptionHandler(self):
+        def handle_exception(e_type, e_value, e_traceback, exit=False):
+            # traceback.print_exception(e_type, e_value, e_traceback)
+            self.exc_info = [e_type, e_value, e_traceback]
+        self.exc_info = None
+        self.old_excepthook = sys.excepthook
+        sys.excepthook = handle_exception
+
+    def StartApp(self):
+        self.app = None
+
+    def FinishApp(self):
+        wx.CallAfter(self.app.frame.Close)
+        self.app.MainLoop()
+        self.app = None
+
+    def setUp(self):
+        self.app = None
+
+    def tearDown(self):
+        if self.app is not None and self.app.frame is not None:
+            self.FinishApp()
+
+    def RunUIActions(self, actions):
+        for act in actions:
+            wx.CallAfter(*act)
+            self.ProcessEvents()
+
+    def CheckForErrors(self):
+        if self.exc_info is not None:
+            # reraise catched previously exception
+            exc_type = self.exc_info[0]
+            exc_value = self.exc_info[1]
+            exc_traceback = self.exc_info[2]
+            six.reraise(exc_type, exc_value, exc_traceback)
+
+    def ProcessEvents(self):
+        for dummy in range(0, 30):
+            self.CheckForErrors()
+            wx.Yield()
+            time.sleep(0.01)
+
+
+@ddt.ddt
+class BeremizApplicationTest(UserApplicationTest):
+    """Test Beremiz as whole application"""
+
+    def StartApp(self):
+        self.app = Beremiz.BeremizIDELauncher()
+        # disable default exception handler in Beremiz
+        self.app.InstallExceptionHandler = lambda: None
+        self.InstallExceptionHandler()
+        self.app.handle_exception = sys.excepthook
+        self.app.PreStart()
+        self.ProcessEvents()
+        self.app.frame.Show()
+        self.ProcessEvents()
+        self.app.frame.ShowFullScreen(True)
+        self.ProcessEvents()
+
+    def FinishApp(self):
+        wx.CallAfter(self.app.frame.Close)
+        self.app.MainLoop()
+        time.sleep(1)
+        self.app = None
+
+    def GetSkippedProjectTreeItems(self):
+        """
+        Returns the list of skipped items in the project tree.
+
+        Beremiz test don't need to skip any elemnts in the project tree.
+        """
+        return []
+
+    def OpenAllProjectElements(self):
+        """Open editor for every object in the project tree"""
+        self.app.frame.ProjectTree.ExpandAll()
+        self.ProcessEvents()
+        item = self.app.frame.ProjectTree.GetRootItem()
+        skip = self.GetSkippedProjectTreeItems()
+        tree_id = self.app.frame.ProjectTree.GetId()
+        while item is not None:
+            self.app.frame.ProjectTree.SelectItem(item, True)
+            self.ProcessEvents()
+            if item not in skip:
+                event = wx.lib.agw.customtreectrl.TreeEvent(
+                    wx.lib.agw.customtreectrl.wxEVT_TREE_ITEM_ACTIVATED,
+                    tree_id, item)
+                self.app.frame.OnProjectTreeItemActivated(event)
+            self.ProcessEvents()
+            item = self.app.frame.ProjectTree.GetNextVisible(item)
+
+    def CheckTestProject(self, project):
+        sys.argv = ["", project]
+        self.StartApp()
+        self.OpenAllProjectElements()
+        user_actions = self.GetUserActions()
+        self.RunUIActions(user_actions)
+        self.FinishApp()
+
+    def GetProjectPath(self, project):
+        return os.path.abspath(os.path.join(os.path.dirname(__file__), "..","..","projects", project))
+
+    def GetUserActions(self):
+        """
+        Returns list of user actions that will be executed
+        on every test project by testCheckProject test.
+        """
+        user_actions = [
+            [self.app.frame.SwitchFullScrMode, None],
+            [self.app.frame.SwitchFullScrMode, None],
+            [self.app.frame.CTR._Clean],
+            [self.app.frame.CTR._Build],
+            [self.app.frame.CTR._Connect],
+            [self.app.frame.CTR._Transfer],
+            [self.app.frame.CTR._Run],
+            [self.app.frame.CTR._Stop],
+            [self.app.frame.CTR._Disconnect],
+            [self.app.frame.CTR._Clean],
+        ]
+        return user_actions
+
+    def testStartUp(self):
+        """Checks whether the app starts and finishes correctly"""
+        sys.argv = [""]
+        self.StartApp()
+        self.FinishApp()
+
+    # TODO: also use "exemples/*" projects
+    @ddt.data(
+        #"first_steps",
+        "logging",
+        #"traffic_lights",
+        #"wxGlade",
+        #"python",
+        #"wiimote",
+        # "wxHMI",
+    )
+    @pytest.mark.timeout(300)
+    def testCheckProject(self, name):
+        """
+        Checks that test PLC project can be open,
+        compiled and run on SoftPLC.
+        """
+        project = self.GetProjectPath(name)
+        print("Testing example " + name)
+        self.CheckTestProject(project)
+
+
+# class PLCOpenEditorApplicationTest(BeremizApplicationTest):
+#     """Test PLCOpenEditor as whole application"""
+# 
+#     def StartApp(self):
+#         self.app = PLCOpenEditor.PLCOpenEditorApp()
+#         # disable default exception handler in application
+#         self.app.InstallExceptionHandler = lambda: None
+#         self.InstallExceptionHandler()
+#         self.app.Show()
+#         self.ProcessEvents()
+#         self.app.frame.ShowFullScreen(True)
+#         self.ProcessEvents()
+# 
+#     def FinishApp(self):
+#         wx.CallAfter(self.app.frame.Close)
+#         self.app.MainLoop()
+#         time.sleep(1)
+#         self.app = None
+# 
+#     def GetSkippedProjectTreeItems(self):
+#         """
+#         Returns the list of skipped items in the project tree.
+# 
+#         Root item opens dialog window for project settings.
+#         To avoid code that handles closing dialog windows just skip this item.
+#         """
+#         return [self.app.frame.ProjectTree.GetRootItem()]
+# 
+#     def GetUserActions(self):
+#         return []
+# 
+#     def GetProjectPath(self, project):
+#         """Open PLC program in every Beremiz test project"""
+#         project_dir = BeremizApplicationTest.GetProjectPath(self, project)
+#         return os.path.join(project_dir, "plc.xml")
+# 
+
+if __name__ == '__main__':
+    conftest.init_environment()
+    unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/new_project.sikuli/new_project.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,132 @@
+""" This test opens, builds and runs a new project.
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import *
+
+def test(app):
+    
+    new_project_path = os.path.join(os.path.abspath(os.path.curdir), "new_test_project")
+    
+    # New project path must exist (usually created in directory selection dialog)
+    os.mkdir(new_project_path)
+    
+    app.WaitIdleUI()
+    
+    # Create new project (opens new project directory selection dialog)
+    app.k.New()
+    
+    app.WaitIdleUI()
+    
+    # Move to "Home" section of file selecor, otherwise address is 
+    # "file ignored" at first run
+    app.type("f", Key.CTRL)
+    app.type(Key.ESC)
+    app.type(Key.TAB)
+    
+    # Enter directory by name
+    app.k.Address()
+    
+    # Fill address bar
+    app.type(new_project_path + Key.ENTER)
+    
+    app.WaitIdleUI()
+    
+    # When prompted for creating first program select type ST
+    app.type(Key.TAB*4)  # go to lang dropdown
+    app.type(Key.DOWN*2) # change selected language
+    app.type(Key.ENTER)  # validate
+    
+    app.WaitIdleUI()
+    
+    # Name created program
+    app.type("Test program")
+    
+    app.WaitIdleUI()
+    
+    # Focus on Variable grid
+    app.type(Key.TAB*4)
+    
+    # Add 2 variables
+    app.type(Key.ADD*2)
+    
+    # Focus on ST text
+    app.WaitIdleUI()
+    
+    app.type(Key.TAB*8)
+    
+    app.type("""\
+    LocalVar0 := LocalVar1;
+    {printf("Test OK\\n");fflush(stdout);}
+    """)
+    
+    app.k.Save()
+    
+    # Close ST POU
+    app.type("w", Key.CTRL)
+    
+    app.WaitIdleUI()
+    
+    # Focus project tree and select root item
+    app.type(Key.TAB)
+    
+    app.type(Key.LEFT)
+    
+    app.type(Key.UP)
+    
+    # Edit root item
+    app.type(Key.ENTER)
+    
+    app.WaitIdleUI()
+    
+    # Switch to config tab
+    app.type(Key.RIGHT*2)
+    
+    # Focus on URI
+    app.type(Key.TAB)
+    
+    # Set URI
+    app.type("LOCAL://")
+    
+    # FIXME: Select other field to ensure URI is validated
+    app.type(Key.TAB)
+    
+    app.k.Save()
+    
+    # Close project config editor
+    app.type("w", Key.CTRL)
+    
+    app.WaitIdleUI()
+    
+    # Focus seems undefined at that time (FIXME)
+    # Force focussing on "something" so that next shortcut is taken
+    app.type(Key.TAB)
+    
+    app.waitIdleStdout()
+    
+    app.k.Build()
+    
+    app.waitIdleStdout(5,30)
+    
+    app.k.Connect()
+    
+    app.waitIdleStdout()
+    
+    app.k.Transfer()
+    
+    app.waitIdleStdout()
+    
+    app.k.Run()
+    
+    # wait 10 seconds
+    return app.waitPatternInStdout("Test OK", 10)
+
+
+run_test(test)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/run_python_exemple.sikuli/run_python_exemple.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,39 @@
+""" This test opens, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import *
+
+def test(app):
+    # Start the app
+    
+    app.k.Clean()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Build()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Connect()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Transfer()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Run()
+      
+    # wait 10 seconds for 10 Grumpfs
+    return app.waitPatternInStdout("Grumpf", 10, 10)
+    
+run_test(test, exemple="python")
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/sikuliberemiz.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,335 @@
+"Commons definitions for sikuli based beremiz IDE GUI tests"
+
+import os
+import sys
+import subprocess
+import traceback
+from threading import Thread, Event, Lock
+from time import time as timesec
+
+import sikuli
+
+beremiz_path = os.environ["BEREMIZPATH"]
+python_bin = os.environ.get("BEREMIZPYTHONPATH", "/usr/bin/python")
+
+opj = os.path.join
+
+
+class KBDShortcut:
+    """Send shortut to app by calling corresponding methods.
+
+    example:
+        k = KBDShortcut()
+        k.Clean()
+    """
+
+    fkeys = {"Stop":     sikuli.Key.F4,
+             "Run":      sikuli.Key.F5,
+             "Transfer": sikuli.Key.F6,
+             "Connect":  sikuli.Key.F7,
+             "Clean":    sikuli.Key.F9,
+             "Build":    sikuli.Key.F11,
+             "Save":     ("s",sikuli.Key.CTRL),
+             "New":      ("n",sikuli.Key.CTRL),
+             "Address":  ("l",sikuli.Key.CTRL)}  # to reach address bar in GTK's file selector
+
+    def __init__(self, app):
+        self.app = app
+    
+    def __getattr__(self, name):
+        fkey = self.fkeys[name]
+        if type(fkey) != tuple:
+            fkey = (fkey,)
+
+        def PressShortCut():
+            self.app.sikuliapp.focus()
+            sikuli.type(*fkey)
+            self.app.ReportText("Sending " + name + " shortcut")
+
+        return PressShortCut
+
+
+class IDEIdleObserver:
+    "Detects when IDE is idle. This is particularly handy when staring an operation and witing for the en of it."
+
+    def __init__(self):
+        """
+        Parameters: 
+            app (class BeremizApp)
+        """
+        self.r = sikuli.Region(self.sikuliapp.window())
+
+        self.idechanged = False
+        
+        # 200 was selected because default 50 was still catching cursor blinking in console
+        # FIXME : remove blinking cursor in console
+        self.r.onChange(200,self._OnIDEWindowChange)
+        self.r.observeInBackground()
+
+    def __del__(self):
+        self.r.stopObserver()
+
+    def _OnIDEWindowChange(self, event):
+        self.idechanged = True
+
+    def WaitIdleUI(self, period=1, timeout=15):
+        """
+        Wait for IDE to stop changing
+        Parameters: 
+            period (int): how many seconds with no change to consider idle
+            timeout (int): how long to wait for idle, in seconds
+        """
+        c = max(timeout/period,1)
+        while c > 0:
+            self.idechanged = False
+            sikuli.wait(period)
+            if not self.idechanged:
+                break
+            c = c - 1
+
+        self.ReportScreenShot("UI is idle" if c != 0 else "UI is not idle")
+
+        if c == 0:
+            raise Exception("Window did not idle before timeout")
+
+ 
+class stdoutIdleObserver:
+    "Detects when IDE's stdout is idle. Can be more reliable than pixel based version (false changes ?)"
+
+    def __init__(self):
+        """
+        Parameters: 
+            app (class BeremizApp)
+        """
+        self.stdoutchanged = False
+
+        self.event = Event()
+
+        self.pattern = None
+        self.success_event = Event()
+
+        self.thread = Thread(target = self._waitStdoutProc).start()
+
+    def __del__(self):
+        pass  # self.thread.join() ?
+
+    def _waitStdoutProc(self):
+        while True:
+            a = self.proc.stdout.readline()
+            if len(a) == 0 or a is None: 
+                break
+            sys.stdout.write(a)
+            self.ReportOutput(a)
+            self.event.set()
+            if self.pattern is not None and a.find(self.pattern) >= 0:
+                sys.stdout.write("found pattern in '" + a +"'")
+                self.success_event.set()
+
+    def waitForChangeAndIdleStdout(self, period=2, timeout=15):
+        """
+        Wait for IDE'stdout to start changing
+        Parameters: 
+            timeout (int): how long to wait for change, in seconds
+        """
+        start_time = timesec()
+
+        wait_result = self.event.wait(timeout)
+
+        self.ReportScreenShot("stdout changed" if wait_result else "stdout didn't change")
+
+        if wait_result:
+            self.event.clear()
+        else:
+            raise Exception("Stdout didn't become active before timeout")
+
+        self.waitIdleStdout(period, timeout - (timesec() - start_time))
+
+    def waitIdleStdout(self, period=2, timeout=15):
+        """
+        Wait for IDE'stdout to stop changing
+        Parameters: 
+            period (int): how many seconds with no change to consider idle
+            timeout (int): how long to wait for idle, in seconds
+        """
+        end_time = timesec() + timeout
+        self.event.clear()
+        while timesec() < end_time:
+            if self.event.wait(period):
+                # no timeout -> got event -> not idle -> loop again
+                self.event.clear()
+            else:
+                # timeout -> no event -> idle -> exit
+                self.ReportScreenShot("stdout is idle")
+                return True
+
+        self.ReportScreenShot("stdout did not idle")
+
+        raise Exception("Stdout did not idle before timeout")
+
+    def waitPatternInStdout(self, pattern, timeout, count=1):
+        found = 0
+        self.pattern = pattern
+        end_time = timesec() + timeout
+        self.event.clear()
+        while True:
+            remain = end_time - timesec()
+            if remain <= 0 :
+                res = False
+                break
+
+            res = self.success_event.wait(remain)
+            if res:
+                self.success_event.clear()
+                found = found + 1
+                if found >= count:
+                    break
+        self.pattern = None
+        self.ReportScreenShot("found pattern" if res else "pattern not found")
+        return res
+
+class BeremizApp(IDEIdleObserver, stdoutIdleObserver):
+    def __init__(self, projectpath=None, exemple=None):
+        """
+        Starts Beremiz IDE, waits for main window to appear, maximize it.
+
+            Parameters: 
+                projectpath (str): path to project to open
+                exemple (str): path relative to exemples directory
+
+            Returns:
+                Sikuli App class instance
+        """
+
+        self.screenshotnum = 0
+        self.starttime = timesec()
+        self.screen = sikuli.Screen()
+
+        self.report = open("report.html", "w")
+        self.report.write("""<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="color-scheme" content="light dark">
+    <title>Test report</title>
+  </head>
+  <body>
+""")
+
+        command = [python_bin, opj(beremiz_path,"Beremiz.py"), "--log=/dev/stdout"]
+
+        if exemple is not None:
+            command.append(opj(beremiz_path,"exemples",exemple))
+        elif projectpath is not None:
+            command.append(projectpath)
+
+        # App class is broken in Sikuli 2.0.5: can't start process with arguments.
+        # 
+        # Workaround : - use subprocess module to spawn IDE process,
+        #              - use wmctrl to find IDE window details and maximize it
+        #              - pass exact window title to App class constructor
+
+        self.ReportText("Launching " + repr(command))
+
+        self.proc = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=0)
+
+        # Window are macthed against process' PID
+        ppid = self.proc.pid
+
+        # Timeout 5s
+        c = 50
+        while c > 0:
+            # equiv to "wmctrl -l -p | grep $pid"
+            try:
+                wlist = filter(lambda l:(len(l)>2 and l[2]==str(ppid)), map(lambda s:s.split(None,4), subprocess.check_output(["wmctrl", "-l", "-p"]).splitlines()))
+            except subprocess.CalledProcessError:
+                wlist = []
+
+            # window with no title only has 4 fields do describe it
+            # beremiz splashcreen has no title
+            # wait until main window is visible
+            if len(wlist) == 1 and len(wlist[0]) == 5:
+                windowID,_zero,wpid,_XID,wtitle = wlist[0] 
+                break
+
+            sikuli.wait(0.1)
+            c = c - 1
+
+        if c == 0:
+            raise Exception("Couldn't find Beremiz window")
+
+        # Maximize window x and y
+        subprocess.check_call(["wmctrl", "-i", "-r", windowID, "-b", "add,maximized_vert,maximized_horz"])
+
+        # switchApp creates an App object by finding window by title, is not supposed to spawn a process
+        self.sikuliapp = sikuli.switchApp(wtitle)
+        self.k = KBDShortcut(self)
+
+        IDEIdleObserver.__init__(self)
+        stdoutIdleObserver.__init__(self)
+
+        # stubs for common sikuli calls to allow adding hooks later
+        for name in ["click","doubleClick","type","rightClick","wait"]:
+            def makeMyMeth(n):
+                def myMeth(*args, **kwargs):
+                    self.ReportScreenShot("Begin: " + n + "(" + repr(args) + "," + repr(kwargs) + ")")
+                    try:
+                        getattr(sikuli, n)(*args, **kwargs)
+                    finally:
+                        self.ReportScreenShot("end: " + n + "(" + repr(args) + "," + repr(kwargs) + ")")
+                return myMeth
+            setattr(self, name, makeMyMeth(name))
+
+    def close(self):
+        self.sikuliapp.close()
+        self.sikuliapp = None
+        self.report.write("""
+  </body>
+</html>""")
+        self.report.close()
+
+    def __del__(self):
+        if self.sikuliapp is not None:
+            self.sikuliapp.close()
+        IDEIdleObserver.__del__(self)
+        stdoutIdleObserver.__del__(self)
+
+    def ReportScreenShot(self, msg):
+        elapsed = "%.3fs: "%(timesec() - self.starttime)
+        fname = "capture"+str(self.screenshotnum)+".png"
+        cap = self.screen.capture(self.r)
+        cap.save(".", fname)
+        self.screenshotnum = self.screenshotnum + 1
+        self.report.write( "<p>" + elapsed + msg + "<br/><img src=\""+ fname + "\">" + "</p>")
+
+    def ReportText(self, text):
+        elapsed = "%.3fs: "%(timesec() - self.starttime)
+        self.report.write("<p>" + elapsed + text + "</p>")
+
+    def ReportOutput(self, text):
+        elapsed = "%.3fs: "%(timesec() - self.starttime)
+        self.report.write("<pre>" + elapsed + text + "</pre>")
+
+
+def run_test(func, *args, **kwargs):
+    app = BeremizApp(*args, **kwargs)
+    try:
+        success = func(app)
+    except:
+        # sadly, sys.excepthook is broken in sikuli/jython 
+        # purpose of this run_test function is to work around it.
+        # and catch exception cleanly anyhow
+        e_type, e_value, e_traceback = sys.exc_info()
+        err_msg = "\n".join(traceback.format_exception(e_type, e_value, e_traceback))
+        sys.stdout.write(err_msg)
+        app.ReportOutput(err_msg)
+        success = False
+
+    app.close()
+
+    if success:
+        sikuli.exit(0)
+    else:
+        sikuli.exit(1)
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/wx_widgets.pytest/test_CustomIntCtrl.py	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+# Copyright (C) 2017: Andrey Skvortsov
+#
+# See COPYING file for copyrights details.
+#
+# This program 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
+# of the License, or (at your option) any later version.
+#
+# This program 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 program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+
+from __future__ import absolute_import
+from __future__ import division
+import unittest
+import time
+
+import wx
+import conftest
+import controls.CustomIntCtrl
+
+
+class TestCustomIntCtrl(unittest.TestCase):
+    def setUp(self):
+        self.app = wx.App()
+        self.frame = wx.Frame(None)
+
+    def tearDown(self):
+        self.frame.Destroy()
+        wx.CallAfter(wx.Exit)
+        self.app.MainLoop()
+
+    def testMaxLimit(self):
+        """Test working upper bound"""
+        self.AddControls()
+        self.int_ctrl.SetValue(self.max_val + 100)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+        self.assertEqual(self.int_ctrl.GetValue(), self.max_val)
+
+    def testMinLimit(self):
+        """Test working lower bound"""
+        self.AddControls()
+        self.int_ctrl.SetValue(self.min_val - 100)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(self.int_ctrl.GetValue(), self.min_val)
+
+    def testCorrectValue(self):
+        """Test case if no limiting is necessary"""
+        self.AddControls()
+        val = (self.max_val + self.min_val) // 2
+        self.int_ctrl.SetValue(val)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(self.int_ctrl.GetValue(), val)
+
+    def testEventBinding(self):
+        """Test event sending after edit and bound checks are done"""
+        self.AddControls()
+        self.event_happend = False
+
+        def EventHandler(event):
+            self.event_happend = True
+            event.Skip()
+
+        self.int_ctrl.Bind(controls.CustomIntCtrl.EVT_CUSTOM_INT, EventHandler)
+
+        val = (self.max_val + self.min_val) // 2
+
+        self.int_ctrl.SetValue(val)
+        self.ProcessEvents()
+        self.txt_ctrl.SetFocus()
+
+        self.ProcessEvents()
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(self.int_ctrl.GetValue(), val)
+        self.assertTrue(self.event_happend)
+
+    def testLongNumbers(self):
+        """Test support of long integer"""
+        self.AddControls()
+        val = 40000000000
+        self.int_ctrl.SetMax(val)
+        self.int_ctrl.SetValue(val)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(val, val)
+
+    def ProcessEvents(self):
+        for dummy in range(0, 10):
+            wx.Yield()
+            time.sleep(0.01)
+
+    def AddControls(self):
+        vs = wx.BoxSizer(wx.VERTICAL)
+        self.int_ctrl = controls.CustomIntCtrl(self.frame)
+        self.txt_ctrl = wx.TextCtrl(self.frame)
+        vs.Add(self.int_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
+        vs.Add(self.txt_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
+        self.frame.SetSizer(vs)
+        vs.Fit(self.frame)
+        self.frame.Show()
+        self.frame.Raise()
+
+        self.min_val = 50
+        self.max_val = 100
+        self.int_ctrl.SetBounds(self.min_val, self.max_val)
+        self.ProcessEvents()
+
+
+if __name__ == '__main__':
+    conftest.init_environment()
+    unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/Dockerfile	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,73 @@
+#
+# Dockerfile for Beremiz
+# This container is used to run tests for Beremiz
+#
+FROM ubuntu:focal  
+                                        
+ENV TERM xterm-256color
+    
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
+ARG UNAME=testing
+ENV UNAME ${UNAME}
+ARG UID=1000
+ARG GID=1000
+RUN groupadd -g $GID $UNAME
+RUN useradd -m -u $UID -g $GID -s /bin/bash $UNAME
+
+RUN set -xe \
+    && apt-get update \
+    && TZ="America/Paris" \
+       DEBIAN_FRONTEND="noninteractive" \
+       apt-get install -y --no-install-recommends \
+               `# run sikuli` \
+               wget \
+               libopencv4.2-java \
+               openjdk-11-jre \
+               \
+               `# run X based tests` \
+               fluxbox \
+               wmctrl xdotool xvfb \
+               x11vnc xterm xnest \
+               \
+               `# to build tested apps` \
+               build-essential automake flex bison mercurial \
+               libgtk-3-dev libgl1-mesa-dev libglu1-mesa-dev \
+               libpython2.7-dev libssl-dev \
+               python2 virtualenv cmake
+
+# link obtained from https://raiman.github.io/SikuliX1/downloads.html
+RUN set -xe && \
+    wget -qP /usr/local/bin \
+        https://launchpad.net/sikuli/sikulix/2.0.5/+download/sikulixide-2.0.5.jar && \
+    echo 0795f1e0866ee5a7a84e4c89793ea78c /usr/local/bin/sikulixide-2.0.5.jar | md5sum -c && \
+    ( echo '#!/bin/sh' && \
+      echo "exec java -jar /usr/local/bin/sikulixide-*.jar \"\$@\"" \
+    ) | install /dev/stdin /usr/local/bin/sikulix
+
+
+RUN env echo -e '#!/bin/bash\nmake -f /home/testing/src/beremiz/tests/Makefile $*' > /usr/local/bin/do_tests
+RUN chmod +x /usr/local/bin/do_tests
+
+USER $UNAME
+
+RUN mkdir /home/$UNAME/build /home/$UNAME/src /home/$UNAME/test
+
+RUN virtualenv --python=$(which python2) ~/beremizenv
+
+RUN ~/beremizenv/bin/pip install \
+        pytest pytest-timeout ddt \
+        lxml future matplotlib zeroconf2 enum34 pyro sslpsk posix_spawn \
+        twisted nevow autobahn click opcua \
+        wxPython==4.1.1
+
+# Point to python binary test scripts will use
+ENV BEREMIZPYTHONPATH /home/$UNAME/beremizenv/bin/python
+
+# easy to remember 'do_tests' alias to invoke main makefile
+ARG OWNDIRBASENAME=beremiz
+ENV OWNDIRBASENAME ${OWNDIRBASENAME}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/build_docker_image.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -e
+
+echo "Building docker image"
+docker build \
+    --build-arg UID=$(id -u) \
+    --build-arg GID=$(id -g) \
+    -t beremiz_sikuli .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/clean_docker_container.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+# delete container
+docker rm beremiz_sikuli_current
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/clean_docker_image.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+# delete image
+docker rmi beremiz_sikuli
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/create_docker_container.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+
+# source directory containing beremiz, matiec, etc..
+SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd ../../../../.. && pwd )"
+echo "SOURCE direcory : $SRCDIR"
+
+# absolute path to test directory. ~/test if not given as only argument
+TESTDIR=${1:-~/test}
+mkdir -p $TESTDIR
+echo "TEST direcory : $TESTDIR"
+
+UNAME=testing
+UHOME=/home/$UNAME
+
+# define TESTDEBUG in env to enable dev-mode. This enables :
+#   - debug pasthrough for Xnest
+#   - VNC port passthrough
+DEBUGARGS="-v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 -e DISPLAY=$DISPLAY -p 5900:5900"
+
+echo "Creating docker container"
+docker create \
+       --name beremiz_sikuli_current \
+       -v $SRCDIR:$UHOME/src \
+       -v $TESTDIR:$UHOME/test \
+       `if [ "$TESTDEBUG" == "YES" ]; then echo $DEBUGARGS; fi` \
+       -w $UHOME/test \
+       -i -t beremiz_sikuli /bin/bash
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/do_test_in_docker.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+CONTAINER=beremiz_sikuli_current
+
+docker stop $CONTAINER
+docker start $CONTAINER 
+docker exec $CONTAINER bash -c "do_tests $1"
+docker stop $CONTAINER
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/enter_docker.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+CONTAINER=beremiz_sikuli_current
+
+docker start -i $CONTAINER
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/enter_docker_as_root.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+CONTAINER=beremiz_sikuli_current
+
+docker start $CONTAINER
+docker exec -i -t -u root $CONTAINER bash
+docker stop $CONTAINER
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-sikuli/rebuild_docker.sh	Sun Jul 17 22:53:35 2022 +0200
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+./clean_docker_container.sh || true
+./clean_docker_image.sh || true
+./build_docker_image.sh
+./create_docker_container.sh $1
+
--- a/tests/tools/conftest.py	Thu Jul 14 11:40:27 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz, a Integrated Development Environment for
-# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-# Copyright (C) 2017: Andrey Skvortsov
-#
-# See COPYING file for copyrights details.
-#
-# This program 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
-# of the License, or (at your option) any later version.
-#
-# This program 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 program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-
-
-from __future__ import absolute_import
-import os
-import sys
-
-# import pytest
-# import xvfbwrapper
-
-
-def init_environment():
-    """Append module root directory to sys.path"""
-    try:
-        import Beremiz as _Beremiz
-    except ImportError:
-        sys.path.append(
-            os.path.abspath(
-                os.path.join(
-                    os.path.dirname(__file__), '..', '..')
-            )
-        )
-
-
-init_environment()
-
-#
-# Something seems to be broken in Beremiz application,
-# because after tests in test_application.py during Xvfb shutdown
-# pytest returns error message:
-# pytest: Fatal IO error 11 (Die Ressource ist zur Zeit nicht verfügbar) on X server :2821.
-#
-# As a result of that pytest returns code 1 as some tests were failed,
-# but they aren't.
-#
-# To avoid this Xvfb is launched and killed not by pytest.
-# $ Xvfb :42 -screen 0 1280x1024x24 &
-# $ export DISPLAY=:42
-# $ pytest --timeout=10 ./tests/tools
-# $ pkill -9 Xvfb
-#
-# TODO: find root of this problem.
-
-
-# vdisplay = None
-#
-# @pytest.fixture(scope="session", autouse=True)
-# def start_xvfb_server(request):
-#     global vdisplay
-#     vdisplay = xvfbwrapper.Xvfb(width=1280, height=720)
-#     vdisplay.start()
-#     request.addfinalizer(stop_xvfb_server)
-#
-# def stop_xvfb_server():
-#     if vdisplay is not None:
-#         vdisplay.stop()
--- a/tests/tools/run_python_tests.sh	Thu Jul 14 11:40:27 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-#!/bin/sh
-
-
-
-cleanup()
-{
-    find $PYTEST_DIR -name '*.pyc' -delete
-}
-
-
-
-print_help()
-{
-    echo "Usage: run_python_tests.sh [--on-local-xserver]"
-    echo ""
-    echo "--on-local-xserver"
-    echo "                all tests are run on local X-server. "
-    echo "                User can see test in action."
-    echo "                Any interaction (mouse, keyboard) should be avoided"
-    echo "                By default without arguments script runs pytest on virtual X serverf."
-    echo ""
-
-    exit 1
-}
-
-main()
-{
-    LC_ALL=ru_RU.utf-8
-    PYTEST_DIR=./tests/tools
-
-    if [ ! -d $PYTEST_DIR ]; then
-	echo "Script should be run from top directory in repository"
-	exit 1;
-    fi
-
-    use_xvfb=0
-    if [ "$1" != "--on-local-xserver" ]; then
-	export DISPLAY=:42
-	use_xvfb=1
-	Xvfb $DISPLAY -screen 0 1280x1024x24 &
-	sleep 1
-    fi
-
-
-    cleanup
-
-    ret=0
-    DELAY=400
-    KILL_DELAY=$(($DELAY + 30))
-    timeout -k $KILL_DELAY $DELAY pytest --timeout=10 ./tests/tools
-    ret=$?
-
-    cleanup
-
-    [ $use_xvfb = 1 ] && pkill -9 Xvfb
-    exit $ret
-}
-
-
-[ "$1" = "--help" -o "$1" = "-h" ] && print_help
-main $@
--- a/tests/tools/test_CustomIntCtrl.py	Thu Jul 14 11:40:27 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz, a Integrated Development Environment for
-# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-# Copyright (C) 2017: Andrey Skvortsov
-#
-# See COPYING file for copyrights details.
-#
-# This program 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
-# of the License, or (at your option) any later version.
-#
-# This program 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 program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-
-
-from __future__ import absolute_import
-from __future__ import division
-import unittest
-import time
-
-import wx
-import conftest
-import controls.CustomIntCtrl
-
-
-class TestCustomIntCtrl(unittest.TestCase):
-    def setUp(self):
-        self.app = wx.App()
-        self.frame = wx.Frame(None)
-
-    def tearDown(self):
-        self.frame.Destroy()
-        wx.CallAfter(self.app.Exit)
-        self.app.MainLoop()
-
-    def testMaxLimit(self):
-        """Test working upper bound"""
-        self.AddControls()
-        self.int_ctrl.SetValue(self.max_val + 100)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-        self.assertEqual(self.int_ctrl.GetValue(), self.max_val)
-
-    def testMinLimit(self):
-        """Test working lower bound"""
-        self.AddControls()
-        self.int_ctrl.SetValue(self.min_val - 100)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(self.int_ctrl.GetValue(), self.min_val)
-
-    def testCorrectValue(self):
-        """Test case if no limiting is necessary"""
-        self.AddControls()
-        val = (self.max_val + self.min_val) // 2
-        self.int_ctrl.SetValue(val)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(self.int_ctrl.GetValue(), val)
-
-    def testEventBinding(self):
-        """Test event sending after edit and bound checks are done"""
-        self.AddControls()
-        self.event_happend = False
-
-        def EventHandler(event):
-            self.event_happend = True
-            event.Skip()
-
-        self.int_ctrl.Bind(controls.CustomIntCtrl.EVT_CUSTOM_INT, EventHandler)
-
-        val = (self.max_val + self.min_val) // 2
-
-        self.int_ctrl.SetValue(val)
-        self.ProcessEvents()
-        self.txt_ctrl.SetFocus()
-
-        self.ProcessEvents()
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(self.int_ctrl.GetValue(), val)
-        self.assertTrue(self.event_happend)
-
-    def testLongNumbers(self):
-        """Test support of long integer"""
-        self.AddControls()
-        val = 40000000000
-        self.int_ctrl.SetMax(val)
-        self.int_ctrl.SetValue(val)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(val, val)
-
-    def ProcessEvents(self):
-        for dummy in range(0, 10):
-            wx.Yield()
-            time.sleep(0.01)
-
-    def AddControls(self):
-        vs = wx.BoxSizer(wx.VERTICAL)
-        self.int_ctrl = controls.CustomIntCtrl(self.frame)
-        self.txt_ctrl = wx.TextCtrl(self.frame)
-        vs.Add(self.int_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
-        vs.Add(self.txt_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
-        self.frame.SetSizer(vs)
-        vs.Fit(self.frame)
-        self.frame.Show()
-        self.frame.Raise()
-
-        self.min_val = 50
-        self.max_val = 100
-        self.int_ctrl.SetBounds(self.min_val, self.max_val)
-        self.ProcessEvents()
-
-
-if __name__ == '__main__':
-    conftest.init_environment()
-    unittest.main()
--- a/tests/tools/test_application.py	Thu Jul 14 11:40:27 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz, a Integrated Development Environment for
-# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-# Copyright (C) 2017: Andrey Skvortsov
-#
-# See COPYING file for copyrights details.
-#
-# This program 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
-# of the License, or (at your option) any later version.
-#
-# This program 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 program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-
-
-from __future__ import absolute_import
-from __future__ import print_function
-import os
-import sys
-import unittest
-import time
-
-import six
-import pytest
-import wx
-import ddt
-
-import conftest
-import Beremiz
-import PLCOpenEditor
-
-
-class UserApplicationTest(unittest.TestCase):
-    def InstallExceptionHandler(self):
-        def handle_exception(e_type, e_value, e_traceback, exit=False):
-            # traceback.print_exception(e_type, e_value, e_traceback)
-            self.exc_info = [e_type, e_value, e_traceback]
-        self.exc_info = None
-        self.old_excepthook = sys.excepthook
-        sys.excepthook = handle_exception
-
-    def StartApp(self):
-        self.app = None
-
-    def FinishApp(self):
-        wx.CallAfter(self.app.frame.Close)
-        self.app.MainLoop()
-        self.app = None
-
-    def setUp(self):
-        self.app = None
-
-    def tearDown(self):
-        if self.app is not None and self.app.frame is not None:
-            self.FinishApp()
-
-    def RunUIActions(self, actions):
-        for act in actions:
-            wx.CallAfter(*act)
-            self.ProcessEvents()
-
-    def CheckForErrors(self):
-        if self.exc_info is not None:
-            # reraise catched previously exception
-            exc_type = self.exc_info[0]
-            exc_value = self.exc_info[1]
-            exc_traceback = self.exc_info[2]
-            six.reraise(exc_type, exc_value, exc_traceback)
-
-    def ProcessEvents(self):
-        for dummy in range(0, 30):
-            self.CheckForErrors()
-            wx.Yield()
-            time.sleep(0.01)
-
-
-@ddt.ddt
-class BeremizApplicationTest(UserApplicationTest):
-    """Test Beremiz as whole application"""
-
-    def StartApp(self):
-        self.app = Beremiz.BeremizIDELauncher()
-        # disable default exception handler in Beremiz
-        self.app.InstallExceptionHandler = lambda: None
-        self.InstallExceptionHandler()
-        self.app.handle_exception = sys.excepthook
-        self.app.PreStart()
-        self.ProcessEvents()
-        self.app.frame.Show()
-        self.ProcessEvents()
-        self.app.frame.ShowFullScreen(True)
-        self.ProcessEvents()
-
-    def FinishApp(self):
-        wx.CallAfter(self.app.frame.Close)
-        self.app.MainLoop()
-        time.sleep(1)
-        self.app = None
-
-    def GetSkippedProjectTreeItems(self):
-        """
-        Returns the list of skipped items in the project tree.
-
-        Beremiz test don't need to skip any elemnts in the project tree.
-        """
-        return []
-
-    def OpenAllProjectElements(self):
-        """Open editor for every object in the project tree"""
-        self.app.frame.ProjectTree.ExpandAll()
-        self.ProcessEvents()
-        item = self.app.frame.ProjectTree.GetRootItem()
-        skip = self.GetSkippedProjectTreeItems()
-        tree_id = self.app.frame.ProjectTree.GetId()
-        while item is not None:
-            self.app.frame.ProjectTree.SelectItem(item, True)
-            self.ProcessEvents()
-            if item not in skip:
-                event = wx.lib.agw.customtreectrl.TreeEvent(
-                    wx.lib.agw.customtreectrl.wxEVT_TREE_ITEM_ACTIVATED,
-                    tree_id, item)
-                self.app.frame.OnProjectTreeItemActivated(event)
-            self.ProcessEvents()
-            item = self.app.frame.ProjectTree.GetNextVisible(item)
-
-    def CheckTestProject(self, project):
-        sys.argv = ["", project]
-        self.StartApp()
-        self.OpenAllProjectElements()
-        user_actions = self.GetUserActions()
-        self.RunUIActions(user_actions)
-        self.FinishApp()
-
-    def GetProjectPath(self, project):
-        return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", project))
-
-    def GetUserActions(self):
-        """
-        Returns list of user actions that will be executed
-        on every test project by testCheckProject test.
-        """
-        user_actions = [
-            [self.app.frame.SwitchFullScrMode, None],
-            [self.app.frame.SwitchFullScrMode, None],
-            [self.app.frame.CTR._Clean],
-            [self.app.frame.CTR._Build],
-            [self.app.frame.CTR._Connect],
-            [self.app.frame.CTR._Transfer],
-            [self.app.frame.CTR._Run],
-            [self.app.frame.CTR._Stop],
-            [self.app.frame.CTR._Disconnect],
-            [self.app.frame.CTR._Clean],
-        ]
-        return user_actions
-
-    def testStartUp(self):
-        """Checks whether the app starts and finishes correctly"""
-        sys.argv = [""]
-        self.StartApp()
-        self.FinishApp()
-
-    @ddt.data(
-        "first_steps",
-        "logging",
-        "traffic_lights",
-        "wxGlade",
-        "python",
-        "wiimote",
-        "wxHMI",
-    )
-    @pytest.mark.timeout(30)
-    def testCheckProject(self, name):
-        """
-        Checks that test PLC project can be open,
-        compiled and run on SoftPLC.
-        """
-        project = self.GetProjectPath(name)
-        print("Testing example " + name)
-        self.CheckTestProject(project)
-
-
-class PLCOpenEditorApplicationTest(BeremizApplicationTest):
-    """Test PLCOpenEditor as whole application"""
-
-    def StartApp(self):
-        self.app = PLCOpenEditor.PLCOpenEditorApp()
-        # disable default exception handler in application
-        self.app.InstallExceptionHandler = lambda: None
-        self.InstallExceptionHandler()
-        self.app.Show()
-        self.ProcessEvents()
-        self.app.frame.ShowFullScreen(True)
-        self.ProcessEvents()
-
-    def FinishApp(self):
-        wx.CallAfter(self.app.frame.Close)
-        self.app.MainLoop()
-        time.sleep(1)
-        self.app = None
-
-    def GetSkippedProjectTreeItems(self):
-        """
-        Returns the list of skipped items in the project tree.
-
-        Root item opens dialog window for project settings.
-        To avoid code that handles closing dialog windows just skip this item.
-        """
-        return [self.app.frame.ProjectTree.GetRootItem()]
-
-    def GetUserActions(self):
-        return []
-
-    def GetProjectPath(self, project):
-        """Open PLC program in every Beremiz test project"""
-        project_dir = BeremizApplicationTest.GetProjectPath(self, project)
-        return os.path.join(project_dir, "plc.xml")
-
-
-if __name__ == '__main__':
-    conftest.init_environment()
-    unittest.main()
--- a/util/BitmapLibrary.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/util/BitmapLibrary.py	Sun Jul 17 22:53:35 2022 +0200
@@ -71,7 +71,7 @@
             height = max(bmp1.GetHeight(), bmp2.GetHeight())
 
             # Create bitmap with both icons
-            bmp = wx.EmptyBitmap(width, height)
+            bmp = wx.Bitmap(width, height)
             dc = wx.MemoryDC()
             dc.SelectObject(bmp)
             dc.Clear()
--- a/util/ExceptionHandler.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/util/ExceptionHandler.py	Sun Jul 17 22:53:35 2022 +0200
@@ -49,7 +49,7 @@
         trcbck_lst.append(trcbck)
 
     # Allow clicking....
-    cap = wx.Window_GetCapture()
+    cap = wx.Window.GetCapture()
     if cap:
         cap.ReleaseMouse()
 
@@ -81,8 +81,14 @@
 
 
 def get_last_traceback(tb):
-    while tb.tb_next:
-        tb = tb.tb_next
+    while True:
+        if not hasattr(tb, "tb_next"):
+            break
+        if tb.tb_next:
+            tb = tb.tb_next
+        else:
+            break
+
     return tb
 
 
@@ -93,7 +99,7 @@
 ignored_exceptions = []  # a problem with a line in a module is only reported once per session
 
 
-def AddExceptHook(app_version='[No version]'):
+def AddExceptHook(app_version='[No version]', logf = None):
 
     def save_bug_report(e_type, e_value, e_traceback, bug_report_path, date):
         info = {
@@ -111,7 +117,8 @@
         if e_traceback:
             info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value)
             last_tb = get_last_traceback(e_traceback)
-            exception_locals = last_tb.tb_frame.f_locals  # the locals at the level of the stack trace where the exception actually occurred
+            # save locals at the level of the stack trace where the exception actually occurred
+            exception_locals = last_tb.tb_frame.f_locals if last_tb else {};
             info['locals'] = format_namespace(exception_locals)
             if 'self' in exception_locals:
                 try:
@@ -125,13 +132,17 @@
         lst = info.keys()
         lst.sort()
         for a in lst:
-            output.write(a + ":\n" + str(info[a]) + "\n\n")
+            line = a + ":\n" + str(info[a]) + "\n\n"
+            output.write(line)
+            if logf is not None:
+                logf.write(line)
         output.close()
 
     def handle_exception(e_type, e_value, e_traceback, exit=False):
         traceback.print_exception(e_type, e_value, e_traceback)  # this is very helpful when there's an exception in the rest of this func
         last_tb = get_last_traceback(e_traceback)
-        ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno)
+        ex = (last_tb.tb_frame.f_code.co_filename if last_tb else "unknown",
+              last_tb.tb_frame.f_lineno if last_tb else None)
         if ex not in ignored_exceptions:
             ignored_exceptions.append(ex)
             date = time.ctime()
--- a/util/paths.py	Thu Jul 14 11:40:27 2022 +0200
+++ b/util/paths.py	Sun Jul 17 22:53:35 2022 +0200
@@ -55,3 +55,8 @@
     """
     return os.path.join(AbsParentDir(__file__, 2), name)
 
+def Bpath(*names):
+    """
+    Return path of files in Beremiz project
+    """
+    return os.path.join(AbsParentDir(__file__, 1), *names)