--- a/svghmi/svghmi.py Tue Mar 23 05:08:51 2021 +0100
+++ b/svghmi/svghmi.py Tue Mar 23 05:11:23 2021 +0100
@@ -21,6 +21,7 @@
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
@@ -373,6 +374,13 @@
# 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
@@ -382,7 +390,7 @@
wx.SUNKEN_BORDER |
wx.TR_LINES_AT_ROOT))
- on_hmitree_update = self.SVGHMIEditorUpdater()
+ on_hmitree_update = SVGHMIEditorUpdater(weakref.ref(self))
self.MakeTree()
def _recurseTree(self, current_hmitree_root, current_tc_root):
@@ -391,13 +399,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, None)
+ 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)
+ self.SetPyData(tc_child, None) # TODO
def MakeTree(self):
global hmi_tree_root
@@ -407,7 +415,8 @@
self.root = None
self.DeleteAllItems()
- root_display_name = _("Please build to see HMI Tree") if hmi_tree_root is None else "HMI"
+ 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)
@@ -417,13 +426,226 @@
self.Thaw()
- def SVGHMIEditorUpdater(self):
- selfref = weakref.ref(self)
- def SVGHMIEditorUpdate():
- o = selfref()
- if o is not None:
- wx.CallAfter(o.MakeTree)
- return SVGHMIEditorUpdate
+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):
@@ -432,9 +654,8 @@
style=wx.SUNKEN_BORDER | wx.SP_3D)
self.SelectionTree = HMITreeSelector(self)
- #self.Staging = wx.Panel(self)
- #self.SplitHorizontally(self.SelectionTree, self.Staging, 200)
- self.Initialize(self.SelectionTree)
+ self.Staging = WidgetLibBrowser(self)
+ self.SplitVertically(self.SelectionTree, self.Staging, 300)
class SVGHMIEditor(ConfTreeNodeEditor):
@@ -442,7 +663,6 @@
(_("HMI Tree"), "CreateHMITreeView")]
def CreateHMITreeView(self, parent):
- #self.HMITreeView = HMITreeView(self)
global hmi_tree_root
if hmi_tree_root is None:
@@ -452,7 +672,10 @@
hmitree_backup_file = open(hmitree_backup_path, 'rb')
hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
- return HMITreeSelector(parent)
+
+ #self.HMITreeView = HMITreeView(self)
+ #return HMITreeSelector(parent)
+ return HMITreeView(parent)
class SVGHMI(object):
XSD = """<?xml version="1.0" encoding="utf-8" ?>