--- a/ProjectController.py Wed Jun 01 09:15:26 2022 +0200
+++ b/ProjectController.py Wed Jun 01 09:22:07 2022 +0200
@@ -601,11 +601,11 @@
dirdialog = wx.DirDialog(
- self.AppFrame, _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON)
+ self.AppFrame, _("Create or choose an empty directory to save project"), path, wx.DD_NEW_DIR_BUTTON) answer = dirdialog.ShowModal()
+ newprojectpath = dirdialog.GetPath() - newprojectpath = dirdialog.GetPath()
if os.path.isdir(newprojectpath):
if self.CheckNewProjectPath(self.ProjectPath, newprojectpath):
self.ProjectPath, old_project_path = newprojectpath, self.ProjectPath
@@ -1722,10 +1722,16 @@
for weakcallable, buffer_list in WeakCallableDict.iteritems():
function = getattr(weakcallable, function_name, None)
- function(*tuple([lst[-1] for lst in cargs]))
+ # FIXME: apparently, despite of weak ref objects, + # some dead C/C++ wx object are still reachable from here + # leading to RuntimeError exception + function(*tuple([lst[-1] for lst in cargs])) --- a/docutil/docsvg.py Wed Jun 01 09:15:26 2022 +0200
+++ b/docutil/docsvg.py Wed Jun 01 09:22:07 2022 +0200
@@ -27,21 +27,26 @@
-def get_inkscape_path():
+def _get_inkscape_path(): """ Return the Inkscape binary path """
if wx.Platform == '__WXMSW__':
from six.moves import winreg
- inkcmd = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
- 'Software\\Classes\\svgfile\\shell\\Inkscape\\command')
+ tries = [(winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes\\svgfile\\shell\\Inkscape\\command'), + (winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes\\inkscape.svg\\shell\\open\\command'), + (winreg.HKEY_CURRENT_USER, 'Software\\Classes\\inkscape.svg\\shell\\open\\command')] + for subreg, key in tries: - inkcmd = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
- 'Software\\Classes\\inkscape.svg\\shell\\open\\command')
+ inkcmd = winreg.QueryValue(subreg, key)
return inkcmd.replace('"%1"', '').strip().replace('"', '')
@@ -51,6 +56,36 @@
except subprocess.CalledProcessError:
+def get_inkscape_path(): + """ Return the Inkscape binary path """ + if _inkscape_path is not None: + _inkscape_path = _get_inkscape_path() +def _get_inkscape_version(): + inkpath = get_inkscape_path() + subprocess.check_output([inkpath,"--version"]).split()[1].split('.')) +_inkscape_version = None +def get_inkscape_version(): + global _inkscape_version + if _inkscape_version is not None: + return _inkscape_version + _inkscape_version = _get_inkscape_version() + return _inkscape_version """ Generic function to open SVG file """
--- a/exemples/svghmi_traffic_light/svghmi_0@svghmi/confnode.xml Wed Jun 01 09:15:26 2022 +0200
+++ b/exemples/svghmi_traffic_light/svghmi_0@svghmi/confnode.xml Wed Jun 01 09:22:07 2022 +0200
@@ -1,2 +1,2 @@
<?xml version='1.0' encoding='utf-8'?>
-<SVGHMI xmlns:xsd="http://www.w3.org/2001/XMLSchema" OnWatchdog="echo Watchdog for {name} !" WatchdogInitial="10" WatchdogInterval="5" EnableWatchdog="true" Path="{name}"/>
+<SVGHMI xmlns:xsd="http://www.w3.org/2001/XMLSchema" WatchdogInitial="10" WatchdogInterval="5" EnableWatchdog="true" Path="{name}"/> --- a/svghmi/analyse_widget.xslt Wed Jun 01 09:15:26 2022 +0200
+++ b/svghmi/analyse_widget.xslt Wed Jun 01 09:22:07 2022 +0200
@@ -925,8 +925,8 @@
<path name="value" count="1+" accepts="HMI_INT,HMI_REAL">
<xsl:text>value</xsl:text>
- <arg name="size" accepts="int">
- <xsl:text>buffer size</xsl:text>
+ <arg name="xrange" accepts="int,time"> + <xsl:text>X axis range expressed either in samples or duration.</xsl:text> <arg name="xformat" count="optional" accepts="string">
<xsl:text>format string for X label</xsl:text>
@@ -934,12 +934,6 @@
<arg name="yformat" count="optional" accepts="string">
<xsl:text>format string for Y label</xsl:text>
- <arg name="xmin" count="optional" accepts="int,real">
- <xsl:text>minimum value foe X axis</xsl:text>
- <arg name="xmax" count="optional" accepts="int,real">
- <xsl:text>maximum value for X axis</xsl:text>
<xsl:template mode="document" match="@* | node()">
--- a/svghmi/gen_index_xhtml.xslt Wed Jun 01 09:15:26 2022 +0200
+++ b/svghmi/gen_index_xhtml.xslt Wed Jun 01 09:22:07 2022 +0200
@@ -1556,15 +1556,15 @@
<xsl:text> if(typeof(init) == "function"){
<xsl:text> init.call(this);
- <xsl:text> // } catch(err) {
- <xsl:text> // console.log(err);
+ <xsl:text> } catch(err) { + <xsl:text> console.log(err); @@ -7780,7 +7780,7 @@
<xsl:text> activate(val) {
- <xsl:text> let [active, inactive] = val ? ["none",""] : ["", "none"];
+ <xsl:text> let [active, inactive] = val ? ["","none"] : ["none", ""]; <xsl:text> if (this.active_elt)
@@ -7865,8 +7865,8 @@
<path name="value" count="1+" accepts="HMI_INT,HMI_REAL">
<xsl:text>value</xsl:text>
- <arg name="size" accepts="int">
- <xsl:text>buffer size</xsl:text>
+ <arg name="xrange" accepts="int,time"> + <xsl:text>X axis range expressed either in samples or duration.</xsl:text> <arg name="xformat" count="optional" accepts="string">
<xsl:text>format string for X label</xsl:text>
@@ -7874,12 +7874,6 @@
<arg name="yformat" count="optional" accepts="string">
<xsl:text>format string for Y label</xsl:text>
- <arg name="xmin" count="optional" accepts="int,real">
- <xsl:text>minimum value foe X axis</xsl:text>
- <arg name="xmax" count="optional" accepts="int,real">
- <xsl:text>maximum value for X axis</xsl:text>
<xsl:template match="widget[@type='XYGraph']" mode="widget_class">
<xsl:text>class </xsl:text>
--- a/svghmi/svghmi.py Wed Jun 01 09:15:26 2022 +0200
+++ b/svghmi/svghmi.py Wed Jun 01 09:22:07 2022 +0200
@@ -282,6 +282,10 @@
(_("HMI Tree"), "CreateSVGHMI_UI")]
+ def __init__(self, parent, controler, window): + ConfTreeNodeEditor.__init__(self, parent, controler, window) + self.Controler = controler def CreateSVGHMI_UI(self, parent):
@@ -292,25 +296,29 @@
hmitree_backup_file = open(hmitree_backup_path, 'rb')
hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot())
- ret = SVGHMI_UI(parent, Register_SVGHMI_UI_for_HMI_tree_updates)
+ ret = SVGHMI_UI(parent, self.Controler, Register_SVGHMI_UI_for_HMI_tree_updates) on_hmitree_update(hmi_tree_root)
if wx.Platform == '__WXMSW__':
- browser_launch_cmd="cmd.exe /c 'start msedge {url}'"
+ "launch":"cmd.exe /c 'start msedge {url}'", + "watchdog":"cmd.exe /k 'echo watchdog for {url} !'"} - browser_launch_cmd="chromium {url}"
+ "launch":"chromium {url}", + "watchdog":"echo Watchdog for {name} !"} XSD = """<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="SVGHMI">
- <xsd:attribute name="OnStart" type="xsd:string" use="optional" default="%s"/>
+ <xsd:attribute name="OnStart" type="xsd:string" use="optional" default="%(launch)s"/> <xsd:attribute name="OnStop" type="xsd:string" use="optional" default=""/>
- <xsd:attribute name="OnWatchdog" type="xsd:string" use="optional" default=""/>
+ <xsd:attribute name="OnWatchdog" type="xsd:string" use="optional" default="%(watchdog)s"/> <xsd:attribute name="EnableWatchdog" type="xsd:boolean" use="optional" default="false"/>
<xsd:attribute name="WatchdogInitial" use="optional" default="30">
@@ -342,7 +350,7 @@
EditorType = SVGHMIEditor
@@ -399,9 +407,13 @@
if from_project_path is not None:
shutil.copyfile(self._getSVGpath(from_project_path),
- shutil.copyfile(self._getPOTpath(from_project_path),
- # XXX TODO copy .PO files
+ potpath = self._getPOTpath(from_project_path) + if os.path.isfile(potpath): + shutil.copyfile(potpath, self._getPOTpath()) + for _name, pofile in GetPoFiles(from_project_path): + shutil.copy(pofile, self.CTNPath()) def GetSVGGeometry(self):
--- a/svghmi/ui.py Wed Jun 01 09:15:26 2022 +0200
+++ b/svghmi/ui.py Wed Jun 01 09:22:07 2022 +0200
@@ -11,6 +11,7 @@
from threading import Thread, Lock
from functools import reduce
from itertools import izip
@@ -26,7 +27,7 @@
import util.paths as paths
from IDEFrame import EncodeFileSystemPath, DecodeFileSystemPath
-from docutil import get_inkscape_path
+from docutil import get_inkscape_path, get_inkscape_version from util.ProcessLogger import ProcessLogger
@@ -276,8 +277,10 @@
_conf_key = "SVGHMIWidgetLib"
+thumbnail_temp_path = None class WidgetLibBrowser(wx.SplitterWindow):
- def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
+ def __init__(self, parent, controler, id=wx.ID_ANY, pos=wx.DefaultPosition, wx.SplitterWindow.__init__(self, parent,
@@ -287,6 +290,7 @@
+ self.Controler = controler self.Config = wx.ConfigBase.Get()
self.libdir = self.RecallLibDir()
@@ -455,10 +459,14 @@
self.msg = _("Inkscape is not installed.")
+ export_opt = "-o" if get_inkscape_version()[0] > 0 else "-e" # TODO: spawn a thread, to decouple thumbnail gen
status, result, _err_result = ProcessLogger(
- '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
+ self.Controler.GetCTRoot().logger, + '"' + inkpath + '" "' + svgpath + '" ' + + export_opt + ' "' + thumbpath + '" -D -h ' + str(_preview_height)).spin()
self.msg = _("Inkscape couldn't generate thumbnail.")
@@ -470,10 +478,28 @@
Called when tree item is selected
@param event: wx.TreeEvent
+ global thumbnail_temp_path item_pydata = self.widgetpicker.GetPyData(event.GetItem())
if item_pydata is not None:
- dname = os.path.dirname(svgpath)
+ if thumbnail_temp_path is None: + dname = os.path.dirname(svgpath) + thumbdir = os.path.join(dname, ".svghmithumbs") + if not os.path.exists(thumbdir): + # library not writable : use temp dir + thumbnail_temp_path = os.path.join( + tempfile.gettempdir(), "svghmithumbs") + thumbdir = thumbnail_temp_path + if not os.path.exists(thumbdir): + thumbdir = thumbnail_temp_path fname = os.path.basename(svgpath)
hasher = hashlib.new('md5')
with open(svgpath, 'rb') as afile:
@@ -485,30 +511,24 @@
digest = hasher.hexdigest()
thumbfname = os.path.splitext(fname)[0]+"_"+digest+".png"
- thumbdir = os.path.join(dname, ".svghmithumbs")
thumbpath = os.path.join(thumbdir, thumbfname)
have_thumb = os.path.exists(thumbpath)
- if not os.path.exists(thumbdir):
- have_thumb = self.GenThumbnail(svgpath, thumbpath)
- self.bmp = wx.Bitmap(thumbpath) if have_thumb else None
+ self.Controler.GetCTRoot().logger.write( + "Rendering preview of " + fname + " widget.\n") + have_thumb = self.GenThumbnail(svgpath, thumbpath) - self.selected_SVG = svgpath if have_thumb else None
+ self.bmp = wx.Bitmap(thumbpath) if have_thumb else None - self.AnalyseWidgetAndUpdateUI(fname)
+ self.selected_SVG = svgpath if have_thumb else None - self.SetMessage(self.msg)
+ self.AnalyseWidgetAndUpdateUI(fname)
- self.msg = _("Widget library must be writable")
+ self.SetMessage(self.msg)
def OnHMITreeNodeSelection(self, hmitree_nodes):
self.hmitree_nodes = hmitree_nodes
@@ -687,12 +707,12 @@
class SVGHMI_UI(wx.SplitterWindow):
- def __init__(self, parent, register_for_HMI_tree_updates):
+ def __init__(self, parent, controler, 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.Staging = WidgetLibBrowser(self, controler) self.SplitVertically(self.SelectionTree, self.Staging, 300)
register_for_HMI_tree_updates(weakref.ref(self))
--- a/util/ProcessLogger.py Wed Jun 01 09:15:26 2022 +0200
+++ b/util/ProcessLogger.py Wed Jun 01 09:22:07 2022 +0200
@@ -139,7 +139,7 @@
+ if _debug and self.logger: self.logger.write("(DEBUG) launching:\n" + self.Command_str + "\n")
self.Proc = subprocess.Popen(self.Command, **popenargs)
--- a/util/misc.py Wed Jun 01 09:15:26 2022 +0200
+++ b/util/misc.py Wed Jun 01 09:22:07 2022 +0200
@@ -28,7 +28,8 @@
from __future__ import absolute_import
from functools import reduce
from util.BitmapLibrary import AddBitmapFolder
@@ -42,8 +43,19 @@
for root, dirs, files in os.walk(path):
files = [f for f in files if not f[0] == '.']
dirs[:] = [d for d in dirs if not d[0] == '.']
+ if sys.platform.startswith('win'): + testdirpath = os.path.join(root, "testdir_" + str(random.randint(0, 4294967296))) + if os.access(root, os.W_OK) is not True: - if os.access(root, os.W_OK) is not True or os.access(os.path.join(root, name), os.W_OK) is not True:
+ if os.access(os.path.join(root, name), os.W_OK) is not True: