merged
authorEdouard Tisserant
Wed, 01 Jun 2022 09:22:07 +0200
changeset 3507 e87a2daace80
parent 3506 ca312be56929 (current diff)
parent 3500 a88ac1760faf (diff)
child 3508 14d696d7d54e
merged
svghmi/analyse_widget.xslt
svghmi/gen_dnd_widget_svg.xslt
svghmi/gen_index_xhtml.xslt
--- 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