--- 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 @@
else:
path = os.getenv("HOME")
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()
dirdialog.Destroy()
if answer == wx.ID_OK:
- 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)
if function is not None:
- if buffer_list:
- function(*cargs)
- else:
- 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
+ try:
+ if buffer_list:
+ function(*cargs)
+ else:
+ function(*tuple([lst[-1] for lst in cargs]))
+ except RuntimeError:
+ pass
def GetTicktime(self):
return self._Ticktime
--- 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 @@
import wx
import subprocess
-def get_inkscape_path():
+
+def _get_inkscape_path():
""" Return the Inkscape binary path """
if wx.Platform == '__WXMSW__':
from six.moves import winreg
inkcmd = None
- try:
- inkcmd = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
- 'Software\\Classes\\svgfile\\shell\\Inkscape\\command')
- except OSError:
+ 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:
try:
- inkcmd = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
- 'Software\\Classes\\inkscape.svg\\shell\\open\\command')
+ inkcmd = winreg.QueryValue(subreg, key)
+ break;
except OSError:
- return None
+ pass
+
+ if inkcmd is None:
+ return None
return inkcmd.replace('"%1"', '').strip().replace('"', '')
@@ -51,6 +56,36 @@
except subprocess.CalledProcessError:
return None
+_inkscape_path = None
+def get_inkscape_path():
+ """ Return the Inkscape binary path """
+
+ global _inkscape_path
+
+ if _inkscape_path is not None:
+ return _inkscape_path
+
+ _inkscape_path = _get_inkscape_path()
+ return _inkscape_path
+
+
+def _get_inkscape_version():
+ inkpath = get_inkscape_path()
+ if inkpath is None:
+ return None
+ return map(int,
+ 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
+
def open_svg(svgfile):
""" 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>
</path>
- <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>
<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>
- <arg name="xmin" count="optional" accepts="int,real">
- <xsl:text>minimum value foe X axis</xsl:text>
- </arg>
- <arg name="xmax" count="optional" accepts="int,real">
- <xsl:text>maximum value for X axis</xsl:text>
- </arg>
</xsl:template>
<xsl:template mode="document" match="@* | node()">
<xsl:copy>
--- 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>
<xsl:text> if(typeof(init) == "function"){
</xsl:text>
- <xsl:text> // try {
+ <xsl:text> try {
</xsl:text>
<xsl:text> init.call(this);
</xsl:text>
- <xsl:text> // } catch(err) {
-</xsl:text>
- <xsl:text> // console.log(err);
-</xsl:text>
- <xsl:text> // }
+ <xsl:text> } catch(err) {
+</xsl:text>
+ <xsl:text> console.log(err);
+</xsl:text>
+ <xsl:text> }
</xsl:text>
<xsl:text> }
</xsl:text>
@@ -7780,7 +7780,7 @@
</xsl:text>
<xsl:text> activate(val) {
</xsl:text>
- <xsl:text> let [active, inactive] = val ? ["none",""] : ["", "none"];
+ <xsl:text> let [active, inactive] = val ? ["","none"] : ["none", ""];
</xsl:text>
<xsl:text> if (this.active_elt)
</xsl:text>
@@ -7865,8 +7865,8 @@
<path name="value" count="1+" accepts="HMI_INT,HMI_REAL">
<xsl:text>value</xsl:text>
</path>
- <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>
<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>
- <arg name="xmin" count="optional" accepts="int,real">
- <xsl:text>minimum value foe X axis</xsl:text>
- </arg>
- <arg name="xmax" count="optional" accepts="int,real">
- <xsl:text>maximum value for X axis</xsl:text>
- </arg>
</xsl:template>
<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 @@
CONFNODEEDITOR_TABS = [
(_("HMI Tree"), "CreateSVGHMI_UI")]
+ def __init__(self, parent, controler, window):
+ ConfTreeNodeEditor.__init__(self, parent, controler, window)
+ self.Controler = controler
+
def CreateSVGHMI_UI(self, parent):
global hmi_tree_root
@@ -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)
return ret
if wx.Platform == '__WXMSW__':
- browser_launch_cmd="cmd.exe /c 'start msedge {url}'"
+ default_cmds={
+ "launch":"cmd.exe /c 'start msedge {url}'",
+ "watchdog":"cmd.exe /k 'echo watchdog for {url} !'"}
else:
- browser_launch_cmd="chromium {url}"
+ default_cmds={
+ "launch":"chromium {url}",
+ "watchdog":"echo Watchdog for {name} !"}
class SVGHMI(object):
XSD = """<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="SVGHMI">
<xsd:complexType>
- <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">
<xsd:simpleType>
@@ -342,7 +350,7 @@
</xsd:complexType>
</xsd:element>
</xsd:schema>
- """%browser_launch_cmd
+ """%default_cmds
EditorType = SVGHMIEditor
@@ -399,9 +407,13 @@
if from_project_path is not None:
shutil.copyfile(self._getSVGpath(from_project_path),
self._getSVGpath())
- shutil.copyfile(self._getPOTpath(from_project_path),
- self._getPOTpath())
- # XXX TODO copy .PO files
+
+ potpath = self._getPOTpath(from_project_path)
+ if os.path.isfile(potpath):
+ shutil.copyfile(potpath, self._getPOTpath())
+ # copy .PO files
+ for _name, pofile in GetPoFiles(from_project_path):
+ shutil.copy(pofile, self.CTNPath())
return True
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 @@
import hashlib
import weakref
import re
+import tempfile
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"
_preview_height = 200
_preview_margin = 5
+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,
size=wx.DefaultSize):
wx.SplitterWindow.__init__(self, parent,
@@ -287,6 +290,7 @@
self.msg = None
self.hmitree_nodes = []
self.selected_SVG = None
+ self.Controler = controler
self.Config = wx.ConfigBase.Get()
self.libdir = self.RecallLibDir()
@@ -455,10 +459,14 @@
if inkpath is None:
self.msg = _("Inkscape is not installed.")
return False
+
+ export_opt = "-o" if get_inkscape_version()[0] > 0 else "-e"
+
# TODO: spawn a thread, to decouple thumbnail gen
status, result, _err_result = ProcessLogger(
- None,
- '"' + inkpath + '" "' + svgpath + '" -e "' + thumbpath +
+ self.Controler.GetCTRoot().logger,
+ '"' + inkpath + '" "' + svgpath + '" ' +
+ export_opt + ' "' + thumbpath +
'" -D -h ' + str(_preview_height)).spin()
if status != 0:
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
+ event.Skip()
item_pydata = self.widgetpicker.GetPyData(event.GetItem())
if item_pydata is not None:
svgpath = item_pydata
- dname = os.path.dirname(svgpath)
+
+ if thumbnail_temp_path is None:
+ try:
+ dname = os.path.dirname(svgpath)
+ thumbdir = os.path.join(dname, ".svghmithumbs")
+ if not os.path.exists(thumbdir):
+ os.mkdir(thumbdir)
+ except Exception :
+ # 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):
+ os.mkdir(thumbdir)
+ else:
+ thumbdir = thumbnail_temp_path
+
fname = os.path.basename(svgpath)
hasher = hashlib.new('md5')
with open(svgpath, 'rb') as afile:
@@ -485,30 +511,24 @@
break
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)
- try:
- if not have_thumb:
- if not os.path.exists(thumbdir):
- os.mkdir(thumbdir)
- 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.AnalyseWidgetAndUpdateUI(fname)
-
- self.SetMessage(self.msg)
-
- except IOError:
- self.msg = _("Widget library must be writable")
+ if not have_thumb:
+ self.Controler.GetCTRoot().logger.write(
+ "Rendering preview of " + fname + " widget.\n")
+ 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.AnalyseWidgetAndUpdateUI(fname)
+
+ self.SetMessage(self.msg)
self.Refresh()
- event.Skip()
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/svghmi/widget_tooglebutton.ysl2 Wed Jun 01 09:15:26 2022 +0200
+++ b/svghmi/widget_tooglebutton.ysl2 Wed Jun 01 09:22:07 2022 +0200
@@ -38,7 +38,7 @@
}
activate(val) {
- let [active, inactive] = val ? ["none",""] : ["", "none"];
+ let [active, inactive] = val ? ["","none"] : ["none", ""];
if (this.active_elt)
this.active_elt.style.display = active;
if (this.inactive_elt)
--- 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 @@
else:
self.timeout = None
- if _debug:
+ 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
-import os
+import os,sys
+import random
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'):
+ try:
+ testdirpath = os.path.join(root, "testdir_" + str(random.randint(0, 4294967296)))
+ os.mkdir(testdirpath)
+ os.rmdir(testdirpath)
+ except:
+ return False
+ else:
+ if os.access(root, os.W_OK) is not True:
+ return False
+
for name in files:
- 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:
return False
return True