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@2789: import hashlib Edouard@2816: import weakref edouard@3252: import re edouard@3482: import tempfile edouard@3261: from threading import Thread, Lock edouard@3252: from functools import reduce edouard@3259: from itertools import izip edouard@3252: from operator import or_ edouard@3221: from tempfile import NamedTemporaryFile Edouard@2745: Edouard@2745: import wx Edouard@3245: from wx.lib.scrolledpanel import ScrolledPanel Edouard@2816: edouard@3221: from lxml import etree edouard@3221: from lxml.etree import XSLTApplyError edouard@3221: from XSLTransform import XSLTransform edouard@3221: edouard@3221: import util.paths as paths edouard@3193: from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath edouard@3482: from docutil import get_inkscape_path, get_inkscape_version Edouard@2745: Edouard@2756: from util.ProcessLogger import ProcessLogger Edouard@2816: edouard@3221: ScriptDirectory = paths.AbsDir(__file__) edouard@3221: edouard@3247: HMITreeDndMagicWord = "text/beremiz-hmitree" edouard@3247: Edouard@2818: class HMITreeSelector(wx.TreeCtrl): Edouard@2818: def __init__(self, parent): edouard@3247: 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@3247: self.ordered_items = [] edouard@3247: self.parent = parent edouard@3247: Edouard@2818: self.MakeTree() Edouard@2818: edouard@3247: self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeNodeSelection) edouard@3247: self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag) edouard@3247: 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@3303: self.SetItemData(tc_child, c) 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@3303: self.SetItemData(tc_child, c) Edouard@2818: edouard@3247: def OnTreeNodeSelection(self, event): edouard@3247: items = self.GetSelections() edouard@3247: items_pydata = [self.GetPyData(item) for item in items] edouard@3247: edouard@3247: # append new items to ordered item list edouard@3247: for item_pydata in items_pydata: edouard@3247: if item_pydata not in self.ordered_items: edouard@3247: self.ordered_items.append(item_pydata) edouard@3247: edouard@3247: # filter out vanished items edouard@3247: self.ordered_items = [ edouard@3247: item_pydata edouard@3247: for item_pydata in self.ordered_items edouard@3247: if item_pydata in items_pydata] edouard@3247: edouard@3247: self.parent.OnHMITreeNodeSelection(self.ordered_items) edouard@3247: edouard@3247: def OnTreeBeginDrag(self, event): edouard@3247: """ edouard@3247: Called when a drag is started in tree edouard@3247: @param event: wx.TreeEvent edouard@3247: """ edouard@3247: if self.ordered_items: edouard@3247: # Just send a recognizable mime-type, drop destination edouard@3247: # will get python data from parent edouard@3247: data = wx.CustomDataObject(HMITreeDndMagicWord) edouard@3247: dragSource = wx.DropSource(self) edouard@3247: dragSource.SetData(data) edouard@3247: dragSource.DoDragDrop() edouard@3247: edouard@3201: def MakeTree(self, hmi_tree_root=None): 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@3303: self.SetItemData(self.root, hmi_tree_root) 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@3303: self.SetItemData(current_tc_root, None) edouard@3193: dirlist = [] edouard@3193: res.pop() edouard@3193: tc_child = self.AppendItem(current_tc_root, f) edouard@3303: self.SetItemData(tc_child, p) edouard@3193: return res edouard@3193: edouard@3193: def MakeTree(self, lib_dir = None): 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@3303: self.SetItemData(self.root, None) edouard@3193: Edouard@3253: if lib_dir is not None and os.path.exists(lib_dir): edouard@3193: self._recurseTree(lib_dir, self.root, []) edouard@3193: self.Expand(self.root) edouard@3193: edouard@3193: self.Thaw() edouard@3193: edouard@3247: class PathDropTarget(wx.DropTarget): edouard@3247: edouard@3247: def __init__(self, parent): edouard@3247: data = wx.CustomDataObject(HMITreeDndMagicWord) edouard@3247: wx.DropTarget.__init__(self, data) edouard@3247: self.ParentWindow = parent edouard@3247: edouard@3247: def OnDrop(self, x, y): edouard@3247: self.ParentWindow.OnHMITreeDnD() edouard@3247: return True edouard@3247: edouard@3247: class ParamEditor(wx.Panel): edouard@3247: def __init__(self, parent, paramdesc): edouard@3247: wx.Panel.__init__(self, parent.main_panel) edouard@3247: label = paramdesc.get("name")+ ": " + paramdesc.get("accepts") edouard@3247: if paramdesc.text: edouard@3247: label += "\n\"" + paramdesc.text + "\"" edouard@3243: self.desc = wx.StaticText(self, label=label) edouard@3247: self.valid_bmp = wx.ArtProvider.GetBitmap(wx.ART_TICK_MARK, wx.ART_TOOLBAR, (16,16)) edouard@3247: self.invalid_bmp = wx.ArtProvider.GetBitmap(wx.ART_CROSS_MARK, wx.ART_TOOLBAR, (16,16)) edouard@3243: self.validity_sbmp = wx.StaticBitmap(self, -1, self.invalid_bmp) edouard@3243: self.edit = wx.TextCtrl(self) edouard@3247: self.edit_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0) edouard@3247: self.edit_sizer.AddGrowableCol(0) edouard@3243: self.edit_sizer.AddGrowableRow(0) edouard@3243: self.edit_sizer.Add(self.edit, flag=wx.GROW) edouard@3243: self.edit_sizer.Add(self.validity_sbmp, flag=wx.GROW) edouard@3243: self.main_sizer = wx.BoxSizer(wx.VERTICAL) edouard@3243: self.main_sizer.Add(self.desc, flag=wx.GROW) edouard@3243: self.main_sizer.Add(self.edit_sizer, flag=wx.GROW) edouard@3243: self.SetSizer(self.main_sizer) edouard@3243: self.main_sizer.Fit(self) edouard@3243: edouard@3261: def GetValue(self): edouard@3261: return self.edit.GetValue() edouard@3261: edouard@3252: def setValidity(self, validity): edouard@3252: if validity is not None: edouard@3252: bmp = self.valid_bmp if validity else self.invalid_bmp edouard@3252: self.validity_sbmp.SetBitmap(bmp) edouard@3252: self.validity_sbmp.Show(True) edouard@3252: else : edouard@3252: self.validity_sbmp.Show(False) edouard@3252: edouard@3252: models = { typename: re.compile(regex) for typename, regex in [ edouard@3252: ("string", r".*"), Edouard@3263: ("int", r"^-?([1-9][0-9]*|0)$"), Edouard@3263: ("real", r"^-?([1-9][0-9]*|0)(\.[0-9]+)?$")]} edouard@3251: edouard@3247: class ArgEditor(ParamEditor): edouard@3259: def __init__(self, parent, argdesc, prefillargdesc): edouard@3252: ParamEditor.__init__(self, parent, argdesc) edouard@3252: self.ParentObj = parent edouard@3252: self.argdesc = argdesc edouard@3252: self.Bind(wx.EVT_TEXT, self.OnArgChanged, self.edit) edouard@3259: prefill = "" if prefillargdesc is None else prefillargdesc.get("value") edouard@3259: self.edit.SetValue(prefill) edouard@3259: # TODO add a button to add more ArgEditror instance edouard@3259: # when ordinality is multiple edouard@3252: edouard@3252: def OnArgChanged(self, event): edouard@3252: txt = self.edit.GetValue() edouard@3252: accepts = self.argdesc.get("accepts").split(',') edouard@3252: self.setValidity( edouard@3252: reduce(or_, edouard@3252: map(lambda typename: edouard@3252: models[typename].match(txt) is not None, edouard@3252: accepts), edouard@3252: False) edouard@3252: if accepts and txt else None) edouard@3261: self.ParentObj.RegenSVGLater() edouard@3252: event.Skip() edouard@3247: edouard@3247: class PathEditor(ParamEditor): edouard@3247: def __init__(self, parent, pathdesc): edouard@3247: ParamEditor.__init__(self, parent, pathdesc) edouard@3247: self.ParentObj = parent edouard@3251: self.pathdesc = pathdesc edouard@3247: DropTarget = PathDropTarget(self) edouard@3247: self.edit.SetDropTarget(DropTarget) Edouard@3265: self.edit.SetHint(_("Drag'n'drop HMI variable here")) edouard@3251: self.Bind(wx.EVT_TEXT, self.OnPathChanged, self.edit) edouard@3247: edouard@3247: def OnHMITreeDnD(self): edouard@3247: self.ParentObj.GotPathDnDOn(self) edouard@3247: edouard@3251: def SetPath(self, hmitree_node): edouard@3251: self.edit.ChangeValue(hmitree_node.hmi_path()) edouard@3252: self.setValidity( edouard@3252: hmitree_node.nodetype in self.pathdesc.get("accepts").split(",")) edouard@3247: edouard@3247: def OnPathChanged(self, event): edouard@3251: # TODO : find corresponding hmitre node and type to update validity edouard@3251: # Lazy way : hide validity edouard@3252: self.setValidity(None) edouard@3261: self.ParentObj.RegenSVGLater() edouard@3247: event.Skip() edouard@3247: edouard@3259: def KeepDoubleNewLines(txt): edouard@3259: return "\n\n".join(map( edouard@3259: lambda s:re.sub(r'\s+',' ',s), edouard@3259: txt.split("\n\n"))) edouard@3247: edouard@3193: _conf_key = "SVGHMIWidgetLib" edouard@3193: _preview_height = 200 edouard@3241: _preview_margin = 5 edouard@3482: thumbnail_temp_path = None edouard@3482: edouard@3243: class WidgetLibBrowser(wx.SplitterWindow): edouard@3482: def __init__(self, parent, controler, id=wx.ID_ANY, pos=wx.DefaultPosition, edouard@3193: size=wx.DefaultSize): edouard@3193: edouard@3243: wx.SplitterWindow.__init__(self, parent, edouard@3243: style=wx.SUNKEN_BORDER | wx.SP_3D) edouard@3193: edouard@3193: self.bmp = None edouard@3193: self.msg = None edouard@3247: self.hmitree_nodes = [] edouard@3193: self.selected_SVG = None edouard@3482: self.Controler = controler edouard@3193: edouard@3193: self.Config = wx.ConfigBase.Get() edouard@3193: self.libdir = self.RecallLibDir() Edouard@3274: if self.libdir is None: Edouard@3274: self.libdir = os.path.join(ScriptDirectory, "widgetlib") edouard@3193: Edouard@3245: self.picker_desc_splitter = wx.SplitterWindow(self, style=wx.SUNKEN_BORDER | wx.SP_3D) Edouard@3245: Edouard@3245: self.picker_panel = wx.Panel(self.picker_desc_splitter) edouard@3243: self.picker_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) edouard@3243: self.picker_sizer.AddGrowableCol(0) edouard@3243: self.picker_sizer.AddGrowableRow(1) edouard@3243: edouard@3243: self.widgetpicker = WidgetPicker(self.picker_panel, self.libdir) edouard@3243: self.libbutton = wx.Button(self.picker_panel, -1, _("Select SVG widget library")) edouard@3243: edouard@3243: self.picker_sizer.Add(self.libbutton, flag=wx.GROW) edouard@3243: self.picker_sizer.Add(self.widgetpicker, flag=wx.GROW) edouard@3243: self.picker_sizer.Layout() edouard@3243: self.picker_panel.SetAutoLayout(True) edouard@3243: self.picker_panel.SetSizer(self.picker_sizer) edouard@3243: edouard@3243: self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnWidgetSelection, self.widgetpicker) edouard@3243: self.Bind(wx.EVT_BUTTON, self.OnSelectLibDir, self.libbutton) edouard@3243: edouard@3243: edouard@3243: Edouard@3245: self.main_panel = ScrolledPanel(parent=self, Edouard@3245: name='MiscellaneousPanel', Edouard@3245: style=wx.TAB_TRAVERSAL) edouard@3243: edouard@3243: self.main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0) edouard@3241: self.main_sizer.AddGrowableCol(0) edouard@3243: self.main_sizer.AddGrowableRow(2) edouard@3243: edouard@3247: self.staticmsg = wx.StaticText(self, label = _("Drag selected Widget from here to Inkscape")) edouard@3243: self.preview = wx.Panel(self.main_panel, size=(-1, _preview_height + _preview_margin*2)) edouard@3237: self.signature_sizer = wx.BoxSizer(wx.VERTICAL) edouard@3247: self.args_box = wx.StaticBox(self.main_panel, -1, edouard@3247: _("Widget's arguments"), edouard@3259: style = wx.ALIGN_CENTRE_HORIZONTAL) edouard@3247: self.args_sizer = wx.StaticBoxSizer(self.args_box, wx.VERTICAL) edouard@3247: self.paths_box = wx.StaticBox(self.main_panel, -1, edouard@3247: _("Widget's variables"), edouard@3259: style = wx.ALIGN_CENTRE_HORIZONTAL) edouard@3247: self.paths_sizer = wx.StaticBoxSizer(self.paths_box, wx.VERTICAL) edouard@3247: self.signature_sizer.Add(self.args_sizer, flag=wx.GROW) edouard@3247: self.signature_sizer.AddSpacer(5) edouard@3247: self.signature_sizer.Add(self.paths_sizer, flag=wx.GROW) edouard@3247: self.main_sizer.Add(self.staticmsg, flag=wx.GROW) edouard@3241: self.main_sizer.Add(self.preview, flag=wx.GROW) edouard@3243: self.main_sizer.Add(self.signature_sizer, flag=wx.GROW) edouard@3241: self.main_sizer.Layout() edouard@3243: self.main_panel.SetAutoLayout(True) edouard@3243: self.main_panel.SetSizer(self.main_sizer) edouard@3243: self.main_sizer.Fit(self.main_panel) edouard@3193: self.preview.Bind(wx.EVT_PAINT, self.OnPaint) edouard@3213: self.preview.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) edouard@3193: Edouard@3245: self.desc = wx.TextCtrl(self.picker_desc_splitter, size=wx.Size(-1, 160), Edouard@3245: style=wx.TE_READONLY | wx.TE_MULTILINE) Edouard@3245: Edouard@3245: self.picker_desc_splitter.SplitHorizontally(self.picker_panel, self.desc, 400) Edouard@3245: self.SplitVertically(self.main_panel, self.picker_desc_splitter, 300) edouard@3193: edouard@3221: self.tempf = None edouard@3193: edouard@3261: self.RegenSVGThread = None edouard@3261: self.RegenSVGLock = Lock() edouard@3261: self.RegenSVGTimer = wx.Timer(self, -1) edouard@3261: self.RegenSVGParams = None edouard@3261: self.Bind(wx.EVT_TIMER, edouard@3261: self.RegenSVG, edouard@3261: self.RegenSVGTimer) edouard@3261: edouard@3247: self.args_editors = [] edouard@3241: self.paths_editors = [] edouard@3241: Edouard@3263: def SetMessage(self, msg): Edouard@3263: self.staticmsg.SetLabel(msg) Edouard@3263: self.main_sizer.Layout() Edouard@3263: edouard@3241: def ResetSignature(self): edouard@3247: self.args_sizer.Clear() edouard@3247: for editor in self.args_editors: edouard@3247: editor.Destroy() edouard@3247: self.args_editors = [] edouard@3247: edouard@3247: self.paths_sizer.Clear() edouard@3241: for editor in self.paths_editors: edouard@3241: editor.Destroy() edouard@3241: self.paths_editors = [] edouard@3241: edouard@3259: def AddArgToSignature(self, arg, prefillarg): edouard@3259: new_editor = ArgEditor(self, arg, prefillarg) edouard@3247: self.args_editors.append(new_editor) edouard@3247: self.args_sizer.Add(new_editor, flag=wx.GROW) edouard@3247: edouard@3241: def AddPathToSignature(self, path): edouard@3247: new_editor = PathEditor(self, path) edouard@3241: self.paths_editors.append(new_editor) edouard@3247: self.paths_sizer.Add(new_editor, flag=wx.GROW) edouard@3247: edouard@3247: def GotPathDnDOn(self, target_editor): edouard@3247: dndindex = self.paths_editors.index(target_editor) edouard@3247: edouard@3251: for hmitree_node,editor in zip(self.hmitree_nodes, edouard@3247: self.paths_editors[dndindex:]): edouard@3251: editor.SetPath(hmitree_node) edouard@3241: edouard@3261: self.RegenSVGNow() edouard@3261: 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@3241: dc.DrawBitmap(self.bmp, (sz.width - w)/2, _preview_margin) edouard@3193: 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@3482: edouard@3482: export_opt = "-o" if get_inkscape_version()[0] > 0 else "-e" edouard@3482: edouard@3193: # TODO: spawn a thread, to decouple thumbnail gen edouard@3193: status, result, _err_result = ProcessLogger( edouard@3482: self.Controler.GetCTRoot().logger, edouard@3482: '"' + inkpath + '" "' + svgpath + '" ' + edouard@3482: export_opt + ' "' + 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@3482: global thumbnail_temp_path edouard@3482: event.Skip() edouard@3193: item_pydata = self.widgetpicker.GetPyData(event.GetItem()) edouard@3193: if item_pydata is not None: edouard@3193: svgpath = item_pydata edouard@3482: edouard@3482: if thumbnail_temp_path is None: edouard@3482: try: edouard@3482: dname = os.path.dirname(svgpath) edouard@3482: thumbdir = os.path.join(dname, ".svghmithumbs") edouard@3482: if not os.path.exists(thumbdir): edouard@3482: os.mkdir(thumbdir) edouard@3482: except Exception : edouard@3482: # library not writable : use temp dir edouard@3482: thumbnail_temp_path = os.path.join( edouard@3482: tempfile.gettempdir(), "svghmithumbs") edouard@3482: thumbdir = thumbnail_temp_path edouard@3482: if not os.path.exists(thumbdir): edouard@3482: os.mkdir(thumbdir) edouard@3482: else: edouard@3482: thumbdir = thumbnail_temp_path edouard@3482: 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: thumbpath = os.path.join(thumbdir, thumbfname) edouard@3193: edouard@3193: have_thumb = os.path.exists(thumbpath) edouard@3193: edouard@3482: if not have_thumb: edouard@3482: self.Controler.GetCTRoot().logger.write( edouard@3482: "Rendering preview of " + fname + " widget.\n") edouard@3482: have_thumb = self.GenThumbnail(svgpath, thumbpath) edouard@3482: edouard@3482: self.bmp = wx.Bitmap(thumbpath) if have_thumb else None edouard@3482: edouard@3482: self.selected_SVG = svgpath if have_thumb else None edouard@3482: edouard@3482: self.AnalyseWidgetAndUpdateUI(fname) edouard@3482: edouard@3482: self.SetMessage(self.msg) edouard@3193: edouard@3193: self.Refresh() edouard@3193: edouard@3231: def OnHMITreeNodeSelection(self, hmitree_nodes): edouard@3247: self.hmitree_nodes = hmitree_nodes edouard@3193: edouard@3213: def OnLeftDown(self, evt): edouard@3221: if self.tempf is not None: edouard@3221: filename = self.tempf.name edouard@3213: data = wx.FileDataObject() edouard@3213: data.AddFile(filename) edouard@3213: dropSource = wx.DropSource(self) edouard@3213: dropSource.SetData(data) edouard@3213: dropSource.DoDragDrop(wx.Drag_AllowMove) edouard@3213: edouard@3261: def RegenSVGLater(self, when=1): Edouard@3263: self.SetMessage(_("SVG generation pending")) edouard@3261: self.RegenSVGTimer.Start(milliseconds=when*1000, oneShot=True) edouard@3261: edouard@3261: def RegenSVGNow(self): edouard@3261: self.RegenSVGLater(when=0) edouard@3261: edouard@3261: def RegenSVG(self, event): Edouard@3263: self.SetMessage(_("Generating SVG...")) edouard@3261: args = [arged.GetValue() for arged in self.args_editors] Edouard@3263: while args and not args[-1]: args.pop(-1) edouard@3261: paths = [pathed.GetValue() for pathed in self.paths_editors] Edouard@3263: while paths and not paths[-1]: paths.pop(-1) edouard@3261: if self.RegenSVGLock.acquire(True): edouard@3261: self.RegenSVGParams = (args, paths) edouard@3261: if self.RegenSVGThread is None: edouard@3261: self.RegenSVGThread = \ edouard@3261: Thread(target=self.RegenSVGProc, edouard@3261: name="RegenSVGThread").start() edouard@3261: self.RegenSVGLock.release() edouard@3261: event.Skip() edouard@3261: edouard@3261: def RegenSVGProc(self): edouard@3261: self.RegenSVGLock.acquire(True) edouard@3261: edouard@3261: newparams = self.RegenSVGParams edouard@3261: self.RegenSVGParams = None edouard@3261: edouard@3261: while newparams is not None: edouard@3261: self.RegenSVGLock.release() edouard@3261: edouard@3261: res = self.GenDnDSVG(newparams) edouard@3261: edouard@3261: self.RegenSVGLock.acquire(True) edouard@3261: edouard@3261: newparams = self.RegenSVGParams edouard@3261: self.RegenSVGParams = None edouard@3261: edouard@3261: self.RegenSVGThread = None edouard@3261: edouard@3261: self.RegenSVGLock.release() edouard@3261: edouard@3261: wx.CallAfter(self.DoneRegenSVG) edouard@3221: edouard@3261: def DoneRegenSVG(self): Edouard@3263: self.SetMessage(self.msg if self.msg else _("SVG ready for drag'n'drop")) edouard@3261: edouard@3247: def AnalyseWidgetAndUpdateUI(self, fname): edouard@3235: self.msg = "" Edouard@3245: self.ResetSignature() edouard@3235: edouard@3235: try: edouard@3235: if self.selected_SVG is None: edouard@3235: raise Exception(_("No widget selected")) edouard@3235: edouard@3235: transform = XSLTransform( edouard@3235: os.path.join(ScriptDirectory, "analyse_widget.xslt"),[]) edouard@3235: edouard@3235: svgdom = etree.parse(self.selected_SVG) edouard@3235: edouard@3241: signature = transform.transform(svgdom) edouard@3235: edouard@3235: for entry in transform.get_error_log(): edouard@3235: self.msg += "XSLT: " + entry.message + "\n" edouard@3235: edouard@3235: except Exception as e: edouard@3235: self.msg += str(e) edouard@3259: return edouard@3235: except XSLTApplyError as e: edouard@3247: self.msg += "Widget " + fname + " analysis error: " + e.message edouard@3259: return edouard@3241: edouard@3259: self.msg += "Widget " + fname + ": OK" edouard@3259: edouard@3259: widgets = signature.getroot() edouard@3259: widget = widgets.find("widget") edouard@3259: defs = widget.find("defs") edouard@3259: # Keep double newlines (to mark paragraphs) edouard@3259: widget_desc = widget.find("desc") edouard@3259: self.desc.SetValue( Edouard@3279: fname + ":\n\n" + ( edouard@3259: _("No description given") if widget_desc is None else edouard@3259: KeepDoubleNewLines(widget_desc.text) edouard@3259: ) + "\n\n" + Edouard@3279: defs.find("type").text + " Widget: "+defs.find("shortdesc").text+"\n\n" + edouard@3259: KeepDoubleNewLines(defs.find("longdesc").text)) edouard@3259: prefillargs = widget.findall("arg") edouard@3259: args = defs.findall("arg") edouard@3259: # extend args description in prefilled args in longer edouard@3259: # (case of variable list of args) edouard@3259: if len(prefillargs) < len(args): edouard@3259: prefillargs += [None]*(len(args)-len(prefillargs)) edouard@3259: if args and len(prefillargs) > len(args): edouard@3259: # TODO: check ordinality of last arg edouard@3259: # TODO: check that only last arg has multiple ordinality edouard@3259: args += [args[-1]]*(len(prefillargs)-len(args)) edouard@3259: self.args_box.Show(len(args)!=0) edouard@3259: for arg, prefillarg in izip(args,prefillargs): edouard@3259: self.AddArgToSignature(arg, prefillarg) Edouard@3454: Edouard@3454: # TODO support predefined path count (as for XYGraph) edouard@3259: paths = defs.findall("path") edouard@3259: self.paths_box.Show(len(paths)!=0) edouard@3259: for path in paths: edouard@3259: self.AddPathToSignature(path) edouard@3259: Edouard@3454: # # TODO DEAD CODE ? Edouard@3454: # for widget in widgets: Edouard@3454: # widget_type = widget.get("type") Edouard@3454: # for path in widget.iterchildren("path"): Edouard@3454: # path_value = path.get("value") Edouard@3454: # path_accepts = map( Edouard@3454: # str.strip, path.get("accepts", '')[1:-1].split(',')) edouard@3235: Edouard@3245: self.main_panel.SetupScrolling(scroll_x=False) Edouard@3245: edouard@3261: def GetWidgetParams(self, _context): edouard@3261: args,paths = self.GenDnDSVGParams edouard@3261: root = etree.Element("params") edouard@3261: for arg in args: edouard@3261: etree.SubElement(root, "arg", value=arg) edouard@3261: for path in paths: edouard@3261: etree.SubElement(root, "path", value=path) edouard@3261: return root edouard@3261: edouard@3261: edouard@3261: def GenDnDSVG(self, newparams): edouard@3221: self.msg = "" edouard@3221: edouard@3261: self.GenDnDSVGParams = newparams edouard@3261: edouard@3221: if self.tempf is not None: edouard@3221: os.unlink(self.tempf.name) edouard@3221: self.tempf = None edouard@3221: edouard@3221: try: edouard@3221: if self.selected_SVG is None: edouard@3221: raise Exception(_("No widget selected")) edouard@3221: edouard@3221: transform = XSLTransform( edouard@3221: os.path.join(ScriptDirectory, "gen_dnd_widget_svg.xslt"), Edouard@3263: [("GetWidgetParams", self.GetWidgetParams)]) edouard@3221: edouard@3221: svgdom = etree.parse(self.selected_SVG) edouard@3221: edouard@3261: result = transform.transform(svgdom) edouard@3221: edouard@3221: for entry in transform.get_error_log(): edouard@3221: self.msg += "XSLT: " + entry.message + "\n" edouard@3221: edouard@3221: self.tempf = NamedTemporaryFile(suffix='.svg', delete=False) edouard@3221: result.write(self.tempf, encoding="utf-8") edouard@3221: self.tempf.close() edouard@3221: edouard@3221: except Exception as e: edouard@3221: self.msg += str(e) edouard@3221: except XSLTApplyError as e: edouard@3221: self.msg += "Widget transform error: " + e.message edouard@3221: edouard@3221: def __del__(self): edouard@3221: if self.tempf is not None: edouard@3221: os.unlink(self.tempf.name) Edouard@2816: edouard@3201: class SVGHMI_UI(wx.SplitterWindow): edouard@3201: edouard@3482: def __init__(self, parent, controler, register_for_HMI_tree_updates): 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@3482: self.Staging = WidgetLibBrowser(self, controler) edouard@3193: self.SplitVertically(self.SelectionTree, self.Staging, 300) edouard@3201: register_for_HMI_tree_updates(weakref.ref(self)) edouard@3201: edouard@3201: def HMITreeUpdate(self, hmi_tree_root): edouard@3247: self.SelectionTree.MakeTree(hmi_tree_root) edouard@3247: edouard@3247: def OnHMITreeNodeSelection(self, hmitree_nodes): edouard@3247: self.Staging.OnHMITreeNodeSelection(hmitree_nodes)