SVGHMI: split svghmi.py into svghmi.py (Config Tree Node + code gen) and ui.py (UI for HMI tree and Widget picking) svghmi
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Thu, 25 Mar 2021 13:07:52 +0100
branchsvghmi
changeset 3201 6dadc1690284
parent 3197 0f41c1e2c121
child 3202 5d379934d5c9
SVGHMI: split svghmi.py into svghmi.py (Config Tree Node + code gen) and ui.py (UI for HMI tree and Widget picking)
svghmi/svghmi.py
svghmi/ui.py
--- a/svghmi/svghmi.py	Wed Mar 24 05:34:46 2021 +0100
+++ b/svghmi/svghmi.py	Thu Mar 25 13:07:52 2021 +0100
@@ -10,7 +10,6 @@
 import os
 import shutil
 import hashlib
-import weakref
 import shlex
 import time
 
@@ -19,7 +18,6 @@
 from lxml import etree
 from lxml.etree import XSLTApplyError
 
-from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
 import util.paths as paths
 from POULibrary import POULibrary
 from docutil import open_svg, get_inkscape_path
@@ -29,8 +27,10 @@
 import targets
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
 from XSLTransform import XSLTransform
-from svghmi.i18n import EtreeToMessages, SaveCatalog, ReadTranslations, MatchTranslations, TranslationToEtree, open_pofile
+from svghmi.i18n import EtreeToMessages, SaveCatalog, ReadTranslations,\
+                        MatchTranslations, TranslationToEtree, open_pofile
 from svghmi.hmi_tree import HMI_TYPES, HMITreeNode, SPECIAL_NODES 
+from svghmi.ui import SVGHMI_UI
 
 
 ScriptDirectory = paths.AbsDir(__file__)
@@ -146,7 +146,7 @@
                 self.FatalError("SVGHMI : " + message)
 
         if on_hmitree_update is not None:
-            on_hmitree_update()
+            on_hmitree_update(hmi_tree_root)
 
         variable_decl_array = []
         extern_variables_declarations = []
@@ -237,295 +237,21 @@
                 # to ensure placement before other CTN generated code in execution order
 
 
-def SVGHMIEditorUpdater(ref):
-    def SVGHMIEditorUpdate():
-        o = ref()
-        if o is not None:
-            wx.CallAfter(o.MakeTree)
-    return SVGHMIEditorUpdate
-
-class HMITreeSelector(wx.TreeCtrl):
-    def __init__(self, parent):
-        global on_hmitree_update
-        wx.TreeCtrl.__init__(self, parent, style=(
-            wx.TR_MULTIPLE |
-            wx.TR_HAS_BUTTONS |
-            wx.SUNKEN_BORDER |
-            wx.TR_LINES_AT_ROOT))
-
-        on_hmitree_update = SVGHMIEditorUpdater(weakref.ref(self))
-        self.MakeTree()
-
-    def _recurseTree(self, current_hmitree_root, current_tc_root):
-        for c in current_hmitree_root.children:
-            if hasattr(c, "children"):
-                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, None) # TODO
-
-                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, None) # TODO
-
-    def MakeTree(self):
-        global hmi_tree_root
-
-        self.Freeze()
-
-        self.root = None
-        self.DeleteAllItems()
-
-        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, None)
-
-        if hmi_tree_root is not None:
-            self._recurseTree(hmi_tree_root, self.root)
-            self.Expand(self.root)
-
-        self.Thaw()
-
-class WidgetPicker(wx.TreeCtrl):
-    def __init__(self, parent, initialdir=None):
-        wx.TreeCtrl.__init__(self, parent, style=(
-            wx.TR_MULTIPLE |
-            wx.TR_HAS_BUTTONS |
-            wx.SUNKEN_BORDER |
-            wx.TR_LINES_AT_ROOT))
-
-        self.MakeTree(initialdir)
-
-    def _recurseTree(self, current_dir, current_tc_root, dirlist):
-        """
-        recurse through subdirectories, but creates tree nodes 
-        only when (sub)directory conbtains .svg file
-        """
-        res = []
-        for f in sorted(os.listdir(current_dir)):
-            p = os.path.join(current_dir,f)
-            if os.path.isdir(p):
-
-                r = self._recurseTree(p, current_tc_root, dirlist + [f])
-                if len(r) > 0 :
-                    res = r
-                    dirlist = []
-                    current_tc_root = res.pop()
-
-            elif os.path.splitext(f)[1].upper() == ".SVG":
-                if len(dirlist) > 0 :
-                    res = []
-                    for d in dirlist:
-                        current_tc_root = self.AppendItem(current_tc_root, d)
-                        res.append(current_tc_root)
-                        self.SetPyData(current_tc_root, None)
-                    dirlist = []
-                    res.pop()
-                tc_child = self.AppendItem(current_tc_root, f)
-                self.SetPyData(tc_child, p)
-        return res
-
-    def MakeTree(self, lib_dir = None):
-        global hmi_tree_root
-
-        self.Freeze()
-
-        self.root = None
-        self.DeleteAllItems()
-
-        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)
-
-        if lib_dir is not None:
-            self._recurseTree(lib_dir, self.root, [])
-            self.Expand(self.root)
-
-        self.Thaw()
-
-_conf_key = "SVGHMIWidgetLib"
-_preview_height = 200
-class WidgetLibBrowser(wx.Panel):
-    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
-                 size=wx.DefaultSize):
-
-        wx.Panel.__init__(self, parent, id, pos, size)     
-
-        self.bmp = None
-        self.msg = None
-        self.hmitree_node = None
-        self.selected_SVG = None
-
-        self.Config = wx.ConfigBase.Get()
-        self.libdir = self.RecallLibDir()
-
-        sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0)
-        sizer.AddGrowableCol(0)
-        sizer.AddGrowableRow(1)
-        self.libbutton = wx.Button(self, -1, _("Select SVG widget library"))
-        self.widgetpicker = WidgetPicker(self, self.libdir)
-        self.preview = wx.Panel(self, size=(-1, _preview_height + 10))  #, style=wx.SIMPLE_BORDER)
-        #self.preview.SetBackgroundColour(wx.WHITE)
-        sizer.AddWindow(self.libbutton, flag=wx.GROW)
-        sizer.AddWindow(self.widgetpicker, flag=wx.GROW)
-        sizer.AddWindow(self.preview, flag=wx.GROW)
-        sizer.Layout()
-        self.SetAutoLayout(True)
-        self.SetSizer(sizer)
-        sizer.Fit(self)
-        self.Bind(wx.EVT_BUTTON, self.OnSelectLibDir, self.libbutton)
-        self.preview.Bind(wx.EVT_PAINT, self.OnPaint)
-
-        self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnWidgetSelection, self.widgetpicker)
-
-        self.msg = _("Drag selected Widget from here to Inkscape")
-
-    def RecallLibDir(self):
-        conf = self.Config.Read(_conf_key)
-        if len(conf) == 0:
-            return None
-        else:
-            return DecodeFileSystemPath(conf)
-
-    def RememberLibDir(self, path):
-        self.Config.Write(_conf_key,
-                          EncodeFileSystemPath(path))
-        self.Config.Flush()
-
-    def DrawPreview(self):
-        """
-        Refresh preview panel 
-        """
-        # Init preview panel paint device context
-        dc = wx.PaintDC(self.preview)
-        dc.Clear()
-
-        if self.bmp:
-            # Get Preview panel size
-            sz = self.preview.GetClientSize()
-            w = self.bmp.GetWidth()
-            dc.DrawBitmap(self.bmp, (sz.width - w)/2, 5)
-
-        if self.msg:
-            dc.SetFont(self.GetFont())
-            dc.DrawText(self.msg, 25,25)
-
-
-    def OnSelectLibDir(self, event):
-        defaultpath = self.RecallLibDir()
-        if defaultpath == None:
-            defaultpath = os.path.expanduser("~")
-
-        dialog = wx.DirDialog(self, _("Choose a widget library"), defaultpath,
-                              style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
-
-        if dialog.ShowModal() == wx.ID_OK:
-            self.libdir = dialog.GetPath()
-            self.RememberLibDir(self.libdir)
-            self.widgetpicker.MakeTree(self.libdir)
-
-        dialog.Destroy()
-
-    def OnPaint(self, event):
-        """
-        Called when Preview panel needs to be redrawn
-        @param event: wx.PaintEvent
-        """
-        self.DrawPreview()
-        event.Skip()
-
-    def GenThumbnail(self, svgpath, thumbpath):
-        inkpath = get_inkscape_path()
-        if inkpath is None:
-            self.msg = _("Inkscape is not installed.")
-            return False
-        # TODO: spawn a thread, to decouple thumbnail gen
-        status, result, _err_result = ProcessLogger(
-            None,
-            '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
-            '" -D -h ' + str(_preview_height)).spin()
-        if status != 0:
-            self.msg = _("Inkscape couldn't generate thumbnail.")
-            return False
-        return True
-
-    def OnWidgetSelection(self, event):
-        """
-        Called when tree item is selected
-        @param event: wx.TreeEvent
-        """
-        item_pydata = self.widgetpicker.GetPyData(event.GetItem())
-        if item_pydata is not None:
-            svgpath = item_pydata
-            dname = os.path.dirname(svgpath)
-            fname = os.path.basename(svgpath)
-            hasher = hashlib.new('md5')
-            with open(svgpath, 'rb') as afile:
-                while True:
-                    buf = afile.read(65536)
-                    if len(buf) > 0:
-                        hasher.update(buf)
-                    else:
-                        break
-            digest = hasher.hexdigest()
-            thumbfname = os.path.splitext(fname)[0]+"_"+digest+".png"
-            thumbdir = os.path.join(dname, ".svghmithumbs") 
-            thumbpath = os.path.join(thumbdir, thumbfname) 
-
-            self.msg = None
-            have_thumb = os.path.exists(thumbpath)
-
-            if not have_thumb:
-                try:
-                    if not os.path.exists(thumbdir):
-                        os.mkdir(thumbdir)
-                except IOError:
-                    self.msg = _("Widget library must be writable")
-                else:
-                    have_thumb = self.GenThumbnail(svgpath, thumbpath)
-
-            self.bmp = wx.Bitmap(thumbpath) if have_thumb else None
-
-            self.selected_SVG = svgpath if have_thumb else None
-            self.ValidateWidget()
-
-            self.Refresh()
-        event.Skip()
-
-    def OnHMITreeNodeSelection(self, hmitree_node):
-        self.hmitree_node = hmitree_node
-        self.ValidateWidget()
-        self.Refresh()
-
-    def ValidateWidget(self):
-        if self.selected_SVG is not None:
-            if self.hmitree_node is not None:
-                pass
-        # XXX TODO: 
-        #      - check SVG is valid for selected HMI tree item
-        #      - prepare for D'n'D
-
-
-class HMITreeView(wx.SplitterWindow):
-
-    def __init__(self, parent):
-        wx.SplitterWindow.__init__(self, parent,
-                                   style=wx.SUNKEN_BORDER | wx.SP_3D)
-
-        self.SelectionTree = HMITreeSelector(self)
-        self.Staging = WidgetLibBrowser(self)
-        self.SplitVertically(self.SelectionTree, self.Staging, 300)
+def Register_SVGHMI_UI_for_HMI_tree_updates(ref):
+    global on_hmitree_update
+    def HMITreeUpdate(_hmi_tree_root):
+        obj = ref()
+        if obj is not None:
+            obj.HMITreeUpdate(_hmi_tree_root)
+
+    on_hmitree_update = HMITreeUpdate
 
 
 class SVGHMIEditor(ConfTreeNodeEditor):
     CONFNODEEDITOR_TABS = [
-        (_("HMI Tree"), "CreateHMITreeView")]
-
-    def CreateHMITreeView(self, parent):
+        (_("HMI Tree"), "CreateSVGHMI_UI")]
+
+    def CreateSVGHMI_UI(self, parent):
         global hmi_tree_root
 
         if hmi_tree_root is None:
@@ -535,10 +261,7 @@
                 hmitree_backup_file = open(hmitree_backup_path, 'rb')
                 hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
 
-
-        #self.HMITreeView = HMITreeView(self)
-        #return HMITreeSelector(parent)
-        return HMITreeView(parent)
+        return SVGHMI_UI(parent, Register_SVGHMI_UI_for_HMI_tree_updates)
 
 class SVGHMI(object):
     XSD = """<?xml version="1.0" encoding="utf-8" ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/ui.py	Thu Mar 25 13:07:52 2021 +0100
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz
+# Copyright (C) 2021: Edouard TISSERANT
+#
+# See COPYING file for copyrights details.
+
+from __future__ import absolute_import
+import os
+import hashlib
+import weakref
+
+import wx
+
+from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
+from docutil import get_inkscape_path
+
+from util.ProcessLogger import ProcessLogger
+
+def SVGHMIEditorUpdater(ref):
+    def SVGHMIEditorUpdate():
+        o = ref()
+        if o is not None:
+            wx.CallAfter(o.MakeTree)
+    return SVGHMIEditorUpdate
+
+class HMITreeSelector(wx.TreeCtrl):
+    def __init__(self, parent):
+        global on_hmitree_update
+        wx.TreeCtrl.__init__(self, parent, style=(
+            wx.TR_MULTIPLE |
+            wx.TR_HAS_BUTTONS |
+            wx.SUNKEN_BORDER |
+            wx.TR_LINES_AT_ROOT))
+
+        on_hmitree_update = SVGHMIEditorUpdater(weakref.ref(self))
+        self.MakeTree()
+
+    def _recurseTree(self, current_hmitree_root, current_tc_root):
+        for c in current_hmitree_root.children:
+            if hasattr(c, "children"):
+                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, None) # TODO
+
+                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, None) # TODO
+
+    def MakeTree(self, hmi_tree_root=None):
+
+        self.Freeze()
+
+        self.root = None
+        self.DeleteAllItems()
+
+        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, None)
+
+        if hmi_tree_root is not None:
+            self._recurseTree(hmi_tree_root, self.root)
+            self.Expand(self.root)
+
+        self.Thaw()
+
+class WidgetPicker(wx.TreeCtrl):
+    def __init__(self, parent, initialdir=None):
+        wx.TreeCtrl.__init__(self, parent, style=(
+            wx.TR_MULTIPLE |
+            wx.TR_HAS_BUTTONS |
+            wx.SUNKEN_BORDER |
+            wx.TR_LINES_AT_ROOT))
+
+        self.MakeTree(initialdir)
+
+    def _recurseTree(self, current_dir, current_tc_root, dirlist):
+        """
+        recurse through subdirectories, but creates tree nodes 
+        only when (sub)directory conbtains .svg file
+        """
+        res = []
+        for f in sorted(os.listdir(current_dir)):
+            p = os.path.join(current_dir,f)
+            if os.path.isdir(p):
+
+                r = self._recurseTree(p, current_tc_root, dirlist + [f])
+                if len(r) > 0 :
+                    res = r
+                    dirlist = []
+                    current_tc_root = res.pop()
+
+            elif os.path.splitext(f)[1].upper() == ".SVG":
+                if len(dirlist) > 0 :
+                    res = []
+                    for d in dirlist:
+                        current_tc_root = self.AppendItem(current_tc_root, d)
+                        res.append(current_tc_root)
+                        self.SetPyData(current_tc_root, None)
+                    dirlist = []
+                    res.pop()
+                tc_child = self.AppendItem(current_tc_root, f)
+                self.SetPyData(tc_child, p)
+        return res
+
+    def MakeTree(self, lib_dir = None):
+
+        self.Freeze()
+
+        self.root = None
+        self.DeleteAllItems()
+
+        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)
+
+        if lib_dir is not None:
+            self._recurseTree(lib_dir, self.root, [])
+            self.Expand(self.root)
+
+        self.Thaw()
+
+_conf_key = "SVGHMIWidgetLib"
+_preview_height = 200
+class WidgetLibBrowser(wx.Panel):
+    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
+                 size=wx.DefaultSize):
+
+        wx.Panel.__init__(self, parent, id, pos, size)     
+
+        self.bmp = None
+        self.msg = None
+        self.hmitree_node = None
+        self.selected_SVG = None
+
+        self.Config = wx.ConfigBase.Get()
+        self.libdir = self.RecallLibDir()
+
+        sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0)
+        sizer.AddGrowableCol(0)
+        sizer.AddGrowableRow(1)
+        self.libbutton = wx.Button(self, -1, _("Select SVG widget library"))
+        self.widgetpicker = WidgetPicker(self, self.libdir)
+        self.preview = wx.Panel(self, size=(-1, _preview_height + 10))  #, style=wx.SIMPLE_BORDER)
+        #self.preview.SetBackgroundColour(wx.WHITE)
+        sizer.AddWindow(self.libbutton, flag=wx.GROW)
+        sizer.AddWindow(self.widgetpicker, flag=wx.GROW)
+        sizer.AddWindow(self.preview, flag=wx.GROW)
+        sizer.Layout()
+        self.SetAutoLayout(True)
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+        self.Bind(wx.EVT_BUTTON, self.OnSelectLibDir, self.libbutton)
+        self.preview.Bind(wx.EVT_PAINT, self.OnPaint)
+
+        self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnWidgetSelection, self.widgetpicker)
+
+        self.msg = _("Drag selected Widget from here to Inkscape")
+
+    def RecallLibDir(self):
+        conf = self.Config.Read(_conf_key)
+        if len(conf) == 0:
+            return None
+        else:
+            return DecodeFileSystemPath(conf)
+
+    def RememberLibDir(self, path):
+        self.Config.Write(_conf_key,
+                          EncodeFileSystemPath(path))
+        self.Config.Flush()
+
+    def DrawPreview(self):
+        """
+        Refresh preview panel 
+        """
+        # Init preview panel paint device context
+        dc = wx.PaintDC(self.preview)
+        dc.Clear()
+
+        if self.bmp:
+            # Get Preview panel size
+            sz = self.preview.GetClientSize()
+            w = self.bmp.GetWidth()
+            dc.DrawBitmap(self.bmp, (sz.width - w)/2, 5)
+
+        if self.msg:
+            dc.SetFont(self.GetFont())
+            dc.DrawText(self.msg, 25,25)
+
+
+    def OnSelectLibDir(self, event):
+        defaultpath = self.RecallLibDir()
+        if defaultpath == None:
+            defaultpath = os.path.expanduser("~")
+
+        dialog = wx.DirDialog(self, _("Choose a widget library"), defaultpath,
+                              style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
+
+        if dialog.ShowModal() == wx.ID_OK:
+            self.libdir = dialog.GetPath()
+            self.RememberLibDir(self.libdir)
+            self.widgetpicker.MakeTree(self.libdir)
+
+        dialog.Destroy()
+
+    def OnPaint(self, event):
+        """
+        Called when Preview panel needs to be redrawn
+        @param event: wx.PaintEvent
+        """
+        self.DrawPreview()
+        event.Skip()
+
+    def GenThumbnail(self, svgpath, thumbpath):
+        inkpath = get_inkscape_path()
+        if inkpath is None:
+            self.msg = _("Inkscape is not installed.")
+            return False
+        # TODO: spawn a thread, to decouple thumbnail gen
+        status, result, _err_result = ProcessLogger(
+            None,
+            '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
+            '" -D -h ' + str(_preview_height)).spin()
+        if status != 0:
+            self.msg = _("Inkscape couldn't generate thumbnail.")
+            return False
+        return True
+
+    def OnWidgetSelection(self, event):
+        """
+        Called when tree item is selected
+        @param event: wx.TreeEvent
+        """
+        item_pydata = self.widgetpicker.GetPyData(event.GetItem())
+        if item_pydata is not None:
+            svgpath = item_pydata
+            dname = os.path.dirname(svgpath)
+            fname = os.path.basename(svgpath)
+            hasher = hashlib.new('md5')
+            with open(svgpath, 'rb') as afile:
+                while True:
+                    buf = afile.read(65536)
+                    if len(buf) > 0:
+                        hasher.update(buf)
+                    else:
+                        break
+            digest = hasher.hexdigest()
+            thumbfname = os.path.splitext(fname)[0]+"_"+digest+".png"
+            thumbdir = os.path.join(dname, ".svghmithumbs") 
+            thumbpath = os.path.join(thumbdir, thumbfname) 
+
+            self.msg = None
+            have_thumb = os.path.exists(thumbpath)
+
+            if not have_thumb:
+                try:
+                    if not os.path.exists(thumbdir):
+                        os.mkdir(thumbdir)
+                except IOError:
+                    self.msg = _("Widget library must be writable")
+                else:
+                    have_thumb = self.GenThumbnail(svgpath, thumbpath)
+
+            self.bmp = wx.Bitmap(thumbpath) if have_thumb else None
+
+            self.selected_SVG = svgpath if have_thumb else None
+            self.ValidateWidget()
+
+            self.Refresh()
+        event.Skip()
+
+    def OnHMITreeNodeSelection(self, hmitree_node):
+        self.hmitree_node = hmitree_node
+        self.ValidateWidget()
+        self.Refresh()
+
+    def ValidateWidget(self):
+        if self.selected_SVG is not None:
+            if self.hmitree_node is not None:
+                pass
+        # XXX TODO: 
+        #      - check SVG is valid for selected HMI tree item
+        #      - prepare for D'n'D
+
+
+class SVGHMI_UI(wx.SplitterWindow):
+
+    def __init__(self, parent, register_for_HMI_tree_updates):
+        wx.SplitterWindow.__init__(self, parent,
+                                   style=wx.SUNKEN_BORDER | wx.SP_3D)
+
+        self.SelectionTree = HMITreeSelector(self)
+        self.Staging = WidgetLibBrowser(self)
+        self.SplitVertically(self.SelectionTree, self.Staging, 300)
+        register_for_HMI_tree_updates(weakref.ref(self))
+
+    def HMITreeUpdate(self, hmi_tree_root):
+            self.SelectionTree.MakeTree(hmi_tree_root)
+