diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/__init__.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/__init__.py new file mode 100644 index 000000000..2d954c2f8 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/__init__.py @@ -0,0 +1,27 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +""" +""" + +from .util import getLoaders diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/__main__.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/__main__.py new file mode 100644 index 000000000..c3d609195 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/__main__.py @@ -0,0 +1,18 @@ +import sys +from pprint import pprint + +from .util import getLoaders + + +def main(conf): + cfg = {} + loaders = getLoaders(conf) + for loader in loaders: + cfg.update(loader.load()) + + cfg = loaders[0].to_dict(cfg) + + pprint(cfg) + + +main(sys.argv[1]) diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/abstract.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/abstract.py new file mode 100644 index 000000000..ba0fa18a3 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/abstract.py @@ -0,0 +1,94 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +"""""" +import abc + +from taurus.qt.qtgui.taurusgui.utils import ( + AppletDescription, + ExternalApp, + PanelDescription, + ToolBarDescription, +) + +# Python 2/3 compatibility +if not hasattr(abc, "ABC"): + setattr(abc, "ABC", abc.ABCMeta("ABC", (object,), {})) + + +__all__ = ["AbstractConfigLoader"] + + +class AbstractConfigLoader(abc.ABC): + """ + Abstract class for config loaders. + It defines interface which has to be implemneted by subclass in + order for it to be ConfigLoader + """ + + CONFIG_VALUES = [ + "GUI_NAME", + "ORGANIZATION", + "CUSTOM_LOGO", + "ORGANIZATION_LOGO", + "SINGLE_INSTANCE", + "MANUAL_URI", + "INIFILE", + "EXTRA_CATALOG_WIDGETS", + ] + + DESCRIPTIONS = [ + (AppletDescription, "AppletDescriptions"), + (ExternalApp, "ExternalApps"), + (PanelDescription, "PanelDescriptions"), + (ToolBarDescription, "ToolBarDescriptions"), + ] + + def __init__(self, confname): + self._confname = confname + + @abc.abstractmethod + def supports(self, confname): + """ + Return True or False for support of specific configuration passed + """ + return None + + @abc.abstractmethod + def load(self): + """ + This method is meant to load actual data from file on disk. + Return dictionary with configuration. + """ + return {} + + def to_dict(self, conf): + """ + Generate pure Python dictionary from conf + """ + + for _, section in self.DESCRIPTIONS: + objs = [o.to_dict() for o in conf.get(section, [])] + conf[section] = objs + + return conf diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/examples/config.json b/lib/taurus/qt/qtgui/taurusgui/config_loader/examples/config.json new file mode 100644 index 000000000..95489ba1e --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/examples/config.json @@ -0,0 +1,69 @@ +{ + "GUI_NAME": "EXAMPLE 01", + "ORGANIZATION": "Taurus", + "MANUAL_URI": "http://www.taurus-scada.org", + "SYNOPTIC": ["images/example01.jdw", "images/syn2.jdw"], + "INSTRUMENTS_FROM_POOL": false, + "PanelDescriptions": [ + { + "name": "NeXus Browser", + "classname": "taurus.qt.qtgui.extra_nexus.TaurusNeXusBrowser" + }, + { + "name": "BigInstrument", + "classname": "taurus.qt.qtgui.panel.TaurusAttrForm", + "model": "sys/tg_test/1" + }, + { + "name": "NeXus Browser", + "classname": "taurus.qt.qtgui.extra_nexus.TaurusNeXusBrowser" + }, + { + "name": "instrument1", + "classname": "taurus.qt.qtgui.panel.TaurusForm", + "model": [ + "sys/tg_test/1/double_scalar", + "sys/tg_test/1/short_scalar_ro", + "sys/tg_test/1/float_spectrum_ro", + "sys/tg_test/1/double_spectrum" + ] + }, + { + "name": "instrument2", + "classname": "taurus.qt.qtgui.panel.TaurusForm", + "model": [ + "sys/tg_test/1/wave", + "sys/tg_test/1/boolean_scalar" + ] + }, + { + "name": "Selected Instrument", + "classname": "taurus.external.qt.Qt.QLineEdit", + "sharedDataRead": { + "SelectedInstrument": "setText" + }, + "sharedDataWrite": { + "SelectedInstrument": "textEdited" + } + } + ], + "ToolBarDescriptions": [ + { + "name": "Empty Toolbar", + "classname": "taurus.external.qt.Qt.QToolBar" + } + ], + "ExternalApps": [ + { + "name": "xterm", + "cmdargs": ["xterm", "spock"], + "text": "Spock", + "icon": "utilities-terminal" + } + ], + "EXTRA_CATALOG_WIDGETS": [ + ["taurus.external.qt.Qt.QLineEdit", "logos:taurus.png"], + ["taurus.external.qt.Qt.QSpinBox", "images/syn2.jpg"], + ["taurus.external.qt.Qt.QLabel", null] + ] +} \ No newline at end of file diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/examples/config.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/examples/config.py new file mode 100644 index 000000000..e00aecc26 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/examples/config.py @@ -0,0 +1,70 @@ + + +from taurus.qt.qtgui.taurusgui.utils import PanelDescription, ExternalApp, ToolBarDescription, AppletDescription + +GUI_NAME = 'EXAMPLE 01' +ORGANIZATION = 'Taurus' + +MANUAL_URI = 'http://www.taurus-scada.org' + +SYNOPTIC = ['images/example01.jdw', 'images/syn2.jdw'] + + +INSTRUMENTS_FROM_POOL = False + + +nxbrowser = PanelDescription( + 'NeXus Browser', + classname='taurus.qt.qtgui.extra_nexus.TaurusNeXusBrowser' +) + +i0 = PanelDescription( + 'BigInstrument', + classname='taurus.qt.qtgui.panel.TaurusAttrForm', + model='sys/tg_test/1' +) + +i1 = PanelDescription( + 'instrument1', + classname='taurus.qt.qtgui.panel.TaurusForm', + model=['sys/tg_test/1/double_scalar', + 'sys/tg_test/1/short_scalar_ro', + 'sys/tg_test/1/float_spectrum_ro', + 'sys/tg_test/1/double_spectrum'] +) + +i2 = PanelDescription( + 'instrument2', + classname='taurus.qt.qtgui.panel.TaurusForm', + model=['sys/tg_test/1/wave', + 'sys/tg_test/1/boolean_scalar'] +) + +trend = PanelDescription( + 'Trend', + classname='taurus.qt.qtgui.plot.TaurusTrend', + model=['sys/tg_test/1/double_scalar'] +) + +connectionDemo = PanelDescription( + 'Selected Instrument', + classname='taurus.external.qt.Qt.QLineEdit', # A pure Qt widget! + sharedDataRead={'SelectedInstrument': 'setText'}, + sharedDataWrite={'SelectedInstrument': 'textEdited'} +) + +dummytoolbar = ToolBarDescription( + 'Empty Toolbar', + classname='taurus.external.qt.Qt.QToolBar' +) + +xterm = ExternalApp( + cmdargs=['xterm', 'spock'], text="Spock", icon='utilities-terminal') +hdfview = ExternalApp(["hdfview"]) +pymca = ExternalApp(['pymca']) + +EXTRA_CATALOG_WIDGETS = [ + ('taurus.external.qt.Qt.QLineEdit', 'logos:taurus.png'), # a resource + ('taurus.external.qt.Qt.QSpinBox', 'images/syn2.jpg'), # relative + # ('taurus.external.Qt.QTextEdit','/tmp/foo.png'), # absolute + ('taurus.external.qt.Qt.QLabel', None)] # none diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/jsonconf.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/jsonconf.py new file mode 100644 index 000000000..5c1afb4a5 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/jsonconf.py @@ -0,0 +1,93 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +"""""" + +import json +import os + +from taurus.qt.qtgui.taurusgui.config_loader.abstract import ( + AbstractConfigLoader, +) +from taurus.qt.qtgui.taurusgui.config_loader.util import ConfigLoaderError + +__all__ = ["JsonConfigLoader"] + + +class JsonConfigLoader(AbstractConfigLoader): + """ + Loads configuration for TaurusGui from JSON file + """ + + def __init__(self, confname): + super(JsonConfigLoader, self).__init__(confname) + self._data = {} + + def _get_objects(self, klass, section): + """ + Helper function to get list of Python objects from dictionary + """ + objs = [] + for o in self._data.get(section, []): + if isinstance(o, dict): + objs.append(klass(**o)) + return objs + + @staticmethod + def supports(confname): + if os.path.isfile(confname): + ext = os.path.splitext(confname)[-1] + if ext == ".json": + return True + return False + + def _get_data(self): + try: + with open(self._confname, "r") as fp: + self._data = json.load(fp) + except IOError as e: + raise ConfigLoaderError( + "Problem with accessing config file: " + str(e) + ) + except ValueError as e: + raise ConfigLoaderError( + "Problem with config file decoding: " + str(e) + ) + + def load(self): + self._get_data() + + tmp = {} + + for v in self.CONFIG_VALUES: + if v in self._data: + tmp[v] = self._data[v] + + for klass, section in self.DESCRIPTIONS: + tmp[section] = self._get_objects(klass, section) + + tmp["CONF_DIR"] = os.path.abspath( + os.path.dirname(self._confname) + ) + + return tmp diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/pyconf.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/pyconf.py new file mode 100644 index 000000000..c739f64bc --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/pyconf.py @@ -0,0 +1,145 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +"""""" + +import inspect +import os +import pkgutil +import sys +import types + +from taurus.qt.qtgui.taurusgui.config_loader.abstract import ( + AbstractConfigLoader, +) +from taurus.qt.qtgui.taurusgui.config_loader.util import ConfigLoaderError + +__all__ = ["PyConfigLoader"] + + +class PyConfigLoader(AbstractConfigLoader): + """ + Loads configuration for TaurusGui from Python module or package + """ + + def __init__(self, confname): + super(PyConfigLoader, self).__init__(confname) + self._mod = types.ModuleType( + "__dummy_conf_module_%s__" % confname + ) # dummy module + + def _get_objects(self, klass): + objs = [ + obj + for name, obj in inspect.getmembers(self._mod) + if isinstance(obj, klass) + ] + return objs + + def _importConfiguration(self): + """returns the module corresponding to `confname` or to + `tgconf_`. Note: the `conf` subdirectory of the directory in + which taurusgui.py file is installed is temporally prepended to sys.path + """ + confsubdir = os.path.join( + os.path.abspath(os.path.dirname(__file__)), "conf" + ) # the path to a conf subdirectory of the place where taurusgui.py is + oldpath = sys.path + try: + # add the conf subdirectory dir to the pythonpath + sys.path = [confsubdir] + sys.path + mod = __import__(self._confname) + except ImportError: + altconfname = "tgconf_%s" % self._confname + try: + mod = __import__(altconfname) + except ImportError: + msg = "cannot import %s or %s" % (self._confname, altconfname) + raise ConfigLoaderError(msg) + finally: + sys.path = oldpath # restore the previous sys.path + return mod + + @staticmethod + def supports(confname): + if os.path.exists(confname): + if os.path.isfile(confname): # happy path, we got file + ext = os.path.splitext(confname)[-1] + if ext == ".py": + return True + return False + + elif os.path.isdir(confname): + # if it's directory, assume it's importable Python package + return True + return False + else: + # not exisitng path, check if it is in top-level modules + if confname in list(pkgutil.iter_modules()): + return True + return False + + def _get_data(self): + """Reads a configuration file + + :param confname: (str or None) the name of module located in the + PYTHONPATH or in the conf subdirectory of the directory + in which taurusgui.py file is installed. + This method will try to import . + If that fails, it will try to import + `tgconf_`. + Alternatively, `confname` can be the path to the + configuration module (not necessarily in the + PYTHONPATH). + `confname` can also be None, in which case a dummy + empty module will be used. + """ + + # import the python config file + if os.path.exists(self._confname): # if confname is a dir or file name + import imp + + path, name = os.path.split(self._confname) + name, _ = os.path.splitext(name) + try: + f, filename, data = imp.find_module(name, [path]) + self._mod = imp.load_module(name, f, filename, data) + except ImportError: + self._mod = self._importConfiguration() + else: # if confname is not a dir name, we assume it is a module name in the python path + self._mod = self._importConfiguration() + + def load(self): + self._get_data() + + tmp = {} + tmp["CONF_DIR"] = os.path.abspath(os.path.dirname(self._mod.__file__)) + + for v in self.CONFIG_VALUES: + if hasattr(self._mod, v): + tmp[v] = getattr(self._mod, v) + + for klass, section in self.DESCRIPTIONS: + tmp[section] = self._get_objects(klass) + + return tmp diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/util.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/util.py new file mode 100644 index 000000000..b47ea7bc7 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/util.py @@ -0,0 +1,78 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +""" +This module provides factory 'getLoader' for proper ConfigLoader object +detected from 'confname' string. Each config loader has to implement interface +defined by AbstractConfigLoader class in +taurus.qt.qtgui.taurusgui.config_loader.abstract +""" + + +import pkg_resources + +from taurus import warning + +__all__ = ["getLoaders", "ConfigLoaderError"] + + +class ConfigLoaderError(Exception): + """ + Base exception raised by ConfigLoader + """ + + def __init__(self, message): + message = "Exception raised while loading configuration: " + message + super(ConfigLoaderError, self).__init__(message) + + +def getLoaders(confname): + """ + Discover proper config loader based on passed string. + It can be either path to file or directory or Python + abolute path to module with configuration. + + :param confname: name of configuration + :return: A AbstractConfigLoader subclass object + """ + + EP_GROUP_LOADERS = "taurus.gui.loaders" + + loaders = [] + for ep in pkg_resources.iter_entry_points(EP_GROUP_LOADERS): + try: + loader = ep.load() + if loader.supports(confname): + loaders.append(loader(confname)) + except Exception as e: + warning( + "Could not load config loader plugin '%s. Reason: '%s", + ep.name, + e, + ) + if not loaders: + raise NotImplementedError( + "No supported config loader for '%s'" % confname + ) + else: + return loaders diff --git a/lib/taurus/qt/qtgui/taurusgui/config_loader/xmlconf.py b/lib/taurus/qt/qtgui/taurusgui/config_loader/xmlconf.py new file mode 100644 index 000000000..e0bd96d88 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_loader/xmlconf.py @@ -0,0 +1,127 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +"""""" + +import os + +from lxml import etree +from taurus.qt.qtgui.taurusgui.config_loader.abstract import ( + AbstractConfigLoader, +) +from taurus.qt.qtgui.taurusgui.config_loader.util import ConfigLoaderError +from taurus.qt.qtgui.taurusgui.utils import ( + AppletDescription, + ExternalApp, + PanelDescription, + ToolBarDescription, +) + +__all__ = ["XmlConfigLoader"] + + +class XmlConfigLoader(AbstractConfigLoader): + """ + Loads configuration for TaurusGui from XML file + """ + + def __init__(self, confname): + super(XmlConfigLoader, self).__init__(confname) + self._root = etree.fromstring("") + + def _get(self, nodename, default=None): + """ + Helper method for getting data from XML + """ + name = self._root.find(nodename) + if name is None or name.text is None: + return default + else: + return name.text + + def _get_objects(self, klass, section): + """ + Helper method to get list of objects of given Python class + """ + objs = [] + obj = self._root.find(section) # 's' is for plural form + if obj is not None: + for child in obj: + if child.tag == klass.__name__: + child_str = etree.tostring(child, encoding="unicode") + o = klass.fromXml(child_str) + if o is not None: + objs.append(o) + return objs + + @staticmethod + def supports(confname): + if os.path.isfile(confname): + ext = os.path.splitext(confname)[-1] + if ext == ".xml": + return True + return False + + def _get_data(self): + """ + Get the xml root node from the xml configuration file + """ + try: + with open(self._confname, "r") as xmlFile: + self._root = etree.fromstring(xmlFile.read()) + except IOError as e: + raise ConfigLoaderError( + "Problem with accessing config file: " + str(e) + ) + except Exception as e: + msg = 'Error reading the XML file: "%s"' % self._confname + raise ConfigLoaderError(msg) + + def load(self): + self._get_data() + + tmp = {} + + for v in self.CONFIG_VALUES: + name = self._root.find(v) + if name is not None and name.text is not None: + tmp[v] = name.text + + for klass, section in self.DESCRIPTIONS: + tmp[section] = self._get_objects(klass, section) + + tmp["CONF_DIR"] = os.path.abspath(os.path.dirname(self._confname)) + + return tmp + + @property + def synoptics(self): + synoptic = [] + node = self._root.find("SYNOPTIC") + if (node is not None) and (node.text is not None): + for child in node: + s = child.get("str") + # we do not append empty strings + if s: + synoptic.append(s) + return synoptic diff --git a/lib/taurus/qt/qtgui/taurusgui/config_properties/__init__.py b/lib/taurus/qt/qtgui/taurusgui/config_properties/__init__.py new file mode 100644 index 000000000..b9f831f6d --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_properties/__init__.py @@ -0,0 +1,27 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +""" +""" + +from .util import getConfigPropertiesLoaders diff --git a/lib/taurus/qt/qtgui/taurusgui/config_properties/bck_compat.py b/lib/taurus/qt/qtgui/taurusgui/config_properties/bck_compat.py new file mode 100644 index 000000000..5474b815f --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_properties/bck_compat.py @@ -0,0 +1,42 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + + +""" +Module for backwards-compatible configuration loading +""" + + +def loadXmlConf(gui): + xml_conf = gui.getConfigValue("XML_CONF") + if xml_conf is None: + return + + try: + from taurus.qt.qtgui.taurusgui.config_loader.xmlconf import XmlConfigLoader + + xcl = XmlConfigLoader(xml_conf) + cfg = xcl.load() + gui.__config.update(cfg) + except Exception as e: + gui.warning("Could not load configuration from XML_CONF=%s" % xml_conf) diff --git a/lib/taurus/qt/qtgui/taurusgui/config_properties/deprecated.py b/lib/taurus/qt/qtgui/taurusgui/config_properties/deprecated.py new file mode 100644 index 000000000..3b52ed767 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_properties/deprecated.py @@ -0,0 +1,80 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +""" +Provide loading of deprecated TaurusGUI config properties +""" + +from taurus.external.qt import Qt + + +def loadMonitor(gui): + monitor_model = gui.getConfigValue("MONITOR", "") + if not monitor_model: + return + + try: + gui.splashScreen().showMessage("Creating applet monitor") + except AttributeError: + pass + + from taurus.qt.qtgui.qwt5.monitor import TaurusMonitorTiny + + w = TaurusMonitorTiny() + w.setModel(monitor_model) + # add the widget to the applets toolbar + gui.jorgsBar.addWidget(w) + # register the toolbar as delegate + gui.registerConfigDelegate(w, "monitor") + + +def loadConsole(gui): + """ + Deprecated CONSOLE command (if you need a IPython Console, just add a + Panel with a `silx.gui.console.IPythonWidget` + """ + # TODO: remove this method when making deprecation efective + if not gui.getConfigValue("CONSOLE", []): + return + + msg = ( + "createConsole() and the 'CONSOLE' configuration key are " + + "deprecated since 4.0.4. Add a panel with a " + + "silx.gui.console.IPythonWidget widdget instead" + ) + gui.deprecated(msg) + try: + from silx.gui.console import IPythonWidget + except ImportError: + gui.warning( + "Cannot import taurus.qt.qtgui.console. " + + "The Console Panel will not be available" + ) + return + console = IPythonWidget() + gui.createPanel( + console, + "Console", + permanent=True, + icon=Qt.QIcon.fromTheme("utilities-terminal"), + ) diff --git a/lib/taurus/qt/qtgui/taurusgui/config_properties/jdraw.py b/lib/taurus/qt/qtgui/taurusgui/config_properties/jdraw.py new file mode 100644 index 000000000..c0ab807e0 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_properties/jdraw.py @@ -0,0 +1,62 @@ +import os +import sys + +from future.utils import string_types +import taurus +from taurus.external.qt import Qt + + +def loadSynoptic(gui): + # Synoptics + synoptic = gui.getConfigValue("SYNOPTIC") + if isinstance(synoptic, string_types): # old config file style + gui.warning( + 'Deprecated usage of synoptic keyword (now it expects a list of paths). Please update your configuration file to: "synoptic=[\'%s\']".' % synoptic) + synoptic = [synoptic] + for s in synoptic: + _createMainSynoptic(gui, s) + + +def _createMainSynoptic(gui, synopticname): + ''' + Creates a synoptic panel and registers it as "SelectedInstrument" + reader and writer (allowing selecting instruments from synoptic + ''' + try: + jdwFileName = os.path.join(gui._confDirectory, synopticname) + from taurus.qt.qtgui.graphic import TaurusJDrawSynopticsView + synoptic = TaurusJDrawSynopticsView() + synoptic.setModel(jdwFileName) + gui.__synoptics.append(synoptic) + except Exception as e: + # print repr(e) + msg = 'Error loading synoptic file "%s".\nSynoptic won\'t be available' % jdwFileName + gui.error(msg) + gui.traceback(level=taurus.Info) + result = Qt.QMessageBox.critical(gui, 'Initialization error', '%s\n\n%s' % ( + msg, repr(e)), Qt.QMessageBox.Abort | Qt.QMessageBox.Ignore) + if result == Qt.QMessageBox.Abort: + sys.exit() + + Qt.qApp.SDM.connectWriter( + "SelectedInstrument", synoptic, "graphicItemSelected") + Qt.qApp.SDM.connectReader( + "SelectedInstrument", synoptic.selectGraphicItem) + + # find an unique (and short) name + name = os.path.splitext(os.path.basename(synopticname))[0] + if len(name) > 10: + name = 'Syn' + i = 2 + prefix = name + while name in gui.__panels: + name = '%s_%i' % (prefix, i) + i += 1 + + synopticpanel = gui.createPanel(synoptic, name, permanent=True, + icon=Qt.QIcon.fromTheme( + 'image-x-generic')) + + if gui.QUICK_ACCESS_TOOLBAR_ENABLED: + toggleSynopticAction = synopticpanel.toggleViewAction() + gui.quickAccessToolBar.addAction(toggleSynopticAction) diff --git a/lib/taurus/qt/qtgui/taurusgui/config_properties/sardana.py b/lib/taurus/qt/qtgui/taurusgui/config_properties/sardana.py new file mode 100644 index 000000000..3e55b21cc --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_properties/sardana.py @@ -0,0 +1,191 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + +""" +Module for loading Sardana-specific config properties +""" + +from operator import attrgetter + +from taurus import Device + + +def loadMacroServerName(gui): + macro_server_name = gui.getConfigValue("MACROSERVER_NAME", "") + if macro_server_name: + gui.macroserverNameChanged.emit(macro_server_name) + return macro_server_name + + +def loadMacroBroker(gui): + """configure macro infrastructure""" + ms = gui.getConfigValue("MACROSERVER_NAME", "") + mp = gui.getConfigValue("MACRO_PANELS", True) + # macro infrastructure will only be created if MACROSERVER_NAME is set + if ms and mp is True: + from sardana.taurus.qt.qtgui.macrolistener import MacroBroker + + gui.__macroBroker = MacroBroker(gui) + + +def loadDoorName(gui): + door_name = gui.getConfigValue("DOOR_NAME", True) + if door_name: + gui.doorNameChanged.emit(door_name) + + @staticmethod + def _loadMacroEditorsPath(gui): + macro_editors_path = gui.getConfigValue( + "MACRO_EDITORS_PATH", True + ) + if macro_editors_path: + from sardana.taurus.qt.qtgui.extra_macroexecutor.macroparameterseditor.macroparameterseditor import ( + ParamEditorManager, + ) + + ParamEditorManager().parsePaths(macro_editors_path) + ParamEditorManager().browsePaths() + +def loadInstrumentsFromPool(self, gui): + """ + Get panel descriptions from pool if required + """ + # todo: needs heavy refactor + ms = gui.getConfigValue("MACROSERVER_NAME", "") + if not ms: + return + + instruments_from_pool = gui.getConfigValue( + "INSTRUMENTS_FROM_POOL", False + ) + if instruments_from_pool: + try: + gui.splashScreen().showMessage( + "Gathering Instrument info from Pool" + ) + except AttributeError: + pass + + pool_instruments = _getInstrumentsFromPool(gui, ms) + if pool_instruments: + _createInstrumentPanels(gui, pool_instruments) + + +def _getInstrumentsFromPool(gui, macroservername): + """ + Get Instruments information form Pool. Return models for + each instrument. + + :param TaurusGui gui: instance of TaurusGui + :param str macroservername: name of MacroServer + + :return: Dicionary with Pool instruments where values are lists + of models + :rtype: dict()>) + """ + # todo: needs heavy refactor + instrument_dict = {} + try: + ms = Device(macroservername) + instruments = ms.getElementsOfType("Instrument") + if instruments is None: + raise Exception() + except Exception as e: + msg = 'Could not fetch Instrument list from "%s": %s' % ( + macroservername, + str(e), + ) + raise type(e)(msg) + + for i in instruments.values(): + instrument_dict[i.full_name] = [] + + pool_elements = [] + for kls in ("Moveable", "ExpChannel", "IORegister"): + pool_elements += sorted( + ms.getElementsWithInterface(kls).values(), + key=attrgetter("name"), + ) + + for elem in pool_elements: + instrument = elem.instrument + if instrument: + # ----------------------------------------------------------- + # Support sardana v<2.4 (which used tango names instead of + # taurus full names + e_name = elem.full_name + if not e_name.startswith("tango://"): + e_name = "tango://%s" % e_name + # ----------------------------------------------------------- + instrument_dict[instrument].append(e_name) + # filter out empty panels + ret = [i for i in instrument_dict if len(instrument_dict[i]) > 0] + return ret + + +def _createInstrumentPanels(gui, poolinstruments): + """ + Create GUI panels from Sardana Pool instruments. Each panel is a + TaurusForm grouping together all those elements that belong to + the same instrument according to the Pool info + + :param TaurusGui gui: isntance of TaurusGui + :param dict()) poolinstruments: dictionary where keys + are panel names and + values are lists of + models + :return: None + """ + + for name, model in poolinstruments.items(): + try: + try: + gui.splashScreen().showMessage( + "Creating instrument panel %s" % name + ) + except AttributeError: + pass + from taurus.qt.qtgui.panel.taurusform import TaurusForm + + w = TaurusForm() + + # ------------------------------------------------------------- + # Backwards-compat. Remove when removing CW map support + if gui._customWidgetMap: + w.setCustomWidgetMap(gui._customWidgetMap) + # ------------------------------------------------------------- + w.setModel(model) + + # the pool instruments may change when the pool config changes, + # so we do not store their config + gui.createPanel( + w, + name, + floating=False, + registerconfig=False, + instrumentkey=gui.IMPLICIT_ASSOCIATION, + permanent=True, + ) + except Exception as e: + msg = "Cannot create instrument panel %s: %s" % (name, str(e)) + raise type(e)(msg) diff --git a/lib/taurus/qt/qtgui/taurusgui/config_properties/util.py b/lib/taurus/qt/qtgui/taurusgui/config_properties/util.py new file mode 100644 index 000000000..662f9d395 --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/config_properties/util.py @@ -0,0 +1,49 @@ +############################################################################# +## +# This file is part of Taurus +## +# http://taurus-scada.org +## +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +# Taurus is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +## +# Taurus 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 Lesser General Public License for more details. +## +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +########################################################################### + + +import pkg_resources + +from taurus import warning + +__all__ = ["getConfigPropertiesLoaders"] + + +def getConfigPropertiesLoaders(): + """ + Get list of all registered callables + that can load additional config properties + """ + EP_GROUP_PROPS = "taurus.gui.properties" + + loaders = [] + for ep in pkg_resources.iter_entry_points(EP_GROUP_PROPS): + try: + loaders.append(ep.load()) + except Exception as e: + warning( + "Could not load config property plugin '%s. Reason: '%s", + ep.name, + e, + ) + return loaders diff --git a/lib/taurus/qt/qtgui/taurusgui/taurusgui.py b/lib/taurus/qt/qtgui/taurusgui/taurusgui.py index b51b2296f..cc51c4597 100644 --- a/lib/taurus/qt/qtgui/taurusgui/taurusgui.py +++ b/lib/taurus/qt/qtgui/taurusgui/taurusgui.py @@ -33,9 +33,7 @@ import copy import click import weakref -import inspect -from future.utils import string_types from lxml import etree import taurus @@ -46,12 +44,12 @@ from taurus.qt.qtgui.util import TaurusWidgetFactory from taurus.qt.qtgui.base import TaurusBaseWidget, TaurusBaseComponent from taurus.qt.qtgui.container import TaurusMainWindow -from taurus.qt.qtgui.taurusgui.utils import (ExternalApp, PanelDescription, - ToolBarDescription, - AppletDescription) +from taurus.qt.qtgui.taurusgui.utils import ExternalApp, PanelDescription from taurus.qt.qtgui.util.ui import UILoadable from taurus.qt.qtgui.taurusgui.utils import ExternalAppAction from taurus.core.util.log import deprecation_decorator +from taurus.qt.qtgui.taurusgui.config_loader import getLoaders +from taurus.qt.qtgui.taurusgui.confif_properties import getConfigPropertiesLoaders __all__ = ["DockWidgetPanel", "TaurusGui"] @@ -302,6 +300,24 @@ def __init__(self, parent=None, confname=None, configRecursionDepth=None, if configRecursionDepth is not None: self.defaultConfigRecursionDepth = configRecursionDepth + # default gui configuration + self.__conf = { + "GUI_NAME": confname, + "ORGANIZATION": str(Qt.qApp.organizationName()) or 'Taurus', + "CUSTOM_LOGO": 'logos:taurus.png', + "ORGANIZATION_LOGO": getattr(tauruscustomsettings, "ORGANIZATION_LOGO", "logos:taurus.png"), + "SINGLE_INSTANCE": True, + "EXTRA_CATALOG_WIDGETS": [], + "MANUAL_URI": taurus.Release.url, + "SYNOPTIC": [], + "CONF_DIR": os.path.expanduser("~"), # maybe '~/.config' ? + "INIFILE": os.path.join(os.path.expanduser("~", "default.ini")), + "PanelDescriptions": [], + "ToolbarDescriptions": [], + "AppletDescriptions": [], + "ExternalApps": [], + } + self.__panels = {} self.__external_app = {} self.__external_app_actions = {} @@ -864,189 +880,19 @@ def createCustomPanel(self, paneldesc=None): msg = 'Panel %s created. Drag items to it or use the context menu to customize it' % w.name self.newShortMessage.emit(msg) - def createMainSynoptic(self, synopticname): - ''' - Creates a synoptic panel and registers it as "SelectedInstrument" - reader and writer (allowing selecting instruments from synoptic - ''' - try: - jdwFileName = os.path.join(self._confDirectory, synopticname) - from taurus.qt.qtgui.graphic import TaurusJDrawSynopticsView - synoptic = TaurusJDrawSynopticsView() - synoptic.setModel(jdwFileName) - self.__synoptics.append(synoptic) - except Exception as e: - # print repr(e) - msg = 'Error loading synoptic file "%s".\nSynoptic won\'t be available' % jdwFileName - self.error(msg) - self.traceback(level=taurus.Info) - result = Qt.QMessageBox.critical(self, 'Initialization error', '%s\n\n%s' % ( - msg, repr(e)), Qt.QMessageBox.Abort | Qt.QMessageBox.Ignore) - if result == Qt.QMessageBox.Abort: - sys.exit() - - Qt.qApp.SDM.connectWriter( - "SelectedInstrument", synoptic, "graphicItemSelected") - Qt.qApp.SDM.connectReader( - "SelectedInstrument", synoptic.selectGraphicItem) - - # find an unique (and short) name - name = os.path.splitext(os.path.basename(synopticname))[0] - if len(name) > 10: - name = 'Syn' - i = 2 - prefix = name - while name in self.__panels: - name = '%s_%i' % (prefix, i) - i += 1 - - synopticpanel = self.createPanel(synoptic, name, permanent=True, - icon=Qt.QIcon.fromTheme( - 'image-x-generic')) - - if self.QUICK_ACCESS_TOOLBAR_ENABLED: - toggleSynopticAction = synopticpanel.toggleViewAction() - self.quickAccessToolBar.addAction(toggleSynopticAction) - - def createConsole(self, kernels): - msg = ('createConsole() and the "CONSOLE" configuration key are ' + - 'deprecated since 4.0.4. Add a panel with a ' + - 'silx.gui.console.IPythonWidget widdget instead') - self.deprecated(msg) - try: - from silx.gui.console import IPythonWidget - except ImportError: - self.warning('Cannot import taurus.qt.qtgui.console. ' + - 'The Console Panel will not be available') - return - console = IPythonWidget() - self.createPanel(console, "Console", permanent=True, - icon=Qt.QIcon.fromTheme('utilities-terminal')) - - def createInstrumentsFromPool(self, macroservername): - ''' - Creates a list of instrument panel descriptions by gathering the info - from the Pool. Each panel is a TaurusForm grouping together all those - elements that belong to the same instrument according to the Pool info - - :return: (list) - ''' - instrument_dict = {} - try: - ms = taurus.Device(macroservername) - instruments = ms.getElementsOfType('Instrument') - if instruments is None: - raise Exception() - except Exception as e: - msg = 'Could not fetch Instrument list from "%s"' % macroservername - self.error(msg) - result = Qt.QMessageBox.critical(self, 'Initialization error', '%s\n\n%s' % ( - msg, repr(e)), Qt.QMessageBox.Abort | Qt.QMessageBox.Ignore) - if result == Qt.QMessageBox.Abort: - sys.exit() - return [] - for i in instruments.values(): - i_name = i.full_name - #i_name, i_unknown, i_type, i_pools = i.split() - i_view = PanelDescription( - i_name, classname='TaurusForm', floating=False, model=[]) - instrument_dict[i_name] = i_view - - from operator import attrgetter - pool_elements = sorted(ms.getElementsWithInterface( - 'Moveable').values(), key=attrgetter('name')) - pool_elements += sorted(ms.getElementsWithInterface( - 'ExpChannel').values(), key=attrgetter('name')) - pool_elements += sorted(ms.getElementsWithInterface( - 'IORegister').values(), key=attrgetter('name')) - for elem in pool_elements: - instrument = elem.instrument - if instrument: - i_name = instrument - # ----------------------------------------------------------- - # Support sardana v<2.4 (which used tango names instead of - # taurus full names - e_name = elem.full_name - if not e_name.startswith("tango://"): - e_name = "tango://%s" % e_name - # ----------------------------------------------------------- - instrument_dict[i_name].model.append(e_name) - # filter out empty panels - ret = [instrument for instrument in instrument_dict.values() - if len(instrument.model) > 0] - return ret - - def __getVarFromXML(self, root, nodename, default=None): - name = root.find(nodename) - if name is None or name.text is None: - return default - else: - return name.text - - def _importConfiguration(self, confname): - '''returns the module corresponding to `confname` or to - `tgconf_`. Note: the `conf` subdirectory of the directory in - which taurusgui.py file is installed is temporally prepended to sys.path - ''' - confsubdir = os.path.join(os.path.abspath(os.path.dirname( - __file__)), 'conf') # the path to a conf subdirectory of the place where taurusgui.py is - oldpath = sys.path - try: - # add the conf subdirectory dir to the pythonpath - sys.path = [confsubdir] + sys.path - conf = __import__(confname) - except ImportError: - altconfname = "tgconf_%s" % confname - try: - conf = __import__(altconfname) - except ImportError: - msg = 'cannot import %s or %s' % (confname, altconfname) - self.error(msg) - Qt.QMessageBox.critical( - self, 'Initialization error', msg, Qt.QMessageBox.Abort) - sys.exit() - finally: - sys.path = oldpath # restore the previous sys.path - return conf + def getConfigValue(self, field, default=None): + if default is not None: + return self.__conf.get(field, default) + return self.__conf[field] def loadConfiguration(self, confname): '''Reads a configuration file - - :param confname: (str or None) the name of module located in the - PYTHONPATH or in the conf subdirectory of the directory - in which taurusgui.py file is installed. - This method will try to import . - If that fails, it will try to import - `tgconf_`. - Alternatively, `confname` can be the path to the - configuration module (not necessarily in the - PYTHONPATH). - `confname` can also be None, in which case a dummy - empty module will be used. ''' - - # import the python config file try: - if confname is None: - import types - conf = types.ModuleType( - '__dummy_conf_module__') # dummy module - confname = str(Qt.qApp.applicationName()) - self._confDirectory = '' - elif os.path.exists(confname): # if confname is a dir or file name - import imp - path, name = os.path.split(confname) - name, _ = os.path.splitext(name) - try: - f, filename, data = imp.find_module(name, [path]) - conf = imp.load_module(name, f, filename, data) - confname = name - except ImportError: - conf = self._importConfiguration(confname) - self._confDirectory = os.path.dirname(conf.__file__) - else: # if confname is not a dir name, we assume it is a module name in the python path - conf = self._importConfiguration(confname) - self._confDirectory = os.path.dirname(conf.__file__) + loaders = getLoaders(confname) + for loader in loaders: + self.__conf.update(loader.load()) + self._confDirectory = self.getConfigValue("CONF_DIR") except Exception: import traceback msg = 'Error loading configuration: %s' % traceback.format_exc() # repr(e) @@ -1055,80 +901,49 @@ def loadConfiguration(self, confname): self, 'Initialization error', msg, Qt.QMessageBox.Abort) sys.exit() - xmlroot = self._loadXmlConfig(conf) - - self._loadAppName(conf, confname, xmlroot) - self._loadOrgName(conf, xmlroot) - self._loadCustomLogo(conf, xmlroot) + self._loadAppName(confname) + self._loadOrgName() + self._loadCustomLogo() # do some extra config if we have a TaurusApplication _app = Qt.QApplication.instance() if hasattr(_app, 'basicConfig'): _app.basicConfig() - self._loadOrgLogo(conf, xmlroot) + self._loadOrgLogo() - self._loadSingleInstance(conf, xmlroot) + self._loadSingleInstance() - self._loadExtraCatalogWidgets(conf, xmlroot) - self._loadManualUri(conf, xmlroot) - POOLINSTRUMENTS = self._loadSardanaOptions(conf, xmlroot) - self._loadSynoptic(conf, xmlroot) - # TODO: remove deprecated _loadConsole - self._loadConsole(conf, xmlroot) + self._loadExtraCatalogWidgets() + self._loadManualUri() - self._loadCustomPanels(conf, xmlroot, POOLINSTRUMENTS) - self._loadCustomToolBars(conf, xmlroot) - self._loadCustomApplets(conf, xmlroot) - self._loadExternalApps(conf, xmlroot) - self._loadIniFile(conf, xmlroot) - - def _loadXmlConfig(self, conf): - """ - Get the xml root node from the xml configuration file - """ + self._loadCustomPanels() + self._loadCustomToolBars() + self._loadCustomApplets() + self._loadExternalApps() + self._loadIniFile() - xml_config = getattr(conf, 'XML_CONFIG', None) - if xml_config is None: - self._xmlConfigFileName = None - else: - self._xmlConfigFileName = os.path.join( - self._confDirectory, xml_config) - # default fallback (in case of I/O or parse errors) - xmlroot = etree.fromstring('') - if xml_config is not None: - try: - # If a relative name was given, the conf directory will be used - # as base path - xmlfname = os.path.join(self._confDirectory, xml_config) - xmlFile = open(xmlfname, 'r') - xmlstring = xmlFile.read() - xmlFile.close() - xmlroot = etree.fromstring(xmlstring) - except Exception as e: - msg = 'Error reading the XML file: "%s"' % xmlfname - self.error(msg) - self.traceback(level=taurus.Info) - result = Qt.QMessageBox.critical(self, 'Initialization error', '%s\nReason:"%s"' % ( - msg, repr(e)), Qt.QMessageBox.Abort | Qt.QMessageBox.Ignore) - if result == Qt.QMessageBox.Abort: - sys.exit() - return xmlroot - - def _loadAppName(self, conf, confname, xmlroot): - appname = getattr(conf, 'GUI_NAME', self.__getVarFromXML( - xmlroot, "GUI_NAME", confname)) + extra_config_property_loaders = getConfigPropertiesLoaders() + if extra_config_property_loaders: + for ext in extra_config_property_loaders: + try: + ext(self) + except Exception: + import traceback + msg = 'Error loading extra configuration property: %s' % traceback.format_exc() # repr(e) + self.warning(msg) + + def _loadAppName(self, confname): + appname = self.getConfigValue("GUI_NAME") Qt.qApp.setApplicationName(appname) self.setWindowTitle(appname) - def _loadOrgName(self, conf, xmlroot): - orgname = getattr(conf, 'ORGANIZATION', self.__getVarFromXML( - xmlroot, "ORGANIZATION", str(Qt.qApp.organizationName()) or 'Taurus')) + def _loadOrgName(self): + orgname = self.getConfigValue("ORGANIZATION") Qt.qApp.setOrganizationName(orgname) - def _loadCustomLogo(self, conf, xmlroot): - custom_logo = getattr(conf, 'CUSTOM_LOGO', getattr( - conf, 'LOGO', self.__getVarFromXML(xmlroot, "CUSTOM_LOGO", 'logos:taurus.png'))) + def _loadCustomLogo(self): + custom_logo = self.getConfigValue("CUSTOM_LOGO") if Qt.QFile.exists(custom_logo): custom_icon = Qt.QIcon(custom_logo) else: @@ -1138,15 +953,8 @@ def _loadCustomLogo(self, conf, xmlroot): if self.APPLETS_TOOLBAR_ENABLED: self.jorgsBar.addAction(custom_icon, Qt.qApp.applicationName()) - def _loadOrgLogo(self, conf, xmlroot): - logo = getattr(tauruscustomsettings, - "ORGANIZATION_LOGO", - "logos:taurus.png") - org_logo = getattr(conf, - "ORGANIZATION_LOGO", - self.__getVarFromXML(xmlroot, - "ORGANIZATION_LOGO", - logo)) + def _loadOrgLogo(self): + org_logo = self.getConfigValue("ORGANIZATION_LOGO") if Qt.QFile.exists(org_logo): org_icon = Qt.QIcon(org_logo) else: @@ -1155,12 +963,12 @@ def _loadOrgLogo(self, conf, xmlroot): if self.APPLETS_TOOLBAR_ENABLED: self.jorgsBar.addAction(org_icon, Qt.qApp.organizationName()) - def _loadSingleInstance(self, conf, xmlroot): + def _loadSingleInstance(self): """ if required, enforce that only one instance of this GUI can be run """ - single_inst = getattr(conf, 'SINGLE_INSTANCE', (self.__getVarFromXML( - xmlroot, "SINGLE_INSTANCE", 'True').lower() == 'true')) + single_inst = self.getConfigValue("SINGLE_INSTANCE") + if single_inst: if not self.checkSingleInstance(): msg = 'Only one instance of %s is allowed to run the same time' % ( @@ -1170,12 +978,12 @@ def _loadSingleInstance(self, conf, xmlroot): self, 'Multiple copies', msg, Qt.QMessageBox.Abort) sys.exit(1) - def _loadExtraCatalogWidgets(self, conf, xmlroot): + def _loadExtraCatalogWidgets(self): """ get custom widget catalog entries """ # @todo: support also loading from xml - extra_catalog_widgets = getattr(conf, 'EXTRA_CATALOG_WIDGETS', []) + extra_catalog_widgets = self.getConfigValue("EXTRA_CATALOG_WIDGETS") self._extraCatalogWidgets = [] for class_name, pix_map_name in extra_catalog_widgets: # If a relative file name is given, the conf directory will be used @@ -1184,126 +992,23 @@ def _loadExtraCatalogWidgets(self, conf, xmlroot): pix_map_name = os.path.join(self._confDirectory, pix_map_name) self._extraCatalogWidgets.append((class_name, pix_map_name)) - def _loadManualUri(self, conf, xmlroot): + def _loadManualUri(self): """ manual panel """ - manual_uri = getattr(conf, 'MANUAL_URI', - self.__getVarFromXML(xmlroot, "MANUAL_URI", - taurus.Release.url)) + manual_uri = self.getConfigValue("MANUAL_URI") self.setHelpManualURI(manual_uri) if self.HELP_MENU_ENABLED: self.createPanel(self.helpManualBrowser, 'Manual', permanent=True, icon=Qt.QIcon.fromTheme('help-browser')) - ### SARDANA MACRO STUFF ON - def _loadSardanaOptions(self, conf, xmlroot): - """configure macro infrastructure""" - ms = self._loadMacroServerName(conf, xmlroot) - mp = self._loadMacroPanels(conf, xmlroot) - # macro infrastructure will only be created if MACROSERVER_NAME is set - if ms is not None and mp is True: - from sardana.taurus.qt.qtgui.macrolistener import MacroBroker - self.__macroBroker = MacroBroker(self) - self._loadDoorName(conf, xmlroot) - self._loadMacroEditorsPath(conf, xmlroot) - pool_instruments = self._loadInstrumentsFromPool(conf, xmlroot, ms) - return pool_instruments - - def _loadMacroServerName(self, conf, xmlroot): - macro_server_name = getattr(conf, "MACROSERVER_NAME", self.__getVarFromXML( - xmlroot, "MACROSERVER_NAME", None)) - if macro_server_name: - self.macroserverNameChanged.emit(macro_server_name) - return macro_server_name - - def _loadMacroPanels(self, conf, xmlroot): - macro_panels = getattr(conf, "MACRO_PANELS", self.__getVarFromXML( - xmlroot, "MACRO_PANELS", True)) - return macro_panels - - def _loadDoorName(self, conf, xmlroot): - door_name = getattr(conf, "DOOR_NAME", - self.__getVarFromXML(xmlroot, "DOOR_NAME", '')) - if door_name: - self.doorNameChanged.emit(door_name) - - def _loadMacroEditorsPath(self, conf, xmlroot): - macro_editors_path = getattr(conf, "MACROEDITORS_PATH", self.__getVarFromXML( - xmlroot, "MACROEDITORS_PATH", "")) - if macro_editors_path: - from sardana.taurus.qt.qtgui.extra_macroexecutor.macroparameterseditor.macroparameterseditor import \ - ParamEditorManager - ParamEditorManager().parsePaths(macro_editors_path) - ParamEditorManager().browsePaths() - - def _loadInstrumentsFromPool(self, conf, xmlroot, macro_server_name): - """ - Get panel descriptions from pool if required - """ - instruments_from_pool = getattr(conf, "INSTRUMENTS_FROM_POOL", (self.__getVarFromXML( - xmlroot, "INSTRUMENTS_FROM_POOL", "False").lower() == "true")) - if instruments_from_pool: - try: - self.splashScreen().showMessage("Gathering Instrument info from Pool") - except AttributeError: - pass - pool_instruments = self.createInstrumentsFromPool( - macro_server_name) # auto create instruments from pool - else: - pool_instruments = [] - return pool_instruments - ### SARDANA MACRO STUFF OFF - - def _loadSynoptic(self, conf, xmlroot): - # Synoptics - synoptic = getattr(conf, 'SYNOPTIC', None) - if isinstance(synoptic, string_types): # old config file style - self.warning( - 'Deprecated usage of synoptic keyword (now it expects a list of paths). Please update your configuration file to: "synoptic=[\'%s\']".' % synoptic) - synoptic = [synoptic] - if synoptic is None: # we look in the xml config file if not present in the python config - synoptic = [] - node = xmlroot.find("SYNOPTIC") - if (node is not None) and (node.text is not None): - for child in node: - s = child.get("str") - # we do not append empty strings - if s is not None and len(s): - synoptic.append(s) - for s in synoptic: - self.createMainSynoptic(s) - - def _loadConsole(self, conf, xmlroot): - """ - Deprecated CONSOLE command (if you need a IPython Console, just add a - Panel with a `silx.gui.console.IPythonWidget` - """ - # TODO: remove this method when making deprecation efective - console = getattr(conf, 'CONSOLE', self.__getVarFromXML( - xmlroot, "CONSOLE", [])) - if console: - self.createConsole([]) - - def _loadCustomPanels(self, conf, xmlroot, poolinstruments=None): + def _loadCustomPanels(self): """ get custom panel descriptions from the python config file, xml config and create panels based on the panel descriptions """ - custom_panels = [obj for name, obj in inspect.getmembers( - conf) if isinstance(obj, PanelDescription)] - - panel_descriptions = xmlroot.find("PanelDescriptions") - if panel_descriptions is not None: - for child in panel_descriptions: - if child.tag == "PanelDescription": - child_str = etree.tostring(child, encoding='unicode') - pd = PanelDescription.fromXml(child_str) - if pd is not None: - custom_panels.append(pd) - - for p in custom_panels + poolinstruments: + for p in self.getConfigValue("PanelDescriptions"): try: try: self.splashScreen().showMessage("Creating panel %s" % p.name) @@ -1347,16 +1052,13 @@ def _loadCustomPanels(self, conf, xmlroot, poolinstruments=None): w.setFormat(p.widget_formatter) icon = p.icon - # the pool instruments may change when the pool config changes, - # so we do not store their config - registerconfig = p not in poolinstruments # create a panel self.createPanel( w, p.name, floating=p.floating, - registerconfig=registerconfig, + registerconfig=True, instrumentkey=instrumentkey, permanent=True, icon=icon @@ -1371,24 +1073,12 @@ def _loadCustomPanels(self, conf, xmlroot, poolinstruments=None): if result == Qt.QMessageBox.Abort: sys.exit() - def _loadCustomToolBars(self, conf, xmlroot): + def _loadCustomToolBars(self): """ get custom toolbars descriptions from the python config file, xml config and create toolbars based on the descriptions """ - custom_toolbars = [obj for name, obj in inspect.getmembers( - conf) if isinstance(obj, ToolBarDescription)] - - tool_bar_descriptions = xmlroot.find("ToolBarDescriptions") - if tool_bar_descriptions is not None: - for child in tool_bar_descriptions: - if child.tag == "ToolBarDescription": - child_str = etree.tostring(child, encoding='unicode') - d = ToolBarDescription.fromXml(child_str) - if d is not None: - custom_toolbars.append(d) - - for d in custom_toolbars: + for d in self.getConfigValue("ToolbarDescriptinos"): try: try: self.splashScreen().showMessage("Creating Toolbar %s" % d.name) @@ -1416,32 +1106,12 @@ def _loadCustomToolBars(self, conf, xmlroot): if result == Qt.QMessageBox.Abort: sys.exit() - def _loadCustomApplets(self, conf, xmlroot): + def _loadCustomApplets(self): """ get custom applet descriptions from the python config file, xml config and create applet based on the descriptions """ - custom_applets = [] - # for backwards compatibility - MONITOR = getattr( - conf, "MONITOR", self.__getVarFromXML(xmlroot, "MONITOR", [])) - if MONITOR: - custom_applets.append(AppletDescription( - "monitor", classname="TaurusMonitorTiny", model=MONITOR)) - - custom_applets += [obj for name, obj in inspect.getmembers( - conf) if isinstance(obj, AppletDescription)] - - applet_descriptions = xmlroot.find("AppletDescriptions") - if applet_descriptions is not None: - for child in applet_descriptions: - if child.tag == "AppletDescription": - child_str = etree.tostring(child, encoding='unicode') - d = AppletDescription.fromXml(child_str) - if d is not None: - custom_applets.append(d) - - for d in custom_applets: + for d in self.getConfigValue("AppletDescriptions"): try: try: self.splashScreen().showMessage("Creating applet %s" % d.name) @@ -1465,36 +1135,20 @@ def _loadCustomApplets(self, conf, xmlroot): if result == Qt.QMessageBox.Abort: sys.exit() - def _loadExternalApps(self, conf, xmlroot): + def _loadExternalApps(self): """ add external applications from both the python and the xml config files """ - external_apps = [obj for name, obj in inspect.getmembers( - conf) if isinstance(obj, ExternalApp)] - - ext_apps_node = xmlroot.find("ExternalApps") - if ext_apps_node is not None: - for child in ext_apps_node: - if child.tag == "ExternalApp": - child_str = etree.tostring(child, encoding='unicode') - ea = ExternalApp.fromXml(child_str) - if ea is not None: - external_apps.append(ea) - - for a in external_apps: + for a in self.getConfigValue("ExternalApps"): self._external_app_names.append(str(a.getAction().text())) self.addExternalAppLauncher(a.getAction()) - def _loadIniFile(self, conf, xmlroot): + def _loadIniFile(self): """ get the "factory settings" filename. By default, it is called "default.ini" and resides in the configuration dir """ - - ini_file = getattr(conf, 'INIFILE', self.__getVarFromXML( - xmlroot, "INIFILE", "default.ini")) - # if a relative name is given, the conf dir is used as the root path - ini_file_name = os.path.join(self._confDirectory, ini_file) + ini_file = self.getConfigValue("INIFILE") # read the settings (or the factory settings if the regular file is not # found) @@ -1505,7 +1159,7 @@ def _loadIniFile(self, conf, xmlroot): self.splashScreen().showMessage(msg) except AttributeError: pass - self.loadSettings(factorySettingsFileName=ini_file_name) + self.loadSettings(factorySettingsFileName=ini_file) def setLockView(self, locked): self.setModifiableByUser(not locked) diff --git a/lib/taurus/qt/qtgui/taurusgui/test/test_configs/good.py b/lib/taurus/qt/qtgui/taurusgui/test/test_configs/good.py new file mode 100644 index 000000000..749e02dda --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/test/test_configs/good.py @@ -0,0 +1,22 @@ +from taurus.qt.qtgui.taurusgui import utils + + +GUI_NAME = "TestGUI" +ORGANIZATION = "TestOrg" +CUSTOM_LOGO = "" +ORGANIZATION_LOGO = "" +SINGLE_INSTANCE = False +MANUAL_URI = "http://example.com" +INIFILE = "" + +p1 = utils.PanelDescription( + "testpanel1", + classname="taurus.qt.qtgui.panel.TaurusForm", + model=["eval:1"], +) + +p2 = utils.PanelDescription( + "testpanel2", + classname="taurus.qt.qtgui.panel.TaurusForm", + model=["eval:2"], +) diff --git a/lib/taurus/qt/qtgui/taurusgui/test/test_python_config.py b/lib/taurus/qt/qtgui/taurusgui/test/test_python_config.py new file mode 100644 index 000000000..c72d6dfcc --- /dev/null +++ b/lib/taurus/qt/qtgui/taurusgui/test/test_python_config.py @@ -0,0 +1,26 @@ +import os + +from taurus.qt.qtgui.taurusgui import TaurusGui +from taurus.qt.qtgui.panel import TaurusForm + + +def test_good_config(qtbot): + conf = os.path.join(os.path.abspath(__file__), "test_configs", "good.py") + gui = TaurusGui(confname=conf, configRecursionDepth=0) + + qtbot.addWidget(gui) + assert gui.getConfigValue("GUI_NAME") == "TestGUI" + assert gui.getConfigValue("ORGANIZATION") == "TestOrg" + assert gui.getConfigValue("CUSTOM_LOGO") == "" + assert gui.getConfigValue("ORGANIZATION_LOGO") == "" + assert gui.getConfigValue("SINGLE_INSTANCE") is False + assert gui.getConfigValue("MANUAL_URI") == "http://example.com" + assert gui.getConfigValue("INIFILE") == "" + + w1 = gui.getPanel('testpanel1').widget() + qtbot.addWidget(w1) + assert isinstance(w1, TaurusForm) + + w2 = gui.getPanel('testpanel2').widget() + qtbot.addWidget(w2) + assert isinstance(w2, TaurusForm) diff --git a/lib/taurus/qt/qtgui/taurusgui/utils.py b/lib/taurus/qt/qtgui/taurusgui/utils.py index a1d9ade0c..8b54eead9 100644 --- a/lib/taurus/qt/qtgui/taurusgui/utils.py +++ b/lib/taurus/qt/qtgui/taurusgui/utils.py @@ -113,6 +113,14 @@ def fromXml(xmlstring): return ExternalApp(" ".join((command, params)), text=text, icon=icon) + def to_dict(self): + tmp = { + "args": self.args, + "kwargs": self.kwargs, + } + + return tmp + class TaurusGuiComponentDescription(object): ''' @@ -343,6 +351,23 @@ def fromXml(xmlstring): floating=floating, sharedDataWrite=sharedDataWrite, sharedDataRead=sharedDataRead, model=model) + def to_dict(self): + """ + Return dictionary representation of data + """ + tmp = { + "name": self.name, + "classname": self.classname, + "modulename": self.modulename, + "widgetname": self.widgetname, + "floating": self.floating, + "sharedDataWrite": self.sharedDataWrite, + "sharedDataRead": self.sharedDataRead, + "model": self.model, + } + + return tmp + #========================================================================= # Properties #========================================================================= @@ -433,6 +458,22 @@ def fromPanel(panel): widget_properties=panel.widget_properties ) + def to_dict(self): + """ + Return dictionary representation of data + """ + from_parent = TaurusGuiComponentDescription.to_dict(self) + custom = { + "instrumentkey": self.instrumentkey, + "icon": self.icon, + "model_in_config": self.model_in_config, + "modifiable_by_user": self.modifiable_by_user, + "widget_formatter": self.widget_formatter, + "widget_properties": self.widget_properties, + } + from_parent.update(custom) + return from_parent + class ToolBarDescription(TaurusGuiComponentDescription): ''' diff --git a/setup.py b/setup.py index 3ae782d9f..114f5744a 100644 --- a/setup.py +++ b/setup.py @@ -136,6 +136,23 @@ def get_release_info(): '{:.5f} = taurus.qt.qtgui.base:floatFormatter', ] +config_loaders = [ + 'taurus.py = taurus.qt.qtgui.taurusgui.config_loader.pyconf:PyConfigLoader', + 'taurus.json = taurus.qt.qtgui.taurusgui.config_loader.jsonconf:JsonConfigLoader', + 'taurus.xml = taurus.qt.qtgui.taurusgui.config_loader.xmlconf:XmlConfigLoader', +] + +config_properties = [ + 'DOOR_NAME = taurus.qt.qtgui.taurusgui.config_properties.sardana:loadDoorName', + 'MACROSERVER_NAME = taurus.qt.qtgui.taurusgui.config_properties.sardana:loadMacroServerName', + 'MACRO_PANELS = taurus.qt.qtgui.taurusgui.config_properties.sardana:loadMacroBroker', + 'INSTRUMENTS_FROM_POOL = taurus.qt.qtgui.taurusgui.config_properties.sardana:loadInstrumentsFromPool', + 'MONITOR = taurus.qt.qtgui.taurusgui.config_properties.deprecated:loadMonitor', + 'CONSOLE = taurus.qt.qtgui.taurusgui.config_properties.deprecated:loadConsole', + 'XML_CONF = taurus.qt.qtgui.taurusgui.config_properties.bck_compat:loadXmlConf', + 'SYNOPTIC = taurus.qt.qtgui.taurusgui.config_properties.jdraw:loadSynoptic' +] + entry_points = { 'console_scripts': console_scripts, 'taurus.cli.subcommands': taurus_subcommands, @@ -145,6 +162,8 @@ def get_release_info(): 'taurus.trend.alts': trend_alternatives, 'taurus.trend2d.alts': trend2d_alternatives, 'taurus.image.alts': image_alternatives, + 'taurus.gui.loaders': config_loaders, + 'taurus.gui.properties' : config_properties, } classifiers = [