Edouard@2745: #!/usr/bin/env python
Edouard@2745: # -*- coding: utf-8 -*-
Edouard@2745:
Edouard@2745: # This file is part of Beremiz
edouard@3197: # Copyright (C) 2021: Edouard TISSERANT
Edouard@2745: #
Edouard@2745: # See COPYING file for copyrights details.
Edouard@2745:
Edouard@2745: from __future__ import absolute_import
Edouard@2745: import os
Edouard@2745: import shutil
Edouard@2789: import hashlib
Edouard@2816: import weakref
Edouard@2823: import shlex
edouard@3156: import time
Edouard@2745:
Edouard@2745: import wx
Edouard@2816:
Edouard@2816: from lxml import etree
Edouard@2816: from lxml.etree import XSLTApplyError
Edouard@2745:
edouard@3193: from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
Edouard@2745: import util.paths as paths
Edouard@2745: from POULibrary import POULibrary
Edouard@2756: from docutil import open_svg, get_inkscape_path
Edouard@2745:
Edouard@2756: from util.ProcessLogger import ProcessLogger
Edouard@2764: from runtime.typemapping import DebugTypesSize
Edouard@2767: import targets
Edouard@2816: from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
Edouard@2816: from XSLTransform import XSLTransform
Edouard@3114: from svghmi.i18n import EtreeToMessages, SaveCatalog, ReadTranslations, MatchTranslations, TranslationToEtree, open_pofile
edouard@3197: from svghmi.hmi_tree import HMI_TYPES, HMITreeNode, SPECIAL_NODES
Edouard@2745:
Edouard@2753:
Edouard@2753: ScriptDirectory = paths.AbsDir(__file__)
Edouard@2753:
Edouard@2788:
Edouard@2763: # module scope for HMITree root
Edouard@2763: # so that CTN can use HMITree deduced in Library
Edouard@2817: # note: this only works because library's Generate_C is
Edouard@2763: # systematicaly invoked before CTN's CTNGenerate_C
Edouard@2763:
Edouard@2763: hmi_tree_root = None
Edouard@2757:
Edouard@2817: on_hmitree_update = None
Edouard@2816:
Edouard@2745: class SVGHMILibrary(POULibrary):
Edouard@2745: def GetLibraryPath(self):
Edouard@2750: return paths.AbsNeighbourFile(__file__, "pous.xml")
Edouard@2745:
Edouard@2749: def Generate_C(self, buildpath, varlist, IECCFLAGS):
Edouard@2822: global hmi_tree_root, on_hmitree_update
Edouard@2749:
Edouard@2757: """
Edouard@2757: PLC Instance Tree:
Edouard@2757: prog0
Edouard@2757: +->v1 HMI_INT
Edouard@2757: +->v2 HMI_INT
Edouard@2757: +->fb0 (type mhoo)
Edouard@2814: | +->va HMI_NODE
Edouard@2757: | +->v3 HMI_INT
Edouard@2757: | +->v4 HMI_INT
Edouard@2757: |
Edouard@2757: +->fb1 (type mhoo)
Edouard@2814: | +->va HMI_NODE
Edouard@2757: | +->v3 HMI_INT
Edouard@2757: | +->v4 HMI_INT
Edouard@2757: |
Edouard@2757: +->fb2
Edouard@2757: +->v5 HMI_IN
Edouard@2757:
Edouard@2757: HMI tree:
Edouard@2757: hmi0
Edouard@2757: +->v1
Edouard@2757: +->v2
Edouard@2814: +->fb0 class:va
Edouard@2757: | +-> v3
Edouard@2757: | +-> v4
Edouard@2757: |
Edouard@2814: +->fb1 class:va
Edouard@2757: | +-> v3
Edouard@2757: | +-> v4
Edouard@2757: |
Edouard@2757: +->v5
Edouard@2757:
Edouard@2757: """
Edouard@2757:
Edouard@2749: # Filter known HMI types
Edouard@2749: hmi_types_instances = [v for v in varlist if v["derived"] in HMI_TYPES]
Edouard@2758:
edouard@2890: hmi_tree_root = None
edouard@2890:
edouard@2890: # take first HMI_NODE (placed as special node), make it root
edouard@2890: for i,v in enumerate(hmi_types_instances):
edouard@2890: path = v["IEC_path"].split(".")
edouard@2890: derived = v["derived"]
Edouard@2965: if derived == "HMI_NODE":
edouard@2890: hmi_tree_root = HMITreeNode(path, "", derived, v["type"], v["vartype"], v["C_path"])
edouard@2890: hmi_types_instances.pop(i)
edouard@2890: break
edouard@2890:
Edouard@3051: if hmi_tree_root is None:
Edouard@3051: self.FatalError("SVGHMI : Library is selected but not used. Please either deselect it in project config or add a SVGHMI node to project.")
Edouard@2757:
Edouard@2757: # deduce HMI tree from PLC HMI_* instances
Edouard@2757: for v in hmi_types_instances:
Edouard@2822: path = v["IEC_path"].split(".")
Edouard@2758: # ignores variables starting with _TMP_
Edouard@2758: if path[-1].startswith("_TMP_"):
Edouard@2758: continue
Edouard@2814: derived = v["derived"]
Edouard@2814: kwargs={}
Edouard@2814: if derived == "HMI_NODE":
Edouard@2822: # TODO : make problem if HMI_NODE used in CONFIG or RESOURCE
Edouard@2814: name = path[-2]
Edouard@2814: kwargs['hmiclass'] = path[-1]
Edouard@2814: else:
Edouard@2814: name = path[-1]
Edouard@2822: new_node = HMITreeNode(path, name, derived, v["type"], v["vartype"], v["C_path"], **kwargs)
Edouard@2965: placement_result = hmi_tree_root.place_node(new_node)
Edouard@2965: if placement_result is not None:
Edouard@2965: cause, problematic_node = placement_result
Edouard@2965: if cause == "Non_Unique":
Edouard@2965: message = _("HMI tree nodes paths are not unique.\nConflicting variable: {} {}").format(
Edouard@2965: ".".join(problematic_node.path),
Edouard@2965: ".".join(new_node.path))
Edouard@2965:
Edouard@2965: last_FB = None
Edouard@2965: for v in varlist:
Edouard@2965: if v["vartype"] == "FB":
Edouard@2965: last_FB = v
Edouard@2965: if v["C_path"] == problematic_node:
Edouard@2965: break
Edouard@2965: if last_FB is not None:
Edouard@2965: failing_parent = last_FB["type"]
Edouard@2965: message += "\n"
Edouard@2965: message += _("Solution: Add HMI_NODE at beginning of {}").format(failing_parent)
Edouard@2965:
Edouard@2965: elif cause in ["Late_HMI_NODE", "Duplicate_HMI_NODE"]:
Edouard@2965: cause, problematic_node = placement_result
Edouard@2965: message = _("There must be only one occurrence of HMI_NODE before any HMI_* variable in POU.\nConflicting variable: {} {}").format(
Edouard@2965: ".".join(problematic_node.path),
Edouard@2965: ".".join(new_node.path))
Edouard@2965:
Edouard@2965: self.FatalError("SVGHMI : " + message)
Edouard@2757:
Edouard@2817: if on_hmitree_update is not None:
Edouard@2817: on_hmitree_update()
Edouard@2816:
Edouard@2764: variable_decl_array = []
Edouard@2764: extern_variables_declarations = []
Edouard@2765: buf_index = 0
Edouard@2775: item_count = 0
Edouard@2822: found_heartbeat = False
Edouard@2822:
Edouard@2822: hearbeat_IEC_path = ['CONFIG', 'HEARTBEAT']
Edouard@2828:
Edouard@2764: for node in hmi_tree_root.traverse():
Edouard@2828: if not found_heartbeat and node.path == hearbeat_IEC_path:
Edouard@2822: hmi_tree_hearbeat_index = item_count
Edouard@2822: found_heartbeat = True
Edouard@2822: extern_variables_declarations += [
Edouard@2822: "#define heartbeat_index "+str(hmi_tree_hearbeat_index)
Edouard@2822: ]
Edouard@2866: if hasattr(node, "iectype"):
Edouard@2764: sz = DebugTypesSize.get(node.iectype, 0)
Edouard@2764: variable_decl_array += [
Edouard@2822: "{&(" + node.cpath + "), " + node.iectype + {
Edouard@2764: "EXT": "_P_ENUM",
Edouard@2764: "IN": "_P_ENUM",
Edouard@2764: "MEM": "_O_ENUM",
Edouard@2764: "OUT": "_O_ENUM",
Edouard@2764: "VAR": "_ENUM"
Edouard@2765: }[node.vartype] + ", " +
Edouard@2768: str(buf_index) + ", 0, }"]
Edouard@2765: buf_index += sz
Edouard@2775: item_count += 1
Edouard@2764: if len(node.path) == 1:
Edouard@2764: extern_variables_declarations += [
Edouard@2764: "extern __IEC_" + node.iectype + "_" +
Edouard@2764: "t" if node.vartype is "VAR" else "p"
Edouard@2822: + node.cpath + ";"]
Edouard@2828:
Edouard@2822: assert(found_heartbeat)
Edouard@2764:
Edouard@2764: # TODO : filter only requiered external declarations
Edouard@2828: for v in varlist:
Edouard@2828: if v["C_path"].find('.') < 0:
Edouard@2764: extern_variables_declarations += [
Edouard@2764: "extern %(type)s %(C_path)s;" % v]
Edouard@2764:
Edouard@2764: # TODO check if programs need to be declared separately
Edouard@2764: # "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" %
Edouard@2764: # p for p in self._ProgramList]),
Edouard@2764:
Edouard@2771: # C code to observe/access HMI tree variables
Edouard@2749: svghmi_c_filepath = paths.AbsNeighbourFile(__file__, "svghmi.c")
Edouard@2749: svghmi_c_file = open(svghmi_c_filepath, 'r')
Edouard@2749: svghmi_c_code = svghmi_c_file.read()
Edouard@2749: svghmi_c_file.close()
Edouard@2817: svghmi_c_code = svghmi_c_code % {
Edouard@2764: "variable_decl_array": ",\n".join(variable_decl_array),
Edouard@2764: "extern_variables_declarations": "\n".join(extern_variables_declarations),
Edouard@2767: "buffer_size": buf_index,
Edouard@2775: "item_count": item_count,
Edouard@2768: "var_access_code": targets.GetCode("var_access.c"),
Edouard@2788: "PLC_ticktime": self.GetCTR().GetTicktime(),
Edouard@2789: "hmi_hash_ints": ",".join(map(str,hmi_tree_root.hash()))
Edouard@2764: }
Edouard@2749:
Edouard@2749: gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c")
Edouard@2749: gen_svghmi_c = open(gen_svghmi_c_path, 'w')
Edouard@2749: gen_svghmi_c.write(svghmi_c_code)
Edouard@2749: gen_svghmi_c.close()
Edouard@2749:
Edouard@2771: # Python based WebSocket HMITree Server
Edouard@2771: svghmiserverfile = open(paths.AbsNeighbourFile(__file__, "svghmi_server.py"), 'r')
Edouard@2771: svghmiservercode = svghmiserverfile.read()
Edouard@2771: svghmiserverfile.close()
Edouard@2771:
Edouard@2993: runtimefile_path = os.path.join(buildpath, "runtime_00_svghmi.py")
Edouard@2771: runtimefile = open(runtimefile_path, 'w')
Edouard@2771: runtimefile.write(svghmiservercode)
Edouard@2771: runtimefile.close()
Edouard@2771:
edouard@3176: # Backup HMI Tree in XML form so that it can be loaded without building
edouard@3176: hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
edouard@3180: hmitree_backup_file = open(hmitree_backup_path, 'wb')
edouard@3176: hmitree_backup_file.write(etree.tostring(hmi_tree_root.etree()))
edouard@3176: hmitree_backup_file.close()
edouard@3176:
Edouard@2771: return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "",
Edouard@2993: ("runtime_00_svghmi.py", open(runtimefile_path, "rb")))
Edouard@2993: # ^
Edouard@2993: # note the double zero after "runtime_",
Edouard@2993: # to ensure placement before other CTN generated code in execution order
Edouard@2745:
Edouard@2816:
edouard@3193: def SVGHMIEditorUpdater(ref):
edouard@3193: def SVGHMIEditorUpdate():
edouard@3193: o = ref()
edouard@3193: if o is not None:
edouard@3193: wx.CallAfter(o.MakeTree)
edouard@3193: return SVGHMIEditorUpdate
edouard@3193:
Edouard@2818: class HMITreeSelector(wx.TreeCtrl):
Edouard@2818: def __init__(self, parent):
Edouard@2818: global on_hmitree_update
edouard@3177: wx.TreeCtrl.__init__(self, parent, style=(
edouard@3177: wx.TR_MULTIPLE |
edouard@3177: wx.TR_HAS_BUTTONS |
edouard@3177: wx.SUNKEN_BORDER |
edouard@3177: wx.TR_LINES_AT_ROOT))
Edouard@2818:
edouard@3193: on_hmitree_update = SVGHMIEditorUpdater(weakref.ref(self))
Edouard@2818: self.MakeTree()
Edouard@2818:
Edouard@2818: def _recurseTree(self, current_hmitree_root, current_tc_root):
Edouard@2818: for c in current_hmitree_root.children:
Edouard@2818: if hasattr(c, "children"):
Edouard@2818: display_name = ('{} (class={})'.format(c.name, c.hmiclass)) \
Edouard@2818: if c.hmiclass is not None else c.name
Edouard@2818: tc_child = self.AppendItem(current_tc_root, display_name)
edouard@3193: self.SetPyData(tc_child, None) # TODO
Edouard@2818:
Edouard@2818: self._recurseTree(c,tc_child)
Edouard@2818: else:
Edouard@2818: display_name = '{} {}'.format(c.nodetype[4:], c.name)
Edouard@2818: tc_child = self.AppendItem(current_tc_root, display_name)
edouard@3193: self.SetPyData(tc_child, None) # TODO
Edouard@2818:
Edouard@2818: def MakeTree(self):
Edouard@2818: global hmi_tree_root
Edouard@2818:
Edouard@2818: self.Freeze()
Edouard@2818:
Edouard@2818: self.root = None
Edouard@2818: self.DeleteAllItems()
Edouard@2818:
edouard@3193: root_display_name = _("Please build to see HMI Tree") \
edouard@3193: if hmi_tree_root is None else "HMI"
Edouard@2818: self.root = self.AddRoot(root_display_name)
Edouard@2818: self.SetPyData(self.root, None)
Edouard@2818:
Edouard@2818: if hmi_tree_root is not None:
Edouard@2818: self._recurseTree(hmi_tree_root, self.root)
edouard@3177: self.Expand(self.root)
Edouard@2818:
Edouard@2818: self.Thaw()
Edouard@2816:
edouard@3193: class WidgetPicker(wx.TreeCtrl):
edouard@3193: def __init__(self, parent, initialdir=None):
edouard@3193: wx.TreeCtrl.__init__(self, parent, style=(
edouard@3193: wx.TR_MULTIPLE |
edouard@3193: wx.TR_HAS_BUTTONS |
edouard@3193: wx.SUNKEN_BORDER |
edouard@3193: wx.TR_LINES_AT_ROOT))
edouard@3193:
edouard@3193: self.MakeTree(initialdir)
edouard@3193:
edouard@3193: def _recurseTree(self, current_dir, current_tc_root, dirlist):
edouard@3193: """
edouard@3193: recurse through subdirectories, but creates tree nodes
edouard@3193: only when (sub)directory conbtains .svg file
edouard@3193: """
edouard@3193: res = []
edouard@3193: for f in sorted(os.listdir(current_dir)):
edouard@3193: p = os.path.join(current_dir,f)
edouard@3193: if os.path.isdir(p):
edouard@3193:
edouard@3193: r = self._recurseTree(p, current_tc_root, dirlist + [f])
edouard@3193: if len(r) > 0 :
edouard@3193: res = r
edouard@3193: dirlist = []
edouard@3193: current_tc_root = res.pop()
edouard@3193:
edouard@3193: elif os.path.splitext(f)[1].upper() == ".SVG":
edouard@3193: if len(dirlist) > 0 :
edouard@3193: res = []
edouard@3193: for d in dirlist:
edouard@3193: current_tc_root = self.AppendItem(current_tc_root, d)
edouard@3193: res.append(current_tc_root)
edouard@3193: self.SetPyData(current_tc_root, None)
edouard@3193: dirlist = []
edouard@3193: res.pop()
edouard@3193: tc_child = self.AppendItem(current_tc_root, f)
edouard@3193: self.SetPyData(tc_child, p)
edouard@3193: return res
edouard@3193:
edouard@3193: def MakeTree(self, lib_dir = None):
edouard@3193: global hmi_tree_root
edouard@3193:
edouard@3193: self.Freeze()
edouard@3193:
edouard@3193: self.root = None
edouard@3193: self.DeleteAllItems()
edouard@3193:
edouard@3193: root_display_name = _("Please select widget library directory") \
edouard@3193: if lib_dir is None else os.path.basename(lib_dir)
edouard@3193: self.root = self.AddRoot(root_display_name)
edouard@3193: self.SetPyData(self.root, None)
edouard@3193:
edouard@3193: if lib_dir is not None:
edouard@3193: self._recurseTree(lib_dir, self.root, [])
edouard@3193: self.Expand(self.root)
edouard@3193:
edouard@3193: self.Thaw()
edouard@3193:
edouard@3193: _conf_key = "SVGHMIWidgetLib"
edouard@3193: _preview_height = 200
edouard@3193: class WidgetLibBrowser(wx.Panel):
edouard@3193: def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
edouard@3193: size=wx.DefaultSize):
edouard@3193:
edouard@3193: wx.Panel.__init__(self, parent, id, pos, size)
edouard@3193:
edouard@3193: self.bmp = None
edouard@3193: self.msg = None
edouard@3193: self.hmitree_node = None
edouard@3193: self.selected_SVG = None
edouard@3193:
edouard@3193: self.Config = wx.ConfigBase.Get()
edouard@3193: self.libdir = self.RecallLibDir()
edouard@3193:
edouard@3193: sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0)
edouard@3193: sizer.AddGrowableCol(0)
edouard@3193: sizer.AddGrowableRow(1)
edouard@3193: self.libbutton = wx.Button(self, -1, _("Select SVG widget library"))
edouard@3193: self.widgetpicker = WidgetPicker(self, self.libdir)
edouard@3193: self.preview = wx.Panel(self, size=(-1, _preview_height + 10)) #, style=wx.SIMPLE_BORDER)
edouard@3193: #self.preview.SetBackgroundColour(wx.WHITE)
edouard@3193: sizer.AddWindow(self.libbutton, flag=wx.GROW)
edouard@3193: sizer.AddWindow(self.widgetpicker, flag=wx.GROW)
edouard@3193: sizer.AddWindow(self.preview, flag=wx.GROW)
edouard@3193: sizer.Layout()
edouard@3193: self.SetAutoLayout(True)
edouard@3193: self.SetSizer(sizer)
edouard@3193: sizer.Fit(self)
edouard@3193: self.Bind(wx.EVT_BUTTON, self.OnSelectLibDir, self.libbutton)
edouard@3193: self.preview.Bind(wx.EVT_PAINT, self.OnPaint)
edouard@3193:
edouard@3193: self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnWidgetSelection, self.widgetpicker)
edouard@3193:
edouard@3193: self.msg = _("Drag selected Widget from here to Inkscape")
edouard@3193:
edouard@3193: def RecallLibDir(self):
edouard@3193: conf = self.Config.Read(_conf_key)
edouard@3193: if len(conf) == 0:
edouard@3193: return None
edouard@3193: else:
edouard@3193: return DecodeFileSystemPath(conf)
edouard@3193:
edouard@3193: def RememberLibDir(self, path):
edouard@3193: self.Config.Write(_conf_key,
edouard@3193: EncodeFileSystemPath(path))
edouard@3193: self.Config.Flush()
edouard@3193:
edouard@3193: def DrawPreview(self):
edouard@3193: """
edouard@3193: Refresh preview panel
edouard@3193: """
edouard@3193: # Init preview panel paint device context
edouard@3193: dc = wx.PaintDC(self.preview)
edouard@3193: dc.Clear()
edouard@3193:
edouard@3193: if self.bmp:
edouard@3193: # Get Preview panel size
edouard@3193: sz = self.preview.GetClientSize()
edouard@3193: w = self.bmp.GetWidth()
edouard@3193: dc.DrawBitmap(self.bmp, (sz.width - w)/2, 5)
edouard@3193:
edouard@3193: if self.msg:
edouard@3193: dc.SetFont(self.GetFont())
edouard@3193: dc.DrawText(self.msg, 25,25)
edouard@3193:
edouard@3193:
edouard@3193: def OnSelectLibDir(self, event):
edouard@3193: defaultpath = self.RecallLibDir()
edouard@3193: if defaultpath == None:
edouard@3193: defaultpath = os.path.expanduser("~")
edouard@3193:
edouard@3193: dialog = wx.DirDialog(self, _("Choose a widget library"), defaultpath,
edouard@3193: style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
edouard@3193:
edouard@3193: if dialog.ShowModal() == wx.ID_OK:
edouard@3193: self.libdir = dialog.GetPath()
edouard@3193: self.RememberLibDir(self.libdir)
edouard@3193: self.widgetpicker.MakeTree(self.libdir)
edouard@3193:
edouard@3193: dialog.Destroy()
edouard@3193:
edouard@3193: def OnPaint(self, event):
edouard@3193: """
edouard@3193: Called when Preview panel needs to be redrawn
edouard@3193: @param event: wx.PaintEvent
edouard@3193: """
edouard@3193: self.DrawPreview()
edouard@3193: event.Skip()
edouard@3193:
edouard@3193: def GenThumbnail(self, svgpath, thumbpath):
edouard@3193: inkpath = get_inkscape_path()
edouard@3193: if inkpath is None:
edouard@3193: self.msg = _("Inkscape is not installed.")
edouard@3193: return False
edouard@3193: # TODO: spawn a thread, to decouple thumbnail gen
edouard@3193: status, result, _err_result = ProcessLogger(
edouard@3193: None,
edouard@3193: '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
edouard@3193: '" -D -h ' + str(_preview_height)).spin()
edouard@3193: if status != 0:
edouard@3193: self.msg = _("Inkscape couldn't generate thumbnail.")
edouard@3193: return False
edouard@3193: return True
edouard@3193:
edouard@3193: def OnWidgetSelection(self, event):
edouard@3193: """
edouard@3193: Called when tree item is selected
edouard@3193: @param event: wx.TreeEvent
edouard@3193: """
edouard@3193: item_pydata = self.widgetpicker.GetPyData(event.GetItem())
edouard@3193: if item_pydata is not None:
edouard@3193: svgpath = item_pydata
edouard@3193: dname = os.path.dirname(svgpath)
edouard@3193: fname = os.path.basename(svgpath)
edouard@3193: hasher = hashlib.new('md5')
edouard@3193: with open(svgpath, 'rb') as afile:
edouard@3193: while True:
edouard@3193: buf = afile.read(65536)
edouard@3193: if len(buf) > 0:
edouard@3193: hasher.update(buf)
edouard@3193: else:
edouard@3193: break
edouard@3193: digest = hasher.hexdigest()
edouard@3193: thumbfname = os.path.splitext(fname)[0]+"_"+digest+".png"
edouard@3193: thumbdir = os.path.join(dname, ".svghmithumbs")
edouard@3193: thumbpath = os.path.join(thumbdir, thumbfname)
edouard@3193:
edouard@3193: self.msg = None
edouard@3193: have_thumb = os.path.exists(thumbpath)
edouard@3193:
edouard@3193: if not have_thumb:
edouard@3193: try:
edouard@3193: if not os.path.exists(thumbdir):
edouard@3193: os.mkdir(thumbdir)
edouard@3193: except IOError:
edouard@3193: self.msg = _("Widget library must be writable")
edouard@3193: else:
edouard@3193: have_thumb = self.GenThumbnail(svgpath, thumbpath)
edouard@3193:
edouard@3193: self.bmp = wx.Bitmap(thumbpath) if have_thumb else None
edouard@3193:
edouard@3193: self.selected_SVG = svgpath if have_thumb else None
edouard@3193: self.ValidateWidget()
edouard@3193:
edouard@3193: self.Refresh()
edouard@3193: event.Skip()
edouard@3193:
edouard@3193: def OnHMITreeNodeSelection(self, hmitree_node):
edouard@3193: self.hmitree_node = hmitree_node
edouard@3193: self.ValidateWidget()
edouard@3193: self.Refresh()
edouard@3193:
edouard@3193: def ValidateWidget(self):
edouard@3193: if self.selected_SVG is not None:
edouard@3193: if self.hmitree_node is not None:
edouard@3193: pass
edouard@3193: # XXX TODO:
edouard@3193: # - check SVG is valid for selected HMI tree item
edouard@3193: # - prepare for D'n'D
edouard@3193:
Edouard@2816:
Edouard@2818: class HMITreeView(wx.SplitterWindow):
Edouard@2818:
Edouard@2818: def __init__(self, parent):
Edouard@2818: wx.SplitterWindow.__init__(self, parent,
Edouard@2818: style=wx.SUNKEN_BORDER | wx.SP_3D)
Edouard@2818:
Edouard@2818: self.SelectionTree = HMITreeSelector(self)
edouard@3193: self.Staging = WidgetLibBrowser(self)
edouard@3193: self.SplitVertically(self.SelectionTree, self.Staging, 300)
Edouard@2818:
Edouard@2818:
Edouard@2818: class SVGHMIEditor(ConfTreeNodeEditor):
Edouard@2818: CONFNODEEDITOR_TABS = [
Edouard@2818: (_("HMI Tree"), "CreateHMITreeView")]
Edouard@2818:
Edouard@2816: def CreateHMITreeView(self, parent):
edouard@3176: global hmi_tree_root
edouard@3176:
edouard@3176: if hmi_tree_root is None:
edouard@3176: buildpath = self.Controler.GetCTRoot()._getBuildPath()
edouard@3176: hmitree_backup_path = os.path.join(buildpath, "hmitree.xml")
edouard@3176: if os.path.exists(hmitree_backup_path):
edouard@3180: hmitree_backup_file = open(hmitree_backup_path, 'rb')
edouard@3176: hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
edouard@3176:
edouard@3193:
edouard@3193: #self.HMITreeView = HMITreeView(self)
edouard@3193: #return HMITreeSelector(parent)
edouard@3193: return HMITreeView(parent)
Edouard@2818:
Edouard@2745: class SVGHMI(object):
Edouard@2771: XSD = """
Edouard@2745: