SVGHMI: split svghmi.py into svghmi.py (Config Tree Node + code gen) and ui.py (UI for HMI tree and Widget picking)
--- 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)
+