beremiz

Parents 0f41c1e2c121
Children 5d379934d5c9
SVGHMI: split svghmi.py into svghmi.py (Config Tree Node + code gen) and ui.py (UI for HMI tree and Widget picking)
  • +14 -291
    svghmi/svghmi.py
  • +305 -0
    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()
    +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)
    - 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)
    + on_hmitree_update = HMITreeUpdate
    class SVGHMIEditor(ConfTreeNodeEditor):
    CONFNODEEDITOR_TABS = [
    - (_("HMI Tree"), "CreateHMITreeView")]
    + (_("HMI Tree"), "CreateSVGHMI_UI")]
    - def CreateHMITreeView(self, parent):
    + 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)
    +