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@2745: Edouard@2745: import wx Edouard@2816: edouard@3193: from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath edouard@3201: from docutil import get_inkscape_path Edouard@2745: Edouard@2756: from util.ProcessLogger import ProcessLogger Edouard@2816: 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@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@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@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: 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@3213: self.preview.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 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@3213: def OnLeftDown(self, evt): edouard@3213: if self.selected_SVG is not None: edouard@3213: # TODO replace with generated widget file edouard@3213: filename = self.selected_SVG 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@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@3201: class SVGHMI_UI(wx.SplitterWindow): edouard@3201: edouard@3201: def __init__(self, parent, 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@3193: self.Staging = WidgetLibBrowser(self) 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@3201: self.SelectionTree.MakeTree(hmi_tree_root) edouard@3201: