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@2745: import shutil Edouard@2789: import hashlib Edouard@2823: import shlex edouard@3156: import time Edouard@2745: Edouard@2745: import wx Edouard@2816: Edouard@2816: from lxml import etree Edouard@2816: from lxml.etree import XSLTApplyError Edouard@2745: Edouard@2745: import util.paths as paths Edouard@2745: from POULibrary import POULibrary Edouard@2756: from docutil import open_svg, get_inkscape_path Edouard@2745: Edouard@2756: from util.ProcessLogger import ProcessLogger Edouard@2764: from runtime.typemapping import DebugTypesSize Edouard@2767: import targets Edouard@2816: from editors.ConfTreeNodeEditor import ConfTreeNodeEditor Edouard@2816: from XSLTransform import XSLTransform edouard@3201: from svghmi.i18n import EtreeToMessages, SaveCatalog, ReadTranslations,\ edouard@3201: MatchTranslations, TranslationToEtree, open_pofile edouard@3197: from svghmi.hmi_tree import HMI_TYPES, HMITreeNode, SPECIAL_NODES edouard@3201: from svghmi.ui import SVGHMI_UI Edouard@2745: Edouard@2753: Edouard@2753: ScriptDirectory = paths.AbsDir(__file__) Edouard@2753: Edouard@2788: Edouard@2763: # module scope for HMITree root Edouard@2763: # so that CTN can use HMITree deduced in Library Edouard@2817: # note: this only works because library's Generate_C is Edouard@2763: # systematicaly invoked before CTN's CTNGenerate_C Edouard@2763: Edouard@2763: hmi_tree_root = None Edouard@2757: Edouard@2817: on_hmitree_update = None Edouard@2816: Edouard@2745: class SVGHMILibrary(POULibrary): Edouard@2745: def GetLibraryPath(self): Edouard@2750: return paths.AbsNeighbourFile(__file__, "pous.xml") Edouard@2745: Edouard@2749: def Generate_C(self, buildpath, varlist, IECCFLAGS): Edouard@2822: global hmi_tree_root, on_hmitree_update Edouard@2749: Edouard@2757: """ Edouard@2757: PLC Instance Tree: Edouard@2757: prog0 Edouard@2757: +->v1 HMI_INT Edouard@2757: +->v2 HMI_INT Edouard@2757: +->fb0 (type mhoo) Edouard@2814: | +->va HMI_NODE Edouard@2757: | +->v3 HMI_INT Edouard@2757: | +->v4 HMI_INT Edouard@2757: | Edouard@2757: +->fb1 (type mhoo) Edouard@2814: | +->va HMI_NODE Edouard@2757: | +->v3 HMI_INT Edouard@2757: | +->v4 HMI_INT Edouard@2757: | Edouard@2757: +->fb2 Edouard@2757: +->v5 HMI_IN Edouard@2757: Edouard@2757: HMI tree: Edouard@2757: hmi0 Edouard@2757: +->v1 Edouard@2757: +->v2 Edouard@2814: +->fb0 class:va Edouard@2757: | +-> v3 Edouard@2757: | +-> v4 Edouard@2757: | Edouard@2814: +->fb1 class:va Edouard@2757: | +-> v3 Edouard@2757: | +-> v4 Edouard@2757: | Edouard@2757: +->v5 Edouard@2757: Edouard@2757: """ Edouard@2757: Edouard@2749: # Filter known HMI types Edouard@2749: hmi_types_instances = [v for v in varlist if v["derived"] in HMI_TYPES] Edouard@2758: edouard@2890: hmi_tree_root = None edouard@2890: edouard@2890: # take first HMI_NODE (placed as special node), make it root edouard@2890: for i,v in enumerate(hmi_types_instances): edouard@2890: path = v["IEC_path"].split(".") edouard@2890: derived = v["derived"] Edouard@2965: if derived == "HMI_NODE": edouard@2890: hmi_tree_root = HMITreeNode(path, "", derived, v["type"], v["vartype"], v["C_path"]) edouard@2890: hmi_types_instances.pop(i) edouard@2890: break edouard@2890: Edouard@3051: if hmi_tree_root is None: Edouard@3051: self.FatalError("SVGHMI : Library is selected but not used. Please either deselect it in project config or add a SVGHMI node to project.") Edouard@2757: Edouard@2757: # deduce HMI tree from PLC HMI_* instances Edouard@2757: for v in hmi_types_instances: Edouard@2822: path = v["IEC_path"].split(".") Edouard@2758: # ignores variables starting with _TMP_ Edouard@2758: if path[-1].startswith("_TMP_"): Edouard@2758: continue Edouard@2814: derived = v["derived"] Edouard@2814: kwargs={} Edouard@2814: if derived == "HMI_NODE": Edouard@2822: # TODO : make problem if HMI_NODE used in CONFIG or RESOURCE Edouard@2814: name = path[-2] Edouard@2814: kwargs['hmiclass'] = path[-1] Edouard@2814: else: Edouard@2814: name = path[-1] Edouard@2822: new_node = HMITreeNode(path, name, derived, v["type"], v["vartype"], v["C_path"], **kwargs) Edouard@2965: placement_result = hmi_tree_root.place_node(new_node) Edouard@2965: if placement_result is not None: Edouard@2965: cause, problematic_node = placement_result Edouard@2965: if cause == "Non_Unique": Edouard@2965: message = _("HMI tree nodes paths are not unique.\nConflicting variable: {} {}").format( Edouard@2965: ".".join(problematic_node.path), Edouard@2965: ".".join(new_node.path)) Edouard@2965: Edouard@2965: last_FB = None Edouard@2965: for v in varlist: Edouard@2965: if v["vartype"] == "FB": Edouard@2965: last_FB = v Edouard@2965: if v["C_path"] == problematic_node: Edouard@2965: break Edouard@2965: if last_FB is not None: Edouard@2965: failing_parent = last_FB["type"] Edouard@2965: message += "\n" Edouard@2965: message += _("Solution: Add HMI_NODE at beginning of {}").format(failing_parent) Edouard@2965: Edouard@2965: elif cause in ["Late_HMI_NODE", "Duplicate_HMI_NODE"]: Edouard@2965: cause, problematic_node = placement_result Edouard@2965: message = _("There must be only one occurrence of HMI_NODE before any HMI_* variable in POU.\nConflicting variable: {} {}").format( Edouard@2965: ".".join(problematic_node.path), Edouard@2965: ".".join(new_node.path)) Edouard@2965: Edouard@2965: self.FatalError("SVGHMI : " + message) Edouard@2757: Edouard@2817: if on_hmitree_update is not None: edouard@3201: on_hmitree_update(hmi_tree_root) Edouard@2816: Edouard@2764: variable_decl_array = [] Edouard@2764: extern_variables_declarations = [] Edouard@2765: buf_index = 0 Edouard@2775: item_count = 0 Edouard@2822: found_heartbeat = False Edouard@2822: Edouard@2822: hearbeat_IEC_path = ['CONFIG', 'HEARTBEAT'] Edouard@2828: Edouard@2764: for node in hmi_tree_root.traverse(): Edouard@2828: if not found_heartbeat and node.path == hearbeat_IEC_path: Edouard@2822: hmi_tree_hearbeat_index = item_count Edouard@2822: found_heartbeat = True Edouard@2822: extern_variables_declarations += [ Edouard@2822: "#define heartbeat_index "+str(hmi_tree_hearbeat_index) Edouard@2822: ] Edouard@2866: if hasattr(node, "iectype"): Edouard@2764: sz = DebugTypesSize.get(node.iectype, 0) Edouard@2764: variable_decl_array += [ Edouard@2822: "{&(" + node.cpath + "), " + node.iectype + { Edouard@2764: "EXT": "_P_ENUM", Edouard@2764: "IN": "_P_ENUM", Edouard@2764: "MEM": "_O_ENUM", Edouard@2764: "OUT": "_O_ENUM", Edouard@2764: "VAR": "_ENUM" Edouard@2765: }[node.vartype] + ", " + Edouard@2768: str(buf_index) + ", 0, }"] Edouard@2765: buf_index += sz Edouard@2775: item_count += 1 Edouard@2764: if len(node.path) == 1: Edouard@2764: extern_variables_declarations += [ Edouard@2764: "extern __IEC_" + node.iectype + "_" + Edouard@2764: "t" if node.vartype is "VAR" else "p" Edouard@2822: + node.cpath + ";"] Edouard@2828: Edouard@2822: assert(found_heartbeat) Edouard@2764: Edouard@2764: # TODO : filter only requiered external declarations Edouard@2828: for v in varlist: Edouard@2828: if v["C_path"].find('.') < 0: Edouard@2764: extern_variables_declarations += [ Edouard@2764: "extern %(type)s %(C_path)s;" % v] Edouard@2764: Edouard@2764: # TODO check if programs need to be declared separately Edouard@2764: # "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" % Edouard@2764: # p for p in self._ProgramList]), Edouard@2764: Edouard@2771: # C code to observe/access HMI tree variables Edouard@2749: svghmi_c_filepath = paths.AbsNeighbourFile(__file__, "svghmi.c") Edouard@2749: svghmi_c_file = open(svghmi_c_filepath, 'r') Edouard@2749: svghmi_c_code = svghmi_c_file.read() Edouard@2749: svghmi_c_file.close() Edouard@2817: svghmi_c_code = svghmi_c_code % { Edouard@2764: "variable_decl_array": ",\n".join(variable_decl_array), Edouard@2764: "extern_variables_declarations": "\n".join(extern_variables_declarations), Edouard@2767: "buffer_size": buf_index, Edouard@2775: "item_count": item_count, Edouard@2768: "var_access_code": targets.GetCode("var_access.c"), Edouard@2788: "PLC_ticktime": self.GetCTR().GetTicktime(), Edouard@2789: "hmi_hash_ints": ",".join(map(str,hmi_tree_root.hash())) Edouard@2764: } Edouard@2749: Edouard@2749: gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c") Edouard@2749: gen_svghmi_c = open(gen_svghmi_c_path, 'w') Edouard@2749: gen_svghmi_c.write(svghmi_c_code) Edouard@2749: gen_svghmi_c.close() Edouard@2749: Edouard@2771: # Python based WebSocket HMITree Server Edouard@2771: svghmiserverfile = open(paths.AbsNeighbourFile(__file__, "svghmi_server.py"), 'r') Edouard@2771: svghmiservercode = svghmiserverfile.read() Edouard@2771: svghmiserverfile.close() Edouard@2771: Edouard@2993: runtimefile_path = os.path.join(buildpath, "runtime_00_svghmi.py") Edouard@2771: runtimefile = open(runtimefile_path, 'w') Edouard@2771: runtimefile.write(svghmiservercode) Edouard@2771: runtimefile.close() Edouard@2771: edouard@3176: # Backup HMI Tree in XML form so that it can be loaded without building edouard@3176: hmitree_backup_path = os.path.join(buildpath, "hmitree.xml") edouard@3180: hmitree_backup_file = open(hmitree_backup_path, 'wb') edouard@3176: hmitree_backup_file.write(etree.tostring(hmi_tree_root.etree())) edouard@3176: hmitree_backup_file.close() edouard@3176: Edouard@2771: return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "", Edouard@2993: ("runtime_00_svghmi.py", open(runtimefile_path, "rb"))) Edouard@2993: # ^ Edouard@2993: # note the double zero after "runtime_", Edouard@2993: # to ensure placement before other CTN generated code in execution order Edouard@2745: Edouard@2816: edouard@3201: def Register_SVGHMI_UI_for_HMI_tree_updates(ref): edouard@3201: global on_hmitree_update edouard@3201: def HMITreeUpdate(_hmi_tree_root): edouard@3201: obj = ref() edouard@3201: if obj is not None: edouard@3201: obj.HMITreeUpdate(_hmi_tree_root) edouard@3201: edouard@3201: on_hmitree_update = HMITreeUpdate Edouard@2818: Edouard@2818: Edouard@2818: class SVGHMIEditor(ConfTreeNodeEditor): Edouard@2818: CONFNODEEDITOR_TABS = [ edouard@3201: (_("HMI Tree"), "CreateSVGHMI_UI")] edouard@3201: edouard@3201: def CreateSVGHMI_UI(self, parent): edouard@3176: global hmi_tree_root edouard@3176: edouard@3176: if hmi_tree_root is None: edouard@3176: buildpath = self.Controler.GetCTRoot()._getBuildPath() edouard@3176: hmitree_backup_path = os.path.join(buildpath, "hmitree.xml") edouard@3176: if os.path.exists(hmitree_backup_path): edouard@3180: hmitree_backup_file = open(hmitree_backup_path, 'rb') edouard@3176: hmi_tree_root = HMITreeNode.from_etree(etree.parse(hmitree_backup_file).getroot()) edouard@3176: edouard@3201: return SVGHMI_UI(parent, Register_SVGHMI_UI_for_HMI_tree_updates) Edouard@2818: Edouard@2745: class SVGHMI(object): Edouard@2771: XSD = """ Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2823: Edouard@2823: Edouard@2823: Edouard@2831: Edouard@2831: Edouard@2745: Edouard@2745: Edouard@2745: Edouard@2745: """ Edouard@2816: Edouard@2816: EditorType = SVGHMIEditor Edouard@2745: Edouard@2745: ConfNodeMethods = [ Edouard@2745: { Edouard@2745: "bitmap": "ImportSVG", Edouard@2745: "name": _("Import SVG"), Edouard@2745: "tooltip": _("Import SVG"), Edouard@2745: "method": "_ImportSVG" Edouard@2745: }, Edouard@2745: { Edouard@3112: "bitmap": "EditSVG", # should be something different Edouard@2745: "name": _("Inkscape"), Edouard@2745: "tooltip": _("Edit HMI"), Edouard@2745: "method": "_StartInkscape" Edouard@2745: }, Edouard@3112: { Edouard@3112: "bitmap": "OpenPOT", # should be something different Edouard@3112: "name": _("New lang"), Edouard@3112: "tooltip": _("Open non translated message catalog (POT) to start new language"), Edouard@3112: "method": "_OpenPOT" Edouard@3112: }, Edouard@3112: Edouard@3112: { Edouard@3112: "bitmap": "EditPO", # should be something different Edouard@3112: "name": _("Edit lang"), Edouard@3112: "tooltip": _("Edit existing message catalog (PO) for specific language"), Edouard@3112: "method": "_EditPO" Edouard@3112: }, Edouard@2788: Edouard@2771: # TODO : HMITree button Edouard@2771: # - can drag'n'drop variabes to Inkscape Edouard@2745: ] Edouard@2745: Edouard@2745: def _getSVGpath(self, project_path=None): Edouard@2745: if project_path is None: Edouard@2745: project_path = self.CTNPath() Edouard@2781: return os.path.join(project_path, "svghmi.svg") Edouard@2745: edouard@3108: def _getPOTpath(self, project_path=None): edouard@3108: if project_path is None: edouard@3108: project_path = self.CTNPath() edouard@3108: return os.path.join(project_path, "messages.pot") Edouard@2745: Edouard@2745: def OnCTNSave(self, from_project_path=None): Edouard@2745: if from_project_path is not None: Edouard@2745: shutil.copyfile(self._getSVGpath(from_project_path), Edouard@2745: self._getSVGpath()) Edouard@3112: shutil.copyfile(self._getPOTpath(from_project_path), Edouard@3112: self._getPOTpath()) Edouard@3112: # XXX TODO copy .PO files Edouard@2750: return True Edouard@2745: Edouard@2753: def GetSVGGeometry(self): Edouard@3170: self.ProgressStart("inkscape", "collecting SVG geometry (Inkscape)") Edouard@2756: # invoke inskscape -S, csv-parse output, produce elements Edouard@2756: InkscapeGeomColumns = ["Id", "x", "y", "w", "h"] Edouard@2756: Edouard@2756: inkpath = get_inkscape_path() Edouard@3052: Edouard@3052: if inkpath is None: Edouard@3052: self.FatalError("SVGHMI: inkscape is not installed.") Edouard@3052: Edouard@2756: svgpath = self._getSVGpath() Edouard@3032: status, result, _err_result = ProcessLogger(self.GetCTRoot().logger, Edouard@3032: '"' + inkpath + '" -S "' + svgpath + '"', Edouard@2756: no_stdout=True, Edouard@2756: no_stderr=True).spin() Edouard@3032: if status != 0: Edouard@3052: self.FatalError("SVGHMI: inkscape couldn't extract geometry from given SVG.") Edouard@3032: Edouard@2756: res = [] Edouard@2756: for line in result.split(): Edouard@2756: strippedline = line.strip() Edouard@2756: attrs = dict( Edouard@2756: zip(InkscapeGeomColumns, line.strip().split(','))) Edouard@2756: Edouard@2756: res.append(etree.Element("bbox", **attrs)) Edouard@2756: Edouard@3170: self.ProgressEnd("inkscape") Edouard@2756: return res Edouard@2753: Edouard@2763: def GetHMITree(self): Edouard@2763: global hmi_tree_root Edouard@3170: self.ProgressStart("hmitree", "getting HMI tree") Edouard@2788: res = [hmi_tree_root.etree(add_hash=True)] Edouard@3170: self.ProgressEnd("hmitree") Edouard@2763: return res Edouard@2763: edouard@3108: def GetTranslations(self, _context, msgs): Edouard@3170: self.ProgressStart("i18n", "getting Translations") edouard@3113: messages = EtreeToMessages(msgs) edouard@3113: edouard@3140: if len(messages) == 0: Edouard@3170: self.ProgressEnd("i18n") edouard@3140: return edouard@3140: edouard@3113: SaveCatalog(self._getPOTpath(), messages) edouard@3113: edouard@3113: translations = ReadTranslations(self.CTNPath()) edouard@3113: edouard@3113: langs,translated_messages = MatchTranslations(translations, messages, edouard@3113: errcallback=self.GetCTRoot().logger.write_warning) edouard@3113: edouard@3165: ret = TranslationToEtree(langs,translated_messages) edouard@3165: Edouard@3170: self.ProgressEnd("i18n") edouard@3165: edouard@3165: return ret edouard@3108: Edouard@3170: times_msgs = {} Edouard@3170: indent = 1 Edouard@3170: def ProgressStart(self, k, m): Edouard@3170: self.times_msgs[k] = (time.time(), m) Edouard@3170: self.GetCTRoot().logger.write(" "*self.indent + "Start %s...\n"%m) Edouard@3170: self.indent = self.indent + 1 Edouard@3170: Edouard@3170: def ProgressEnd(self, k): Edouard@3170: t = time.time() Edouard@3170: oldt, m = self.times_msgs[k] Edouard@3170: self.indent = self.indent - 1 Edouard@3170: self.GetCTRoot().logger.write(" "*self.indent + "... finished in %.3fs\n"%(t - oldt)) edouard@3156: Edouard@2745: def CTNGenerate_C(self, buildpath, locations): Edouard@2745: Edouard@2771: location_str = "_".join(map(str, self.GetCurrentLocation())) Edouard@2771: view_name = self.BaseParams.getName() Edouard@2771: Edouard@2745: svgfile = self._getSVGpath() Edouard@2771: Edouard@2771: res = ([], "", False) Edouard@2771: Edouard@2812: target_fname = "svghmi_"+location_str+".xhtml" Edouard@2771: edouard@3180: build_path = self._getBuildPath() edouard@3180: target_path = os.path.join(build_path, target_fname) edouard@3180: hash_path = os.path.join(build_path, "svghmi.md5") Edouard@2771: edouard@3156: self.GetCTRoot().logger.write("SVGHMI:\n") edouard@3156: Edouard@2745: if os.path.exists(svgfile): Edouard@2753: edouard@3180: hasher = hashlib.md5() edouard@3180: hmi_tree_root._hash(hasher) edouard@3180: with open(svgfile, 'rb') as afile: edouard@3180: while True: edouard@3180: buf = afile.read(65536) edouard@3180: if len(buf) > 0: edouard@3180: hasher.update(buf) edouard@3180: else: edouard@3180: break edouard@3180: digest = hasher.hexdigest() edouard@3180: edouard@3180: if os.path.exists(hash_path): edouard@3180: with open(hash_path, 'rb') as digest_file: edouard@3180: last_digest = digest_file.read() edouard@3180: else: edouard@3180: last_digest = None edouard@3180: edouard@3180: if digest != last_digest: edouard@3180: edouard@3180: transform = XSLTransform(os.path.join(ScriptDirectory, "gen_index_xhtml.xslt"), edouard@3180: [("GetSVGGeometry", lambda *_ignored:self.GetSVGGeometry()), edouard@3180: ("GetHMITree", lambda *_ignored:self.GetHMITree()), edouard@3180: ("GetTranslations", self.GetTranslations), edouard@3180: ("ProgressStart", lambda _ign,k,m:self.ProgressStart(str(k),str(m))), edouard@3180: ("ProgressEnd", lambda _ign,k:self.ProgressEnd(str(k)))]) edouard@3180: edouard@3180: self.ProgressStart("svg", "source SVG parsing") edouard@3180: edouard@3180: # load svg as a DOM with Etree edouard@3180: svgdom = etree.parse(svgfile) edouard@3180: edouard@3180: self.ProgressEnd("svg") edouard@3180: edouard@3180: # call xslt transform on Inkscape's SVG to generate XHTML edouard@3180: try: edouard@3180: self.ProgressStart("xslt", "XSLT transform") edouard@3180: result = transform.transform(svgdom) # , profile_run=True) edouard@3180: self.ProgressEnd("xslt") edouard@3180: except XSLTApplyError as e: edouard@3180: self.FatalError("SVGHMI " + view_name + ": " + e.message) edouard@3180: finally: edouard@3180: for entry in transform.get_error_log(): edouard@3180: message = "SVGHMI: "+ entry.message + "\n" edouard@3180: self.GetCTRoot().logger.write_warning(message) edouard@3180: edouard@3180: target_file = open(target_path, 'wb') edouard@3180: result.write(target_file, encoding="utf-8") edouard@3180: target_file.close() edouard@3180: edouard@3180: # print(str(result)) edouard@3180: # print(transform.xslt.error_log) edouard@3180: # print(etree.tostring(result.xslt_profile,pretty_print=True)) edouard@3180: edouard@3180: with open(hash_path, 'wb') as digest_file: edouard@3180: digest_file.write(digest) edouard@3180: else: edouard@3180: self.GetCTRoot().logger.write(" No changes - XSLT transformation skipped\n") Edouard@2753: Edouard@2745: else: edouard@3180: target_file = open(target_path, 'wb') Edouard@2771: target_file.write(""" Edouard@2771: Edouard@2771: Edouard@2771:

No SVG file provided

Edouard@2771: Edouard@2771: Edouard@2771: """) edouard@3180: target_file.close() Edouard@2771: Edouard@2772: res += ((target_fname, open(target_path, "rb")),) Edouard@2828: Edouard@2823: svghmi_cmds = {} Edouard@2823: for thing in ["Start", "Stop", "Watchdog"]: Edouard@2823: given_command = self.GetParamsAttributes("SVGHMI.On"+thing)["value"] edouard@2824: svghmi_cmds[thing] = ( edouard@2824: "Popen(" + Edouard@2828: repr(shlex.split(given_command.format(port="8008", name=view_name))) + Edouard@2834: ")") if given_command else "pass # no command given" Edouard@2772: Edouard@2993: runtimefile_path = os.path.join(buildpath, "runtime_%s_svghmi_.py" % location_str) Edouard@2771: runtimefile = open(runtimefile_path, 'w') Edouard@2771: runtimefile.write(""" Edouard@2831: # TODO : multiple watchdog (one for each svghmi instance) Edouard@2831: def svghmi_watchdog_trigger(): Edouard@2828: {svghmi_cmds[Watchdog]} edouard@2824: Edouard@2831: svghmi_watchdog = None Edouard@2831: Edouard@2993: def _runtime_{location}_svghmi_start(): Edouard@2831: global svghmi_watchdog Edouard@2831: svghmi_root.putChild( Edouard@2831: '{view_name}', Edouard@2831: NoCacheFile('{xhtml}', Edouard@2831: defaultType='application/xhtml+xml')) Edouard@2831: Edouard@2828: {svghmi_cmds[Start]} edouard@2824: Edouard@2831: svghmi_watchdog = Watchdog( Edouard@2831: {watchdog_initial}, Edouard@2831: {watchdog_interval}, Edouard@2831: svghmi_watchdog_trigger) Edouard@2831: Edouard@2993: def _runtime_{location}_svghmi_stop(): Edouard@2831: global svghmi_watchdog Edouard@2831: if svghmi_watchdog is not None: Edouard@2831: svghmi_watchdog.cancel() Edouard@2831: svghmi_watchdog = None Edouard@2831: edouard@2824: svghmi_root.delEntity('{view_name}') Edouard@2828: {svghmi_cmds[Stop]} edouard@2824: edouard@2824: """.format(location=location_str, edouard@2824: xhtml=target_fname, edouard@2824: view_name=view_name, Edouard@2831: svghmi_cmds=svghmi_cmds, Edouard@2831: watchdog_initial = self.GetParamsAttributes("SVGHMI.WatchdogInitial")["value"], Edouard@2831: watchdog_interval = self.GetParamsAttributes("SVGHMI.WatchdogInterval")["value"], Edouard@2831: )) Edouard@2771: Edouard@2771: runtimefile.close() Edouard@2771: Edouard@2993: res += (("runtime_%s_svghmi.py" % location_str, open(runtimefile_path, "rb")),) Edouard@2745: Edouard@2745: return res Edouard@2745: Edouard@2745: def _ImportSVG(self): Edouard@2745: dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a SVG file"), os.getcwd(), "", _("SVG files (*.svg)|*.svg|All files|*.*"), wx.OPEN) Edouard@2745: if dialog.ShowModal() == wx.ID_OK: Edouard@2745: svgpath = dialog.GetPath() Edouard@2745: if os.path.isfile(svgpath): Edouard@2745: shutil.copy(svgpath, self._getSVGpath()) Edouard@2745: else: Edouard@2745: self.GetCTRoot().logger.write_error(_("No such SVG file: %s\n") % svgpath) Edouard@2745: dialog.Destroy() Edouard@2745: Edouard@2745: def _StartInkscape(self): Edouard@2745: svgfile = self._getSVGpath() Edouard@2745: open_inkscape = True Edouard@2745: if not self.GetCTRoot().CheckProjectPathPerm(): Edouard@2745: dialog = wx.MessageDialog(self.GetCTRoot().AppFrame, Edouard@2745: _("You don't have write permissions.\nOpen Inkscape anyway ?"), Edouard@2745: _("Open Inkscape"), Edouard@2745: wx.YES_NO | wx.ICON_QUESTION) Edouard@2745: open_inkscape = dialog.ShowModal() == wx.ID_YES Edouard@2745: dialog.Destroy() Edouard@2745: if open_inkscape: Edouard@2745: if not os.path.isfile(svgfile): Edouard@2745: svgfile = None Edouard@2745: open_svg(svgfile) Edouard@2822: edouard@3108: def _StartPOEdit(self, POFile): edouard@3108: open_poedit = True edouard@3108: if not self.GetCTRoot().CheckProjectPathPerm(): edouard@3108: dialog = wx.MessageDialog(self.GetCTRoot().AppFrame, edouard@3108: _("You don't have write permissions.\nOpen POEdit anyway ?"), edouard@3108: _("Open POEdit"), edouard@3108: wx.YES_NO | wx.ICON_QUESTION) edouard@3108: open_poedit = dialog.ShowModal() == wx.ID_YES edouard@3108: dialog.Destroy() edouard@3108: if open_poedit: Edouard@3112: open_pofile(POFile) Edouard@3112: Edouard@3112: def _EditPO(self): edouard@3108: """ Select a specific translation and edit it with POEdit """ Edouard@3112: project_path = self.CTNPath() Edouard@3112: dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a PO file"), project_path, "", _("PO files (*.po)|*.po"), wx.OPEN) Edouard@3112: if dialog.ShowModal() == wx.ID_OK: Edouard@3112: POFile = dialog.GetPath() Edouard@3112: if os.path.isfile(POFile): edouard@3113: if os.path.relpath(POFile, project_path) == os.path.basename(POFile): Edouard@3112: self._StartPOEdit(POFile) Edouard@3112: else: Edouard@3112: self.GetCTRoot().logger.write_error(_("PO file misplaced: %s is not in %s\n") % (POFile,project_path)) Edouard@3112: else: Edouard@3158: self.GetCTRoot().logger.write_error(_("PO file does not exist: %s\n") % POFile) Edouard@3112: dialog.Destroy() Edouard@3112: Edouard@3112: def _OpenPOT(self): edouard@3108: """ Start POEdit with untouched empty catalog """ edouard@3108: POFile = self._getPOTpath() Edouard@3158: if os.path.isfile(POFile): Edouard@3158: self._StartPOEdit(POFile) Edouard@3158: else: Edouard@3158: self.GetCTRoot().logger.write_error(_("POT file does not exist, add translatable text (label starting with '_') in Inkscape first\n")) edouard@3108: Edouard@2822: def CTNGlobalInstances(self): Edouard@2822: # view_name = self.BaseParams.getName() Edouard@2822: # return [ (view_name + "_" + name, iec_type, "") for name, iec_type in SPECIAL_NODES] Edouard@2822: # TODO : move to library level for multiple hmi Edouard@2822: return [(name, iec_type, "") for name, iec_type in SPECIAL_NODES] Edouard@2822: Edouard@3159: def GetIconName(self): Edouard@3159: return "SVGHMI"