--- a/svghmi/svghmi.py Mon Mar 29 10:26:21 2021 +0200
+++ b/svghmi/svghmi.py Mon Mar 29 10:26:42 2021 +0200
@@ -2,17 +2,14 @@
# This file is part of Beremiz
-# Copyright (C) 2019: Edouard TISSERANT
+# Copyright (C) 2021: Edouard TISSERANT # See COPYING file for copyrights details.
from __future__ import absolute_import
-from itertools import izip, imap
-from pprint import pformat
@@ -21,7 +18,6 @@
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
@@ -31,143 +27,14 @@
from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
from XSLTransform import XSLTransform
-from svghmi.i18n import EtreeToMessages, SaveCatalog, ReadTranslations, MatchTranslations, TranslationToEtree, open_pofile
-HMI_TYPES = HMI_TYPES_DESC.keys()
+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__)
-class HMITreeNode(object):
- def __init__(self, path, name, nodetype, iectype = None, vartype = None, cpath = None, hmiclass = None):
- self.nodetype = nodetype
- self.hmiclass = hmiclass
- if iectype is not None:
- if nodetype in ["HMI_NODE"]:
- def pprint(self, indent = 0):
- res = ">"*indent + pformat(self.__dict__, indent = indent, depth = 1) + "\n"
- if hasattr(self, "children"):
- res += "\n".join([child.pprint(indent = indent + 1)
- for child in self.children])
- def place_node(self, node):
- potential_siblings = {}
- for child in self.children:
- if child.path is not None:
- for child_path_item, node_path_item in izip(child.path, node.path):
- if child_path_item == node_path_item:
- # Match can only be HMI_NODE, and the whole path of node
- # must match candidate node (except for name part)
- # since candidate would become child of that node
- if in_common > known_best_match and \
- child.nodetype == "HMI_NODE" and \
- in_common == len(child.path) - 1:
- known_best_match = in_common
- potential_siblings[child.path[
- -2 if child.nodetype == "HMI_NODE" else -1]] = child
- if best_child is not None:
- if node.nodetype == "HMI_NODE" and best_child.path[:-1] == node.path[:-1]:
- return "Duplicate_HMI_NODE", best_child
- return best_child.place_node(node)
- candidate_name = node.path[-2 if node.nodetype == "HMI_NODE" else -1]
- if candidate_name in potential_siblings:
- return "Non_Unique", potential_siblings[candidate_name]
- if node.nodetype == "HMI_NODE" and len(self.children) > 0:
- prev = self.children[-1]
- if prev.path[:-1] == node.path[:-1]:
- return "Late_HMI_NODE",prev
- self.children.append(node)
- def etree(self, add_hash=False):
- attribs = dict(name=self.name)
- if self.path is not None:
- attribs["path"] = ".".join(self.path)
- if self.hmiclass is not None:
- attribs["class"] = self.hmiclass
- attribs["hash"] = ",".join(map(str,self.hash()))
- res = etree.Element(self.nodetype, **attribs)
- if hasattr(self, "children"):
- for child_etree in imap(lambda c:c.etree(), self.children):
- res.append(child_etree)
- def from_etree(cls, enode):
- alternative constructor, restoring HMI Tree from XML backup
- note: all C-related information is gone,
- this restore is only for tree display and widget picking
- attributes = enode.attrib
- name = attributes["name"]
- path = attributes["path"].split('.') if "path" in attributes else None
- hmiclass = attributes.get("class", None)
- # hash is computed on demand
- node = cls(path, name, nodetype, hmiclass=hmiclass)
- for child in enode.iterchildren():
- node.children.append(cls.from_etree(child))
- if hasattr(self, "children"):
- for c in self.children:
- for yoodl in c.traverse():
- """ Produce a hash, any change in HMI tree structure change that hash """
- # limit size to HMI_HASH_SIZE as in svghmi.c
- return map(ord,s.digest())[:8]
- s.update(str((self.name,self.nodetype)))
- if hasattr(self, "children"):
- for c in self.children:
# module scope for HMITree root
# so that CTN can use HMITree deduced in Library
@@ -178,10 +45,6 @@
-SPECIAL_NODES = [("HMI_ROOT", "HMI_NODE"),
- ("heartbeat", "HMI_INT")]
- # ("current_page", "HMI_STRING")])
class SVGHMILibrary(POULibrary):
def GetLibraryPath(self):
return paths.AbsNeighbourFile(__file__, "pous.xml")
@@ -283,7 +146,7 @@
self.FatalError("SVGHMI : " + message)
if on_hmitree_update is not None:
+ on_hmitree_update(hmi_tree_root) extern_variables_declarations = []
@@ -374,295 +237,21 @@
# to ensure placement before other CTN generated code in execution order
-def SVGHMIEditorUpdater(ref):
- def SVGHMIEditorUpdate():
- wx.CallAfter(o.MakeTree)
- return SVGHMIEditorUpdate
-class HMITreeSelector(wx.TreeCtrl):
- def __init__(self, parent):
- global on_hmitree_update
- wx.TreeCtrl.__init__(self, parent, style=(
- on_hmitree_update = SVGHMIEditorUpdater(weakref.ref(self))
- 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)
- display_name = '{} {}'.format(c.nodetype[4:], c.name)
- tc_child = self.AppendItem(current_tc_root, display_name)
- self.SetPyData(tc_child, None) # TODO
- 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)
-class WidgetPicker(wx.TreeCtrl):
- def __init__(self, parent, initialdir=None):
- wx.TreeCtrl.__init__(self, parent, style=(
- 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
- for f in sorted(os.listdir(current_dir)):
- p = os.path.join(current_dir,f)
- r = self._recurseTree(p, current_tc_root, dirlist + [f])
- current_tc_root = res.pop()
- elif os.path.splitext(f)[1].upper() == ".SVG":
- current_tc_root = self.AppendItem(current_tc_root, d)
- res.append(current_tc_root)
- self.SetPyData(current_tc_root, None)
- tc_child = self.AppendItem(current_tc_root, f)
- self.SetPyData(tc_child, p)
- def MakeTree(self, lib_dir = None):
- 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, [])
-_conf_key = "SVGHMIWidgetLib"
-class WidgetLibBrowser(wx.Panel):
- def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
- wx.Panel.__init__(self, parent, id, pos, size)
- self.hmitree_node = None
- self.selected_SVG = None
- self.Config = wx.ConfigBase.Get()
- self.libdir = self.RecallLibDir()
+def Register_SVGHMI_UI_for_HMI_tree_updates(ref): + global on_hmitree_update + def HMITreeUpdate(_hmi_tree_root): + obj.HMITreeUpdate(_hmi_tree_root) - 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)
- self.SetAutoLayout(True)
- 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)
- return DecodeFileSystemPath(conf)
- def RememberLibDir(self, path):
- self.Config.Write(_conf_key,
- EncodeFileSystemPath(path))
- # Init preview panel paint device context
- dc = wx.PaintDC(self.preview)
- # Get Preview panel size
- sz = self.preview.GetClientSize()
- w = self.bmp.GetWidth()
- dc.DrawBitmap(self.bmp, (sz.width - w)/2, 5)
- 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)
- def OnPaint(self, event):
- Called when Preview panel needs to be redrawn
- @param event: wx.PaintEvent
- def GenThumbnail(self, svgpath, thumbpath):
- inkpath = get_inkscape_path()
- self.msg = _("Inkscape is not installed.")
- # TODO: spawn a thread, to decouple thumbnail gen
- status, result, _err_result = ProcessLogger(
- '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
- '" -D -h ' + str(_preview_height)).spin()
- self.msg = _("Inkscape couldn't generate thumbnail.")
- 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:
- dname = os.path.dirname(svgpath)
- fname = os.path.basename(svgpath)
- hasher = hashlib.new('md5')
- with open(svgpath, 'rb') as afile:
- buf = afile.read(65536)
- digest = hasher.hexdigest()
- thumbfname = os.path.splitext(fname)[0]+"_"+digest+".png"
- thumbdir = os.path.join(dname, ".svghmithumbs")
- thumbpath = os.path.join(thumbdir, thumbfname)
- have_thumb = os.path.exists(thumbpath)
- if not os.path.exists(thumbdir):
- self.msg = _("Widget library must be writable")
- 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
- def OnHMITreeNodeSelection(self, hmitree_node):
- self.hmitree_node = hmitree_node
- def ValidateWidget(self):
- if self.selected_SVG is not None:
- if self.hmitree_node is not None:
- # - check SVG is valid for selected HMI tree item
-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)
+ on_hmitree_update = HMITreeUpdate class SVGHMIEditor(ConfTreeNodeEditor):
- (_("HMI Tree"), "CreateHMITreeView")]
+ (_("HMI Tree"), "CreateSVGHMI_UI")] - def CreateHMITreeView(self, parent):
+ def CreateSVGHMI_UI(self, parent): if hmi_tree_root is None:
@@ -672,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) XSD = """<?xml version="1.0" encoding="utf-8" ?>