author | Andrey Skvortsov <andrej.skvortzov@gmail.com> |
Mon, 14 Aug 2017 21:09:48 +0300 | |
changeset 1731 | 6ebd9c40b2be |
parent 1730 | 64d8f52bc8c8 |
child 1732 | 94ffe74e6895 |
--- a/connectors/PYRO/__init__.py Mon Aug 14 19:13:01 2017 +0300 +++ b/connectors/PYRO/__init__.py Mon Aug 14 21:09:48 2017 +0300 @@ -1,199 +1,199 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# This file is part of Beremiz, a Integrated Development Environment for -# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. -# -# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD -# -# See COPYING file for copyrights details. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import Pyro -import Pyro.core -import Pyro.util -from Pyro.errors import PyroError -import traceback -from time import sleep -import copy -import socket -service_type = '_PYRO._tcp.local.' -import os.path -# this module attribute contains a list of DNS-SD (Zeroconf) service types -# supported by this connector confnode. -# -# for connectors that do not support DNS-SD, this attribute can be omitted -# or set to an empty list. - -def PYRO_connector_factory(uri, confnodesroot): - """ - This returns the connector to Pyro style PLCobject - """ - confnodesroot.logger.write(_("PYRO connecting to URI : %s\n") % uri) - - servicetype, location = uri.split("://") - if servicetype == "PYROS": - schemename = "PYROLOCSSL" - # Protect against name->IP substitution in Pyro3 - Pyro.config.PYRO_DNS_URI = True - # Beware Pyro lib need str path, not unicode - # don't rely on PYRO_STORAGE ! see documentation - Pyro.config.PYROSSL_CERTDIR = os.path.abspath(str(confnodesroot.ProjectPath) + '/certs') - if not os.path.exists(Pyro.config.PYROSSL_CERTDIR): - confnodesroot.logger.write_error( - 'Error : the directory %s is missing for SSL certificates (certs_dir).' - 'Please fix it in your project.\n' % Pyro.config.PYROSSL_CERTDIR) - return None - else: - confnodesroot.logger.write(_("PYRO using certificates in '%s' \n") - % (Pyro.config.PYROSSL_CERTDIR)) - Pyro.config.PYROSSL_CERT = "client.crt" - Pyro.config.PYROSSL_KEY = "client.key" - # Ugly Monkey Patching - def _gettimeout(self): - return self.timeout - - def _settimeout(self, timeout): - self.timeout = timeout - from M2Crypto.SSL import Connection - Connection.timeout = None - Connection.gettimeout = _gettimeout - Connection.settimeout = _settimeout - # M2Crypto.SSL.Checker.WrongHost: Peer certificate commonName does not - # match host, expected 127.0.0.1, got server - Connection.clientPostConnectionCheck = None - else: - schemename = "PYROLOC" - if location.find(service_type) != -1: - try: - from util.Zeroconf import Zeroconf - r = Zeroconf() - i = r.getServiceInfo(service_type, location) - if i is None: - raise Exception("'%s' not found" % location) - ip = str(socket.inet_ntoa(i.getAddress())) - port = str(i.getPort()) - newlocation = ip + ':' + port - confnodesroot.logger.write(_("'{a1}' is located at {a2}\n").format(a1 = location, a2 = newlocation)) - location = newlocation - r.close() - except Exception, msg: - confnodesroot.logger.write_error(_("MDNS resolution failure for '%s'\n") % location) - confnodesroot.logger.write_error(traceback.format_exc()) - return None - - # Try to get the proxy object - try: - RemotePLCObjectProxy = Pyro.core.getAttrProxyForURI(schemename + "://" + location + "/PLCObject") - except Exception, msg: - confnodesroot.logger.write_error(_("Connection to '%s' failed.\n") % location) - confnodesroot.logger.write_error(traceback.format_exc()) - return None - - def PyroCatcher(func, default=None): - """ - A function that catch a Pyro exceptions, write error to logger - and return default value when it happen - """ - def catcher_func(*args, **kwargs): - try: - return func(*args, **kwargs) - except Pyro.errors.ConnectionClosedError, e: - confnodesroot.logger.write_error(_("Connection lost!\n")) - confnodesroot._SetConnector(None) - except Pyro.errors.ProtocolError, e: - confnodesroot.logger.write_error(_("Pyro exception: %s\n") % e) - except Exception, e: - # confnodesroot.logger.write_error(traceback.format_exc()) - errmess = ''.join(Pyro.util.getPyroTraceback(e)) - confnodesroot.logger.write_error(errmess + "\n") - print errmess - confnodesroot._SetConnector(None) - return default - return catcher_func - - # Check connection is effective. - # lambda is for getattr of GetPLCstatus to happen inside catcher - if PyroCatcher(lambda: RemotePLCObjectProxy.GetPLCstatus())() is None: - confnodesroot.logger.write_error(_("Cannot get PLC status - connection failed.\n")) - return None - - class PyroProxyProxy(object): - """ - A proxy proxy class to handle Beremiz Pyro interface specific behavior. - And to put Pyro exception catcher in between caller and Pyro proxy - """ - def __init__(self): - # for safe use in from debug thread, must create a copy - self.RemotePLCObjectProxyCopy = None - - def GetPyroProxy(self): - """ - This func returns the real Pyro Proxy. - Use this if you musn't keep reference to it. - """ - return RemotePLCObjectProxy - - def _PyroStartPLC(self, *args, **kwargs): - """ - confnodesroot._connector.GetPyroProxy() is used - rather than RemotePLCObjectProxy because - object is recreated meanwhile, - so we must not keep ref to it here - """ - current_status, log_count = confnodesroot._connector.GetPyroProxy().GetPLCstatus() - if current_status == "Dirty": - """ - Some bad libs with static symbols may polute PLC - ask runtime to suicide and come back again - """ - confnodesroot.logger.write(_("Force runtime reload\n")) - confnodesroot._connector.GetPyroProxy().ForceReload() - confnodesroot._Disconnect() - # let remote PLC time to resurect.(freeze app) - sleep(0.5) - confnodesroot._Connect() - self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy()) - return confnodesroot._connector.GetPyroProxy().StartPLC(*args, **kwargs) - StartPLC = PyroCatcher(_PyroStartPLC, False) - - def _PyroGetTraceVariables(self): - """ - for safe use in from debug thread, must use the copy - """ - if self.RemotePLCObjectProxyCopy is None: - self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy()) - return self.RemotePLCObjectProxyCopy.GetTraceVariables() - GetTraceVariables = PyroCatcher(_PyroGetTraceVariables, ("Broken", None)) - - def _PyroGetPLCstatus(self): - return RemotePLCObjectProxy.GetPLCstatus() - GetPLCstatus = PyroCatcher(_PyroGetPLCstatus, ("Broken", None)) - - def _PyroRemoteExec(self, script, **kwargs): - return RemotePLCObjectProxy.RemoteExec(script, **kwargs) - RemoteExec = PyroCatcher(_PyroRemoteExec, (-1, "RemoteExec script failed!")) - - def __getattr__(self, attrName): - member = self.__dict__.get(attrName, None) - if member is None: - def my_local_func(*args, **kwargs): - return RemotePLCObjectProxy.__getattr__(attrName)(*args, **kwargs) - member = PyroCatcher(my_local_func, None) - self.__dict__[attrName] = member - return member - - return PyroProxyProxy() +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# +# See COPYING file for copyrights details. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import Pyro +import Pyro.core +import Pyro.util +from Pyro.errors import PyroError +import traceback +from time import sleep +import copy +import socket +service_type = '_PYRO._tcp.local.' +import os.path +# this module attribute contains a list of DNS-SD (Zeroconf) service types +# supported by this connector confnode. +# +# for connectors that do not support DNS-SD, this attribute can be omitted +# or set to an empty list. + +def PYRO_connector_factory(uri, confnodesroot): + """ + This returns the connector to Pyro style PLCobject + """ + confnodesroot.logger.write(_("PYRO connecting to URI : %s\n") % uri) + + servicetype, location = uri.split("://") + if servicetype == "PYROS": + schemename = "PYROLOCSSL" + # Protect against name->IP substitution in Pyro3 + Pyro.config.PYRO_DNS_URI = True + # Beware Pyro lib need str path, not unicode + # don't rely on PYRO_STORAGE ! see documentation + Pyro.config.PYROSSL_CERTDIR = os.path.abspath(str(confnodesroot.ProjectPath) + '/certs') + if not os.path.exists(Pyro.config.PYROSSL_CERTDIR): + confnodesroot.logger.write_error( + 'Error : the directory %s is missing for SSL certificates (certs_dir).' + 'Please fix it in your project.\n' % Pyro.config.PYROSSL_CERTDIR) + return None + else: + confnodesroot.logger.write(_("PYRO using certificates in '%s' \n") + % (Pyro.config.PYROSSL_CERTDIR)) + Pyro.config.PYROSSL_CERT = "client.crt" + Pyro.config.PYROSSL_KEY = "client.key" + # Ugly Monkey Patching + def _gettimeout(self): + return self.timeout + + def _settimeout(self, timeout): + self.timeout = timeout + from M2Crypto.SSL import Connection + Connection.timeout = None + Connection.gettimeout = _gettimeout + Connection.settimeout = _settimeout + # M2Crypto.SSL.Checker.WrongHost: Peer certificate commonName does not + # match host, expected 127.0.0.1, got server + Connection.clientPostConnectionCheck = None + else: + schemename = "PYROLOC" + if location.find(service_type) != -1: + try: + from util.Zeroconf import Zeroconf + r = Zeroconf() + i = r.getServiceInfo(service_type, location) + if i is None: + raise Exception("'%s' not found" % location) + ip = str(socket.inet_ntoa(i.getAddress())) + port = str(i.getPort()) + newlocation = ip + ':' + port + confnodesroot.logger.write(_("'{a1}' is located at {a2}\n").format(a1 = location, a2 = newlocation)) + location = newlocation + r.close() + except Exception, msg: + confnodesroot.logger.write_error(_("MDNS resolution failure for '%s'\n") % location) + confnodesroot.logger.write_error(traceback.format_exc()) + return None + + # Try to get the proxy object + try: + RemotePLCObjectProxy = Pyro.core.getAttrProxyForURI(schemename + "://" + location + "/PLCObject") + except Exception, msg: + confnodesroot.logger.write_error(_("Connection to '%s' failed.\n") % location) + confnodesroot.logger.write_error(traceback.format_exc()) + return None + + def PyroCatcher(func, default=None): + """ + A function that catch a Pyro exceptions, write error to logger + and return default value when it happen + """ + def catcher_func(*args, **kwargs): + try: + return func(*args, **kwargs) + except Pyro.errors.ConnectionClosedError, e: + confnodesroot.logger.write_error(_("Connection lost!\n")) + confnodesroot._SetConnector(None) + except Pyro.errors.ProtocolError, e: + confnodesroot.logger.write_error(_("Pyro exception: %s\n") % e) + except Exception, e: + # confnodesroot.logger.write_error(traceback.format_exc()) + errmess = ''.join(Pyro.util.getPyroTraceback(e)) + confnodesroot.logger.write_error(errmess + "\n") + print errmess + confnodesroot._SetConnector(None) + return default + return catcher_func + + # Check connection is effective. + # lambda is for getattr of GetPLCstatus to happen inside catcher + if PyroCatcher(lambda: RemotePLCObjectProxy.GetPLCstatus())() is None: + confnodesroot.logger.write_error(_("Cannot get PLC status - connection failed.\n")) + return None + + class PyroProxyProxy(object): + """ + A proxy proxy class to handle Beremiz Pyro interface specific behavior. + And to put Pyro exception catcher in between caller and Pyro proxy + """ + def __init__(self): + # for safe use in from debug thread, must create a copy + self.RemotePLCObjectProxyCopy = None + + def GetPyroProxy(self): + """ + This func returns the real Pyro Proxy. + Use this if you musn't keep reference to it. + """ + return RemotePLCObjectProxy + + def _PyroStartPLC(self, *args, **kwargs): + """ + confnodesroot._connector.GetPyroProxy() is used + rather than RemotePLCObjectProxy because + object is recreated meanwhile, + so we must not keep ref to it here + """ + current_status, log_count = confnodesroot._connector.GetPyroProxy().GetPLCstatus() + if current_status == "Dirty": + """ + Some bad libs with static symbols may polute PLC + ask runtime to suicide and come back again + """ + confnodesroot.logger.write(_("Force runtime reload\n")) + confnodesroot._connector.GetPyroProxy().ForceReload() + confnodesroot._Disconnect() + # let remote PLC time to resurect.(freeze app) + sleep(0.5) + confnodesroot._Connect() + self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy()) + return confnodesroot._connector.GetPyroProxy().StartPLC(*args, **kwargs) + StartPLC = PyroCatcher(_PyroStartPLC, False) + + def _PyroGetTraceVariables(self): + """ + for safe use in from debug thread, must use the copy + """ + if self.RemotePLCObjectProxyCopy is None: + self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy()) + return self.RemotePLCObjectProxyCopy.GetTraceVariables() + GetTraceVariables = PyroCatcher(_PyroGetTraceVariables, ("Broken", None)) + + def _PyroGetPLCstatus(self): + return RemotePLCObjectProxy.GetPLCstatus() + GetPLCstatus = PyroCatcher(_PyroGetPLCstatus, ("Broken", None)) + + def _PyroRemoteExec(self, script, **kwargs): + return RemotePLCObjectProxy.RemoteExec(script, **kwargs) + RemoteExec = PyroCatcher(_PyroRemoteExec, (-1, "RemoteExec script failed!")) + + def __getattr__(self, attrName): + member = self.__dict__.get(attrName, None) + if member is None: + def my_local_func(*args, **kwargs): + return RemotePLCObjectProxy.__getattr__(attrName)(*args, **kwargs) + member = PyroCatcher(my_local_func, None) + self.__dict__[attrName] = member + return member + + return PyroProxyProxy()
--- a/connectors/__init__.py Mon Aug 14 19:13:01 2017 +0300 +++ b/connectors/__init__.py Mon Aug 14 21:09:48 2017 +0300 @@ -1,66 +1,66 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# This file is part of Beremiz, a Integrated Development Environment for -# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. -# -# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD -# Copyright (C) 2017: Andrey Skvortsov -# -# See COPYING file for copyrights details. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -# Package initialisation - -from os import listdir, path -import util.paths as paths - -_base_path = paths.AbsDir(__file__) - - -def _GetLocalConnectorClassFactory(name): - return lambda: getattr(__import__(name, globals(), locals()), name + "_connector_factory") - -connectors = {name:_GetLocalConnectorClassFactory(name) - for name in listdir(_base_path) - if path.isdir(path.join(_base_path, name)) - and not name.startswith("__")} - - -def ConnectorFactory(uri, confnodesroot): - """ - Return a connector corresponding to the URI - or None if cannot connect to URI - """ - servicetype = uri.split("://")[0].upper() - if servicetype == "LOCAL": - # Local is special case - # pyro connection to local runtime - # started on demand, listening on random port - servicetype = "PYRO" - runtime_port = confnodesroot.AppFrame.StartLocalRuntime( - taskbaricon=True) - uri = "PYROLOC://127.0.0.1:" + str(runtime_port) - elif servicetype in connectors: - pass - elif servicetype[-1] == 'S' and servicetype[:-1] in connectors: - servicetype = servicetype[:-1] - else: - return None - - # import module according to uri type - connectorclass = connectors[servicetype]() - return connectorclass(uri, confnodesroot) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# Copyright (C) 2017: Andrey Skvortsov +# +# See COPYING file for copyrights details. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# Package initialisation + +from os import listdir, path +import util.paths as paths + +_base_path = paths.AbsDir(__file__) + + +def _GetLocalConnectorClassFactory(name): + return lambda: getattr(__import__(name, globals(), locals()), name + "_connector_factory") + +connectors = {name:_GetLocalConnectorClassFactory(name) + for name in listdir(_base_path) + if path.isdir(path.join(_base_path, name)) + and not name.startswith("__")} + + +def ConnectorFactory(uri, confnodesroot): + """ + Return a connector corresponding to the URI + or None if cannot connect to URI + """ + servicetype = uri.split("://")[0].upper() + if servicetype == "LOCAL": + # Local is special case + # pyro connection to local runtime + # started on demand, listening on random port + servicetype = "PYRO" + runtime_port = confnodesroot.AppFrame.StartLocalRuntime( + taskbaricon=True) + uri = "PYROLOC://127.0.0.1:" + str(runtime_port) + elif servicetype in connectors: + pass + elif servicetype[-1] == 'S' and servicetype[:-1] in connectors: + servicetype = servicetype[:-1] + else: + return None + + # import module according to uri type + connectorclass = connectors[servicetype]() + return connectorclass(uri, confnodesroot)
--- a/controls/EnhancedStatusBar.py Mon Aug 14 19:13:01 2017 +0300 +++ b/controls/EnhancedStatusBar.py Mon Aug 14 21:09:48 2017 +0300 @@ -1,248 +1,248 @@ -# --------------------------------------------------------------------------- # -# ENHANCEDSTATUSBAR wxPython IMPLEMENTATION -# Python Code By: -# -# Andrea Gavana, @ 31 May 2005 -# Nitro, @ 21 September 2005 -# Latest Revision: 21 September 2005, 19.57.20 GMT+2 -# Latest Revision before Latest Revision: 21 September 2005, 18.29.35 GMT+2 -# Latest Revision before Latest Revision before Latest Revision: 31 May 2005, 23.17 CET -# -# -# TODO List/Caveats -# -# 1. Some Requests/Features To Add? -# -# -# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please -# Write To Me At: -# -# -# andrea.gavana@agip.it -# andrea_gavan@tin.it -# -# Or, Obviously, To The wxPython Mailing List!!! -# -# -# licensed under wxWidgets License (GPL compatible) -# End Of Comments -# --------------------------------------------------------------------------- # - -""" Description: - -EnhancedStatusBar Is A Slight Modification (Actually A Subclassing) Of wx.StatusBar. -It Allows You To Add Almost Any Widget You Like To The wx.StatusBar Of Your Main -Frame Application And Also To Layout Them Properly. - - -What It Can Do: - -1) Almost All The Functionalities Of wx.StatusBar Are Still Present; -2) You Can Add Different Kind Of Widgets Into Every Field Of The EnhancedStatusBar; -3) The AddWidget() Methods Accepts 2 Layout Inputs: - - horizontalalignment: This Specify The Horizontal Alignment For Your Widget, - And Can Be ESB_EXACT_FIT, ESB_ALIGN_CENTER_HORIZONTAL, ESB_ALIGN_LEFT And - ESB_ALIGN_RIGHT; - - varticalalignment: This Specify The Vertical Alignment For Your Widget, - And Can Be ESB_EXACT_FIT, ESB_ALIGN_CENTER_VERTICAL, ESB_ALIGN_BOTTOM And - ESB_ALIGN_LEFT; - -EnhancedStatusBar Is Freeware And Distributed Under The wxPython License. - -Latest Revision: 21 September 2005, 19.57.20 GMT+2 -Latest Revision before Latest Revision: 21 September 2005, 18.29.35 GMT+2 -Latest Revision before Latest Revision before Latest Revision: 31 May 2005, 23.17 CET - -""" - -import wx - -# Horizontal Alignment Constants -ESB_ALIGN_CENTER_VERTICAL = 1 -ESB_ALIGN_TOP = 2 -ESB_ALIGN_BOTTOM = 3 - -# Vertical Alignment Constants -ESB_ALIGN_CENTER_HORIZONTAL = 11 -ESB_ALIGN_LEFT = 12 -ESB_ALIGN_RIGHT = 13 - -# Exact Fit (Either Horizontal Or Vertical Or Both) Constant -ESB_EXACT_FIT = 20 - - -# --------------------------------------------------------------- -# Class EnhancedStatusBar -# --------------------------------------------------------------- -# This Is The Main Class Implementation. See The Demo For Details -# --------------------------------------------------------------- -class EnhancedStatusBarItem(object): - def __init__(self, widget, pos, horizontalalignment=ESB_ALIGN_CENTER_HORIZONTAL, verticalalignment=ESB_ALIGN_CENTER_VERTICAL): - self.__dict__.update( locals() ) - -class EnhancedStatusBar(wx.StatusBar): - - def __init__(self, parent, id=wx.ID_ANY, style=wx.ST_SIZEGRIP, - name="EnhancedStatusBar"): - """Default Class Constructor. - - EnhancedStatusBar.__init__(self, parent, id=wx.ID_ANY, - style=wx.ST_SIZEGRIP, - name="EnhancedStatusBar") - """ - - wx.StatusBar.__init__(self, parent, id, style, name) - - self._items = {} - self._curPos = 0 - self._parent = parent - - wx.EVT_SIZE(self, self.OnSize) - wx.CallAfter(self.OnSize, None) - - - def OnSize(self, event): - """Handles The wx.EVT_SIZE Events For The StatusBar. - - Actually, All The Calculations Linked To HorizontalAlignment And - VerticalAlignment Are Done In This Function.""" - - for pos, item in self._items.items(): - widget, horizontalalignment, verticalalignment = item.widget, item.horizontalalignment, item.verticalalignment - - rect = self.GetFieldRect(pos) - widgetpos = widget.GetPosition() - widgetsize = widget.GetSize() - - rect = self.GetFieldRect(pos) - - if horizontalalignment == ESB_EXACT_FIT: - - if verticalalignment == ESB_EXACT_FIT: - """ 1 September 2015 Fix fit align """ - widget.SetSize((rect.width-4, rect.height-4)) - widget.SetPosition((rect.x+2, rect.y+2)) - elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: - if widgetsize[1] < rect.width - 1: - diffs = (rect.height - widgetsize[1])/2 - widget.SetSize((rect.width-2, widgetsize[1])) - widget.SetPosition((rect.x-1, rect.y+diffs)) - else: - widget.SetSize((rect.width-2, widgetsize[1])) - widget.SetPosition((rect.x-1, rect.y-1)) - elif verticalalignment == ESB_ALIGN_TOP: - widget.SetSize((rect.width-2, widgetsize[1])) - widget.SetPosition((rect.x-1, rect.y)) - elif verticalalignment == ESB_ALIGN_BOTTOM: - widget.SetSize((rect.width-2, widgetsize[1])) - widget.SetPosition((rect.x-1, rect.height-widgetsize[1])) - - elif horizontalalignment == ESB_ALIGN_LEFT: - - xpos = rect.x - 1 - if verticalalignment == ESB_EXACT_FIT: - widget.SetSize((widgetsize[0], rect.height-2)) - widget.SetPosition((xpos, rect.y-1)) - elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: - if widgetsize[1] < rect.height - 1: - diffs = (rect.height - widgetsize[1])/2 - widget.SetPosition((xpos, rect.y+diffs)) - else: - widget.SetSize((widgetsize[0], rect.height-2)) - widget.SetPosition((xpos, rect.y-1)) - elif verticalalignment == ESB_ALIGN_TOP: - widget.SetPosition((xpos, rect.y)) - elif verticalalignment == ESB_ALIGN_BOTTOM: - widget.SetPosition((xpos, rect.height-widgetsize[1])) - - elif horizontalalignment == ESB_ALIGN_RIGHT: - - xpos = rect.x + rect.width - widgetsize[0] - 1 - if verticalalignment == ESB_EXACT_FIT: - widget.SetSize((widgetsize[0], rect.height-2)) - widget.SetPosition((xpos, rect.y-1)) - elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: - if widgetsize[1] < rect.height - 1: - diffs = (rect.height - widgetsize[1])/2 - widget.SetPosition((xpos, rect.y+diffs)) - else: - widget.SetSize((widgetsize[0], rect.height-2)) - widget.SetPosition((xpos, rect.y-1)) - elif verticalalignment == ESB_ALIGN_TOP: - widget.SetPosition((xpos, rect.y)) - elif verticalalignment == ESB_ALIGN_BOTTOM: - widget.SetPosition((xpos, rect.height-widgetsize[1])) - - elif horizontalalignment == ESB_ALIGN_CENTER_HORIZONTAL: - - xpos = rect.x + (rect.width - widgetsize[0])/2 - 1 - if verticalalignment == ESB_EXACT_FIT: - widget.SetSize((widgetsize[0], rect.height)) - widget.SetPosition((xpos, rect.y)) - elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: - if widgetsize[1] < rect.height - 1: - diffs = (rect.height - widgetsize[1])/2 - widget.SetPosition((xpos, rect.y+diffs)) - else: - widget.SetSize((widgetsize[0], rect.height-1)) - widget.SetPosition((xpos, rect.y+1)) - elif verticalalignment == ESB_ALIGN_TOP: - widget.SetPosition((xpos, rect.y)) - elif verticalalignment == ESB_ALIGN_BOTTOM: - widget.SetPosition((xpos, rect.height-widgetsize[1])) - - - if event is not None: - event.Skip() - - - def AddWidget(self, widget, horizontalalignment=ESB_ALIGN_CENTER_HORIZONTAL, - verticalalignment=ESB_ALIGN_CENTER_VERTICAL, pos = -1): - """Add A Widget To The EnhancedStatusBar. - - Parameters: - - - horizontalalignment: This Can Be One Of: - a) ESB_EXACT_FIT: The Widget Will Fit Horizontally The StatusBar Field Width; - b) ESB_ALIGN_CENTER_HORIZONTAL: The Widget Will Be Centered Horizontally In - The StatusBar Field; - c) ESB_ALIGN_LEFT: The Widget Will Be Left Aligned In The StatusBar Field; - d) ESB_ALIGN_RIGHT: The Widget Will Be Right Aligned In The StatusBar Field; - - - verticalalignment: - a) ESB_EXACT_FIT: The Widget Will Fit Vertically The StatusBar Field Height; - b) ESB_ALIGN_CENTER_VERTICAL: The Widget Will Be Centered Vertically In - The StatusBar Field; - c) ESB_ALIGN_BOTTOM: The Widget Will Be Bottom Aligned In The StatusBar Field; - d) ESB_ALIGN_TOP: The Widget Will Be TOP Aligned In The StatusBar Field; - - """ - - if pos == -1: - pos = self._curPos - self._curPos += 1 - - if self.GetFieldsCount() <= pos: - raise "\nERROR: EnhancedStatusBar has a max of %d items, you tried to set item #%d" % (self.GetFieldsCount(), pos) - - if horizontalalignment not in [ESB_ALIGN_CENTER_HORIZONTAL, ESB_EXACT_FIT, - ESB_ALIGN_LEFT, ESB_ALIGN_RIGHT]: - raise '\nERROR: Parameter "horizontalalignment" Should Be One Of '\ - '"ESB_ALIGN_CENTER_HORIZONTAL", "ESB_ALIGN_LEFT", "ESB_ALIGN_RIGHT"' \ - '"ESB_EXACT_FIT"' - - if verticalalignment not in [ESB_ALIGN_CENTER_VERTICAL, ESB_EXACT_FIT, - ESB_ALIGN_TOP, ESB_ALIGN_BOTTOM]: - raise '\nERROR: Parameter "verticalalignment" Should Be One Of '\ - '"ESB_ALIGN_CENTER_VERTICAL", "ESB_ALIGN_TOP", "ESB_ALIGN_BOTTOM"' \ - '"ESB_EXACT_FIT"' - - - try: - self.RemoveChild(self._items[pos].widget) - self._items[pos].widget.Destroy() - except KeyError: pass - - self._items[pos] = EnhancedStatusBarItem(widget, pos, horizontalalignment, verticalalignment) - - wx.CallAfter(self.OnSize, None) +# --------------------------------------------------------------------------- # +# ENHANCEDSTATUSBAR wxPython IMPLEMENTATION +# Python Code By: +# +# Andrea Gavana, @ 31 May 2005 +# Nitro, @ 21 September 2005 +# Latest Revision: 21 September 2005, 19.57.20 GMT+2 +# Latest Revision before Latest Revision: 21 September 2005, 18.29.35 GMT+2 +# Latest Revision before Latest Revision before Latest Revision: 31 May 2005, 23.17 CET +# +# +# TODO List/Caveats +# +# 1. Some Requests/Features To Add? +# +# +# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please +# Write To Me At: +# +# +# andrea.gavana@agip.it +# andrea_gavan@tin.it +# +# Or, Obviously, To The wxPython Mailing List!!! +# +# +# licensed under wxWidgets License (GPL compatible) +# End Of Comments +# --------------------------------------------------------------------------- # + +""" Description: + +EnhancedStatusBar Is A Slight Modification (Actually A Subclassing) Of wx.StatusBar. +It Allows You To Add Almost Any Widget You Like To The wx.StatusBar Of Your Main +Frame Application And Also To Layout Them Properly. + + +What It Can Do: + +1) Almost All The Functionalities Of wx.StatusBar Are Still Present; +2) You Can Add Different Kind Of Widgets Into Every Field Of The EnhancedStatusBar; +3) The AddWidget() Methods Accepts 2 Layout Inputs: + - horizontalalignment: This Specify The Horizontal Alignment For Your Widget, + And Can Be ESB_EXACT_FIT, ESB_ALIGN_CENTER_HORIZONTAL, ESB_ALIGN_LEFT And + ESB_ALIGN_RIGHT; + - varticalalignment: This Specify The Vertical Alignment For Your Widget, + And Can Be ESB_EXACT_FIT, ESB_ALIGN_CENTER_VERTICAL, ESB_ALIGN_BOTTOM And + ESB_ALIGN_LEFT; + +EnhancedStatusBar Is Freeware And Distributed Under The wxPython License. + +Latest Revision: 21 September 2005, 19.57.20 GMT+2 +Latest Revision before Latest Revision: 21 September 2005, 18.29.35 GMT+2 +Latest Revision before Latest Revision before Latest Revision: 31 May 2005, 23.17 CET + +""" + +import wx + +# Horizontal Alignment Constants +ESB_ALIGN_CENTER_VERTICAL = 1 +ESB_ALIGN_TOP = 2 +ESB_ALIGN_BOTTOM = 3 + +# Vertical Alignment Constants +ESB_ALIGN_CENTER_HORIZONTAL = 11 +ESB_ALIGN_LEFT = 12 +ESB_ALIGN_RIGHT = 13 + +# Exact Fit (Either Horizontal Or Vertical Or Both) Constant +ESB_EXACT_FIT = 20 + + +# --------------------------------------------------------------- +# Class EnhancedStatusBar +# --------------------------------------------------------------- +# This Is The Main Class Implementation. See The Demo For Details +# --------------------------------------------------------------- +class EnhancedStatusBarItem(object): + def __init__(self, widget, pos, horizontalalignment=ESB_ALIGN_CENTER_HORIZONTAL, verticalalignment=ESB_ALIGN_CENTER_VERTICAL): + self.__dict__.update( locals() ) + +class EnhancedStatusBar(wx.StatusBar): + + def __init__(self, parent, id=wx.ID_ANY, style=wx.ST_SIZEGRIP, + name="EnhancedStatusBar"): + """Default Class Constructor. + + EnhancedStatusBar.__init__(self, parent, id=wx.ID_ANY, + style=wx.ST_SIZEGRIP, + name="EnhancedStatusBar") + """ + + wx.StatusBar.__init__(self, parent, id, style, name) + + self._items = {} + self._curPos = 0 + self._parent = parent + + wx.EVT_SIZE(self, self.OnSize) + wx.CallAfter(self.OnSize, None) + + + def OnSize(self, event): + """Handles The wx.EVT_SIZE Events For The StatusBar. + + Actually, All The Calculations Linked To HorizontalAlignment And + VerticalAlignment Are Done In This Function.""" + + for pos, item in self._items.items(): + widget, horizontalalignment, verticalalignment = item.widget, item.horizontalalignment, item.verticalalignment + + rect = self.GetFieldRect(pos) + widgetpos = widget.GetPosition() + widgetsize = widget.GetSize() + + rect = self.GetFieldRect(pos) + + if horizontalalignment == ESB_EXACT_FIT: + + if verticalalignment == ESB_EXACT_FIT: + """ 1 September 2015 Fix fit align """ + widget.SetSize((rect.width-4, rect.height-4)) + widget.SetPosition((rect.x+2, rect.y+2)) + elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: + if widgetsize[1] < rect.width - 1: + diffs = (rect.height - widgetsize[1])/2 + widget.SetSize((rect.width-2, widgetsize[1])) + widget.SetPosition((rect.x-1, rect.y+diffs)) + else: + widget.SetSize((rect.width-2, widgetsize[1])) + widget.SetPosition((rect.x-1, rect.y-1)) + elif verticalalignment == ESB_ALIGN_TOP: + widget.SetSize((rect.width-2, widgetsize[1])) + widget.SetPosition((rect.x-1, rect.y)) + elif verticalalignment == ESB_ALIGN_BOTTOM: + widget.SetSize((rect.width-2, widgetsize[1])) + widget.SetPosition((rect.x-1, rect.height-widgetsize[1])) + + elif horizontalalignment == ESB_ALIGN_LEFT: + + xpos = rect.x - 1 + if verticalalignment == ESB_EXACT_FIT: + widget.SetSize((widgetsize[0], rect.height-2)) + widget.SetPosition((xpos, rect.y-1)) + elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: + if widgetsize[1] < rect.height - 1: + diffs = (rect.height - widgetsize[1])/2 + widget.SetPosition((xpos, rect.y+diffs)) + else: + widget.SetSize((widgetsize[0], rect.height-2)) + widget.SetPosition((xpos, rect.y-1)) + elif verticalalignment == ESB_ALIGN_TOP: + widget.SetPosition((xpos, rect.y)) + elif verticalalignment == ESB_ALIGN_BOTTOM: + widget.SetPosition((xpos, rect.height-widgetsize[1])) + + elif horizontalalignment == ESB_ALIGN_RIGHT: + + xpos = rect.x + rect.width - widgetsize[0] - 1 + if verticalalignment == ESB_EXACT_FIT: + widget.SetSize((widgetsize[0], rect.height-2)) + widget.SetPosition((xpos, rect.y-1)) + elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: + if widgetsize[1] < rect.height - 1: + diffs = (rect.height - widgetsize[1])/2 + widget.SetPosition((xpos, rect.y+diffs)) + else: + widget.SetSize((widgetsize[0], rect.height-2)) + widget.SetPosition((xpos, rect.y-1)) + elif verticalalignment == ESB_ALIGN_TOP: + widget.SetPosition((xpos, rect.y)) + elif verticalalignment == ESB_ALIGN_BOTTOM: + widget.SetPosition((xpos, rect.height-widgetsize[1])) + + elif horizontalalignment == ESB_ALIGN_CENTER_HORIZONTAL: + + xpos = rect.x + (rect.width - widgetsize[0])/2 - 1 + if verticalalignment == ESB_EXACT_FIT: + widget.SetSize((widgetsize[0], rect.height)) + widget.SetPosition((xpos, rect.y)) + elif verticalalignment == ESB_ALIGN_CENTER_VERTICAL: + if widgetsize[1] < rect.height - 1: + diffs = (rect.height - widgetsize[1])/2 + widget.SetPosition((xpos, rect.y+diffs)) + else: + widget.SetSize((widgetsize[0], rect.height-1)) + widget.SetPosition((xpos, rect.y+1)) + elif verticalalignment == ESB_ALIGN_TOP: + widget.SetPosition((xpos, rect.y)) + elif verticalalignment == ESB_ALIGN_BOTTOM: + widget.SetPosition((xpos, rect.height-widgetsize[1])) + + + if event is not None: + event.Skip() + + + def AddWidget(self, widget, horizontalalignment=ESB_ALIGN_CENTER_HORIZONTAL, + verticalalignment=ESB_ALIGN_CENTER_VERTICAL, pos = -1): + """Add A Widget To The EnhancedStatusBar. + + Parameters: + + - horizontalalignment: This Can Be One Of: + a) ESB_EXACT_FIT: The Widget Will Fit Horizontally The StatusBar Field Width; + b) ESB_ALIGN_CENTER_HORIZONTAL: The Widget Will Be Centered Horizontally In + The StatusBar Field; + c) ESB_ALIGN_LEFT: The Widget Will Be Left Aligned In The StatusBar Field; + d) ESB_ALIGN_RIGHT: The Widget Will Be Right Aligned In The StatusBar Field; + + - verticalalignment: + a) ESB_EXACT_FIT: The Widget Will Fit Vertically The StatusBar Field Height; + b) ESB_ALIGN_CENTER_VERTICAL: The Widget Will Be Centered Vertically In + The StatusBar Field; + c) ESB_ALIGN_BOTTOM: The Widget Will Be Bottom Aligned In The StatusBar Field; + d) ESB_ALIGN_TOP: The Widget Will Be TOP Aligned In The StatusBar Field; + + """ + + if pos == -1: + pos = self._curPos + self._curPos += 1 + + if self.GetFieldsCount() <= pos: + raise "\nERROR: EnhancedStatusBar has a max of %d items, you tried to set item #%d" % (self.GetFieldsCount(), pos) + + if horizontalalignment not in [ESB_ALIGN_CENTER_HORIZONTAL, ESB_EXACT_FIT, + ESB_ALIGN_LEFT, ESB_ALIGN_RIGHT]: + raise '\nERROR: Parameter "horizontalalignment" Should Be One Of '\ + '"ESB_ALIGN_CENTER_HORIZONTAL", "ESB_ALIGN_LEFT", "ESB_ALIGN_RIGHT"' \ + '"ESB_EXACT_FIT"' + + if verticalalignment not in [ESB_ALIGN_CENTER_VERTICAL, ESB_EXACT_FIT, + ESB_ALIGN_TOP, ESB_ALIGN_BOTTOM]: + raise '\nERROR: Parameter "verticalalignment" Should Be One Of '\ + '"ESB_ALIGN_CENTER_VERTICAL", "ESB_ALIGN_TOP", "ESB_ALIGN_BOTTOM"' \ + '"ESB_EXACT_FIT"' + + + try: + self.RemoveChild(self._items[pos].widget) + self._items[pos].widget.Destroy() + except KeyError: pass + + self._items[pos] = EnhancedStatusBarItem(widget, pos, horizontalalignment, verticalalignment) + + wx.CallAfter(self.OnSize, None)
--- a/controls/VariablePanel.py Mon Aug 14 19:13:01 2017 +0300 +++ b/controls/VariablePanel.py Mon Aug 14 21:09:48 2017 +0300 @@ -1,984 +1,984 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# This file is part of Beremiz, a Integrated Development Environment for -# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. -# -# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD -# -# See COPYING file for copyrights details. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import os -import re -from types import TupleType, StringType, UnicodeType - -import wx -import wx.grid -import wx.lib.buttons - -from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS, DefaultType -from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT -from dialogs.ArrayTypeDialog import ArrayTypeDialog -from CustomGrid import CustomGrid -from CustomTable import CustomTable -from LocationCellEditor import LocationCellEditor -from util.BitmapLibrary import GetBitmap -from PLCControler import _VariableInfos - -#------------------------------------------------------------------------------- -# Helpers -#------------------------------------------------------------------------------- - -[TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, - POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES -] = range(10) - -def GetVariableTableColnames(location): - _ = lambda x : x - if location: - return ["#", _("Name"), _("Class"), _("Type"), _("Location"), _("Initial Value"), _("Option"), _("Documentation")] - return ["#", _("Name"), _("Class"), _("Type"), _("Initial Value"), _("Option"), _("Documentation")] - -def GetOptions(constant=True, retain=True, non_retain=True): - _ = lambda x : x - options = [""] - if constant: - options.append(_("Constant")) - if retain: - options.append(_("Retain")) - if non_retain: - options.append(_("Non-Retain")) - return options -OPTIONS_DICT = dict([(_(option), option) for option in GetOptions()]) - -def GetFilterChoiceTransfer(): - _ = lambda x : x - return {_("All"): _("All"), _("Interface"): _("Interface"), - _(" Input"): _("Input"), _(" Output"): _("Output"), _(" InOut"): _("InOut"), - _(" External"): _("External"), _("Variables"): _("Variables"), _(" Local"): _("Local"), - _(" Temp"): _("Temp"), _("Global"): _("Global")}#, _("Access") : _("Access")} -VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().iterkeys()]) -VARIABLE_CLASSES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().itervalues()]) - -CheckOptionForClass = {"Local": lambda x: x, - "Temp": lambda x: "", - "Input": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""), - "InOut": lambda x: "", - "Output": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""), - "Global": lambda x: {"Constant": "Constant", "Retain": "Retain"}.get(x, ""), - "External": lambda x: {"Constant": "Constant"}.get(x, "") - } - -LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$") -VARIABLE_NAME_SUFFIX_MODEL = re.compile("([0-9]*)$") - -#------------------------------------------------------------------------------- -# Variables Panel Table -#------------------------------------------------------------------------------- - -class VariableTable(CustomTable): - - """ - A custom wx.grid.Grid Table using user supplied data - """ - def __init__(self, parent, data, colnames): - # The base class must be initialized *first* - CustomTable.__init__(self, parent, data, colnames) - self.old_value = None - - def GetValueByName(self, row, colname): - if row < self.GetNumberRows(): - return getattr(self.data[row], colname) - - def SetValueByName(self, row, colname, value): - if row < self.GetNumberRows(): - setattr(self.data[row], colname, value) - - def GetValue(self, row, col): - if row < self.GetNumberRows(): - if col == 0: - return self.data[row].Number - colname = self.GetColLabelValue(col, False) - if colname == "Initial Value": - colname = "InitialValue" - value = getattr(self.data[row], colname, "") - if colname == "Type" and isinstance(value, TupleType): - if value[0] == "array": - return "ARRAY [%s] OF %s" % (",".join(map(lambda x : "..".join(x), value[2])), value[1]) - if not isinstance(value, (StringType, UnicodeType)): - value = str(value) - if colname in ["Class", "Option"]: - return _(value) - return value - - def SetValue(self, row, col, value): - if col < len(self.colnames): - colname = self.GetColLabelValue(col, False) - if colname == "Name": - self.old_value = getattr(self.data[row], colname) - elif colname == "Class": - value = VARIABLE_CLASSES_DICT[value] - self.SetValueByName(row, "Option", CheckOptionForClass[value](self.GetValueByName(row, "Option"))) - if value == "External": - self.SetValueByName(row, "InitialValue", "") - elif colname == "Option": - value = OPTIONS_DICT[value] - elif colname == "Initial Value": - colname = "InitialValue" - setattr(self.data[row], colname, value) - - def GetOldValue(self): - return self.old_value - - def _GetRowEdit(self, row): - row_edit = self.GetValueByName(row, "Edit") - var_type = self.Parent.GetTagName() - bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type) - if bodytype in ["ST", "IL"]: - row_edit = True; - return row_edit - - def _updateColAttrs(self, grid): - """ - wx.grid.Grid -> update the column attributes to add the - appropriate renderer given the column name. - - Otherwise default to the default renderer. - """ - for row in range(self.GetNumberRows()): - var_class = self.GetValueByName(row, "Class") - var_type = self.GetValueByName(row, "Type") - row_highlights = self.Highlights.get(row, {}) - for col in range(self.GetNumberCols()): - editor = None - renderer = None - colname = self.GetColLabelValue(col, False) - if self.Parent.Debug: - grid.SetReadOnly(row, col, True) - else: - if colname == "Option": - options = GetOptions(constant = var_class in ["Local", "External", "Global"], - retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"], - non_retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"]) - if len(options) > 1: - editor = wx.grid.GridCellChoiceEditor() - editor.SetParameters(",".join(map(_, options))) - else: - grid.SetReadOnly(row, col, True) - elif col != 0 and self._GetRowEdit(row): - grid.SetReadOnly(row, col, False) - if colname == "Name": - editor = wx.grid.GridCellTextEditor() - renderer = wx.grid.GridCellStringRenderer() - elif colname == "Initial Value": - if var_class not in ["External", "InOut"]: - if self.Parent.Controler.IsEnumeratedType(var_type): - editor = wx.grid.GridCellChoiceEditor() - editor.SetParameters(",".join([""] + self.Parent.Controler.GetEnumeratedDataValues(var_type))) - else: - editor = wx.grid.GridCellTextEditor() - renderer = wx.grid.GridCellStringRenderer() - else: - grid.SetReadOnly(row, col, True) - elif colname == "Location": - if var_class in ["Local", "Global"] and self.Parent.Controler.IsLocatableType(var_type): - editor = LocationCellEditor(self, self.Parent.Controler) - renderer = wx.grid.GridCellStringRenderer() - else: - grid.SetReadOnly(row, col, True) - elif colname == "Class": - if len(self.Parent.ClassList) == 1: - grid.SetReadOnly(row, col, True) - else: - editor = wx.grid.GridCellChoiceEditor() - excluded = [] - if self.Parent.IsFunctionBlockType(var_type): - excluded.extend(["Local","Temp"]) - editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded])) - elif colname != "Documentation": - grid.SetReadOnly(row, col, True) - - grid.SetCellEditor(row, col, editor) - grid.SetCellRenderer(row, col, renderer) - - if colname == "Location" and LOCATION_MODEL.match(self.GetValueByName(row, colname)) is None: - highlight_colours = ERROR_HIGHLIGHT - else: - highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1] - grid.SetCellBackgroundColour(row, col, highlight_colours[0]) - grid.SetCellTextColour(row, col, highlight_colours[1]) - self.ResizeRow(grid, row) - -#------------------------------------------------------------------------------- -# Variable Panel Drop Target -#------------------------------------------------------------------------------- - -class VariableDropTarget(wx.TextDropTarget): - ''' - This allows dragging a variable location from somewhere to the Location - column of a variable row. - - The drag source should be a TextDataObject containing a Python tuple like: - ('%ID0.0.0', 'location', 'REAL') - - c_ext/CFileEditor.py has an example of this (you can drag a C extension - variable to the Location column of the variable panel). - ''' - def __init__(self, parent): - wx.TextDropTarget.__init__(self) - self.ParentWindow = parent - - def OnDropText(self, x, y, data): - self.ParentWindow.ParentWindow.Select() - x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y) - col = self.ParentWindow.VariablesGrid.XToCol(x) - row = self.ParentWindow.VariablesGrid.YToRow(y) - message = None - element_type = self.ParentWindow.ElementType - try: - values = eval(data) - except: - message = _("Invalid value \"%s\" for variable grid element")%data - values = None - if not isinstance(values, TupleType): - message = _("Invalid value \"%s\" for variable grid element")%data - values = None - if values is not None: - if col != wx.NOT_FOUND and row != wx.NOT_FOUND: - colname = self.ParentWindow.Table.GetColLabelValue(col, False) - if colname == "Location" and values[1] == "location": - if not self.ParentWindow.Table.GetValueByName(row, "Edit"): - message = _("Can't give a location to a function block instance") - elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]: - message = _("Can only give a location to local or global variables") - else: - location = values[0] - variable_type = self.ParentWindow.Table.GetValueByName(row, "Type") - base_type = self.ParentWindow.Controler.GetBaseType(variable_type) - - if values[2] is not None: - base_location_type = self.ParentWindow.Controler.GetBaseType(values[2]) - if values[2] != variable_type and base_type != base_location_type: - message = _("Incompatible data types between \"{a1}\" and \"{a2}\"").\ - format(a1 = values[2], a2 = variable_type) - - if message is None: - if not location.startswith("%"): - if location[0].isdigit() and base_type != "BOOL": - message = _("Incompatible size of data between \"%s\" and \"BOOL\"")%location - elif location[0] not in LOCATIONDATATYPES: - message = _("Unrecognized data size \"%s\"")%location[0] - elif base_type not in LOCATIONDATATYPES[location[0]]: - message = _("Incompatible size of data between \"{a1}\" and \"{a2}\"").\ - format(a1 = location, a2 = variable_type) - else: - dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow, - _("Select a variable class:"), _("Variable class"), - [_("Input"), _("Output"), _("Memory")], - wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL) - if dialog.ShowModal() == wx.ID_OK: - selected = dialog.GetSelection() - else: - selected = None - dialog.Destroy() - if selected is None: - return - if selected == 0: - location = "%I" + location - elif selected == 1: - location = "%Q" + location - else: - location = "%M" + location - - if message is None: - self.ParentWindow.Table.SetValue(row, col, location) - self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid) - self.ParentWindow.SaveValues() - elif colname == "Initial Value" and values[1] == "Constant": - if not self.ParentWindow.Table.GetValueByName(row, "Edit"): - message = _("Can't set an initial value to a function block instance") - else: - self.ParentWindow.Table.SetValue(row, col, values[0]) - self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid) - self.ParentWindow.SaveValues() - elif (element_type not in ["config", "resource", "function"] and values[1] == "Global" and - self.ParentWindow.Filter in ["All", "Interface", "External"] or - element_type != "function" and values[1] in ["location", "NamedConstant"]): - if values[1] in ["location","NamedConstant"]: - var_name = values[3] - else: - var_name = values[0] - tagname = self.ParentWindow.GetTagName() - dlg = wx.TextEntryDialog( - self.ParentWindow.ParentWindow.ParentWindow, - _("Confirm or change variable name"), - _('Variable Drop'), var_name) - dlg.SetValue(var_name) - var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None - dlg.Destroy() - if var_name is None: - return - elif var_name.upper() in [name.upper() - for name in self.ParentWindow.Controler.\ - GetProjectPouNames(self.ParentWindow.Debug)]: - message = _("\"%s\" pou already exists!")%var_name - elif not var_name.upper() in [name.upper() - for name in self.ParentWindow.Controler.\ - GetEditedElementVariables(tagname, self.ParentWindow.Debug)]: - var_infos = self.ParentWindow.DefaultValue.copy() - var_infos.Name = var_name - var_infos.Type = values[2] - var_infos.Documentation = values[4] - if values[1] == "location": - location = values[0] - if not location.startswith("%"): - dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow, - _("Select a variable class:"), _("Variable class"), - [_("Input"), _("Output"), _("Memory")], - wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL) - if dialog.ShowModal() == wx.ID_OK: - selected = dialog.GetSelection() - else: - selected = None - dialog.Destroy() - if selected is None: - return - if selected == 0: - location = "%I" + location - elif selected == 1: - location = "%Q" + location - else: - location = "%M" + location - if element_type == "functionBlock": - configs = self.ParentWindow.Controler.GetProjectConfigNames( - self.ParentWindow.Debug) - if len(configs) == 0: - return - if not var_name.upper() in [name.upper() - for name in self.ParentWindow.Controler.\ - GetConfigurationVariableNames(configs[0])]: - self.ParentWindow.Controler.AddConfigurationGlobalVar( - configs[0], values[2], var_name, location, "") - var_infos.Class = "External" - else: - if element_type == "program": - var_infos.Class = "Local" - else: - var_infos.Class = "Global" - var_infos.Location = location - elif values[1] == "NamedConstant": - if element_type in ["functionBlock","program"]: - var_infos.Class = "Local" - var_infos.InitialValue = values[0] - else : - return - else: - var_infos.Class = "External" - var_infos.Number = len(self.ParentWindow.Values) - self.ParentWindow.Values.append(var_infos) - self.ParentWindow.SaveValues() - self.ParentWindow.RefreshValues() - else: - message = _("\"%s\" element for this pou already exists!")%var_name - - if message is not None: - wx.CallAfter(self.ShowMessage, message) - - def ShowMessage(self, message): - message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - -#------------------------------------------------------------------------------- -# Variable Panel -#------------------------------------------------------------------------------- - -class VariablePanel(wx.Panel): - - def __init__(self, parent, window, controler, element_type, debug=False): - wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL) - - self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0) - self.MainSizer.AddGrowableCol(0) - self.MainSizer.AddGrowableRow(1) - - controls_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5) - controls_sizer.AddGrowableCol(5) - controls_sizer.AddGrowableRow(0) - self.MainSizer.AddSizer(controls_sizer, border=5, flag=wx.GROW|wx.ALL) - - self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:')) - controls_sizer.AddWindow(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL) - - self.ReturnType = wx.ComboBox(self, - size=wx.Size(145, -1), style=wx.CB_READONLY) - self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType) - controls_sizer.AddWindow(self.ReturnType) - - self.DescriptionLabel = wx.StaticText(self, label=_('Description:')) - controls_sizer.AddWindow(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL) - - self.Description = wx.TextCtrl(self, - size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description) - self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged) - controls_sizer.AddWindow(self.Description) - - class_filter_label = wx.StaticText(self, label=_('Class Filter:')) - controls_sizer.AddWindow(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL) - - self.ClassFilter = wx.ComboBox(self, - size=wx.Size(145, -1), style=wx.CB_READONLY) - self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter) - controls_sizer.AddWindow(self.ClassFilter) - - for name, bitmap, help in [ - ("AddButton", "add_element", _("Add variable")), - ("DeleteButton", "remove_element", _("Remove variable")), - ("UpButton", "up", _("Move variable up")), - ("DownButton", "down", _("Move variable down"))]: - button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), - size=wx.Size(28, 28), style=wx.NO_BORDER) - button.SetToolTipString(help) - setattr(self, name, button) - controls_sizer.AddWindow(button) - - self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL | wx.HSCROLL) - self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) - self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, - self.OnVariablesGridCellChange) - self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, - self.OnVariablesGridCellLeftClick) - self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, - self.OnVariablesGridEditorShown) - self.MainSizer.AddWindow(self.VariablesGrid, flag=wx.GROW) - - self.SetSizer(self.MainSizer) - - self.ParentWindow = window - self.Controler = controler - self.ElementType = element_type - self.Debug = debug - - self.RefreshHighlightsTimer = wx.Timer(self, -1) - self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, - self.RefreshHighlightsTimer) - - self.Filter = "All" - self.FilterChoices = [] - self.FilterChoiceTransfer = GetFilterChoiceTransfer() - - self.DefaultValue = _VariableInfos("", "", "", "", "", True, "", DefaultType, ([], []), 0) - - if element_type in ["config", "resource"]: - self.DefaultTypes = {"All" : "Global"} - else: - self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"} - - if element_type in ["config", "resource"] \ - or element_type in ["program", "transition", "action"]: - # this is an element that can have located variables - self.Table = VariableTable(self, [], GetVariableTableColnames(True)) - - if element_type in ["config", "resource"]: - self.FilterChoices = ["All", "Global"]#,"Access"] - else: - self.FilterChoices = ["All", - "Interface", " Input", " Output", " InOut", " External", - "Variables", " Local", " Temp"]#,"Access"] - - # these condense the ColAlignements list - l = wx.ALIGN_LEFT - c = wx.ALIGN_CENTER - - # Num Name Class Type Loc Init Option Doc - self.ColSizes = [40, 80, 100, 80, 110, 120, 100, 160] - self.ColAlignements = [c, l, l, l, l, l, l, l] - self.ColFixedSizeFlag=[True,False, True, False, True, True, True, False] - - else: - # this is an element that cannot have located variables - self.Table = VariableTable(self, [], GetVariableTableColnames(False)) - - if element_type == "function": - self.FilterChoices = ["All", - "Interface", " Input", " Output", " InOut", - "Variables", " Local"] - else: - self.FilterChoices = ["All", - "Interface", " Input", " Output", " InOut", " External", - "Variables", " Local", " Temp"] - - # these condense the ColAlignements list - l = wx.ALIGN_LEFT - c = wx.ALIGN_CENTER - - # Num Name Class Type Init Option Doc - self.ColSizes = [40, 80, 100, 80, 120, 100, 160] - self.ColAlignements = [c, l, l, l, l, l, l] - self.ColFixedSizeFlag=[True,False, True, False, True, True, False] - - self.PanelWidthMin = sum(self.ColSizes) - - self.ElementType = element_type - self.BodyType = None - - for choice in self.FilterChoices: - self.ClassFilter.Append(_(choice)) - - reverse_transfer = {} - for filter, choice in self.FilterChoiceTransfer.items(): - reverse_transfer[choice] = filter - self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter])) - self.RefreshTypeList() - - self.VariablesGrid.SetTable(self.Table) - self.VariablesGrid.SetButtons({"Add": self.AddButton, - "Delete": self.DeleteButton, - "Up": self.UpButton, - "Down": self.DownButton}) - self.VariablesGrid.SetEditable(not self.Debug) - - def _AddVariable(new_row): - if new_row > 0: - row_content = self.Values[new_row - 1].copy() - - result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content.Name) - if result is not None: - name = row_content.Name[:result.start(1)] - suffix = result.group(1) - if suffix != "": - start_idx = int(suffix) - else: - start_idx = 0 - else: - name = row_content.Name - start_idx = 0 - else: - row_content = None - start_idx = 0 - name = "LocalVar" - - if row_content is not None and row_content.Edit: - row_content = self.Values[new_row - 1].copy() - else: - row_content = self.DefaultValue.copy() - if self.Filter in self.DefaultTypes: - row_content.Class = self.DefaultTypes[self.Filter] - else: - row_content.Class = self.Filter - - row_content.Name = self.Controler.GenerateNewName( - self.TagName, None, name + "%d", start_idx) - - if self.Filter == "All" and len(self.Values) > 0: - self.Values.insert(new_row, row_content) - else: - self.Values.append(row_content) - new_row = self.Table.GetNumberRows() - self.SaveValues() - if self.ElementType == "resource": - self.ParentWindow.RefreshView(variablepanel = False) - self.RefreshValues() - return new_row - setattr(self.VariablesGrid, "_AddRow", _AddVariable) - - def _DeleteVariable(row): - if _GetRowEdit(row): - self.Values.remove(self.Table.GetRow(row)) - self.SaveValues() - if self.ElementType == "resource": - self.ParentWindow.RefreshView(variablepanel = False) - self.RefreshValues() - setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable) - - def _MoveVariable(row, move): - if self.Filter == "All": - new_row = max(0, min(row + move, len(self.Values) - 1)) - if new_row != row: - self.Values.insert(new_row, self.Values.pop(row)) - self.SaveValues() - self.RefreshValues() - return new_row - return row - setattr(self.VariablesGrid, "_MoveRow", _MoveVariable) - - def _GetRowEdit(row): - row_edit = False - if self: - row_edit = self.Table.GetValueByName(row, "Edit") - bodytype = self.Controler.GetEditedElementBodyType(self.TagName) - row_edit = row_edit or (bodytype in ["ST", "IL"]) - return row_edit - - def _RefreshButtons(): - if self: - table_length = len(self.Table.data) - row_class = None - row_edit = True - row = 0 - if table_length > 0: - row = self.VariablesGrid.GetGridCursorRow() - row_edit = _GetRowEdit(row) - self.AddButton.Enable(not self.Debug) - self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit)) - self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All")) - self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All")) - setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons) - - panel_width = window.Parent.ScreenRect.Width - 35 - if panel_width > self.PanelWidthMin: - stretch_cols_width = panel_width - stretch_cols_sum = 0 - for col in range(len(self.ColFixedSizeFlag)): - if self.ColFixedSizeFlag[col]: - stretch_cols_width -= self.ColSizes[col] - else: - stretch_cols_sum += self.ColSizes[col] - - self.VariablesGrid.SetRowLabelSize(0) - for col in range(self.Table.GetNumberCols()): - attr = wx.grid.GridCellAttr() - attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE) - self.VariablesGrid.SetColAttr(col, attr) - self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col]) - if (panel_width > self.PanelWidthMin) and not self.ColFixedSizeFlag[col]: - self.VariablesGrid.SetColSize(col, int((float(self.ColSizes[col])/stretch_cols_sum)*stretch_cols_width)) - else: - self.VariablesGrid.SetColSize(col, self.ColSizes[col]) - - def __del__(self): - self.RefreshHighlightsTimer.Stop() - - def SetTagName(self, tagname): - self.TagName = tagname - self.BodyType = self.Controler.GetEditedElementBodyType(self.TagName) - - def GetTagName(self): - return self.TagName - - def IsFunctionBlockType(self, name): - if (isinstance(name, TupleType) or - self.ElementType != "function" and self.BodyType in ["ST", "IL"]): - return False - else: - return self.Controler.GetBlockType(name, debug=self.Debug) is not None - - def RefreshView(self): - self.PouNames = self.Controler.GetProjectPouNames(self.Debug) - returnType = None - description = None - - words = self.TagName.split("::") - if self.ElementType == "config": - self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug) - elif self.ElementType == "resource": - self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug) - else: - if self.ElementType == "function": - self.ReturnType.Clear() - for data_type in self.Controler.GetDataTypes(self.TagName, debug=self.Debug): - self.ReturnType.Append(data_type) - returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, debug=self.Debug) - description = self.Controler.GetPouDescription(words[1]) - self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, debug=self.Debug) - - if returnType is not None: - self.ReturnType.SetStringSelection(returnType) - self.ReturnType.Enable(not self.Debug) - self.ReturnTypeLabel.Show() - self.ReturnType.Show() - else: - self.ReturnType.Enable(False) - self.ReturnTypeLabel.Hide() - self.ReturnType.Hide() - - if description is not None: - self.Description.SetValue(description) - self.Description.Enable(not self.Debug) - self.DescriptionLabel.Show() - self.Description.Show() - else: - self.Description.Enable(False) - self.DescriptionLabel.Hide() - self.Description.Hide() - - self.RefreshValues() - self.VariablesGrid.RefreshButtons() - self.MainSizer.Layout() - - def OnReturnTypeChanged(self, event): - words = self.TagName.split("::") - self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection()) - self.Controler.BufferProject() - self.ParentWindow.RefreshView(variablepanel = False) - self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) - event.Skip() - - def OnDescriptionChanged(self, event): - words = self.TagName.split("::") - old_description = self.Controler.GetPouDescription(words[1]) - new_description = self.Description.GetValue() - if new_description != old_description: - self.Controler.SetPouDescription(words[1], new_description) - self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) - event.Skip() - - def OnClassFilter(self, event): - self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]] - self.RefreshTypeList() - self.RefreshValues() - self.VariablesGrid.RefreshButtons() - event.Skip() - - def RefreshTypeList(self): - if self.Filter == "All": - self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[choice] not in ["All","Interface","Variables"]] - elif self.Filter == "Interface": - self.ClassList = ["Input","Output","InOut","External"] - elif self.Filter == "Variables": - self.ClassList = ["Local","Temp"] - else: - self.ClassList = [self.Filter] - - def ShowErrorMessage(self, message): - dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR) - dialog.ShowModal() - dialog.Destroy() - - def OnVariablesGridCellChange(self, event): - row, col = event.GetRow(), event.GetCol() - colname = self.Table.GetColLabelValue(col, False) - value = self.Table.GetValue(row, col) - message = None - - if colname == "Name" and value != "": - if not TestIdentifier(value): - message = _("\"%s\" is not a valid identifier!") % value - elif value.upper() in IEC_KEYWORDS: - message = _("\"%s\" is a keyword. It can't be used!") % value - elif value.upper() in self.PouNames: - message = _("A POU named \"%s\" already exists!") % value - elif value.upper() in [var.Name.upper() for var in self.Values if var != self.Table.data[row]]: - message = _("A variable with \"%s\" as name already exists in this pou!") % value - else: - self.SaveValues(False) - old_value = self.Table.GetOldValue() - if old_value != "": - self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value) - self.Controler.BufferProject() - wx.CallAfter(self.ParentWindow.RefreshView, False) - self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) - else: - self.SaveValues() - if colname == "Class": - self.ClearLocation(row, col, value) - wx.CallAfter(self.ParentWindow.RefreshView) - elif colname == "Location": - wx.CallAfter(self.ParentWindow.RefreshView) - - if message is not None: - wx.CallAfter(self.ShowErrorMessage, message) - event.Veto() - else: - event.Skip() - - def ClearLocation(self, row, col, value): - if self.Values[row].Location != '': - if self.Table.GetColLabelValue(col, False) == 'Class' and value not in ["Local", "Global"] or \ - self.Table.GetColLabelValue(col, False) == 'Type' and not self.Controler.IsLocatableType(value): - self.Values[row].Location = '' - self.RefreshValues() - self.SaveValues() - - def BuildStdIECTypesMenu(self,type_menu): - # build a submenu containing standard IEC types - base_menu = wx.Menu(title='') - for base_type in self.Controler.GetBaseTypes(): - new_id = wx.NewId() - base_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type) - self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id) - - type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu) - - def BuildUserTypesMenu(self,type_menu): - # build a submenu containing user-defined types - datatype_menu = wx.Menu(title='') - datatypes = self.Controler.GetDataTypes(basetypes = False, confnodetypes = False) - for datatype in datatypes: - new_id = wx.NewId() - datatype_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) - self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) - - type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu) - - def BuildLibsTypesMenu(self, type_menu): - for category in self.Controler.GetConfNodeDataTypes(): - if len(category["list"]) > 0: - # build a submenu containing confnode types - confnode_datatype_menu = wx.Menu(title='') - for datatype in category["list"]: - new_id = wx.NewId() - confnode_datatype_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) - self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) - - type_menu.AppendMenu(wx.NewId(), category["name"], confnode_datatype_menu) - - def BuildProjectTypesMenu(self, type_menu, classtype): - # build a submenu containing function block types - bodytype = self.Controler.GetEditedElementBodyType(self.TagName) - pouname, poutype = self.Controler.GetEditedElementType(self.TagName) - if classtype in ["Input", "Output", "InOut", "External", "Global"] or \ - poutype != "function" and bodytype in ["ST", "IL"]: - functionblock_menu = wx.Menu(title='') - fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName) - for functionblock_type in fbtypes: - new_id = wx.NewId() - functionblock_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type) - self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id) - - type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu) - - def BuildArrayTypesMenu(self, type_menu): - new_id = wx.NewId() - type_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Array")) - self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id) - - def OnVariablesGridEditorShown(self, event): - row, col = event.GetRow(), event.GetCol() - - label_value = self.Table.GetColLabelValue(col, False) - if label_value == "Type": - old_value = self.Values[row].Type - classtype = self.Table.GetValueByName(row, "Class") - type_menu = wx.Menu(title='') # the root menu - - self.BuildStdIECTypesMenu(type_menu) - - self.BuildUserTypesMenu(type_menu) - - self.BuildLibsTypesMenu(type_menu) - - self.BuildProjectTypesMenu(type_menu,classtype) - - self.BuildArrayTypesMenu(type_menu) - - rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col)) - corner_x = rect.x + rect.width - corner_y = rect.y + self.VariablesGrid.GetColLabelSize() - - # pop up this new menu - self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y) - type_menu.Destroy() - event.Veto() - value = self.Values[row].Type - if old_value != value: - self.ClearLocation(row, col, value) - else: - event.Skip() - - def GetVariableTypeFunction(self, base_type): - def VariableTypeFunction(event): - row = self.VariablesGrid.GetGridCursorRow() - self.Table.SetValueByName(row, "Type", base_type) - self.Table.ResetView(self.VariablesGrid) - self.SaveValues(False) - self.ParentWindow.RefreshView(variablepanel = False) - self.Controler.BufferProject() - self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) - return VariableTypeFunction - - def VariableArrayTypeFunction(self, event): - row = self.VariablesGrid.GetGridCursorRow() - dialog = ArrayTypeDialog(self, - self.Controler.GetDataTypes(self.TagName), - self.Table.GetValueByName(row, "Type")) - if dialog.ShowModal() == wx.ID_OK: - self.Table.SetValueByName(row, "Type", dialog.GetValue()) - self.Table.ResetView(self.VariablesGrid) - self.SaveValues(False) - self.ParentWindow.RefreshView(variablepanel = False) - self.Controler.BufferProject() - self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) - dialog.Destroy() - - def OnVariablesGridCellLeftClick(self, event): - row = event.GetRow() - if not self.Debug and (event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit")): - var_name = self.Table.GetValueByName(row, "Name") - var_class = self.Table.GetValueByName(row, "Class") - var_type = self.Table.GetValueByName(row, "Type") - data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName))) - dragSource = wx.DropSource(self.VariablesGrid) - dragSource.SetData(data) - dragSource.DoDragDrop() - event.Skip() - - def RefreshValues(self): - data = [] - for num, variable in enumerate(self.Values): - if variable.Class in self.ClassList: - variable.Number = num + 1 - data.append(variable) - self.Table.SetData(data) - self.Table.ResetView(self.VariablesGrid) - - def SaveValues(self, buffer = True): - words = self.TagName.split("::") - if self.ElementType == "config": - self.Controler.SetConfigurationGlobalVars(words[1], self.Values) - elif self.ElementType == "resource": - self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values) - else: - if self.ReturnType.IsEnabled(): - self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection()) - self.Controler.SetPouInterfaceVars(words[1], self.Values) - if buffer: - self.Controler.BufferProject() - self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) - -#------------------------------------------------------------------------------- -# Highlights showing functions -#------------------------------------------------------------------------------- - - def OnRefreshHighlightsTimer(self, event): - self.Table.ResetView(self.VariablesGrid) - event.Skip() - - def AddVariableHighlight(self, infos, highlight_type): - if isinstance(infos[0], TupleType): - for i in xrange(*infos[0]): - self.Table.AddHighlight((i,) + infos[1:], highlight_type) - cell_visible = infos[0][0] - else: - self.Table.AddHighlight(infos, highlight_type) - cell_visible = infos[0] - colnames = [colname.lower() for colname in self.Table.colnames] - self.VariablesGrid.MakeCellVisible(cell_visible, colnames.index(infos[1])) - self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True) - - def RemoveVariableHighlight(self, infos, highlight_type): - if isinstance(infos[0], TupleType): - for i in xrange(*infos[0]): - self.Table.RemoveHighlight((i,) + infos[1:], highlight_type) - else: - self.Table.RemoveHighlight(infos, highlight_type) - self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True) - - def ClearHighlights(self, highlight_type=None): - self.Table.ClearHighlights(highlight_type) - self.Table.ResetView(self.VariablesGrid) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# +# See COPYING file for copyrights details. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import os +import re +from types import TupleType, StringType, UnicodeType + +import wx +import wx.grid +import wx.lib.buttons + +from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS, DefaultType +from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT +from dialogs.ArrayTypeDialog import ArrayTypeDialog +from CustomGrid import CustomGrid +from CustomTable import CustomTable +from LocationCellEditor import LocationCellEditor +from util.BitmapLibrary import GetBitmap +from PLCControler import _VariableInfos + +#------------------------------------------------------------------------------- +# Helpers +#------------------------------------------------------------------------------- + +[TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, + POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES +] = range(10) + +def GetVariableTableColnames(location): + _ = lambda x : x + if location: + return ["#", _("Name"), _("Class"), _("Type"), _("Location"), _("Initial Value"), _("Option"), _("Documentation")] + return ["#", _("Name"), _("Class"), _("Type"), _("Initial Value"), _("Option"), _("Documentation")] + +def GetOptions(constant=True, retain=True, non_retain=True): + _ = lambda x : x + options = [""] + if constant: + options.append(_("Constant")) + if retain: + options.append(_("Retain")) + if non_retain: + options.append(_("Non-Retain")) + return options +OPTIONS_DICT = dict([(_(option), option) for option in GetOptions()]) + +def GetFilterChoiceTransfer(): + _ = lambda x : x + return {_("All"): _("All"), _("Interface"): _("Interface"), + _(" Input"): _("Input"), _(" Output"): _("Output"), _(" InOut"): _("InOut"), + _(" External"): _("External"), _("Variables"): _("Variables"), _(" Local"): _("Local"), + _(" Temp"): _("Temp"), _("Global"): _("Global")}#, _("Access") : _("Access")} +VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().iterkeys()]) +VARIABLE_CLASSES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().itervalues()]) + +CheckOptionForClass = {"Local": lambda x: x, + "Temp": lambda x: "", + "Input": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""), + "InOut": lambda x: "", + "Output": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""), + "Global": lambda x: {"Constant": "Constant", "Retain": "Retain"}.get(x, ""), + "External": lambda x: {"Constant": "Constant"}.get(x, "") + } + +LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$") +VARIABLE_NAME_SUFFIX_MODEL = re.compile("([0-9]*)$") + +#------------------------------------------------------------------------------- +# Variables Panel Table +#------------------------------------------------------------------------------- + +class VariableTable(CustomTable): + + """ + A custom wx.grid.Grid Table using user supplied data + """ + def __init__(self, parent, data, colnames): + # The base class must be initialized *first* + CustomTable.__init__(self, parent, data, colnames) + self.old_value = None + + def GetValueByName(self, row, colname): + if row < self.GetNumberRows(): + return getattr(self.data[row], colname) + + def SetValueByName(self, row, colname, value): + if row < self.GetNumberRows(): + setattr(self.data[row], colname, value) + + def GetValue(self, row, col): + if row < self.GetNumberRows(): + if col == 0: + return self.data[row].Number + colname = self.GetColLabelValue(col, False) + if colname == "Initial Value": + colname = "InitialValue" + value = getattr(self.data[row], colname, "") + if colname == "Type" and isinstance(value, TupleType): + if value[0] == "array": + return "ARRAY [%s] OF %s" % (",".join(map(lambda x : "..".join(x), value[2])), value[1]) + if not isinstance(value, (StringType, UnicodeType)): + value = str(value) + if colname in ["Class", "Option"]: + return _(value) + return value + + def SetValue(self, row, col, value): + if col < len(self.colnames): + colname = self.GetColLabelValue(col, False) + if colname == "Name": + self.old_value = getattr(self.data[row], colname) + elif colname == "Class": + value = VARIABLE_CLASSES_DICT[value] + self.SetValueByName(row, "Option", CheckOptionForClass[value](self.GetValueByName(row, "Option"))) + if value == "External": + self.SetValueByName(row, "InitialValue", "") + elif colname == "Option": + value = OPTIONS_DICT[value] + elif colname == "Initial Value": + colname = "InitialValue" + setattr(self.data[row], colname, value) + + def GetOldValue(self): + return self.old_value + + def _GetRowEdit(self, row): + row_edit = self.GetValueByName(row, "Edit") + var_type = self.Parent.GetTagName() + bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type) + if bodytype in ["ST", "IL"]: + row_edit = True; + return row_edit + + def _updateColAttrs(self, grid): + """ + wx.grid.Grid -> update the column attributes to add the + appropriate renderer given the column name. + + Otherwise default to the default renderer. + """ + for row in range(self.GetNumberRows()): + var_class = self.GetValueByName(row, "Class") + var_type = self.GetValueByName(row, "Type") + row_highlights = self.Highlights.get(row, {}) + for col in range(self.GetNumberCols()): + editor = None + renderer = None + colname = self.GetColLabelValue(col, False) + if self.Parent.Debug: + grid.SetReadOnly(row, col, True) + else: + if colname == "Option": + options = GetOptions(constant = var_class in ["Local", "External", "Global"], + retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"], + non_retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"]) + if len(options) > 1: + editor = wx.grid.GridCellChoiceEditor() + editor.SetParameters(",".join(map(_, options))) + else: + grid.SetReadOnly(row, col, True) + elif col != 0 and self._GetRowEdit(row): + grid.SetReadOnly(row, col, False) + if colname == "Name": + editor = wx.grid.GridCellTextEditor() + renderer = wx.grid.GridCellStringRenderer() + elif colname == "Initial Value": + if var_class not in ["External", "InOut"]: + if self.Parent.Controler.IsEnumeratedType(var_type): + editor = wx.grid.GridCellChoiceEditor() + editor.SetParameters(",".join([""] + self.Parent.Controler.GetEnumeratedDataValues(var_type))) + else: + editor = wx.grid.GridCellTextEditor() + renderer = wx.grid.GridCellStringRenderer() + else: + grid.SetReadOnly(row, col, True) + elif colname == "Location": + if var_class in ["Local", "Global"] and self.Parent.Controler.IsLocatableType(var_type): + editor = LocationCellEditor(self, self.Parent.Controler) + renderer = wx.grid.GridCellStringRenderer() + else: + grid.SetReadOnly(row, col, True) + elif colname == "Class": + if len(self.Parent.ClassList) == 1: + grid.SetReadOnly(row, col, True) + else: + editor = wx.grid.GridCellChoiceEditor() + excluded = [] + if self.Parent.IsFunctionBlockType(var_type): + excluded.extend(["Local","Temp"]) + editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded])) + elif colname != "Documentation": + grid.SetReadOnly(row, col, True) + + grid.SetCellEditor(row, col, editor) + grid.SetCellRenderer(row, col, renderer) + + if colname == "Location" and LOCATION_MODEL.match(self.GetValueByName(row, colname)) is None: + highlight_colours = ERROR_HIGHLIGHT + else: + highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1] + grid.SetCellBackgroundColour(row, col, highlight_colours[0]) + grid.SetCellTextColour(row, col, highlight_colours[1]) + self.ResizeRow(grid, row) + +#------------------------------------------------------------------------------- +# Variable Panel Drop Target +#------------------------------------------------------------------------------- + +class VariableDropTarget(wx.TextDropTarget): + ''' + This allows dragging a variable location from somewhere to the Location + column of a variable row. + + The drag source should be a TextDataObject containing a Python tuple like: + ('%ID0.0.0', 'location', 'REAL') + + c_ext/CFileEditor.py has an example of this (you can drag a C extension + variable to the Location column of the variable panel). + ''' + def __init__(self, parent): + wx.TextDropTarget.__init__(self) + self.ParentWindow = parent + + def OnDropText(self, x, y, data): + self.ParentWindow.ParentWindow.Select() + x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y) + col = self.ParentWindow.VariablesGrid.XToCol(x) + row = self.ParentWindow.VariablesGrid.YToRow(y) + message = None + element_type = self.ParentWindow.ElementType + try: + values = eval(data) + except: + message = _("Invalid value \"%s\" for variable grid element")%data + values = None + if not isinstance(values, TupleType): + message = _("Invalid value \"%s\" for variable grid element")%data + values = None + if values is not None: + if col != wx.NOT_FOUND and row != wx.NOT_FOUND: + colname = self.ParentWindow.Table.GetColLabelValue(col, False) + if colname == "Location" and values[1] == "location": + if not self.ParentWindow.Table.GetValueByName(row, "Edit"): + message = _("Can't give a location to a function block instance") + elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]: + message = _("Can only give a location to local or global variables") + else: + location = values[0] + variable_type = self.ParentWindow.Table.GetValueByName(row, "Type") + base_type = self.ParentWindow.Controler.GetBaseType(variable_type) + + if values[2] is not None: + base_location_type = self.ParentWindow.Controler.GetBaseType(values[2]) + if values[2] != variable_type and base_type != base_location_type: + message = _("Incompatible data types between \"{a1}\" and \"{a2}\"").\ + format(a1 = values[2], a2 = variable_type) + + if message is None: + if not location.startswith("%"): + if location[0].isdigit() and base_type != "BOOL": + message = _("Incompatible size of data between \"%s\" and \"BOOL\"")%location + elif location[0] not in LOCATIONDATATYPES: + message = _("Unrecognized data size \"%s\"")%location[0] + elif base_type not in LOCATIONDATATYPES[location[0]]: + message = _("Incompatible size of data between \"{a1}\" and \"{a2}\"").\ + format(a1 = location, a2 = variable_type) + else: + dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow, + _("Select a variable class:"), _("Variable class"), + [_("Input"), _("Output"), _("Memory")], + wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL) + if dialog.ShowModal() == wx.ID_OK: + selected = dialog.GetSelection() + else: + selected = None + dialog.Destroy() + if selected is None: + return + if selected == 0: + location = "%I" + location + elif selected == 1: + location = "%Q" + location + else: + location = "%M" + location + + if message is None: + self.ParentWindow.Table.SetValue(row, col, location) + self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid) + self.ParentWindow.SaveValues() + elif colname == "Initial Value" and values[1] == "Constant": + if not self.ParentWindow.Table.GetValueByName(row, "Edit"): + message = _("Can't set an initial value to a function block instance") + else: + self.ParentWindow.Table.SetValue(row, col, values[0]) + self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid) + self.ParentWindow.SaveValues() + elif (element_type not in ["config", "resource", "function"] and values[1] == "Global" and + self.ParentWindow.Filter in ["All", "Interface", "External"] or + element_type != "function" and values[1] in ["location", "NamedConstant"]): + if values[1] in ["location","NamedConstant"]: + var_name = values[3] + else: + var_name = values[0] + tagname = self.ParentWindow.GetTagName() + dlg = wx.TextEntryDialog( + self.ParentWindow.ParentWindow.ParentWindow, + _("Confirm or change variable name"), + _('Variable Drop'), var_name) + dlg.SetValue(var_name) + var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None + dlg.Destroy() + if var_name is None: + return + elif var_name.upper() in [name.upper() + for name in self.ParentWindow.Controler.\ + GetProjectPouNames(self.ParentWindow.Debug)]: + message = _("\"%s\" pou already exists!")%var_name + elif not var_name.upper() in [name.upper() + for name in self.ParentWindow.Controler.\ + GetEditedElementVariables(tagname, self.ParentWindow.Debug)]: + var_infos = self.ParentWindow.DefaultValue.copy() + var_infos.Name = var_name + var_infos.Type = values[2] + var_infos.Documentation = values[4] + if values[1] == "location": + location = values[0] + if not location.startswith("%"): + dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow, + _("Select a variable class:"), _("Variable class"), + [_("Input"), _("Output"), _("Memory")], + wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL) + if dialog.ShowModal() == wx.ID_OK: + selected = dialog.GetSelection() + else: + selected = None + dialog.Destroy() + if selected is None: + return + if selected == 0: + location = "%I" + location + elif selected == 1: + location = "%Q" + location + else: + location = "%M" + location + if element_type == "functionBlock": + configs = self.ParentWindow.Controler.GetProjectConfigNames( + self.ParentWindow.Debug) + if len(configs) == 0: + return + if not var_name.upper() in [name.upper() + for name in self.ParentWindow.Controler.\ + GetConfigurationVariableNames(configs[0])]: + self.ParentWindow.Controler.AddConfigurationGlobalVar( + configs[0], values[2], var_name, location, "") + var_infos.Class = "External" + else: + if element_type == "program": + var_infos.Class = "Local" + else: + var_infos.Class = "Global" + var_infos.Location = location + elif values[1] == "NamedConstant": + if element_type in ["functionBlock","program"]: + var_infos.Class = "Local" + var_infos.InitialValue = values[0] + else : + return + else: + var_infos.Class = "External" + var_infos.Number = len(self.ParentWindow.Values) + self.ParentWindow.Values.append(var_infos) + self.ParentWindow.SaveValues() + self.ParentWindow.RefreshValues() + else: + message = _("\"%s\" element for this pou already exists!")%var_name + + if message is not None: + wx.CallAfter(self.ShowMessage, message) + + def ShowMessage(self, message): + message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + +#------------------------------------------------------------------------------- +# Variable Panel +#------------------------------------------------------------------------------- + +class VariablePanel(wx.Panel): + + def __init__(self, parent, window, controler, element_type, debug=False): + wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL) + + self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0) + self.MainSizer.AddGrowableCol(0) + self.MainSizer.AddGrowableRow(1) + + controls_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5) + controls_sizer.AddGrowableCol(5) + controls_sizer.AddGrowableRow(0) + self.MainSizer.AddSizer(controls_sizer, border=5, flag=wx.GROW|wx.ALL) + + self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:')) + controls_sizer.AddWindow(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL) + + self.ReturnType = wx.ComboBox(self, + size=wx.Size(145, -1), style=wx.CB_READONLY) + self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType) + controls_sizer.AddWindow(self.ReturnType) + + self.DescriptionLabel = wx.StaticText(self, label=_('Description:')) + controls_sizer.AddWindow(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL) + + self.Description = wx.TextCtrl(self, + size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description) + self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged) + controls_sizer.AddWindow(self.Description) + + class_filter_label = wx.StaticText(self, label=_('Class Filter:')) + controls_sizer.AddWindow(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL) + + self.ClassFilter = wx.ComboBox(self, + size=wx.Size(145, -1), style=wx.CB_READONLY) + self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter) + controls_sizer.AddWindow(self.ClassFilter) + + for name, bitmap, help in [ + ("AddButton", "add_element", _("Add variable")), + ("DeleteButton", "remove_element", _("Remove variable")), + ("UpButton", "up", _("Move variable up")), + ("DownButton", "down", _("Move variable down"))]: + button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), + size=wx.Size(28, 28), style=wx.NO_BORDER) + button.SetToolTipString(help) + setattr(self, name, button) + controls_sizer.AddWindow(button) + + self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL | wx.HSCROLL) + self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) + self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, + self.OnVariablesGridCellChange) + self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, + self.OnVariablesGridCellLeftClick) + self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, + self.OnVariablesGridEditorShown) + self.MainSizer.AddWindow(self.VariablesGrid, flag=wx.GROW) + + self.SetSizer(self.MainSizer) + + self.ParentWindow = window + self.Controler = controler + self.ElementType = element_type + self.Debug = debug + + self.RefreshHighlightsTimer = wx.Timer(self, -1) + self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, + self.RefreshHighlightsTimer) + + self.Filter = "All" + self.FilterChoices = [] + self.FilterChoiceTransfer = GetFilterChoiceTransfer() + + self.DefaultValue = _VariableInfos("", "", "", "", "", True, "", DefaultType, ([], []), 0) + + if element_type in ["config", "resource"]: + self.DefaultTypes = {"All" : "Global"} + else: + self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"} + + if element_type in ["config", "resource"] \ + or element_type in ["program", "transition", "action"]: + # this is an element that can have located variables + self.Table = VariableTable(self, [], GetVariableTableColnames(True)) + + if element_type in ["config", "resource"]: + self.FilterChoices = ["All", "Global"]#,"Access"] + else: + self.FilterChoices = ["All", + "Interface", " Input", " Output", " InOut", " External", + "Variables", " Local", " Temp"]#,"Access"] + + # these condense the ColAlignements list + l = wx.ALIGN_LEFT + c = wx.ALIGN_CENTER + + # Num Name Class Type Loc Init Option Doc + self.ColSizes = [40, 80, 100, 80, 110, 120, 100, 160] + self.ColAlignements = [c, l, l, l, l, l, l, l] + self.ColFixedSizeFlag=[True,False, True, False, True, True, True, False] + + else: + # this is an element that cannot have located variables + self.Table = VariableTable(self, [], GetVariableTableColnames(False)) + + if element_type == "function": + self.FilterChoices = ["All", + "Interface", " Input", " Output", " InOut", + "Variables", " Local"] + else: + self.FilterChoices = ["All", + "Interface", " Input", " Output", " InOut", " External", + "Variables", " Local", " Temp"] + + # these condense the ColAlignements list + l = wx.ALIGN_LEFT + c = wx.ALIGN_CENTER + + # Num Name Class Type Init Option Doc + self.ColSizes = [40, 80, 100, 80, 120, 100, 160] + self.ColAlignements = [c, l, l, l, l, l, l] + self.ColFixedSizeFlag=[True,False, True, False, True, True, False] + + self.PanelWidthMin = sum(self.ColSizes) + + self.ElementType = element_type + self.BodyType = None + + for choice in self.FilterChoices: + self.ClassFilter.Append(_(choice)) + + reverse_transfer = {} + for filter, choice in self.FilterChoiceTransfer.items(): + reverse_transfer[choice] = filter + self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter])) + self.RefreshTypeList() + + self.VariablesGrid.SetTable(self.Table) + self.VariablesGrid.SetButtons({"Add": self.AddButton, + "Delete": self.DeleteButton, + "Up": self.UpButton, + "Down": self.DownButton}) + self.VariablesGrid.SetEditable(not self.Debug) + + def _AddVariable(new_row): + if new_row > 0: + row_content = self.Values[new_row - 1].copy() + + result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content.Name) + if result is not None: + name = row_content.Name[:result.start(1)] + suffix = result.group(1) + if suffix != "": + start_idx = int(suffix) + else: + start_idx = 0 + else: + name = row_content.Name + start_idx = 0 + else: + row_content = None + start_idx = 0 + name = "LocalVar" + + if row_content is not None and row_content.Edit: + row_content = self.Values[new_row - 1].copy() + else: + row_content = self.DefaultValue.copy() + if self.Filter in self.DefaultTypes: + row_content.Class = self.DefaultTypes[self.Filter] + else: + row_content.Class = self.Filter + + row_content.Name = self.Controler.GenerateNewName( + self.TagName, None, name + "%d", start_idx) + + if self.Filter == "All" and len(self.Values) > 0: + self.Values.insert(new_row, row_content) + else: + self.Values.append(row_content) + new_row = self.Table.GetNumberRows() + self.SaveValues() + if self.ElementType == "resource": + self.ParentWindow.RefreshView(variablepanel = False) + self.RefreshValues() + return new_row + setattr(self.VariablesGrid, "_AddRow", _AddVariable) + + def _DeleteVariable(row): + if _GetRowEdit(row): + self.Values.remove(self.Table.GetRow(row)) + self.SaveValues() + if self.ElementType == "resource": + self.ParentWindow.RefreshView(variablepanel = False) + self.RefreshValues() + setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable) + + def _MoveVariable(row, move): + if self.Filter == "All": + new_row = max(0, min(row + move, len(self.Values) - 1)) + if new_row != row: + self.Values.insert(new_row, self.Values.pop(row)) + self.SaveValues() + self.RefreshValues() + return new_row + return row + setattr(self.VariablesGrid, "_MoveRow", _MoveVariable) + + def _GetRowEdit(row): + row_edit = False + if self: + row_edit = self.Table.GetValueByName(row, "Edit") + bodytype = self.Controler.GetEditedElementBodyType(self.TagName) + row_edit = row_edit or (bodytype in ["ST", "IL"]) + return row_edit + + def _RefreshButtons(): + if self: + table_length = len(self.Table.data) + row_class = None + row_edit = True + row = 0 + if table_length > 0: + row = self.VariablesGrid.GetGridCursorRow() + row_edit = _GetRowEdit(row) + self.AddButton.Enable(not self.Debug) + self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit)) + self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All")) + self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All")) + setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons) + + panel_width = window.Parent.ScreenRect.Width - 35 + if panel_width > self.PanelWidthMin: + stretch_cols_width = panel_width + stretch_cols_sum = 0 + for col in range(len(self.ColFixedSizeFlag)): + if self.ColFixedSizeFlag[col]: + stretch_cols_width -= self.ColSizes[col] + else: + stretch_cols_sum += self.ColSizes[col] + + self.VariablesGrid.SetRowLabelSize(0) + for col in range(self.Table.GetNumberCols()): + attr = wx.grid.GridCellAttr() + attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE) + self.VariablesGrid.SetColAttr(col, attr) + self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col]) + if (panel_width > self.PanelWidthMin) and not self.ColFixedSizeFlag[col]: + self.VariablesGrid.SetColSize(col, int((float(self.ColSizes[col])/stretch_cols_sum)*stretch_cols_width)) + else: + self.VariablesGrid.SetColSize(col, self.ColSizes[col]) + + def __del__(self): + self.RefreshHighlightsTimer.Stop() + + def SetTagName(self, tagname): + self.TagName = tagname + self.BodyType = self.Controler.GetEditedElementBodyType(self.TagName) + + def GetTagName(self): + return self.TagName + + def IsFunctionBlockType(self, name): + if (isinstance(name, TupleType) or + self.ElementType != "function" and self.BodyType in ["ST", "IL"]): + return False + else: + return self.Controler.GetBlockType(name, debug=self.Debug) is not None + + def RefreshView(self): + self.PouNames = self.Controler.GetProjectPouNames(self.Debug) + returnType = None + description = None + + words = self.TagName.split("::") + if self.ElementType == "config": + self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug) + elif self.ElementType == "resource": + self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug) + else: + if self.ElementType == "function": + self.ReturnType.Clear() + for data_type in self.Controler.GetDataTypes(self.TagName, debug=self.Debug): + self.ReturnType.Append(data_type) + returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName, debug=self.Debug) + description = self.Controler.GetPouDescription(words[1]) + self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, debug=self.Debug) + + if returnType is not None: + self.ReturnType.SetStringSelection(returnType) + self.ReturnType.Enable(not self.Debug) + self.ReturnTypeLabel.Show() + self.ReturnType.Show() + else: + self.ReturnType.Enable(False) + self.ReturnTypeLabel.Hide() + self.ReturnType.Hide() + + if description is not None: + self.Description.SetValue(description) + self.Description.Enable(not self.Debug) + self.DescriptionLabel.Show() + self.Description.Show() + else: + self.Description.Enable(False) + self.DescriptionLabel.Hide() + self.Description.Hide() + + self.RefreshValues() + self.VariablesGrid.RefreshButtons() + self.MainSizer.Layout() + + def OnReturnTypeChanged(self, event): + words = self.TagName.split("::") + self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection()) + self.Controler.BufferProject() + self.ParentWindow.RefreshView(variablepanel = False) + self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + event.Skip() + + def OnDescriptionChanged(self, event): + words = self.TagName.split("::") + old_description = self.Controler.GetPouDescription(words[1]) + new_description = self.Description.GetValue() + if new_description != old_description: + self.Controler.SetPouDescription(words[1], new_description) + self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + event.Skip() + + def OnClassFilter(self, event): + self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]] + self.RefreshTypeList() + self.RefreshValues() + self.VariablesGrid.RefreshButtons() + event.Skip() + + def RefreshTypeList(self): + if self.Filter == "All": + self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[choice] not in ["All","Interface","Variables"]] + elif self.Filter == "Interface": + self.ClassList = ["Input","Output","InOut","External"] + elif self.Filter == "Variables": + self.ClassList = ["Local","Temp"] + else: + self.ClassList = [self.Filter] + + def ShowErrorMessage(self, message): + dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR) + dialog.ShowModal() + dialog.Destroy() + + def OnVariablesGridCellChange(self, event): + row, col = event.GetRow(), event.GetCol() + colname = self.Table.GetColLabelValue(col, False) + value = self.Table.GetValue(row, col) + message = None + + if colname == "Name" and value != "": + if not TestIdentifier(value): + message = _("\"%s\" is not a valid identifier!") % value + elif value.upper() in IEC_KEYWORDS: + message = _("\"%s\" is a keyword. It can't be used!") % value + elif value.upper() in self.PouNames: + message = _("A POU named \"%s\" already exists!") % value + elif value.upper() in [var.Name.upper() for var in self.Values if var != self.Table.data[row]]: + message = _("A variable with \"%s\" as name already exists in this pou!") % value + else: + self.SaveValues(False) + old_value = self.Table.GetOldValue() + if old_value != "": + self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value) + self.Controler.BufferProject() + wx.CallAfter(self.ParentWindow.RefreshView, False) + self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + else: + self.SaveValues() + if colname == "Class": + self.ClearLocation(row, col, value) + wx.CallAfter(self.ParentWindow.RefreshView) + elif colname == "Location": + wx.CallAfter(self.ParentWindow.RefreshView) + + if message is not None: + wx.CallAfter(self.ShowErrorMessage, message) + event.Veto() + else: + event.Skip() + + def ClearLocation(self, row, col, value): + if self.Values[row].Location != '': + if self.Table.GetColLabelValue(col, False) == 'Class' and value not in ["Local", "Global"] or \ + self.Table.GetColLabelValue(col, False) == 'Type' and not self.Controler.IsLocatableType(value): + self.Values[row].Location = '' + self.RefreshValues() + self.SaveValues() + + def BuildStdIECTypesMenu(self,type_menu): + # build a submenu containing standard IEC types + base_menu = wx.Menu(title='') + for base_type in self.Controler.GetBaseTypes(): + new_id = wx.NewId() + base_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type) + self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id) + + type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu) + + def BuildUserTypesMenu(self,type_menu): + # build a submenu containing user-defined types + datatype_menu = wx.Menu(title='') + datatypes = self.Controler.GetDataTypes(basetypes = False, confnodetypes = False) + for datatype in datatypes: + new_id = wx.NewId() + datatype_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) + self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) + + type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu) + + def BuildLibsTypesMenu(self, type_menu): + for category in self.Controler.GetConfNodeDataTypes(): + if len(category["list"]) > 0: + # build a submenu containing confnode types + confnode_datatype_menu = wx.Menu(title='') + for datatype in category["list"]: + new_id = wx.NewId() + confnode_datatype_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) + self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) + + type_menu.AppendMenu(wx.NewId(), category["name"], confnode_datatype_menu) + + def BuildProjectTypesMenu(self, type_menu, classtype): + # build a submenu containing function block types + bodytype = self.Controler.GetEditedElementBodyType(self.TagName) + pouname, poutype = self.Controler.GetEditedElementType(self.TagName) + if classtype in ["Input", "Output", "InOut", "External", "Global"] or \ + poutype != "function" and bodytype in ["ST", "IL"]: + functionblock_menu = wx.Menu(title='') + fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName) + for functionblock_type in fbtypes: + new_id = wx.NewId() + functionblock_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type) + self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id) + + type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu) + + def BuildArrayTypesMenu(self, type_menu): + new_id = wx.NewId() + type_menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Array")) + self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id) + + def OnVariablesGridEditorShown(self, event): + row, col = event.GetRow(), event.GetCol() + + label_value = self.Table.GetColLabelValue(col, False) + if label_value == "Type": + old_value = self.Values[row].Type + classtype = self.Table.GetValueByName(row, "Class") + type_menu = wx.Menu(title='') # the root menu + + self.BuildStdIECTypesMenu(type_menu) + + self.BuildUserTypesMenu(type_menu) + + self.BuildLibsTypesMenu(type_menu) + + self.BuildProjectTypesMenu(type_menu,classtype) + + self.BuildArrayTypesMenu(type_menu) + + rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col)) + corner_x = rect.x + rect.width + corner_y = rect.y + self.VariablesGrid.GetColLabelSize() + + # pop up this new menu + self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y) + type_menu.Destroy() + event.Veto() + value = self.Values[row].Type + if old_value != value: + self.ClearLocation(row, col, value) + else: + event.Skip() + + def GetVariableTypeFunction(self, base_type): + def VariableTypeFunction(event): + row = self.VariablesGrid.GetGridCursorRow() + self.Table.SetValueByName(row, "Type", base_type) + self.Table.ResetView(self.VariablesGrid) + self.SaveValues(False) + self.ParentWindow.RefreshView(variablepanel = False) + self.Controler.BufferProject() + self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + return VariableTypeFunction + + def VariableArrayTypeFunction(self, event): + row = self.VariablesGrid.GetGridCursorRow() + dialog = ArrayTypeDialog(self, + self.Controler.GetDataTypes(self.TagName), + self.Table.GetValueByName(row, "Type")) + if dialog.ShowModal() == wx.ID_OK: + self.Table.SetValueByName(row, "Type", dialog.GetValue()) + self.Table.ResetView(self.VariablesGrid) + self.SaveValues(False) + self.ParentWindow.RefreshView(variablepanel = False) + self.Controler.BufferProject() + self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + dialog.Destroy() + + def OnVariablesGridCellLeftClick(self, event): + row = event.GetRow() + if not self.Debug and (event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit")): + var_name = self.Table.GetValueByName(row, "Name") + var_class = self.Table.GetValueByName(row, "Class") + var_type = self.Table.GetValueByName(row, "Type") + data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName))) + dragSource = wx.DropSource(self.VariablesGrid) + dragSource.SetData(data) + dragSource.DoDragDrop() + event.Skip() + + def RefreshValues(self): + data = [] + for num, variable in enumerate(self.Values): + if variable.Class in self.ClassList: + variable.Number = num + 1 + data.append(variable) + self.Table.SetData(data) + self.Table.ResetView(self.VariablesGrid) + + def SaveValues(self, buffer = True): + words = self.TagName.split("::") + if self.ElementType == "config": + self.Controler.SetConfigurationGlobalVars(words[1], self.Values) + elif self.ElementType == "resource": + self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values) + else: + if self.ReturnType.IsEnabled(): + self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection()) + self.Controler.SetPouInterfaceVars(words[1], self.Values) + if buffer: + self.Controler.BufferProject() + self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + +#------------------------------------------------------------------------------- +# Highlights showing functions +#------------------------------------------------------------------------------- + + def OnRefreshHighlightsTimer(self, event): + self.Table.ResetView(self.VariablesGrid) + event.Skip() + + def AddVariableHighlight(self, infos, highlight_type): + if isinstance(infos[0], TupleType): + for i in xrange(*infos[0]): + self.Table.AddHighlight((i,) + infos[1:], highlight_type) + cell_visible = infos[0][0] + else: + self.Table.AddHighlight(infos, highlight_type) + cell_visible = infos[0] + colnames = [colname.lower() for colname in self.Table.colnames] + self.VariablesGrid.MakeCellVisible(cell_visible, colnames.index(infos[1])) + self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True) + + def RemoveVariableHighlight(self, infos, highlight_type): + if isinstance(infos[0], TupleType): + for i in xrange(*infos[0]): + self.Table.RemoveHighlight((i,) + infos[1:], highlight_type) + else: + self.Table.RemoveHighlight(infos, highlight_type) + self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True) + + def ClearHighlights(self, highlight_type=None): + self.Table.ClearHighlights(highlight_type) + self.Table.ResetView(self.VariablesGrid)
--- a/i18n/mki18n.py Mon Aug 14 19:13:01 2017 +0300 +++ b/i18n/mki18n.py Mon Aug 14 21:09:48 2017 +0300 @@ -1,503 +1,503 @@ -#! /usr/bin/env python -# -*- coding: iso-8859-1 -*- -# -# PYTHON MODULE: MKI18N.PY -# ========= -# -# Abstract: Make Internationalization (i18n) files for an application. -# -# Copyright Pierre Rouleau. 2003. Released to public domain. -# -# Last update: Saturday, November 8, 2003. @ 15:55:18. -# -# File: ROUP2003N01::C:/dev/python/mki18n.py -# -# RCS $Header: //software/official/MKS/MKS_SI/TV_NT/dev/Python/rcs/mki18n.py 1.5 2003/11/05 19:40:04 PRouleau Exp $ -# -# Update history: -# -# - File created: Saturday, June 7, 2003. by Pierre Rouleau -# - 10/06/03 rcs : RCS Revision 1.1 2003/06/10 10:06:12 PRouleau -# - 10/06/03 rcs : RCS Initial revision -# - 23/08/03 rcs : RCS Revision 1.2 2003/06/10 10:54:27 PRouleau -# - 23/08/03 P.R.: [code:fix] : The strings encoded in this file are encode in iso-8859-1 format. Added the encoding -# notification to Python to comply with Python's 2.3 PEP 263. -# - 23/08/03 P.R.: [feature:new] : Added the '-e' switch which is used to force the creation of the empty English .mo file. -# - 22/10/03 P.R.: [code] : incorporated utility functions in here to make script self sufficient. -# - 05/11/03 rcs : RCS Revision 1.4 2003/10/22 06:39:31 PRouleau -# - 05/11/03 P.R.: [code:fix] : included the unixpath() in this file. -# - 08/11/03 rcs : RCS Revision 1.5 2003/11/05 19:40:04 PRouleau -# -# RCS $Log: $ -# -# -# ----------------------------------------------------------------------------- -""" -mki18n allows you to internationalize your software. You can use it to -create the GNU .po files (Portable Object) and the compiled .mo files -(Machine Object). - -mki18n module can be used from the command line or from within a script (see -the Usage at the end of this page). - - Table of Contents - ----------------- - - makePO() -- Build the Portable Object file for the application -- - catPO() -- Concatenate one or several PO files with the application domain files. -- - makeMO() -- Compile the Portable Object files into the Machine Object stored in the right location. -- - printUsage -- Displays how to use this script from the command line -- - - Scriptexecution -- Runs when invoked from the command line -- - - -NOTE: this module uses GNU gettext utilities. - -You can get the gettext tools from the following sites: - - - `GNU FTP site for gettetx`_ where several versions (0.10.40, 0.11.2, 0.11.5 and 0.12.1) are available. - Note that you need to use `GNU libiconv`_ to use this. Get it from the `GNU - libiconv ftp site`_ and get version 1.9.1 or later. Get the Windows .ZIP - files and install the packages inside c:/gnu. All binaries will be stored - inside c:/gnu/bin. Just put c:/gnu/bin inside your PATH. You will need - the following files: - - - `gettext-runtime-0.12.1.bin.woe32.zip`_ - - `gettext-tools-0.12.1.bin.woe32.zip`_ - - `libiconv-1.9.1.bin.woe32.zip`_ - - -.. _GNU libiconv: http://www.gnu.org/software/libiconv/ -.. _GNU libiconv ftp site: http://www.ibiblio.org/pub/gnu/libiconv/ -.. _gettext-runtime-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-runtime-0.12.1.bin.woe32.zip -.. _gettext-tools-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.12.1.bin.woe32.zip -.. _libiconv-1.9.1.bin.woe32.zip: http://www.ibiblio.org/pub/gnu/libiconv/libiconv-1.9.1.bin.woe32.zip - -""" -# ----------------------------------------------------------------------------- -# Module Import -# ------------- -# -import os -import sys -import wx -import re - -# ----------------------------------------------------------------------------- -# Global variables -# ---------------- -# - -__author__ = "Pierre Rouleau" -__version__= "$Revision: 1.5 $" - -# ----------------------------------------------------------------------------- - -def getlanguageDict(): - languageDict = {} - - if wx.VERSION >= (3, 0, 0): - app = wx.App() - else: - app = wx.PySimpleApp() - - for lang in [x for x in dir(wx) if x.startswith("LANGUAGE")]: - i = wx.Locale(wx.LANGUAGE_DEFAULT).GetLanguageInfo(getattr(wx, lang)) - if i: - languageDict[i.CanonicalName] = i.Description - - return languageDict - - - -def processCustomFiles(filein, fileout, regexp, prefix = ''): - appfil_file = open(filein, 'r') - messages_file = open(fileout, 'r') - messages = messages_file.read() - messages_file.close() - messages_file = open(fileout, 'a') - messages_file.write('\n') - messages_file.write('#: %s\n' % prefix) - messages_file.write('\n') - - words_found = {} - for filepath in appfil_file.xreadlines(): - code_file = open(filepath.strip(), 'r') - for match in regexp.finditer(code_file.read()): - word = match.group(1) - if not words_found.get(word, False) and messages.find("msgid \"%s\"\nmsgstr \"\"" % word) == -1: - words_found[word] = True - messages_file.write('\n') - messages_file.write("msgid \"%s\"\n"%word) - messages_file.write("msgstr \"\"\n") - code_file.close() - - messages_file.close() - appfil_file.close() - - -# ----------------------------------------------------------------------------- -# m a k e P O ( ) -- Build the Portable Object file for the application -- -# ^^^^^^^^^^^^^^^ -# -def makePO(applicationDirectoryPath, applicationDomain=None, verbose=0) : - """Build the Portable Object Template file for the application. - - makePO builds the .pot file for the application stored inside - a specified directory by running xgettext for all application source - files. It finds the name of all files by looking for a file called 'app.fil'. - If this file does not exists, makePo raises an IOError exception. - By default the application domain (the application - name) is the same as the directory name but it can be overridden by the - 'applicationDomain' argument. - - makePO always creates a new file called messages.pot. If it finds files - of the form app_xx.po where 'app' is the application name and 'xx' is one - of the ISO 639 two-letter language codes, makePO resynchronizes those - files with the latest extracted strings (now contained in messages.pot). - This process updates all line location number in the language-specific - .po files and may also create new entries for translation (or comment out - some). The .po file is not changed, instead a new file is created with - the .new extension appended to the name of the .po file. - - By default the function does not display what it is doing. Set the - verbose argument to 1 to force it to print its commands. - """ - - if applicationDomain is None: - applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) - else: - applicationName = applicationDomain - currentDir = os.getcwd() - os.chdir(applicationDirectoryPath) - filelist = 'app.fil' - if not os.path.exists(filelist): - raise IOError(2,'No module file: ' % filelist) - - fileout = 'messages.pot' - # Steps: - # Use xgettext to parse all application modules - # The following switches are used: - # - # -s : sort output by string content (easier to use when we need to merge several .po files) - # --files-from=app.fil : The list of files is taken from the file: app.fil - # --output= : specifies the name of the output file (using a .pot extension) - cmd = 'xgettext -s --no-wrap --language=Python --files-from=' + filelist + ' --output=' + fileout + ' --package-name ' + applicationName - if verbose: print cmd - os.system(cmd) - - XSD_STRING_MODEL = re.compile("<xsd\:(?:element|attribute) name=\"([^\"]*)\"[^\>]*\>") - processCustomFiles(filelist, fileout, XSD_STRING_MODEL, 'Extra XSD strings') - - XML_TC6_STRING_MODEL = re.compile("<documentation>\s*<xhtml\:p><!\[CDATA\[([^\]]*)\]\]></xhtml\:p>\s*</documentation>", re.MULTILINE | re.DOTALL) - processCustomFiles(filelist, fileout, XML_TC6_STRING_MODEL, 'Extra TC6 documentation strings') - - # generate messages.po - cmd = 'msginit --no-wrap --no-translator -i %s -l en_US.UTF-8 -o messages.po' % (fileout) - if verbose: print cmd - os.system(cmd) - - languageDict = getlanguageDict() - - for langCode in languageDict.keys(): - if langCode == 'en': - pass - else: - langPOfileName = "%s_%s.po" % (applicationName , langCode) - if os.path.exists(langPOfileName): - cmd = 'msgmerge -s --no-wrap "%s" %s > "%s.new"' % (langPOfileName, fileout, langPOfileName) - if verbose: print cmd - os.system(cmd) - os.chdir(currentDir) - -# ----------------------------------------------------------------------------- -# c a t P O ( ) -- Concatenate one or several PO files with the application domain files. -- -# ^^^^^^^^^^^^^ -# -def catPO(applicationDirectoryPath, listOf_extraPo, applicationDomain=None, targetDir=None, verbose=0) : - """Concatenate one or several PO files with the application domain files. - """ - - if applicationDomain is None: - applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) - else: - applicationName = applicationDomain - currentDir = os.getcwd() - os.chdir(applicationDirectoryPath) - - languageDict = getlanguageDict() - - for langCode in languageDict.keys(): - if langCode == 'en': - pass - else: - langPOfileName = "%s_%s.po" % (applicationName , langCode) - if os.path.exists(langPOfileName): - fileList = '' - for fileName in listOf_extraPo: - fileList += ("%s_%s.po " % (fileName,langCode)) - cmd = "msgcat -s --no-wrap %s %s > %s.cat" % (langPOfileName, fileList, langPOfileName) - if verbose: print cmd - os.system(cmd) - if targetDir is None: - pass - else: - mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) - cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po.cat" % (mo_targetDir,applicationName,applicationName,langCode) - if verbose: print cmd - os.system(cmd) - os.chdir(currentDir) - -# ----------------------------------------------------------------------------- -# m a k e M O ( ) -- Compile the Portable Object files into the Machine Object stored in the right location. -- -# ^^^^^^^^^^^^^^^ -# -def makeMO(applicationDirectoryPath,targetDir='./locale',applicationDomain=None, verbose=0, forceEnglish=0) : - """Compile the Portable Object files into the Machine Object stored in the right location. - - makeMO converts all translated language-specific PO files located inside - the application directory into the binary .MO files stored inside the - LC_MESSAGES sub-directory for the found locale files. - - makeMO searches for all files that have a name of the form 'app_xx.po' - inside the application directory specified by the first argument. The - 'app' is the application domain name (that can be specified by the - applicationDomain argument or is taken from the directory name). The 'xx' - corresponds to one of the ISO 639 two-letter language codes. - - makeMo stores the resulting files inside a sub-directory of `targetDir` - called xx/LC_MESSAGES where 'xx' corresponds to the 2-letter language - code. - """ - if targetDir is None: - targetDir = './locale' - if verbose: - print "Target directory for .mo files is: %s" % targetDir - - if applicationDomain is None: - applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) - else: - applicationName = applicationDomain - currentDir = os.getcwd() - os.chdir(applicationDirectoryPath) - - languageDict = getlanguageDict() - - for langCode in languageDict.keys(): - if (langCode == 'en') and (forceEnglish==0): - pass - else: - langPOfileName = "%s_%s.po" % (applicationName , langCode) - if os.path.exists(langPOfileName): - mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) - if not os.path.exists(mo_targetDir): - mkdir(mo_targetDir) - cmd = 'msgfmt --output-file="%s/%s.mo" "%s_%s.po"' % (mo_targetDir,applicationName,applicationName,langCode) - if verbose: print cmd - os.system(cmd) - os.chdir(currentDir) - -# ----------------------------------------------------------------------------- -# p r i n t U s a g e -- Displays how to use this script from the command line -- -# ^^^^^^^^^^^^^^^^^^^ -# -def printUsage(errorMsg=None) : - """Displays how to use this script from the command line.""" - print """ - ################################################################################## - # mki18n : Make internationalization files. # - # Uses the GNU gettext system to create PO (Portable Object) files # - # from source code, coimpile PO into MO (Machine Object) files. # - # Supports C,C++,Python source files. # - # # - # Usage: mki18n {OPTION} [appDirPath] # - # # - # Options: # - # -e : When -m is used, forces English .mo file creation # - # -h : prints this help # - # -m : make MO from existing PO files # - # -p : make PO, update PO files: Creates a new messages.pot # - # file. Creates a dom_xx.po.new for every existing # - # language specific .po file. ('xx' stands for the ISO639 # - # two-letter language code and 'dom' stands for the # - # application domain name). mki18n requires that you # - # write a 'app.fil' file which contains the list of all # - # source code to parse. # - # -v : verbose (prints comments while running) # - # --domain=appName : specifies the application domain name. By default # - # the directory name is used. # - # --moTarget=dir : specifies the directory where .mo files are stored. # - # If not specified, the target is './locale' # - # # - # You must specify one of the -p or -m option to perform the work. You can # - # specify the path of the target application. If you leave it out mki18n # - # will use the current directory as the application main directory. # - # # - ##################################################################################""" - if errorMsg: - print "\n ERROR: %s" % errorMsg - -# ----------------------------------------------------------------------------- -# f i l e B a s e O f ( ) -- Return base name of filename -- -# ^^^^^^^^^^^^^^^^^^^^^^^ -# -def fileBaseOf(filename,withPath=0) : - """fileBaseOf(filename,withPath) ---> string - - Return base name of filename. The returned string never includes the extension. - Use os.path.basename() to return the basename with the extension. The - second argument is optional. If specified and if set to 'true' (non zero) - the string returned contains the full path of the file name. Otherwise the - path is excluded. - - [Example] - >>> fn = 'd:/dev/telepath/tvapp/code/test.html' - >>> fileBaseOf(fn) - 'test' - >>> fileBaseOf(fn) - 'test' - >>> fileBaseOf(fn,1) - 'd:/dev/telepath/tvapp/code/test' - >>> fileBaseOf(fn,0) - 'test' - >>> fn = 'abcdef' - >>> fileBaseOf(fn) - 'abcdef' - >>> fileBaseOf(fn,1) - 'abcdef' - >>> fn = "abcdef." - >>> fileBaseOf(fn) - 'abcdef' - >>> fileBaseOf(fn,1) - 'abcdef' - """ - pos = filename.rfind('.') - if pos > 0: - filename = filename[:pos] - if withPath: - return filename - else: - return os.path.basename(filename) -# ----------------------------------------------------------------------------- -# m k d i r ( ) -- Create a directory (and possibly the entire tree) -- -# ^^^^^^^^^^^^^ -# -def mkdir(directory) : - """Create a directory (and possibly the entire tree). - - The os.mkdir() will fail to create a directory if one of the - directory in the specified path does not exist. mkdir() - solves this problem. It creates every intermediate directory - required to create the final path. Under Unix, the function - only supports forward slash separator, but under Windows and MacOS - the function supports the forward slash and the OS separator (backslash - under windows). - """ - - # translate the path separators - directory = unixpath(directory) - # build a list of all directory elements - aList = filter(lambda x: len(x)>0, directory.split('/')) - theLen = len(aList) - # if the first element is a Windows-style disk drive - # concatenate it with the first directory - if aList[0].endswith(':'): - if theLen > 1: - aList[1] = aList[0] + '/' + aList[1] - del aList[0] - theLen -= 1 - # if the original directory starts at root, - # make sure the first element of the list - # starts at root too - if directory[0] == '/': - aList[0] = '/' + aList[0] - # Now iterate through the list, check if the - # directory exists and if not create it - theDir = '' - for i in range(theLen): - theDir += aList[i] - if not os.path.exists(theDir): - os.mkdir(theDir) - theDir += '/' - -# ----------------------------------------------------------------------------- -# u n i x p a t h ( ) -- Return a path name that contains Unix separator. -- -# ^^^^^^^^^^^^^^^^^^^ -# -def unixpath(thePath) : - r"""Return a path name that contains Unix separator. - - [Example] - >>> unixpath(r"d:\test") - 'd:/test' - >>> unixpath("d:/test/file.txt") - 'd:/test/file.txt' - >>> - """ - thePath = os.path.normpath(thePath) - if os.sep == '/': - return thePath - else: - return thePath.replace(os.sep,'/') - -# ----------------------------------------------------------------------------- - -# S c r i p t e x e c u t i o n -- Runs when invoked from the command line -- -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# -if __name__ == "__main__": - import getopt # command line parsing - argc = len(sys.argv) - if argc == 1: - printUsage('Missing argument: specify at least one of -m or -p (or both).') - sys.exit(1) - # If there is some arguments, parse the command line - validOptions = "ehmpv" - validLongOptions = ['domain=', 'moTarget='] - option = {} - option['forceEnglish'] = 0 - option['mo'] = 0 - option['po'] = 0 - option['verbose'] = 0 - option['domain'] = None - option['moTarget'] = None - try: - optionList,pargs = getopt.getopt(sys.argv[1:],validOptions,validLongOptions) - except getopt.GetoptError, e: - printUsage(e[0]) - sys.exit(1) - for (opt,val) in optionList: - if (opt == '-h'): - printUsage() - sys.exit(0) - elif (opt == '-e'): option['forceEnglish'] = 1 - elif (opt == '-m'): option['mo'] = 1 - elif (opt == '-p'): option['po'] = 1 - elif (opt == '-v'): option['verbose'] = 1 - elif (opt == '--domain'): option['domain'] = val - elif (opt == '--moTarget'): option['moTarget'] = val - if len(pargs) == 0: - appDirPath = os.getcwd() - if option['verbose']: - print "No project directory given. Using current one: %s" % appDirPath - elif len(pargs) == 1: - appDirPath = pargs[0] - else: - printUsage('Too many arguments (%u). Use double quotes if you have space in directory name' % len(pargs)) - sys.exit(1) - if option['domain'] is None: - # If no domain specified, use the name of the target directory - option['domain'] = fileBaseOf(appDirPath) - if option['verbose']: - print "Application domain used is: '%s'" % option['domain'] - if option['po']: - try: - makePO(appDirPath,option['domain'],option['verbose']) - except IOError, e: - printUsage(e[1] + '\n You must write a file app.fil that contains the list of all files to parse.') - if option['mo']: - makeMO(appDirPath,option['moTarget'],option['domain'],option['verbose'],option['forceEnglish']) - sys.exit(1) - - -# ----------------------------------------------------------------------------- +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# +# PYTHON MODULE: MKI18N.PY +# ========= +# +# Abstract: Make Internationalization (i18n) files for an application. +# +# Copyright Pierre Rouleau. 2003. Released to public domain. +# +# Last update: Saturday, November 8, 2003. @ 15:55:18. +# +# File: ROUP2003N01::C:/dev/python/mki18n.py +# +# RCS $Header: //software/official/MKS/MKS_SI/TV_NT/dev/Python/rcs/mki18n.py 1.5 2003/11/05 19:40:04 PRouleau Exp $ +# +# Update history: +# +# - File created: Saturday, June 7, 2003. by Pierre Rouleau +# - 10/06/03 rcs : RCS Revision 1.1 2003/06/10 10:06:12 PRouleau +# - 10/06/03 rcs : RCS Initial revision +# - 23/08/03 rcs : RCS Revision 1.2 2003/06/10 10:54:27 PRouleau +# - 23/08/03 P.R.: [code:fix] : The strings encoded in this file are encode in iso-8859-1 format. Added the encoding +# notification to Python to comply with Python's 2.3 PEP 263. +# - 23/08/03 P.R.: [feature:new] : Added the '-e' switch which is used to force the creation of the empty English .mo file. +# - 22/10/03 P.R.: [code] : incorporated utility functions in here to make script self sufficient. +# - 05/11/03 rcs : RCS Revision 1.4 2003/10/22 06:39:31 PRouleau +# - 05/11/03 P.R.: [code:fix] : included the unixpath() in this file. +# - 08/11/03 rcs : RCS Revision 1.5 2003/11/05 19:40:04 PRouleau +# +# RCS $Log: $ +# +# +# ----------------------------------------------------------------------------- +""" +mki18n allows you to internationalize your software. You can use it to +create the GNU .po files (Portable Object) and the compiled .mo files +(Machine Object). + +mki18n module can be used from the command line or from within a script (see +the Usage at the end of this page). + + Table of Contents + ----------------- + + makePO() -- Build the Portable Object file for the application -- + catPO() -- Concatenate one or several PO files with the application domain files. -- + makeMO() -- Compile the Portable Object files into the Machine Object stored in the right location. -- + printUsage -- Displays how to use this script from the command line -- + + Scriptexecution -- Runs when invoked from the command line -- + + +NOTE: this module uses GNU gettext utilities. + +You can get the gettext tools from the following sites: + + - `GNU FTP site for gettetx`_ where several versions (0.10.40, 0.11.2, 0.11.5 and 0.12.1) are available. + Note that you need to use `GNU libiconv`_ to use this. Get it from the `GNU + libiconv ftp site`_ and get version 1.9.1 or later. Get the Windows .ZIP + files and install the packages inside c:/gnu. All binaries will be stored + inside c:/gnu/bin. Just put c:/gnu/bin inside your PATH. You will need + the following files: + + - `gettext-runtime-0.12.1.bin.woe32.zip`_ + - `gettext-tools-0.12.1.bin.woe32.zip`_ + - `libiconv-1.9.1.bin.woe32.zip`_ + + +.. _GNU libiconv: http://www.gnu.org/software/libiconv/ +.. _GNU libiconv ftp site: http://www.ibiblio.org/pub/gnu/libiconv/ +.. _gettext-runtime-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-runtime-0.12.1.bin.woe32.zip +.. _gettext-tools-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.12.1.bin.woe32.zip +.. _libiconv-1.9.1.bin.woe32.zip: http://www.ibiblio.org/pub/gnu/libiconv/libiconv-1.9.1.bin.woe32.zip + +""" +# ----------------------------------------------------------------------------- +# Module Import +# ------------- +# +import os +import sys +import wx +import re + +# ----------------------------------------------------------------------------- +# Global variables +# ---------------- +# + +__author__ = "Pierre Rouleau" +__version__= "$Revision: 1.5 $" + +# ----------------------------------------------------------------------------- + +def getlanguageDict(): + languageDict = {} + + if wx.VERSION >= (3, 0, 0): + app = wx.App() + else: + app = wx.PySimpleApp() + + for lang in [x for x in dir(wx) if x.startswith("LANGUAGE")]: + i = wx.Locale(wx.LANGUAGE_DEFAULT).GetLanguageInfo(getattr(wx, lang)) + if i: + languageDict[i.CanonicalName] = i.Description + + return languageDict + + + +def processCustomFiles(filein, fileout, regexp, prefix = ''): + appfil_file = open(filein, 'r') + messages_file = open(fileout, 'r') + messages = messages_file.read() + messages_file.close() + messages_file = open(fileout, 'a') + messages_file.write('\n') + messages_file.write('#: %s\n' % prefix) + messages_file.write('\n') + + words_found = {} + for filepath in appfil_file.xreadlines(): + code_file = open(filepath.strip(), 'r') + for match in regexp.finditer(code_file.read()): + word = match.group(1) + if not words_found.get(word, False) and messages.find("msgid \"%s\"\nmsgstr \"\"" % word) == -1: + words_found[word] = True + messages_file.write('\n') + messages_file.write("msgid \"%s\"\n"%word) + messages_file.write("msgstr \"\"\n") + code_file.close() + + messages_file.close() + appfil_file.close() + + +# ----------------------------------------------------------------------------- +# m a k e P O ( ) -- Build the Portable Object file for the application -- +# ^^^^^^^^^^^^^^^ +# +def makePO(applicationDirectoryPath, applicationDomain=None, verbose=0) : + """Build the Portable Object Template file for the application. + + makePO builds the .pot file for the application stored inside + a specified directory by running xgettext for all application source + files. It finds the name of all files by looking for a file called 'app.fil'. + If this file does not exists, makePo raises an IOError exception. + By default the application domain (the application + name) is the same as the directory name but it can be overridden by the + 'applicationDomain' argument. + + makePO always creates a new file called messages.pot. If it finds files + of the form app_xx.po where 'app' is the application name and 'xx' is one + of the ISO 639 two-letter language codes, makePO resynchronizes those + files with the latest extracted strings (now contained in messages.pot). + This process updates all line location number in the language-specific + .po files and may also create new entries for translation (or comment out + some). The .po file is not changed, instead a new file is created with + the .new extension appended to the name of the .po file. + + By default the function does not display what it is doing. Set the + verbose argument to 1 to force it to print its commands. + """ + + if applicationDomain is None: + applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) + else: + applicationName = applicationDomain + currentDir = os.getcwd() + os.chdir(applicationDirectoryPath) + filelist = 'app.fil' + if not os.path.exists(filelist): + raise IOError(2,'No module file: ' % filelist) + + fileout = 'messages.pot' + # Steps: + # Use xgettext to parse all application modules + # The following switches are used: + # + # -s : sort output by string content (easier to use when we need to merge several .po files) + # --files-from=app.fil : The list of files is taken from the file: app.fil + # --output= : specifies the name of the output file (using a .pot extension) + cmd = 'xgettext -s --no-wrap --language=Python --files-from=' + filelist + ' --output=' + fileout + ' --package-name ' + applicationName + if verbose: print cmd + os.system(cmd) + + XSD_STRING_MODEL = re.compile("<xsd\:(?:element|attribute) name=\"([^\"]*)\"[^\>]*\>") + processCustomFiles(filelist, fileout, XSD_STRING_MODEL, 'Extra XSD strings') + + XML_TC6_STRING_MODEL = re.compile("<documentation>\s*<xhtml\:p><!\[CDATA\[([^\]]*)\]\]></xhtml\:p>\s*</documentation>", re.MULTILINE | re.DOTALL) + processCustomFiles(filelist, fileout, XML_TC6_STRING_MODEL, 'Extra TC6 documentation strings') + + # generate messages.po + cmd = 'msginit --no-wrap --no-translator -i %s -l en_US.UTF-8 -o messages.po' % (fileout) + if verbose: print cmd + os.system(cmd) + + languageDict = getlanguageDict() + + for langCode in languageDict.keys(): + if langCode == 'en': + pass + else: + langPOfileName = "%s_%s.po" % (applicationName , langCode) + if os.path.exists(langPOfileName): + cmd = 'msgmerge -s --no-wrap "%s" %s > "%s.new"' % (langPOfileName, fileout, langPOfileName) + if verbose: print cmd + os.system(cmd) + os.chdir(currentDir) + +# ----------------------------------------------------------------------------- +# c a t P O ( ) -- Concatenate one or several PO files with the application domain files. -- +# ^^^^^^^^^^^^^ +# +def catPO(applicationDirectoryPath, listOf_extraPo, applicationDomain=None, targetDir=None, verbose=0) : + """Concatenate one or several PO files with the application domain files. + """ + + if applicationDomain is None: + applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) + else: + applicationName = applicationDomain + currentDir = os.getcwd() + os.chdir(applicationDirectoryPath) + + languageDict = getlanguageDict() + + for langCode in languageDict.keys(): + if langCode == 'en': + pass + else: + langPOfileName = "%s_%s.po" % (applicationName , langCode) + if os.path.exists(langPOfileName): + fileList = '' + for fileName in listOf_extraPo: + fileList += ("%s_%s.po " % (fileName,langCode)) + cmd = "msgcat -s --no-wrap %s %s > %s.cat" % (langPOfileName, fileList, langPOfileName) + if verbose: print cmd + os.system(cmd) + if targetDir is None: + pass + else: + mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) + cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po.cat" % (mo_targetDir,applicationName,applicationName,langCode) + if verbose: print cmd + os.system(cmd) + os.chdir(currentDir) + +# ----------------------------------------------------------------------------- +# m a k e M O ( ) -- Compile the Portable Object files into the Machine Object stored in the right location. -- +# ^^^^^^^^^^^^^^^ +# +def makeMO(applicationDirectoryPath,targetDir='./locale',applicationDomain=None, verbose=0, forceEnglish=0) : + """Compile the Portable Object files into the Machine Object stored in the right location. + + makeMO converts all translated language-specific PO files located inside + the application directory into the binary .MO files stored inside the + LC_MESSAGES sub-directory for the found locale files. + + makeMO searches for all files that have a name of the form 'app_xx.po' + inside the application directory specified by the first argument. The + 'app' is the application domain name (that can be specified by the + applicationDomain argument or is taken from the directory name). The 'xx' + corresponds to one of the ISO 639 two-letter language codes. + + makeMo stores the resulting files inside a sub-directory of `targetDir` + called xx/LC_MESSAGES where 'xx' corresponds to the 2-letter language + code. + """ + if targetDir is None: + targetDir = './locale' + if verbose: + print "Target directory for .mo files is: %s" % targetDir + + if applicationDomain is None: + applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) + else: + applicationName = applicationDomain + currentDir = os.getcwd() + os.chdir(applicationDirectoryPath) + + languageDict = getlanguageDict() + + for langCode in languageDict.keys(): + if (langCode == 'en') and (forceEnglish==0): + pass + else: + langPOfileName = "%s_%s.po" % (applicationName , langCode) + if os.path.exists(langPOfileName): + mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) + if not os.path.exists(mo_targetDir): + mkdir(mo_targetDir) + cmd = 'msgfmt --output-file="%s/%s.mo" "%s_%s.po"' % (mo_targetDir,applicationName,applicationName,langCode) + if verbose: print cmd + os.system(cmd) + os.chdir(currentDir) + +# ----------------------------------------------------------------------------- +# p r i n t U s a g e -- Displays how to use this script from the command line -- +# ^^^^^^^^^^^^^^^^^^^ +# +def printUsage(errorMsg=None) : + """Displays how to use this script from the command line.""" + print """ + ################################################################################## + # mki18n : Make internationalization files. # + # Uses the GNU gettext system to create PO (Portable Object) files # + # from source code, coimpile PO into MO (Machine Object) files. # + # Supports C,C++,Python source files. # + # # + # Usage: mki18n {OPTION} [appDirPath] # + # # + # Options: # + # -e : When -m is used, forces English .mo file creation # + # -h : prints this help # + # -m : make MO from existing PO files # + # -p : make PO, update PO files: Creates a new messages.pot # + # file. Creates a dom_xx.po.new for every existing # + # language specific .po file. ('xx' stands for the ISO639 # + # two-letter language code and 'dom' stands for the # + # application domain name). mki18n requires that you # + # write a 'app.fil' file which contains the list of all # + # source code to parse. # + # -v : verbose (prints comments while running) # + # --domain=appName : specifies the application domain name. By default # + # the directory name is used. # + # --moTarget=dir : specifies the directory where .mo files are stored. # + # If not specified, the target is './locale' # + # # + # You must specify one of the -p or -m option to perform the work. You can # + # specify the path of the target application. If you leave it out mki18n # + # will use the current directory as the application main directory. # + # # + ##################################################################################""" + if errorMsg: + print "\n ERROR: %s" % errorMsg + +# ----------------------------------------------------------------------------- +# f i l e B a s e O f ( ) -- Return base name of filename -- +# ^^^^^^^^^^^^^^^^^^^^^^^ +# +def fileBaseOf(filename,withPath=0) : + """fileBaseOf(filename,withPath) ---> string + + Return base name of filename. The returned string never includes the extension. + Use os.path.basename() to return the basename with the extension. The + second argument is optional. If specified and if set to 'true' (non zero) + the string returned contains the full path of the file name. Otherwise the + path is excluded. + + [Example] + >>> fn = 'd:/dev/telepath/tvapp/code/test.html' + >>> fileBaseOf(fn) + 'test' + >>> fileBaseOf(fn) + 'test' + >>> fileBaseOf(fn,1) + 'd:/dev/telepath/tvapp/code/test' + >>> fileBaseOf(fn,0) + 'test' + >>> fn = 'abcdef' + >>> fileBaseOf(fn) + 'abcdef' + >>> fileBaseOf(fn,1) + 'abcdef' + >>> fn = "abcdef." + >>> fileBaseOf(fn) + 'abcdef' + >>> fileBaseOf(fn,1) + 'abcdef' + """ + pos = filename.rfind('.') + if pos > 0: + filename = filename[:pos] + if withPath: + return filename + else: + return os.path.basename(filename) +# ----------------------------------------------------------------------------- +# m k d i r ( ) -- Create a directory (and possibly the entire tree) -- +# ^^^^^^^^^^^^^ +# +def mkdir(directory) : + """Create a directory (and possibly the entire tree). + + The os.mkdir() will fail to create a directory if one of the + directory in the specified path does not exist. mkdir() + solves this problem. It creates every intermediate directory + required to create the final path. Under Unix, the function + only supports forward slash separator, but under Windows and MacOS + the function supports the forward slash and the OS separator (backslash + under windows). + """ + + # translate the path separators + directory = unixpath(directory) + # build a list of all directory elements + aList = filter(lambda x: len(x)>0, directory.split('/')) + theLen = len(aList) + # if the first element is a Windows-style disk drive + # concatenate it with the first directory + if aList[0].endswith(':'): + if theLen > 1: + aList[1] = aList[0] + '/' + aList[1] + del aList[0] + theLen -= 1 + # if the original directory starts at root, + # make sure the first element of the list + # starts at root too + if directory[0] == '/': + aList[0] = '/' + aList[0] + # Now iterate through the list, check if the + # directory exists and if not create it + theDir = '' + for i in range(theLen): + theDir += aList[i] + if not os.path.exists(theDir): + os.mkdir(theDir) + theDir += '/' + +# ----------------------------------------------------------------------------- +# u n i x p a t h ( ) -- Return a path name that contains Unix separator. -- +# ^^^^^^^^^^^^^^^^^^^ +# +def unixpath(thePath) : + r"""Return a path name that contains Unix separator. + + [Example] + >>> unixpath(r"d:\test") + 'd:/test' + >>> unixpath("d:/test/file.txt") + 'd:/test/file.txt' + >>> + """ + thePath = os.path.normpath(thePath) + if os.sep == '/': + return thePath + else: + return thePath.replace(os.sep,'/') + +# ----------------------------------------------------------------------------- + +# S c r i p t e x e c u t i o n -- Runs when invoked from the command line -- +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +if __name__ == "__main__": + import getopt # command line parsing + argc = len(sys.argv) + if argc == 1: + printUsage('Missing argument: specify at least one of -m or -p (or both).') + sys.exit(1) + # If there is some arguments, parse the command line + validOptions = "ehmpv" + validLongOptions = ['domain=', 'moTarget='] + option = {} + option['forceEnglish'] = 0 + option['mo'] = 0 + option['po'] = 0 + option['verbose'] = 0 + option['domain'] = None + option['moTarget'] = None + try: + optionList,pargs = getopt.getopt(sys.argv[1:],validOptions,validLongOptions) + except getopt.GetoptError, e: + printUsage(e[0]) + sys.exit(1) + for (opt,val) in optionList: + if (opt == '-h'): + printUsage() + sys.exit(0) + elif (opt == '-e'): option['forceEnglish'] = 1 + elif (opt == '-m'): option['mo'] = 1 + elif (opt == '-p'): option['po'] = 1 + elif (opt == '-v'): option['verbose'] = 1 + elif (opt == '--domain'): option['domain'] = val + elif (opt == '--moTarget'): option['moTarget'] = val + if len(pargs) == 0: + appDirPath = os.getcwd() + if option['verbose']: + print "No project directory given. Using current one: %s" % appDirPath + elif len(pargs) == 1: + appDirPath = pargs[0] + else: + printUsage('Too many arguments (%u). Use double quotes if you have space in directory name' % len(pargs)) + sys.exit(1) + if option['domain'] is None: + # If no domain specified, use the name of the target directory + option['domain'] = fileBaseOf(appDirPath) + if option['verbose']: + print "Application domain used is: '%s'" % option['domain'] + if option['po']: + try: + makePO(appDirPath,option['domain'],option['verbose']) + except IOError, e: + printUsage(e[1] + '\n You must write a file app.fil that contains the list of all files to parse.') + if option['mo']: + makeMO(appDirPath,option['moTarget'],option['domain'],option['verbose'],option['forceEnglish']) + sys.exit(1) + + +# -----------------------------------------------------------------------------
--- a/targets/__init__.py Mon Aug 14 19:13:01 2017 +0300 +++ b/targets/__init__.py Mon Aug 14 21:09:48 2017 +0300 @@ -1,88 +1,88 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# This file is part of Beremiz, a Integrated Development Environment for -# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. -# -# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD -# Copyright (C) 2017: Andrey Skvortsov -# -# See COPYING file for copyrights details. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -# Package initialisation -#import targets - -""" -Beremiz Targets - -- Target are python packages, containing at least one "XSD" file -- Target class may inherit from a toolchain_(toolchainname) -- The target folder's name must match to name define in the XSD for TargetType -""" - -from os import listdir, path -import util.paths as paths - -_base_path = paths.AbsDir(__file__) -def _GetLocalTargetClassFactory(name): - return lambda:getattr(__import__(name,globals(),locals()), name+"_target") - -targets = dict([(name, {"xsd":path.join(_base_path, name, "XSD"), - "class":_GetLocalTargetClassFactory(name), - "code": { fname: path.join(_base_path, name, fname) - for fname in listdir(path.join(_base_path, name)) - if fname.startswith("plc_%s_main"%name) and - fname.endswith(".c")}}) - for name in listdir(_base_path) - if path.isdir(path.join(_base_path, name)) - and not name.startswith("__")]) - -toolchains = {"gcc": path.join(_base_path, "XSD_toolchain_gcc"), - "makefile": path.join(_base_path, "XSD_toolchain_makefile")} - -def GetBuilder(targetname): - return targets[targetname]["class"]() - -def GetTargetChoices(): - DictXSD_toolchain = {} - targetchoices = "" - - # Get all xsd toolchains - for toolchainname,xsdfilename in toolchains.iteritems() : - if path.isfile(xsdfilename): - DictXSD_toolchain["toolchain_"+toolchainname] = \ - open(xsdfilename).read() - - # Get all xsd targets - for targetname,nfo in targets.iteritems(): - xsd_string = open(nfo["xsd"]).read() - targetchoices += xsd_string%DictXSD_toolchain - - return targetchoices - -def GetTargetCode(targetname): - codedesc = targets[targetname]["code"] - code = "\n".join([open(fpath).read() for fname, fpath in sorted(codedesc.items())]) - return code - -def GetHeader(): - filename = paths.AbsNeighbourFile(__file__,"beremiz.h") - return open(filename).read() - -def GetCode(name): - filename = paths.AbsNeighbourFile(__file__,name) - return open(filename).read() +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# Copyright (C) 2017: Andrey Skvortsov +# +# See COPYING file for copyrights details. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# Package initialisation +#import targets + +""" +Beremiz Targets + +- Target are python packages, containing at least one "XSD" file +- Target class may inherit from a toolchain_(toolchainname) +- The target folder's name must match to name define in the XSD for TargetType +""" + +from os import listdir, path +import util.paths as paths + +_base_path = paths.AbsDir(__file__) +def _GetLocalTargetClassFactory(name): + return lambda:getattr(__import__(name,globals(),locals()), name+"_target") + +targets = dict([(name, {"xsd":path.join(_base_path, name, "XSD"), + "class":_GetLocalTargetClassFactory(name), + "code": { fname: path.join(_base_path, name, fname) + for fname in listdir(path.join(_base_path, name)) + if fname.startswith("plc_%s_main"%name) and + fname.endswith(".c")}}) + for name in listdir(_base_path) + if path.isdir(path.join(_base_path, name)) + and not name.startswith("__")]) + +toolchains = {"gcc": path.join(_base_path, "XSD_toolchain_gcc"), + "makefile": path.join(_base_path, "XSD_toolchain_makefile")} + +def GetBuilder(targetname): + return targets[targetname]["class"]() + +def GetTargetChoices(): + DictXSD_toolchain = {} + targetchoices = "" + + # Get all xsd toolchains + for toolchainname,xsdfilename in toolchains.iteritems() : + if path.isfile(xsdfilename): + DictXSD_toolchain["toolchain_"+toolchainname] = \ + open(xsdfilename).read() + + # Get all xsd targets + for targetname,nfo in targets.iteritems(): + xsd_string = open(nfo["xsd"]).read() + targetchoices += xsd_string%DictXSD_toolchain + + return targetchoices + +def GetTargetCode(targetname): + codedesc = targets[targetname]["code"] + code = "\n".join([open(fpath).read() for fname, fpath in sorted(codedesc.items())]) + return code + +def GetHeader(): + filename = paths.AbsNeighbourFile(__file__,"beremiz.h") + return open(filename).read() + +def GetCode(name): + filename = paths.AbsNeighbourFile(__file__,name) + return open(filename).read()
--- a/targets/toolchain_gcc.py Mon Aug 14 19:13:01 2017 +0300 +++ b/targets/toolchain_gcc.py Mon Aug 14 21:09:48 2017 +0300 @@ -1,237 +1,237 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# This file is part of Beremiz, a Integrated Development Environment for -# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. -# -# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD -# Copyright (C) 2017: Paul Beltyukov -# -# See COPYING file for copyrights details. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import os, re, operator -from util.ProcessLogger import ProcessLogger -import hashlib - -includes_re = re.compile('\s*#include\s*["<]([^">]*)[">].*') - -class toolchain_gcc(): - """ - This abstract class contains GCC specific code. - It cannot be used as this and should be inherited in a target specific - class such as target_linux or target_win32 - """ - def __init__(self, CTRInstance): - self.CTRInstance = CTRInstance - self.buildpath = None - self.SetBuildPath(self.CTRInstance._getBuildPath()) - - def getBuilderCFLAGS(self): - """ - Returns list of builder specific CFLAGS - """ - return [self.CTRInstance.GetTarget().getcontent().getCFLAGS()] - - def getBuilderLDFLAGS(self): - """ - Returns list of builder specific LDFLAGS - """ - return self.CTRInstance.LDFLAGS + \ - [self.CTRInstance.GetTarget().getcontent().getLDFLAGS()] - - def getCompiler(self): - """ - Returns compiler - """ - return self.CTRInstance.GetTarget().getcontent().getCompiler() - - def getLinker(self): - """ - Returns linker - """ - return self.CTRInstance.GetTarget().getcontent().getLinker() - - def GetBinaryCode(self): - try: - return open(self.exe_path, "rb").read() - except Exception, e: - return None - - def _GetMD5FileName(self): - return os.path.join(self.buildpath, "lastbuildPLC.md5") - - def ResetBinaryCodeMD5(self): - self.md5key = None - try: - os.remove(self._GetMD5FileName()) - except Exception, e: - pass - - def GetBinaryCodeMD5(self): - if self.md5key is not None: - return self.md5key - else: - try: - return open(self._GetMD5FileName(), "r").read() - except Exception, e: - return None - - def SetBuildPath(self, buildpath): - if self.buildpath != buildpath: - self.buildpath = buildpath - self.exe = self.CTRInstance.GetProjectName() + self.extension - self.exe_path = os.path.join(self.buildpath, self.exe) - self.md5key = None - self.srcmd5 = {} - - def append_cfile_deps(self, src, deps): - for l in src.splitlines(): - res = includes_re.match(l) - if res is not None: - depfn = res.groups()[0] - if os.path.exists(os.path.join(self.buildpath, depfn)): - deps.append(depfn) - - def concat_deps(self, bn): - # read source - src = open(os.path.join(self.buildpath, bn),"r").read() - # update direct dependencies - deps = [] - self.append_cfile_deps(src, deps) - # recurse through deps - # TODO detect cicular deps. - return reduce(operator.concat, map(self.concat_deps, deps), src) - - def check_and_update_hash_and_deps(self, bn): - # Get latest computed hash and deps - oldhash, deps = self.srcmd5.get(bn,(None,[])) - # read source - src = open(os.path.join(self.buildpath, bn)).read() - # compute new hash - newhash = hashlib.md5(src).hexdigest() - # compare - match = (oldhash == newhash) - if not match: - # file have changed - # update direct dependencies - deps = [] - self.append_cfile_deps(src, deps) - # store that hashand deps - self.srcmd5[bn] = (newhash, deps) - # recurse through deps - # TODO detect cicular deps. - return reduce(operator.and_, map(self.check_and_update_hash_and_deps, deps), match) - - def calc_source_md5(self): - wholesrcdata = "" - for Location, CFilesAndCFLAGS, DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS: - # Get CFiles list to give it to makefile - for CFile, CFLAGS in CFilesAndCFLAGS: - CFileName = os.path.basename(CFile) - wholesrcdata += self.concat_deps(CFileName) - return hashlib.md5(wholesrcdata).hexdigest() - - def calc_md5(self): - return hashlib.md5(self.GetBinaryCode()).hexdigest() - - def build(self): - # Retrieve compiler and linker - self.compiler = self.getCompiler() - self.linker = self.getLinker() - - Builder_CFLAGS = ' '.join(self.getBuilderCFLAGS()) - - ######### GENERATE OBJECT FILES ######################################## - obns = [] - objs = [] - relink = self.GetBinaryCode() is None - for Location, CFilesAndCFLAGS, DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS: - if CFilesAndCFLAGS: - if Location : - self.CTRInstance.logger.write(".".join(map(str,Location))+" :\n") - else: - self.CTRInstance.logger.write(_("PLC :\n")) - - for CFile, CFLAGS in CFilesAndCFLAGS: - if CFile.endswith(".c"): - bn = os.path.basename(CFile) - obn = os.path.splitext(bn)[0]+".o" - objectfilename = os.path.splitext(CFile)[0]+".o" - - match = self.check_and_update_hash_and_deps(bn) - - if match: - self.CTRInstance.logger.write(" [pass] "+bn+" -> "+obn+"\n") - else: - relink = True - - self.CTRInstance.logger.write(" [CC] "+bn+" -> "+obn+"\n") - - status, result, err_result = ProcessLogger( - self.CTRInstance.logger, - "\"%s\" -c \"%s\" -o \"%s\" %s %s"% - (self.compiler, CFile, objectfilename, Builder_CFLAGS, CFLAGS) - ).spin() - - if status : - self.srcmd5.pop(bn) - self.CTRInstance.logger.write_error(_("C compilation of %s failed.\n")%bn) - return False - obns.append(obn) - objs.append(objectfilename) - elif CFile.endswith(".o"): - obns.append(os.path.basename(CFile)) - objs.append(CFile) - - ######### GENERATE OUTPUT FILE ######################################## - # Link all the object files into one binary file - self.CTRInstance.logger.write(_("Linking :\n")) - if relink: - objstring = [] - - # Generate list .o files - listobjstring = '"' + '" "'.join(objs) + '"' - - ALLldflags = ' '.join(self.getBuilderLDFLAGS()) - - self.CTRInstance.logger.write(" [CC] " + ' '.join(obns)+" -> " + self.exe + "\n") - - status, result, err_result = ProcessLogger( - self.CTRInstance.logger, - "\"%s\" %s -o \"%s\" %s"% - (self.linker, - listobjstring, - self.exe_path, - ALLldflags) - ).spin() - - if status : - return False - - else: - self.CTRInstance.logger.write(" [pass] " + ' '.join(obns)+" -> " + self.exe + "\n") - - # Calculate md5 key and get data for the new created PLC - self.md5key = self.calc_md5() - - # Store new PLC filename based on md5 key - f = open(self._GetMD5FileName(), "w") - f.write(self.md5key) - f.close() - - return True - +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# Copyright (C) 2017: Paul Beltyukov +# +# See COPYING file for copyrights details. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import os, re, operator +from util.ProcessLogger import ProcessLogger +import hashlib + +includes_re = re.compile('\s*#include\s*["<]([^">]*)[">].*') + +class toolchain_gcc(): + """ + This abstract class contains GCC specific code. + It cannot be used as this and should be inherited in a target specific + class such as target_linux or target_win32 + """ + def __init__(self, CTRInstance): + self.CTRInstance = CTRInstance + self.buildpath = None + self.SetBuildPath(self.CTRInstance._getBuildPath()) + + def getBuilderCFLAGS(self): + """ + Returns list of builder specific CFLAGS + """ + return [self.CTRInstance.GetTarget().getcontent().getCFLAGS()] + + def getBuilderLDFLAGS(self): + """ + Returns list of builder specific LDFLAGS + """ + return self.CTRInstance.LDFLAGS + \ + [self.CTRInstance.GetTarget().getcontent().getLDFLAGS()] + + def getCompiler(self): + """ + Returns compiler + """ + return self.CTRInstance.GetTarget().getcontent().getCompiler() + + def getLinker(self): + """ + Returns linker + """ + return self.CTRInstance.GetTarget().getcontent().getLinker() + + def GetBinaryCode(self): + try: + return open(self.exe_path, "rb").read() + except Exception, e: + return None + + def _GetMD5FileName(self): + return os.path.join(self.buildpath, "lastbuildPLC.md5") + + def ResetBinaryCodeMD5(self): + self.md5key = None + try: + os.remove(self._GetMD5FileName()) + except Exception, e: + pass + + def GetBinaryCodeMD5(self): + if self.md5key is not None: + return self.md5key + else: + try: + return open(self._GetMD5FileName(), "r").read() + except Exception, e: + return None + + def SetBuildPath(self, buildpath): + if self.buildpath != buildpath: + self.buildpath = buildpath + self.exe = self.CTRInstance.GetProjectName() + self.extension + self.exe_path = os.path.join(self.buildpath, self.exe) + self.md5key = None + self.srcmd5 = {} + + def append_cfile_deps(self, src, deps): + for l in src.splitlines(): + res = includes_re.match(l) + if res is not None: + depfn = res.groups()[0] + if os.path.exists(os.path.join(self.buildpath, depfn)): + deps.append(depfn) + + def concat_deps(self, bn): + # read source + src = open(os.path.join(self.buildpath, bn),"r").read() + # update direct dependencies + deps = [] + self.append_cfile_deps(src, deps) + # recurse through deps + # TODO detect cicular deps. + return reduce(operator.concat, map(self.concat_deps, deps), src) + + def check_and_update_hash_and_deps(self, bn): + # Get latest computed hash and deps + oldhash, deps = self.srcmd5.get(bn,(None,[])) + # read source + src = open(os.path.join(self.buildpath, bn)).read() + # compute new hash + newhash = hashlib.md5(src).hexdigest() + # compare + match = (oldhash == newhash) + if not match: + # file have changed + # update direct dependencies + deps = [] + self.append_cfile_deps(src, deps) + # store that hashand deps + self.srcmd5[bn] = (newhash, deps) + # recurse through deps + # TODO detect cicular deps. + return reduce(operator.and_, map(self.check_and_update_hash_and_deps, deps), match) + + def calc_source_md5(self): + wholesrcdata = "" + for Location, CFilesAndCFLAGS, DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS: + # Get CFiles list to give it to makefile + for CFile, CFLAGS in CFilesAndCFLAGS: + CFileName = os.path.basename(CFile) + wholesrcdata += self.concat_deps(CFileName) + return hashlib.md5(wholesrcdata).hexdigest() + + def calc_md5(self): + return hashlib.md5(self.GetBinaryCode()).hexdigest() + + def build(self): + # Retrieve compiler and linker + self.compiler = self.getCompiler() + self.linker = self.getLinker() + + Builder_CFLAGS = ' '.join(self.getBuilderCFLAGS()) + + ######### GENERATE OBJECT FILES ######################################## + obns = [] + objs = [] + relink = self.GetBinaryCode() is None + for Location, CFilesAndCFLAGS, DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS: + if CFilesAndCFLAGS: + if Location : + self.CTRInstance.logger.write(".".join(map(str,Location))+" :\n") + else: + self.CTRInstance.logger.write(_("PLC :\n")) + + for CFile, CFLAGS in CFilesAndCFLAGS: + if CFile.endswith(".c"): + bn = os.path.basename(CFile) + obn = os.path.splitext(bn)[0]+".o" + objectfilename = os.path.splitext(CFile)[0]+".o" + + match = self.check_and_update_hash_and_deps(bn) + + if match: + self.CTRInstance.logger.write(" [pass] "+bn+" -> "+obn+"\n") + else: + relink = True + + self.CTRInstance.logger.write(" [CC] "+bn+" -> "+obn+"\n") + + status, result, err_result = ProcessLogger( + self.CTRInstance.logger, + "\"%s\" -c \"%s\" -o \"%s\" %s %s"% + (self.compiler, CFile, objectfilename, Builder_CFLAGS, CFLAGS) + ).spin() + + if status : + self.srcmd5.pop(bn) + self.CTRInstance.logger.write_error(_("C compilation of %s failed.\n")%bn) + return False + obns.append(obn) + objs.append(objectfilename) + elif CFile.endswith(".o"): + obns.append(os.path.basename(CFile)) + objs.append(CFile) + + ######### GENERATE OUTPUT FILE ######################################## + # Link all the object files into one binary file + self.CTRInstance.logger.write(_("Linking :\n")) + if relink: + objstring = [] + + # Generate list .o files + listobjstring = '"' + '" "'.join(objs) + '"' + + ALLldflags = ' '.join(self.getBuilderLDFLAGS()) + + self.CTRInstance.logger.write(" [CC] " + ' '.join(obns)+" -> " + self.exe + "\n") + + status, result, err_result = ProcessLogger( + self.CTRInstance.logger, + "\"%s\" %s -o \"%s\" %s"% + (self.linker, + listobjstring, + self.exe_path, + ALLldflags) + ).spin() + + if status : + return False + + else: + self.CTRInstance.logger.write(" [pass] " + ' '.join(obns)+" -> " + self.exe + "\n") + + # Calculate md5 key and get data for the new created PLC + self.md5key = self.calc_md5() + + # Store new PLC filename based on md5 key + f = open(self._GetMD5FileName(), "w") + f.write(self.md5key) + f.close() + + return True +