From 100cc56603db3413d3f24a6be17157f707a6c45c Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 6 Jul 2017 16:35:57 +0200 Subject: [PATCH 001/288] Force hostname conversion from PQDN to FQDN The uses of Partially-Qualified Domain Names causes troubles in the Tango Authority creation. Fix it, reimplementing TangoNameValidatorXXX.getUriGroups methods to use Fully-Qualified Domain Name for the host name. --- lib/taurus/core/tango/tangovalidator.py | 40 +++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/tango/tangovalidator.py b/lib/taurus/core/tango/tangovalidator.py index d3bcfa6f9..9b30807ab 100644 --- a/lib/taurus/core/tango/tangovalidator.py +++ b/lib/taurus/core/tango/tangovalidator.py @@ -30,7 +30,7 @@ __docformat__ = "restructuredtext" - +import socket from taurus.core.taurusvalidator import (TaurusAttributeNameValidator, TaurusDeviceNameValidator, TaurusAuthorityNameValidator) @@ -57,6 +57,17 @@ class TangoAuthorityNameValidator(TaurusAuthorityNameValidator): query = '(?!)' fragment = '(?!)' + def getUriGroups(self, name, strict=None): + '''Reimplementation of getUriGroups to fix the host and authority + name using fully qualified domain name for the host. + ''' + ret = TaurusAuthorityNameValidator.getUriGroups(self, name, strict) + if ret is not None: + fqdn = socket.getfqdn(ret["host"]) + ret["host"] = fqdn + ret["authority"] = "//{host}:{port}".format(**ret) + return ret + class TangoDeviceNameValidator(TaurusDeviceNameValidator): '''Validator for Tango device names. Apart from the standard named @@ -80,6 +91,17 @@ class TangoDeviceNameValidator(TaurusDeviceNameValidator): query = '(?!)' fragment = '(?!)' + def getUriGroups(self, name, strict=None): + '''Reimplementation of getUriGroups to fix the host and authority + name using fully qualified domain name for the host. + ''' + ret = TaurusDeviceNameValidator.getUriGroups(self, name, strict) + if ret is not None and ret.get("host", None) is not None: + fqdn = socket.getfqdn(ret["host"]) + ret["host"] = fqdn + ret["authority"] = "//{host}:{port}".format(**ret) + return ret + def getNames(self, fullname, factory=None, queryAuth=True): '''reimplemented from :class:`TaurusDeviceNameValidator`. It accepts an extra keyword arg `queryAuth` which, if set to False, will prevent the @@ -98,7 +120,10 @@ def getNames(self, fullname, factory=None, queryAuth=True): if default_authority is None: import PyTango - default_authority = "//" + PyTango.ApiUtil.get_env_var('TANGO_HOST') + host, port = PyTango.ApiUtil.get_env_var('TANGO_HOST').split(":") + # Get the fully qualified domain name + host = socket.getfqdn(host) + default_authority = "//{0}:{1}".format(host, port) authority = groups.get('authority') if authority is None: @@ -178,6 +203,17 @@ class TangoAttributeNameValidator(TaurusAttributeNameValidator): query = '(?!)' fragment = '(?P[^# ]*)' + def getUriGroups(self, name, strict=None): + '''Reimplementation of getUriGroups to fix the host and authority + name using fully qualified domain name for the host. + ''' + ret = TaurusDeviceNameValidator.getUriGroups(self, name, strict) + if ret is not None and ret.get("host", None) is not None: + fqdn = socket.getfqdn(ret["host"]) + ret["host"] = fqdn + ret["authority"] = "//{host}:{port}".format(**ret) + return ret + def getNames(self, fullname, factory=None, queryAuth=True, fragment=False): """Returns the complete and short names""" From 8c4a294352549a7f1ca73191a12fda216ccdddb1 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 6 Jul 2017 17:30:16 +0200 Subject: [PATCH 002/288] Fix copy-paste error Testsuite is failing due to a copy-paste error in the previous commit. Fix it --- lib/taurus/core/tango/tangovalidator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangovalidator.py b/lib/taurus/core/tango/tangovalidator.py index 9b30807ab..71cdab2e4 100644 --- a/lib/taurus/core/tango/tangovalidator.py +++ b/lib/taurus/core/tango/tangovalidator.py @@ -207,7 +207,7 @@ def getUriGroups(self, name, strict=None): '''Reimplementation of getUriGroups to fix the host and authority name using fully qualified domain name for the host. ''' - ret = TaurusDeviceNameValidator.getUriGroups(self, name, strict) + ret = TaurusAttributeNameValidator.getUriGroups(self, name, strict) if ret is not None and ret.get("host", None) is not None: fqdn = socket.getfqdn(ret["host"]) ret["host"] = fqdn From 502fb98c71d570e1b4c39d9d783750fee3464424 Mon Sep 17 00:00:00 2001 From: zreszela Date: Tue, 11 Jul 2017 17:30:38 +0200 Subject: [PATCH 003/288] Fix bckcomp for old-style fragments in TV TaurusValue widget and more precisely its DefaultLabelWidget is not backwards compatible with old-style fragments e.g. . Fix it. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 671ae1565..713b5aad6 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -106,11 +106,11 @@ def setModel(self, model): TaurusLabel.setModel(self, None) self.setText(devName) - _BCK_COMPAT_TAGS = {'': '{attr.name}', - '': '{attr.fullname}', - '': '{dev.name}', - '': '{dev_name}', - '': '{dev.fullname}', + _BCK_COMPAT_TAGS = {'': 'name', + '': 'fullname', + '': 'parentObj.name', + '': 'parentObj.name', + '': 'parentObj.fullname', } def getDisplayValue(self, cache=True, fragmentName=None): @@ -121,9 +121,7 @@ def getDisplayValue(self, cache=True, fragmentName=None): new = self._BCK_COMPAT_TAGS.get(old, '{attr.%s}' % old) self.deprecated(dep=old, alt=new) fragmentName = fragmentName.replace(old, new) - attr = self.getModelObj() - dev = attr.getParent() - return fragmentName.format(dev=dev, attr=attr) + return TaurusLabel.getDisplayValue(self, cache, fragmentName) def sizeHint(self): return Qt.QSize(Qt.QLabel.sizeHint(self).width(), 18) @@ -346,7 +344,7 @@ def __init__(self, parent=None, designMode=False, customWidgetMap=None): self._allowWrite = True self._minimumHeight = None - self._labelConfig = '{attr.label}' + self._labelConfig = 'label' self.setModifiableByUser(False) if parent is not None: From 002a3bd6d82cd388edd79d00e3d3e1138acc52a8 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 12 Jul 2017 13:19:43 +0200 Subject: [PATCH 004/288] Add MIXEDcase attribute to TangoSchemeTest device MIXEDcase attribute may be useful for testing if taurus respects case sensitivity of some attribute characteristics e.g. label. Add it. --- lib/taurus/core/tango/test/res/TangoSchemeTest | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest index 9bdfdf7e6..1a9730d00 100755 --- a/lib/taurus/core/tango/test/res/TangoSchemeTest +++ b/lib/taurus/core/tango/test/res/TangoSchemeTest @@ -142,6 +142,7 @@ class TangoSchemeTest(Device): 'uchar_image': dict(unit=default_unit["int"], dtype=[(numpy.uint8,)], max_dim_x=MAXDIMX, max_dim_y=MAXDIMY), + 'MIXEDcase': dict(dtype=str) } extra_cfg = {'short_scalar': dict(min_value=default_ranges['int'][0], @@ -201,6 +202,8 @@ class TangoSchemeTest(Device): **attrs['float_image']) string_image_ro = attribute(access=AttrWriteType.READ, **attrs['string_image']) + MIXEDcase = attribute(access=AttrWriteType.READ, + **attrs['MIXEDcase']) # READ/WRITE ATTRIBUTES # SCALARS @@ -309,6 +312,9 @@ class TangoSchemeTest(Device): def read_uchar_image_ro(self): return [[self.default_rvalue['uchar']] * self.DIMX] * self.DIMY + def read_MIXEDcase(self): + return "MIXEDcase" + # READ/WRITE METHODS # SCALARS def read_boolean_scalar(self): From 6ba6d8d5c3d05c07c64a09633072449abaa57c9a Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 12 Jul 2017 13:25:21 +0200 Subject: [PATCH 005/288] Adapt test_bug126 to the proper use of fragments setLabelConfig is foreseen for selecting a given fragment from the label widget model. The test_bug126 test uses it directly to alternate the label text. This was accidentally working due to the wrong behavior of the setLabelConfig method. Fix the test by using the MIXEDcase attribute. --- lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py index 7c4441a46..0fd9d6463 100644 --- a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py @@ -51,9 +51,8 @@ def test_bug126(self): '''Verify that case is not lost when customizing a label (bug#126)''' w = self._widget # self._widget.setModel('eval:1') - self._widget.setModel('tango:' + DEV_NAME + '/double_scalar') + self._widget.setModel('tango:' + DEV_NAME + '/MIXEDcase') label = 'MIXEDcase' - w.setLabelConfig(label) self.processEvents(repetitions=10, sleep=.1) shownLabel = str(w.labelWidget().text()) msg = 'Shown label ("%s") differs from set label ("%s")' % (shownLabel, From 51d3e7500018ec405d94188a7e9eabeb0c45fc66 Mon Sep 17 00:00:00 2001 From: Philippe Gauron Date: Wed, 12 Jul 2017 15:28:58 +0200 Subject: [PATCH 006/288] fixing typo and menu changes. --- doc/source/users/introduction.rst | 2 +- doc/source/users/ui/arrayeditor.rst | 4 ++-- doc/source/users/ui/plot.rst | 2 +- doc/source/users/ui/taurusgui.rst | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/users/introduction.rst b/doc/source/users/introduction.rst index 3523dcdaf..a8526ff31 100644 --- a/doc/source/users/introduction.rst +++ b/doc/source/users/introduction.rst @@ -13,7 +13,7 @@ systems (such as EPICS_) and data sources. Tango-related examples. We intend to gradually introduce more non-Tango examples -Taurus was developed within the Sardana_ project, but since it has being found +Taurus was developed within the Sardana_ project, but since it has been found useful for other projects not related to Sardana, it has been moved to a separate project (although both projects are kept in sync and share most of their developers). diff --git a/doc/source/users/ui/arrayeditor.rst b/doc/source/users/ui/arrayeditor.rst index 0bd3c5ce5..40d972e0c 100644 --- a/doc/source/users/ui/arrayeditor.rst +++ b/doc/source/users/ui/arrayeditor.rst @@ -14,7 +14,7 @@ The :class:`ArrayEditor` is a widget for graphically editing a spectrum. It consists of two :ref:`plots ` and a *control point area*. The plot on top shows the current and modified spectrum. The other plot shows the difference between the current and the modified spectrum. The Control point -area shows details on the conrol points that have been defined. +area shows details on the control points that have been defined. The spectrum can be modified by adding control points and moving them along the vertical axis, either by setting the value in the controls area or by @@ -29,4 +29,4 @@ controls area. The arrow buttons in the controls area will help in propagating the related value to the other control points to the left or to the right of the selected -one. \ No newline at end of file +one. diff --git a/doc/source/users/ui/plot.rst b/doc/source/users/ui/plot.rst index 0bacef699..b5a639430 100644 --- a/doc/source/users/ui/plot.rst +++ b/doc/source/users/ui/plot.rst @@ -157,7 +157,7 @@ Storing and recovering current configuration Once you have customized the way the plot looks (see the `Plot Configuration dialog`_ section), you may want to save the settings for -later use. This can be done using the `Store current settings` option from the +later use. This can be done using the `Save current settings` option from the `TaurusPlot context menu`_. This will save which curves should be plotted and how they should look. diff --git a/doc/source/users/ui/taurusgui.rst b/doc/source/users/ui/taurusgui.rst index d678ef9b8..439e9d567 100644 --- a/doc/source/users/ui/taurusgui.rst +++ b/doc/source/users/ui/taurusgui.rst @@ -151,7 +151,7 @@ want to extend the application by adding custom panels to provide more features (e.g., to add an extra plot panel, or a new form). You can add a new panel by clicking in the "New Panel" button of the main tool -bar (or selecting `View->Panel->New Panel...`). This will open a dialog offering +bar (or selecting `Panels->New Panel...`). This will open a dialog offering a catalog of different panel types and options for your new panel. Once accepted, the new panel will appear floating, ready to be docked to the main window. @@ -355,4 +355,4 @@ width or height in any of the available "slots". Try to make room by hiding some other panel, or tabifying other panels together, or increasing the main window size. -.. _Sardana: http://www.sardana-controls.org/ \ No newline at end of file +.. _Sardana: http://www.sardana-controls.org/ From b36ac8cdc1b4dcdf9defaac8b8883a51a616be35 Mon Sep 17 00:00:00 2001 From: Philippe Gauron Date: Fri, 21 Jul 2017 17:40:00 +0200 Subject: [PATCH 007/288] Details to add a synoptic panel after taurusgui creation. --- doc/source/users/ui/taurusgui.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/users/ui/taurusgui.rst b/doc/source/users/ui/taurusgui.rst index d678ef9b8..83689fa92 100644 --- a/doc/source/users/ui/taurusgui.rst +++ b/doc/source/users/ui/taurusgui.rst @@ -236,6 +236,8 @@ element in the synoptic will be highlighted). This is very useful because synoptic panels can be used as a sort of quick index or browser to navigate in panel-crowded applications. +To add a Synoptic panel to a taurusgui after the creation of the taurusgui, use the "Add Panel" button (or menu), select the "TaurusJDrawSynopticsView", enter "Advanced settings..." to enter the full path of the JDraw application into the "Model" field. + Also note that you can find a button in the application toolbar for showing/hiding each synoptic panel. @@ -355,4 +357,4 @@ width or height in any of the available "slots". Try to make room by hiding some other panel, or tabifying other panels together, or increasing the main window size. -.. _Sardana: http://www.sardana-controls.org/ \ No newline at end of file +.. _Sardana: http://www.sardana-controls.org/ From 7fc381a2db5e257905b4f1decb7d59a2f567f39b Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Fri, 21 Jul 2017 18:19:56 +0200 Subject: [PATCH 008/288] (minor) shroten lines --- doc/source/users/ui/taurusgui.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/users/ui/taurusgui.rst b/doc/source/users/ui/taurusgui.rst index 83689fa92..b15b908cc 100644 --- a/doc/source/users/ui/taurusgui.rst +++ b/doc/source/users/ui/taurusgui.rst @@ -236,7 +236,10 @@ element in the synoptic will be highlighted). This is very useful because synoptic panels can be used as a sort of quick index or browser to navigate in panel-crowded applications. -To add a Synoptic panel to a taurusgui after the creation of the taurusgui, use the "Add Panel" button (or menu), select the "TaurusJDrawSynopticsView", enter "Advanced settings..." to enter the full path of the JDraw application into the "Model" field. +To add a Synoptic panel to a taurusgui after the creation of the taurusgui, +use the "Add Panel" button (or menu), select the "TaurusJDrawSynopticsView", +enter "Advanced settings..." to enter the full path of the JDraw file +into the "Model" field. Also note that you can find a button in the application toolbar for showing/hiding each synoptic panel. From 15135117f6cc14246b1a8b8aa0c8ecd19f1229a3 Mon Sep 17 00:00:00 2001 From: Philippe Gauron Date: Thu, 20 Jul 2017 12:00:55 +0200 Subject: [PATCH 009/288] Make doc consistent with changes into taurusgui menu. --- doc/source/users/ui/plot.rst | 7 +++---- doc/source/users/ui/taurusgui.rst | 18 +++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/doc/source/users/ui/plot.rst b/doc/source/users/ui/plot.rst index b5a639430..e279ec604 100644 --- a/doc/source/users/ui/plot.rst +++ b/doc/source/users/ui/plot.rst @@ -192,7 +192,7 @@ features can be useful: - Peak locator: :class:`TaurusPlot` can locate and put a mark at the maximum and/or minimum points in the plotted data. You switch this option on and off using the - `Show Peaks` option from the `TaurusPlot context menu`_ or use from the + `Show min` and `Show max` option from the `TaurusPlot context menu`_ or use from the `Peak Markers` option in the `Plot Configuration dialog`_ .. image:: /_static/taurusplot-datainfo03.png @@ -266,9 +266,8 @@ Here are some tips for entering valid date/time values: - The date can be written in various formats. ISO format is recommended (e.g. "1917-10-25"), although others like, e.g. "25/10/1917" are also accepted. - - The time is given in 24 hours format (e.g. "21:45") and may include - (e.g. "21:45:01") secondshours:minutes , if given, may optionally - include seconds: e.g., is + - The time is given in 24 hours format (e.g. "21:45") and may optionnaly + include seconds if given (e.g. "21:45:01") - Date is mandatory while time is optional. If time is given, it must be separated from the date with a single space (e.g. "1917-10-25 21:45:01") diff --git a/doc/source/users/ui/taurusgui.rst b/doc/source/users/ui/taurusgui.rst index 439e9d567..97b56bff5 100644 --- a/doc/source/users/ui/taurusgui.rst +++ b/doc/source/users/ui/taurusgui.rst @@ -174,10 +174,10 @@ future use and which are only meant for the current session. The dialog for selection which custom panels are to be stored permanently -You can also open this dialog from the `Tools->Select panel Configuration` option. +You can also open this dialog from the `Panels->Switch temporary/permanent status...` option. .. tip:: if you want to remove a custom panel from an application, just hide it - and use the `Select panel Configuration` option for making it + and use the `Switch temporary/permanent status...` option for making it "temporary" so that it is not stored for future sessions. .. _perspectives: @@ -203,9 +203,9 @@ functionalities from the `View` menu). shows all available perspectives. The button on the right allows you to save the current arrangement as a perspective. -Switching to another perspective can be done using the `Load perspectives` drop- -down button in the perspectives toolbar (or using the `View->Load perspective` -option). +Switching to another perspective can be done using the `Load perspectives` +drop-down button in the perspectives toolbar (or using the +`View->Load perspective` option). Apart from the pre-defined perspectives, you can always re-arrange panels and store your preferred arrangement as a perspective using the @@ -214,8 +214,8 @@ perspective` option). .. tip:: If you want to backup your current perspectives, or you want to use some perspectives you created in one computer in another computer (or another - user of the same computer) you can do so by using the `File->Export Settings` - option. Similarly, use the `File->Import Settings` option to update the application + user of the same computer) you can do so by using the `File->Export Settings ...` + option. Similarly, use the `File->Import Settings ...` option to update the application perspectives with those contained in the imported file. .. _synopticpanels: @@ -223,7 +223,7 @@ perspective` option). Synoptic panels --------------- -An special type of panel present in some TaurusGui-based applications is the +A special type of panel present in some TaurusGui-based applications is the Synoptics. Synoptics panels are typically used for providing a visual representation of the hardware that is controlled by the GUI. Some elements or areas of the synoptic panel may be *active*, meaning that they can be selected @@ -308,7 +308,7 @@ Also, some other temporary panels may be dynamically created depending on the ex In most specific GUIs the macroserver and door name to use are pre-configured and the user does not need to change them. Sometimes though, you may want to alter it. -You can do so by using the `Tools->Macro execution configuration` option. +You can do so by using the `Taurus->Macro execution configuration...` option. Automatic creation of Instrument panels from Pool info '''''''''''''''''''''''''''''''''''''''''''''''''''''' From b1107f8935a214d77b322468e5cc78e596d67b2e Mon Sep 17 00:00:00 2001 From: Philippe Gauron Date: Mon, 24 Jul 2017 16:20:02 +0200 Subject: [PATCH 010/288] Note about removing RawData from the GUI. --- doc/source/users/ui/plot.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/users/ui/plot.rst b/doc/source/users/ui/plot.rst index e279ec604..53aafe6d8 100644 --- a/doc/source/users/ui/plot.rst +++ b/doc/source/users/ui/plot.rst @@ -148,6 +148,8 @@ where you can add data sets to the plot from the following sources: You can use `x` as a dependent variable, which you can set as regular steps or using an arbitrary expression. +Note that there is actually no way to remove RawData curve from the GUI. + .. figure:: /_static/taurusplot-inputdata02.png :align: center From 52b6fc0cebcefbb495ff51c6e12343f5a61b4bf9 Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 25 Jul 2017 17:35:43 +0200 Subject: [PATCH 011/288] Fix problem in provides metadata PyPI complies when attempting to upload the distribution files (it does not like our Provides metadata). Fix it. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index fc7d0a317..f1474ea4f 100644 --- a/setup.py +++ b/setup.py @@ -44,8 +44,8 @@ def get_release_info(): provides = [ 'taurus', - 'taurus.core', - 'taurus.qt', + # 'taurus.core', + # 'taurus.qt', # 'Taurus-Tango', # [Taurus-Tango] # 'Taurus-Qt', # [Taurus-Qt] # 'Taurus-Qt-PyQwt', # [Taurus-Qt-Plot] From c5c53571fdd9de55548dccff65454b3f2aadca30 Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 25 Jul 2017 17:37:39 +0200 Subject: [PATCH 012/288] Bump version 4.1.0 to 4.1.1-alpha --- .bumpversion.cfg | 2 +- lib/taurus/core/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6f3596ac0..df361c7a0 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 4.1.0 +current_version = 4.1.1-alpha parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index 84f7cd36f..29088836b 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -48,7 +48,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.1.0' +version = '4.1.1-alpha' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: From 19c8703f012f3b4f88ca728d2c962a6f28e77505 Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 25 Jul 2017 17:37:48 +0200 Subject: [PATCH 013/288] Bump version 4.1.1-alpha to 4.1.1 --- .bumpversion.cfg | 2 +- lib/taurus/core/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index df361c7a0..5c831a7ec 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 4.1.1-alpha +current_version = 4.1.1 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index 29088836b..4a1d8c580 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -48,7 +48,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.1.1-alpha' +version = '4.1.1' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: From cb44a238610c097abfed9fe0c3e98705469c9d5c Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 26 Jul 2017 15:37:39 +0200 Subject: [PATCH 014/288] Bump version 4.1.1 to 4.1.2-alpha --- .bumpversion.cfg | 2 +- lib/taurus/core/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5c831a7ec..8a4497824 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 4.1.1 +current_version = 4.1.2-alpha parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index 4a1d8c580..a45e9db55 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -48,7 +48,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.1.1' +version = '4.1.2-alpha' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: From 580303be4d9db1f5744eb9648be721826c5c1c03 Mon Sep 17 00:00:00 2001 From: Philippe Gauron Date: Thu, 27 Jul 2017 15:31:35 +0200 Subject: [PATCH 015/288] Move users/ui introduction upstairs. --- doc/source/users/ui/index.rst | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/doc/source/users/ui/index.rst b/doc/source/users/ui/index.rst index 02dde868f..b571d3f4b 100644 --- a/doc/source/users/ui/index.rst +++ b/doc/source/users/ui/index.rst @@ -4,6 +4,11 @@ User's Interface ================ +This section explains some features that are common to most applications built +with taurus. This is done from the GUI user point of view (not for developers). + +For a detailed list of features of each widget, please refer to the +:ref:`developers-guide-index` .. toctree:: :maxdepth: 2 @@ -21,12 +26,4 @@ User's Interface TaurusDemo Logs Configurations - - - -This section explains some features that are common to most applications built -with taurus. This is done from the GUI user point of view (not for developers). - -For a detailed list of features of each widget, please refer to the -:ref:`developers-guide-index` From a6e35d6a8c4356d4ec8168d8b3b0526dafaf9816 Mon Sep 17 00:00:00 2001 From: Philippe Gauron Date: Fri, 28 Jul 2017 15:23:11 +0200 Subject: [PATCH 016/288] Deleting :hidden: from doc toctree directive. --- doc/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 337b645cc..8d4931b0c 100755 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -35,7 +35,7 @@ Projects related to Taurus :height: 180 .. toctree:: - :hidden: + :maxdepth: 3 Home Page Project Page From 06330d0dcdf2f42b5e9a808e7c161c2690453927 Mon Sep 17 00:00:00 2001 From: mrosanes Date: Wed, 9 Aug 2017 12:44:19 +0200 Subject: [PATCH 017/288] Adapt to Taurus4 using rvalue Adapt to Taurus 4 using '.rvalue' instead of 'value'. --- lib/taurus/core/util/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index 647070618..5697e0895 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -540,7 +540,7 @@ def eventReceived(self, s, t, v): elif t == taurus.core.taurusbasetypes.TaurusEventType.Error: self.fireEvent(None) else: - self.fireEvent(v.value) + self.fireEvent(v.rvalue) def fireEvent(self, v): """Notifies that a given event has arrived @@ -683,7 +683,7 @@ def unlock(self): def eventReceived(self, s, t, v): if t not in (taurus.core.taurusbasetypes.TaurusEventType.Change, taurus.core.taurusbasetypes.TaurusEventType.Periodic): return - self.fireEvent(s, v.value) + self.fireEvent(s, v.rvalue) def fireEvent(self, s, v): t = time.time() From c9b0f8bf90298e2eb7bfd484471db46563d67d35 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 16 Aug 2017 10:22:18 +0200 Subject: [PATCH 018/288] Update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 910cbd1e1..3a7305042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ Note: changes in the [support-3.x] branch (which was split from the master branch after [3.7.1] and maintained in parallel to the develop branch) won't be reflected in this file. +## Unreleased + +### Fixed +- Doc issues (#525, #546) (thanks @PhilLAL !) + + +## [4.1.1] - 2017-07-21 + +### Fixed +- Issue with PyPI metadata (hotfix 4.1.1) + ## [4.1.0] - 2017-07-21 From 94a7d02163da7ba4a0f482dac0be7588afcee93c Mon Sep 17 00:00:00 2001 From: Philippe Gauron Date: Thu, 17 Aug 2017 11:31:22 +0200 Subject: [PATCH 019/288] Incrementing toctree max-depth level --- doc/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 8d4931b0c..e2c35cff2 100755 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -35,7 +35,7 @@ Projects related to Taurus :height: 180 .. toctree:: - :maxdepth: 3 + :maxdepth: 4 Home Page Project Page From 248ca9eb77c53a40494bcc3bd90562eec3f52960 Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 17 Aug 2017 15:18:07 +0200 Subject: [PATCH 020/288] Improve doc index - Move toctree to the top (to generate chapters from doc.rst titles) - Use :hidden: option in the toctree to avoid showing it in html - Remove "related projects" (leaving just a ref to Sardana) --- doc/source/index.rst | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index e2c35cff2..f526eceb3 100755 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,12 +1,22 @@ +.. toctree:: + :hidden: + :titlesonly: + :maxdepth: 4 -Welcome to Taurus's Home Page! + Home Page + Project Page + Download from PyPI + docs + + +Welcome to Taurus Home Page! ============================================= |image1| Taurus is a python framework for control and data acquisition CLIs and GUIs in scientific/industrial environments. -It supports multiple control systems or data sources: Tango_, EPICS_, spec... +It supports multiple control systems or data sources: Tango_, EPICS_, ... New control system libraries can be integrated through plugins. For non-programmers: Taurus allows the creation of fully-featured GUI (with @@ -22,26 +32,14 @@ Of course, Taurus is Free Software (under LGPL). You can download it from PyPi_, access its Documentation_ or get support from its community and the latest code from the `project page `_. -Projects related to Taurus ---------------------------- - -- Taurus uses PyQt_ for the GUIs -- Tango_ is supported vis PyTango_ -- Taurus is part of the Sardana_ suite +See also the related Sardana_ project, which uses Taurus to build its user +interfaces. .. |image1| image:: _static/taurus_showcase01.png :align: middle :height: 180 -.. toctree:: - :maxdepth: 4 - - Home Page - Project Page - Download from PyPI - docs - :Last Update: |today| :Release: |release| @@ -76,7 +74,6 @@ Projects related to Taurus .. _Tango: http://www.tango-controls.org/ -.. _PyTango: http://packages.python.org/PyTango/ .. _EPICS: http://www.aps.anl.gov/epics/ .. _PyQt: http://www.riverbankcomputing.co.uk/software/pyqt/ .. _Sardana: http://sardana-controls.org From c7d82f56eabf652006ae8b1d6a240456c930ea6a Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 17 Aug 2017 15:29:07 +0200 Subject: [PATCH 021/288] (doc) (minor) Fix mistakes in evaluation docs --- lib/taurus/core/evaluation/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/evaluation/__init__.py b/lib/taurus/core/evaluation/__init__.py index 1b4acce28..7dad996f0 100644 --- a/lib/taurus/core/evaluation/__init__.py +++ b/lib/taurus/core/evaluation/__init__.py @@ -53,7 +53,7 @@ where: - The `` segment is optional (except when referring to an - EvaluationDatabase). At this point, only `//localhost` is supported. + EvaluationAuthority). At this point, only `//localhost` is supported. - The `@` is optional (except when referring to an EvaluationDevice). If not given, it defaults to `DefaultEvaluator`. See @@ -172,7 +172,7 @@ class MyClass(object): .. note:: Previous to SEP3, a RFC3986 non-compliant syntax was used for the evaluation scheme (e.g., allowing names such as - ``tango://db=foo;dev=bar;a*b?k=2;a={tango:a/b/c/d}``). + ``eval://db=foo;dev=bar;a*b?k=2;a={tango:a/b/c/d}``). This syntax is now deprecated and should not be used. Taurus will issue warnings if detected. """ From 7dfea4a8fa694670b9b6f5bf5e8a9422e5ba44ca Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 18 Aug 2017 09:49:49 +0200 Subject: [PATCH 022/288] Improve doc index - Move toctree to the top (to generate chapters from doc.rst titles) - Use :hidden: option in the toctree to avoid showing it in html - Remove "related projects" (leaving just a ref to Sardana) --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a7305042..aea1cced1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,14 @@ develop branch) won't be reflected in this file. ## Unreleased +### Changed +- Tango model name validators now always return FQDN instead of PQDN + for the tango host (#488) +- Improved generated PDF doc (#525, #546, #548) (thanks @PhilLAL !) + ### Fixed -- Doc issues (#525, #546) (thanks @PhilLAL !) +- Doc issues +- Deprecation warnings in taurus.core.util.event (#550) ## [4.1.1] - 2017-07-21 From 739945c77a7b719d6b617237970f00ddfb6b92a6 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 23 Aug 2017 11:38:34 +0200 Subject: [PATCH 023/288] Use new docker tags in Travis.yml The docker tags in taurus-test docker image have been updated. Now `debian-{jessie,stretch,buster}` are used insted, while "debian9" and "latest" should no longer be used. Modify travis.yml accordingly --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d82fb472e..aef155b3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: services: - docker env: - - DOCKER_IMG=cpascual/taurus-test:latest + - DOCKER_IMG=cpascual/taurus-test:debian-jessie - python: 2.7 os: linux @@ -18,10 +18,17 @@ matrix: services: - docker env: - - DOCKER_IMG=cpascual/taurus-test:debian9 + - DOCKER_IMG=cpascual/taurus-test:debian-stretch + + - python: 2.7 + os: linux + sudo: required + services: + - docker + env: + - DOCKER_IMG=cpascual/taurus-test:debian-buster before_install: - # run cpascual/taurus-test docker container (Debian8 with taurus-deps and xvfb installed) - docker run -d --name=taurus-test -h taurus-test --volume=`pwd`:/taurus $DOCKER_IMG - sleep 10 From 277e740269e402b1e85800add365ab049834da0f Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 23 Aug 2017 16:32:22 +0200 Subject: [PATCH 024/288] Simplify travis config and allow failures for buster --- .travis.yml | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index aef155b3b..8aacae59e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,32 +1,21 @@ language: python +python: + - "2.7" + sudo: required -matrix: - include: - - python: 2.7 - os: linux - sudo: required - services: - - docker - env: - - DOCKER_IMG=cpascual/taurus-test:debian-jessie +services: + - docker - - python: 2.7 - os: linux - sudo: required - services: - - docker - env: - - DOCKER_IMG=cpascual/taurus-test:debian-stretch +env: + - DOCKER_IMG=cpascual/taurus-test:debian-jessie + - DOCKER_IMG=cpascual/taurus-test:debian-stretch + - DOCKER_IMG=cpascual/taurus-test:debian-buster - - python: 2.7 - os: linux - sudo: required - services: - - docker - env: - - DOCKER_IMG=cpascual/taurus-test:debian-buster +matrix: + allow_failures: + - env: DOCKER_IMG=cpascual/taurus-test:debian-buster before_install: - docker run -d --name=taurus-test -h taurus-test --volume=`pwd`:/taurus $DOCKER_IMG From 902ed67fd99a01745bb1918e1ebc7743e8656d8d Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 24 Aug 2017 09:14:45 +0200 Subject: [PATCH 025/288] Move spyder requirement from 'taurus-qt' to 'taurus-qt-editor' Installing spyder>=3 can be a problem in older systems (e.g. debian 8) because it forces many dependencies (e.g. ipython>4) which may not be wanted. On the other hand, spyder is only needed for taurus.qt.editor. Therefore, make only 'taurus-qt-editor' (instead of 'taurus-qt') depend on spyder>3. --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f1474ea4f..4b9590a6a 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,6 @@ def get_release_info(): # 'PyQt4.Qwt5 >=5.2.0', # [Taurus-Qt-Plot] 'ply >=2.3', # [Taurus-Qt-Synoptic] 'lxml >=2.1', # [Taurus-Qt-TaurusGUI] - 'spyder >=3.0', # [Taurus-Qt-Editor] 'guiqwt >=3', # [Taurus-Qt-Guiqwt] ], 'taurus-tango': ['PyTango >=7.1', @@ -74,6 +73,10 @@ def get_release_info(): ], 'taurus-h5file': ['h5file', ], + # separate the editor from 'taurus-qt' to avoid forcing spyder>3 + # which implies many more dependencies that may be hard on older system + 'taurus-qt-editor': ['spyder >=3', + ], } console_scripts = [ From 4d0f0d9b91ee895b80adb27fbba115fb183078ec Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 24 Aug 2017 10:14:19 +0200 Subject: [PATCH 026/288] Use a dummy TaurusBaseEditor if spyder isnot available Allow importing taurus.qt.editor even if spyder is not installed. In that case, return a dummy widget and log a warning. --- lib/taurus/qt/qtgui/editor/__init__.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/taurus/qt/qtgui/editor/__init__.py b/lib/taurus/qt/qtgui/editor/__init__.py index 16cdc8e33..4958fa7a5 100644 --- a/lib/taurus/qt/qtgui/editor/__init__.py +++ b/lib/taurus/qt/qtgui/editor/__init__.py @@ -27,10 +27,14 @@ __docformat__ = 'restructuredtext' -from .tauruseditor import * +try: + from .tauruseditor import * +except Exception as e: + from taurus.qt.qtgui.display import create_fallback as __create + TaurusBaseEditor = __create("TaurusBaseEditor") + from taurus import warning, debug + warning('Problem with taurus.qt.editor.\n' + ' A dummy TaurusBaseEditor will be used\n' + + ' (maybe you do not have spyder >=3 ?). ') + debug('%r', e) -# try: -# from .tauruseditor import * -# except: -# from taurus.qt.qtgui.display import create_fallback as __create -# TaurusBaseEditor = __create("TaurusBaseEditor") From 499b3bd76842ddac4df21fdffab8985e1ee72682 Mon Sep 17 00:00:00 2001 From: cpascual Date: Mon, 28 Aug 2017 13:28:43 +0200 Subject: [PATCH 027/288] Do not provide a dummy TaurusBaseEditor Simplify the fallback when tauruseditor cannot be imported: do not implement a dummy TaurusBaseEditor at all. Just log the warning. --- lib/taurus/qt/qtgui/editor/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/taurus/qt/qtgui/editor/__init__.py b/lib/taurus/qt/qtgui/editor/__init__.py index 4958fa7a5..3d8d9ffc7 100644 --- a/lib/taurus/qt/qtgui/editor/__init__.py +++ b/lib/taurus/qt/qtgui/editor/__init__.py @@ -30,11 +30,7 @@ try: from .tauruseditor import * except Exception as e: - from taurus.qt.qtgui.display import create_fallback as __create - TaurusBaseEditor = __create("TaurusBaseEditor") from taurus import warning, debug - warning('Problem with taurus.qt.editor.\n' - ' A dummy TaurusBaseEditor will be used\n' + - ' (maybe you do not have spyder >=3 ?). ') + warning('Problem with taurus.qt.editor (hint: is spyder >=3 installed?)') debug('%r', e) From 10487a37734f456d7920f1a87de9db98451628e1 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 29 Aug 2017 14:51:09 +0200 Subject: [PATCH 028/288] Fix Taurus may emit events with outdated data Current implementation may lead to emitting events with the outdated values. The polling mechanism reads the Tango attributes asynchronously (first drop the read requests and later on collect the replies). The more up to date event value may get overriden by an older value corresponding to the polling request dropped prior to the event reception. Implement a filter at TangoAttribute level to drop older events. This filter can be activated/deactivated via a variable in TaurusCustomSettings Fix #216 --- lib/taurus/core/tango/tangoattribute.py | 25 ++++++++++++++++++++++--- lib/taurus/tauruscustomsettings.py | 6 ++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 045a92836..77fd5fb3b 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -41,6 +41,7 @@ from taurus import Manager from taurus.external.pint import Quantity, UR, UndefinedUnitError +from taurus import tauruscustomsettings from taurus.core.taurusattribute import TaurusAttribute from taurus.core.taurusbasetypes import (TaurusEventType, TaurusSerializationMode, @@ -448,8 +449,17 @@ def poll(self, **kwargs): if single: self.read(cache=False) else: - self.__attr_value = self.decode(kwargs.get('value')) + value = self.decode(kwargs.get('value')) self.__attr_err = kwargs.get('error') + filter_old_event = getattr(tauruscustomsettings, + 'FILTER_OLD_TANGO_EVENTS', False) + if self.__attr_value is not None and \ + self.__attr_err is None and \ + filter_old_event and \ + kwargs.get('time') < self.__attr_value.time.totime(): + return + + self.__attr_value = value if self.__attr_err: raise self.__attr_err except PyTango.DevFailed, df: @@ -727,8 +737,17 @@ def _pushAttrEvent(self, event): evt_value is a TaurusValue, an Exception, or None. """ if not event.err: - self.__attr_value, self.__attr_err = self.decode( - event.attr_value), None + attr_value = self.decode(event.attr_value) + filter_old_event = getattr(tauruscustomsettings, + 'FILTER_OLD_TANGO_EVENTS', False) + if self.__attr_value is not None and \ + filter_old_event and \ + event.attr_value.time.totime() < \ + self.__attr_value.time.totime(): + return None, None + + self.__attr_value = attr_value + self.__attr_err = None self.__subscription_state = SubscriptionState.Subscribed self.__subscription_event.set() if not self.isPollingForced(): diff --git a/lib/taurus/tauruscustomsettings.py b/lib/taurus/tauruscustomsettings.py index f4da96303..1158c911d 100755 --- a/lib/taurus/tauruscustomsettings.py +++ b/lib/taurus/tauruscustomsettings.py @@ -64,6 +64,12 @@ # Set your default scheme (if not defined, "tango" is assumed) DEFAULT_SCHEME = "tango" +# Filter old tango events: +#Sometimes TangoAttribute can receive event from the past. See issue #216. +# True discard (Tango) event which its timestamp is older than cache data. +# False (or commented out) for backwards compatibility +FILTER_OLD_TANGO_EVENTS = True + # Extra Taurus schemes. You can add a list of modules to be loaded for # providing support to new schemes # EXTRA_SCHEME_MODULES = ['myownschememodule'] From 367dabe1e7d50a4b8749db7cbd4aeb6ab6af59b9 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 4 Sep 2017 08:18:32 +0200 Subject: [PATCH 029/288] Add DEFAULT_FORMATTER variable Taurus widgets use a default formatter based on the attribute type, but sometimes a custom formatter is needed Add DEFAULT_FORMATTER variable in tauruscustomsetting to allow to set custom fotmatter. The formatter can be a python format string or a formatter path (string with the import path) e.g. DEFAULT_FORMATTER = '{0}' DEFAULT_FORMATTER = 'taurus.core.tango.util.formatter.tangoFormatter --- lib/taurus/tauruscustomsettings.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/taurus/tauruscustomsettings.py b/lib/taurus/tauruscustomsettings.py index f4da96303..14ced32de 100755 --- a/lib/taurus/tauruscustomsettings.py +++ b/lib/taurus/tauruscustomsettings.py @@ -68,6 +68,14 @@ # providing support to new schemes # EXTRA_SCHEME_MODULES = ['myownschememodule'] +# Set a custom formatter. Taurus widgets use a default formatter based on the +# attribute type, but sometimes a custom formatter is needed. +# The formatter can be a python format string or formatter callable +# (in string version) +#e.g. +# DEFAULT_FORMATTER = '{0}' +# DEFAULT_FORMATTER = 'taurus.core.tango.util.formatter.tangoFormatter' + # ---------------------------------------------------------------------------- # PLY (lex/yacc) optimization: 1=Active (default) , 0=disabled. # Set PLY_OPTIMIZE = 0 if you are getting yacc exceptions while loading From 943f2a1ec3a31ddfad2a220f74174fd585f2b89e Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 4 Sep 2017 08:42:01 +0200 Subject: [PATCH 030/288] Add API to set custom formatter Add API to set custom formatter in TaurusBaseWidget. --- lib/taurus/qt/qtgui/base/taurusbase.py | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 9c05e7621..dc0c53cf2 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -1243,6 +1243,48 @@ def __init__(self, name, parent=None, designMode=False): self.call__init__(TaurusBaseComponent, name, parent=parent, designMode=designMode) self._setText = self._findSetTextMethod() + formatter = getattr(taurus.tauruscustomsettings, + 'DEFAULT_FORMATTER', None) + if formatter is not None: + self._setFormatter(formatter) + + def _getFormatter(self, formatter): + '''' Method to get custom formatter + :return: formatter: python fromat string or formatter callable + (in string version) + ''' + try: + moduleName, formatterName = formatter.rsplit('.', 1) + __import__(moduleName) + module = sys.modules[moduleName] + formatter = getattr(module, formatterName) + except: + pass + finally: + return formatter + + def showFormatterDlg(self): + ''' + showFormatterDlg show a dialog to get the formatter from the user. + :return: formatter: python fromat string or formatter callable + (in string version) or None + ''' + formatter, ok = Qt.QInputDialog.getText(self, "Set formatter", + "Enter a formatter:", + Qt.QLineEdit.Normal, "") + if ok and formatter: + return self._getFormatter(formatter) + + return None + + def onSetFormatter(self): + ''' Method to be triggered on setFormatter action + ''' + format = self.showFormatterDlg() + if format is not None: + self.debug( + 'Default format has been changed to: {0}'.format(format)) + self.setFormat(format) # It makes the GUI to hang... If this needs implementing, we should # reimplement it using the Qt parent class, not QWidget... From ddc03b62a61ad3db72e6c42dede5644fd772cfe7 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 5 Sep 2017 10:51:02 +0200 Subject: [PATCH 031/288] Add setFormatter action in taurusvalue setFormatter action will change the format of the taurusvalue read widget. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 671ae1565..23faeace1 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -144,6 +144,8 @@ def contextMenuEvent(self, event): self.taurusValueBuddy().onChangeLabelConfig) menu.addAction("Change Read Widget", self.taurusValueBuddy().onChangeReadWidget) + menu.addAction("Set Formatter", + self.taurusValueBuddy().onSetFormatter) cw_action = menu.addAction( "Change Write Widget", self.taurusValueBuddy().onChangeWriteWidget) # disable the action if the taurusValue is readonly @@ -431,6 +433,11 @@ def setParent(self, parent): # do the base class stuff too Qt.QWidget.setParent(self, parent) + def onSetFormatter(self): + format = self.showFormatterDlg() + if format is not None: + self._readWidget.setFormat(format) + def getAllowWrite(self): return self._allowWrite From 6e9056d9750fb5d820494293809cf1acf81b8ba5 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 5 Sep 2017 10:53:27 +0200 Subject: [PATCH 032/288] Add setFormatter action in taurusform Add setFormatter action for all elements in taurusform. It action has the same behaviour than taurusvalue action. --- lib/taurus/qt/qtgui/panel/taurusform.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index d5cb9ee3d..f2f794bb9 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -137,12 +137,15 @@ def __init__(self, parent=None, self.addAction(self.compactModeAction) self.compactModeAction.triggered[bool].connect(self.setCompact) + self.setFormatterAction = Qt.QAction('Set formatter (all items)', self) + self.addAction(self.setFormatterAction) + self.setFormatterAction.triggered[()].connect(self.onSetFormatter) + self.resetModifiableByUser() self.setSupportedMimeTypes([TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, 'text/plain']) self.resetCompact() - # properties self.registerConfigProperty( self.isWithButtons, self.setWithButtons, 'withButtons') @@ -362,6 +365,12 @@ def setWithButtons(self, trueFalse): def resetWithButtons(self): self.setWithButtons(True) + def onSetFormatter(self): + format = self.showFormatterDlg() + if format: + for item in self.getItems(): + item._readWidget.setFormat(format) + def setCompact(self, compact): self._compact = compact for item in self.getItems(): From 4750deab50092c1995b32733cf046d067aae6cf0 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 5 Sep 2017 11:11:47 +0200 Subject: [PATCH 033/288] Add setFormatter action taurusplot This action allows to change the format of all curves. --- lib/taurus/qt/qtgui/plot/taurusplot.py | 33 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/plot/taurusplot.py index 7873c5283..031c3c4f3 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/plot/taurusplot.py @@ -1177,6 +1177,10 @@ def __initActions(self): self._dataInspectorAction.setChecked(self._pointPicker.isEnabled()) self._dataInspectorAction.toggled[bool].connect(self.toggleDataInspectorMode) + self._setFormatterAction = Qt.QAction( + "Set Formatter...", None) + self._setFormatterAction.triggered[()].connect(self.onSetFormatter) + self._curveStatsAction = Qt.QAction("Calculate statistics", None) self._curveStatsAction.setShortcut(Qt.Qt.Key_S) self._curveStatsAction.triggered[()].connect(self.onCurveStatsAction) @@ -1248,11 +1252,15 @@ def __initActions(self): # add all actions and limit the scope of the key shortcuts to the # widget (default is Window) - for action in (self._dataInspectorAction, self._pauseAction, self._autoscaleAllAxisAction, - self._toggleZoomAxisAction, self._configDialogAction, self._inputDataAction, - self._saveConfigAction, self._loadConfigAction, self._showLegendAction, - self._showMaxAction, self._showMinAction, self._printAction, self._exportPdfAction, - self._exportAsciiAction, self._setCurvesTitleAction, self._curveStatsAction): + for action in (self._dataInspectorAction, self._pauseAction, + self._autoscaleAllAxisAction, + self._toggleZoomAxisAction, self._configDialogAction, + self._inputDataAction, self._saveConfigAction, + self._loadConfigAction, self._showLegendAction, + self._showMaxAction, self._showMinAction, + self._printAction, self._exportPdfAction, + self._exportAsciiAction, self._setCurvesTitleAction, + self._curveStatsAction, self._setFormatterAction): # this is needed to avoid ambiguity when more than one TaurusPlot # is used in the same window action.setShortcutContext(Qt.Qt.WidgetShortcut) @@ -1260,6 +1268,20 @@ def __initActions(self): # that gets the focus (the canvas instead of self) self.canvas().addAction(action) + def onSetFormatter(self): + ''' Method to be triggered on setFormatter action + ''' + format = self.showFormatterDlg() + if format is not None: + self.debug( + 'Default format has been changed to: {0}'.format(format)) + + targetCurveNames = self.curves.iterkeys() + for name in targetCurveNames: + curve = self.curves.get(name, None) + w = getattr(curve, 'owner', curve) + w.setFormat(format) + def dropEvent(self, event): '''reimplemented to support dropping of modelnames in taurusplots''' mtype = self.handleMimeData(event.mimeData(), self.addModels) @@ -2159,6 +2181,7 @@ def _canvasContextMenu(self): menu.addAction(self._showLegendAction) menu.addAction(self._dataInspectorAction) + menu.addAction(self._setFormatterAction) menu.addSeparator() exportSubMenu = menu.addMenu("&Export && Print") From 648556070f13ceed62be55fb890162844d087621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szczepa=C5=84czyk?= Date: Tue, 5 Sep 2017 12:10:11 +0200 Subject: [PATCH 034/288] Fix typos in taurusfactory.py --- lib/taurus/core/taurusfactory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/taurusfactory.py b/lib/taurus/core/taurusfactory.py index b140dfceb..db71cce0b 100644 --- a/lib/taurus/core/taurusfactory.py +++ b/lib/taurus/core/taurusfactory.py @@ -147,7 +147,7 @@ def getAuthority(self, name=None): raise TaurusException(msg) fullname, _, _ = v.getNames(name) - auth = self._devs.get(fullname) + auth = self._auths.get(fullname) if auth is not None: return auth @@ -271,7 +271,7 @@ def supportsScheme(self, scheme): :return: (bool) True if the scheme is supported (False otherwise) """ - return scheme in self.shemes + return scheme in self.schemes def findObject(self, absolute_name): """ Must give an absolute name""" From ef68ec0d80bbb2b3d6bab22dd993e8839fe2688a Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 13 Sep 2017 15:53:16 +0200 Subject: [PATCH 035/288] Set up deployment of travis-generated docs to GH pages Use doctr (https://drdoctr.github.io) to deploy travis-built docs to github.com/taurus-org/taurus-doc repo. The current configuration deploys the docs built on debian-stretch When the trigger is a push to develop, the docs go to the normal dir, while when the trigger is a push to another branch or tag, it goes to docs- --- .travis.yml | 23 +++++++++++++++++++++-- ci/github_deploy_key.enc | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 ci/github_deploy_key.enc diff --git a/.travis.yml b/.travis.yml index 8aacae59e..db283b375 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - - "2.7" + - "3.6" sudo: required @@ -9,6 +9,9 @@ services: - docker env: + global: + - secure: "CCVtBQ8xIrOwUJGXxd81wyD1ng72Hf6d9y2U+5X88aVGTrOa8/hut10C+Jmnyf0NTZmGh/49eVvoWRvLDhjpECFMuO/bLkiNtVjz0VtWAHT2W98QJYmeymPzx86tGa+iAZCwlgXeRQFJCw1eqQvBYnjumMZWb9kj3fqgpqpSRH5SWnuRCmbxOoelmtTTUC8YKkzasAHYs03faR0DCq0oBmDy9nU2cfcRN7oE5wXUfEnDwNaoHbiQA4wiJbzNpBV432bIDtzD7gsFdiIT6ExJVFHi1gWB32bGZdbiDPpej7I2fW5qunbzUXS5doVhoBU67qqhI241RJ+AOBVb+sQnF8gwwi9/5/mcORiSBX7yI59YsOXYwo2YW+8PGr3OlF3t0+z92Q3uPytUXysdtVO4dExnLbV8OEzgmWCkv2M3GIajjE3isYAaBItqSBJHJnRClza4nNg2WLwmqLPBgM4AuSUZEpB/8kbz9kTecVEb13WrlTCNc8KVRGR+EGa4KmWADwOOxurxeb/NsTteOnzdyfrP2TXKeGOkN2uqBGYJaI7OoefsgLG7VF/+Sz4MTETMs/gZojwpO6igKBS1sJlcXujz/kt125b8gcSnrAiU1TjbZIBew/D40H64tpBcuk+dqF6i6HCoV2QmZ1QEpHOSoDk9FEaKMlgKhQj59/cWcI8=" + matrix: - DOCKER_IMG=cpascual/taurus-test:debian-jessie - DOCKER_IMG=cpascual/taurus-test:debian-stretch - DOCKER_IMG=cpascual/taurus-test:debian-buster @@ -22,5 +25,21 @@ before_install: - sleep 10 script: - - docker exec taurus-test /bin/bash -c "cd taurus ; python setup.py install" + - set -e + - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py install" - docker exec taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" + - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" + # deploy sphinx-built docs to taurus-doc repo + - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then + pip install doctr ; + touch /taurus/build/sphinx/html/.nojekyll ; + if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then + doctr deploy docs; + else + doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master ; + fi + fi + +- doctr: + - key-path : '/taurus/ci/github_deploy_key.enc' + - deploy-repo : 'taurus-org/taurus-doc' diff --git a/ci/github_deploy_key.enc b/ci/github_deploy_key.enc new file mode 100644 index 000000000..99431ba75 --- /dev/null +++ b/ci/github_deploy_key.enc @@ -0,0 +1 @@ +gAAAAABZuQvyDarhMR1dsxi0HwOZMmZuh3xh6R-Wm_7L1Ahk_pWGykQZlt0SBf2ywO2l0F44y09V8cuHLQk16B_DzESjxtwFVAjowCiMOYv75MUfXHbcPOiVQFeGzDmt9o9gHLO7U4lqiKl_LTKsbppbnpm-csItyNV1IW610nr-YTcfJEJHKrNdXOU94VqdJmpKl7u0HwRsoCeiC0Wdvs4RGc-IvEhn3fO4wKZCOiCCPLMBtvOcN3ASd3P00yv7x_P3cpxOymn-nEosLGT1R_CQaXnL1IeHahqARScYoKix-RLtDgwwsfVcWJeXhxrOtoTYQa0Q9gR84ubu3bfJJSuzWrbfxZgrIb00TPLGT6sOOcrQZdkJF84ABz_fz2FVkxxWTJ9tqDaamwQHWzGMmESddhrS4Y_QxDDKASFiTbAnzTgpQIxky4H8xnc3pfe-tAsJrJjTXzJ-whdrqUPjfzyDwkJywJiZiDtv3E32JYHRz_saxNBN8vjZVTh6FXJl92gR8hNHgT7webQg2ymcMmkxSiS4SxX8sfznmHfakKAy421yjQGtjQM0Zm7Id8DYtMd6J7obIo4bFpEbROBE3V92Vy1FQdAVKAGVYrMHDsZrxLZoypqLvy8GylGZxW0TslxCk2RfP_3PnLx6scroAdsaYebDv3iId1l7xUMYYx8fB0znJx9QFvLgUnQd1a0pDXGREaJIwoOYJKzy5kMgJ4WLof41uDgHYhA-M354d99UpzQZsdnyB2HeI-brkWSt6Xg_EUeC1S9f_egsBb42v0wznIna5ESYhAzOhGCSipEnBBMUH5-qUZNGIMtsXT59tHQXACp8kId5t_QcdxOgefxUBiz-rR3UKMo63F7IRfcj27OEdZ5rmY8EdgsLtkj9gtWz2TilLqMQEnec1NRfjOtKaPKLpQPs1KWQ-l7GGyyvgPcUiP4FYKAh_whGq1YeTIySXgMQM6fjeqQP1SMcUESLVkH2QFYzYR-zcqJx86HGV6wOMTbrC6G10sl3M5vrBPgPrULsw-5mB1gys4lhaAmfX0TIySwsqhSm0fpe9fxTH6Df1-FEOH8cEH7blQyyaoralpdfqRw4uQMTvjBiGYNn4WZFeC1InC9fFh46tZbZe_oJ7CPaSxkHnEkWkJFxpyW9UHbM-N4Tfozr0C1xCmMD17crTke4M_D2cXF_DN4ywz8VmLOuDdTTZTar9HF7BNrCGiRTGhUDiM4tpC1llrUvHO6Cy1qHrpNfFSJZ69lrwUeKg5Kthn6M6fDyFdKmF7LQcNDys2KCD-ctr5uTbMEHOG58CcB_kTYkzGRIVnRt1CQswGbJ-8dxgBppf_urhHeImp5w0sdvssZ6g7xr4TN203iiumv0ujdH4IdPzpR162pwAXHFtidY4KnbpXH9VxfikH1QRMG0ym0ToaMM_WyWMLCA6wbqFQ_-UMnBET89Q5_j-q9509HBY6FDZV1lz6PHEqTFHhDwZ4_kd91K0M0ohifHaieneU2TK4aklVh4zZslhF52kMhkTY9O-4EeL-cRBudMTvlT11VCj7EDiPvOve29dLoNUYy1p__Q5HLjC--o4ONIYexuQneStfVcHlA6HOOiYf2nUCDEhiLjPaIOJmJy5IP4LuOujXIl_bFMJjU3n9j5FheAvipxvZoi30HchB0zKHBLGv8O2KSUCCgqH0_BAmNIB7p9rakkzD0ttMOdnt-9gKQ2AL0HM27AYBZE7xi_wvqC1JN-W8Y0IioCiqwa3DXZCvNB-_2qZjsSIFmeDb9Qz3RWhG6AuensX6MpQ1mWkWdEYvkuC2D49Ce6LRQcomR7l_rjOrrOa0rVG_L2nr4C190qHqPgxt-k1rrW2Q2EZV4flxtlmgaTbSNO5jvPkeBKW8J32rty9kMc_PGshkWCkz8DpVeHgis4gnuwFT903TvJhFUFMVczUh7HipcM-3sJ__M1AbP8IfDMhWs4RpEwyG7kEn-bwGjcbSXihqfU_rvbBb_EHytBjEtUgfz30yH-UD0Q2Ig4a4c69__f9OLR34--RvrhykBAujBy6_Bt6gu0Ijig1H6zkSprJazDWTIrORdH3168aGKN0AvucJabL-iTJOmo4FTHLP68UgsLhckvnxoRmUWitctUa7qAbQBH-E3_S8Ndl0Y6rjZSVwnh-veYhBoazBV_IQAYKGJ1U9pXdzZ5EgsrMzOoxHutJ1uZpl5LOUv1wHRvquLlbbhp3tAFcobBYPSe5UrVu7MVvCtFUuxH-r8PYbEUTWJJVyE__xq7FIBjSy2Zkg8mjPWlNM9jXGfc7Tfnfcul41rlEcSbdM021eDHSD7M6KQv0CwcCMCptWftfRTricnW6qn29Zx2OKN7amGuN4fu2GmN14hjsmS-ZANGZd9rwOsIDBk8zWF8cbGXQN6jTzYoGPfmB822vPy8aeTTDpBJPDLdESxhISP_9KIx4M6mebKhaBnZ7VXFkjFZYVlftWffDvC56sCdHwE-DGM7UFh4ZNjx-zzT-fFTLjSzKapOqYU8Fz8rMzghsc9Qw2lBOAwb2qeMUTPE4goUOcV5SElvYMrUefQIr8DqTMw6lgba4AEzq21zHCTW1TDanjZI0WNI8teZYnMRIH9_3BP8bvZwaQpnhY0PdDy09xLirKE7RwP7VKqY8aZjp7TS3l5EDlOKnPjRHgLR_nmDfvUdelE2LINGz_k7jdIfCgl0u3_xYyeLz8NfHIB3zmfD59TPV67BO2bYV8xYnXqhiNC16fNzI_AxSujefb580J-a8WvOo0FAHa8xiXkLOARhQQ4Vp6oavah-BEfMoWEZwaig14ZIFbPM330jbJlfwy-nkb7z9HopooWsOvekVq4gYCXll2M0g5wC5rev86IQPxsZbBvogqUv7ZD6vFqiBo59FsNziN7jedMl3diCaaPPjPEDtU2IbMyt9b27OeBIodqCfIylTmjHUPv1-0XrZFF1WOcA26OkYDGwZ9Cwp6buGvHErmdG6KGCSim5il9uYD8CRHmY7g4PGQL6Px9YUARt9TMZAgxbwB9d4Rv7ALLVnA286kgpqj3xeRgRGE8VEtfwqaOzCl06cIij_nRa6ht--CDMah-IRrlC3QMN70339HthhgkBzEVcVS0WO8P1enaNwblM5htYNBcx48o_jVRl6CfvzN38hdXO8eYCASCf_5wmp_50yVjfWcRDckFnXr-WKzc85eIYiHLHkRr6JbXHWcfiFsFCzerKQnNI5fKQKC8Cs_bmkeO56AC7xfOYODgwQWokGhK_aiBfyJZM80i57I473bpjvVKmbXp3XcCOI3Ec_ajrgu_TrHievcn1EKUFYE14lVufIaqFEttN_P_CkQFzLL58-XBvbBzlfMIqEFWQwfJX2nVaSiEBJXKwrZb-PqhoX8YR0qo5MOya3zVVPCI5Qxko5DB9NsJvj8FdPzlp-mobCxqBt2Mn_MHY1p66xSGX-e7TLOrvVGVm3rkn6FWpJhcEEQxd0N3Te7h9fA7UpHygEW1kLoE130m-o1Uc-W0aiL0Zh1LUpENdVWTz5AI4qHS7dfZkBeAE-Qgv86uhV0mMOqTDepd8WqygaFTPFJpblNZX1DfozDjr4Ut8eYhg_DyDmIGKcbdpU5QlcXywTN4G87MY3zodhO-SV5b-8aWiiJOMgjlPPdtDaRHIEdZgWcY3inosO5OiwH_7wYihx5MriL3k35jVbFmxHZXeFsA0vxwQCkejRUcQ6WELOlMh4i2IZJ2WJYYrxwUjYCLIBHiWhfU6h7Ml6BT3jmVk4Om5HNf9WFlwKd4jY-8MsiEpkrUMH4KyGhC26OMr_s_WK9sLzF3wkUYtcOykI-Ql6Gmi3lfitpZnI0daeGs6jPianv9q5yBD6EGtlMRePQbt1SPI3ndBzkS4DlrTAP7-iSFvjNRq1__9Xt-g4TXdWf7w9_hK1syXI3tlP2uVtOEbm7uPZlpG-nhKKfvbOXG66zhbLJpgZRr9zIj_iWBBdZmaZkjOgQh980b-qEBzKX3vTFJkR-PI9gGq8jUMh5AULhX9DQ6E3Z8M0SPZj0-500gYF3LC2WT7U7FmXxIPCfFT7HZrkY_ezm0kDWlqCEo0BG1byvTnzZhhCo_eqcPMXKD80920IDbsSKjMW3hR-WUTwRHjLLzqI2f5_l0nzPpxhytDmhKDFyRtKC-IUrmNcC4ciYmCaCSsBX2QEBJf-k8dkZuzgqqPcrRgsh_dJ0EHcdysx6G7waBih3GzijR2ViQjZLhZtqltmM2ytcNkqLmTfsnNWUuHosckCbd7YVbanHmQ5bb7SUpHQYernrkQrK3uy_IbmzOSDIIMicM--30rSJ4_cQI_E_aLz_u6bqtbk08mrYgzxOKFe7yXB_dKPPqtUcrK4FdkkzQ1NzCEOYqkJXYQK3JnUSY= \ No newline at end of file From a64e0d4aba22db2bb365fd5dcaef637d3ff4a671 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 13 Sep 2017 16:19:36 +0200 Subject: [PATCH 036/288] Move doctr options to command line The doctr key seems to cause trouble. Use command line args instead --- .travis.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index db283b375..5b0b731e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - - "3.6" + - 3.6 sudo: required @@ -11,6 +11,7 @@ services: env: global: - secure: "CCVtBQ8xIrOwUJGXxd81wyD1ng72Hf6d9y2U+5X88aVGTrOa8/hut10C+Jmnyf0NTZmGh/49eVvoWRvLDhjpECFMuO/bLkiNtVjz0VtWAHT2W98QJYmeymPzx86tGa+iAZCwlgXeRQFJCw1eqQvBYnjumMZWb9kj3fqgpqpSRH5SWnuRCmbxOoelmtTTUC8YKkzasAHYs03faR0DCq0oBmDy9nU2cfcRN7oE5wXUfEnDwNaoHbiQA4wiJbzNpBV432bIDtzD7gsFdiIT6ExJVFHi1gWB32bGZdbiDPpej7I2fW5qunbzUXS5doVhoBU67qqhI241RJ+AOBVb+sQnF8gwwi9/5/mcORiSBX7yI59YsOXYwo2YW+8PGr3OlF3t0+z92Q3uPytUXysdtVO4dExnLbV8OEzgmWCkv2M3GIajjE3isYAaBItqSBJHJnRClza4nNg2WLwmqLPBgM4AuSUZEpB/8kbz9kTecVEb13WrlTCNc8KVRGR+EGa4KmWADwOOxurxeb/NsTteOnzdyfrP2TXKeGOkN2uqBGYJaI7OoefsgLG7VF/+Sz4MTETMs/gZojwpO6igKBS1sJlcXujz/kt125b8gcSnrAiU1TjbZIBew/D40H64tpBcuk+dqF6i6HCoV2QmZ1QEpHOSoDk9FEaKMlgKhQj59/cWcI8=" + matrix: - DOCKER_IMG=cpascual/taurus-test:debian-jessie - DOCKER_IMG=cpascual/taurus-test:debian-stretch @@ -34,12 +35,8 @@ script: pip install doctr ; touch /taurus/build/sphinx/html/.nojekyll ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then - doctr deploy docs; + doctr deploy docs --key-path "/taurus/ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; else - doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master ; + doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master --key-path "/taurus/ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; fi fi - -- doctr: - - key-path : '/taurus/ci/github_deploy_key.enc' - - deploy-repo : 'taurus-org/taurus-doc' From c6ceab8a915d6ca5d50d6306afe17efeed6276d4 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 13 Sep 2017 19:24:45 +0200 Subject: [PATCH 037/288] Fix paths on travis machine --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b0b731e3..cb583409c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,10 +33,10 @@ script: # deploy sphinx-built docs to taurus-doc repo - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then pip install doctr ; - touch /taurus/build/sphinx/html/.nojekyll ; + touch build/sphinx/html/.nojekyll ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then - doctr deploy docs --key-path "/taurus/ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; + doctr deploy docs --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; else - doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master --key-path "/taurus/ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; + doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; fi fi From 2c10778b12e3d0144a058c83ecf2f8a49ee4f72f Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 13 Sep 2017 19:52:40 +0200 Subject: [PATCH 038/288] Use docker to avoid permission error --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cb583409c..c1b1ee392 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ script: # deploy sphinx-built docs to taurus-doc repo - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then pip install doctr ; - touch build/sphinx/html/.nojekyll ; + docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then doctr deploy docs --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; else From 1d91c7d1b6f96b92b1969a21d3bf9f4e9916c228 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 13 Sep 2017 20:17:50 +0200 Subject: [PATCH 039/288] Add missing ";" and debug messages --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c1b1ee392..5a82361f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,11 +32,14 @@ script: - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" # deploy sphinx-built docs to taurus-doc repo - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then + echo "Installing doctr"; pip install doctr ; + echo "Creating .nojekyll"; docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; + echo "Deploying docs"; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then doctr deploy docs --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; else doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; - fi + fi; fi From 2108ba113cdde22e491b15b278eacf76282b143b Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 13 Sep 2017 21:05:49 +0200 Subject: [PATCH 040/288] Use set -x for debugging --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5a82361f8..e4604c3b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,16 +27,14 @@ before_install: script: - set -e + - set -x - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py install" - docker exec taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" # deploy sphinx-built docs to taurus-doc repo - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then - echo "Installing doctr"; pip install doctr ; - echo "Creating .nojekyll"; docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; - echo "Deploying docs"; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then doctr deploy docs --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; else From 44f32ad96544b96b1f21ee2125fee7fb9d21de3d Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 13 Sep 2017 22:05:21 +0200 Subject: [PATCH 041/288] Rewrite using doctr key --- .travis.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e4604c3b1..c213e2a4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,15 @@ script: pip install doctr ; docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then - doctr deploy docs --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; + echo "deploying to docs" + doctr deploy docs ; else - doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master --key-path "ci/github_deploy_key.enc" --deploy-repo "taurus-org/taurus-doc; + echo "deploying to docs-$TRAVIS_BRANCH" + doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master ; fi; fi + +doctr: + key-path : ci/github_deploy_key.enc + deploy-repo : taurus-org/taurus-doc + require-master: false From e79f3c24ff6ebea31743dba53e40b7f820d65cf8 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 13 Sep 2017 23:11:58 +0200 Subject: [PATCH 042/288] Add missing ";" --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c213e2a4f..9e1cc16c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,10 +36,10 @@ script: pip install doctr ; docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then - echo "deploying to docs" + echo "deploying to docs" ; doctr deploy docs ; else - echo "deploying to docs-$TRAVIS_BRANCH" + echo "deploying to docs-$TRAVIS_BRANCH" ; doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master ; fi; fi From dc5bc96f1421addab37ae8536dc3146bea1b8bfb Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 13 Sep 2017 23:40:11 +0200 Subject: [PATCH 043/288] Add sync and built-docs options --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e1cc16c0..eff18e511 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ script: doctr deploy docs ; else echo "deploying to docs-$TRAVIS_BRANCH" ; - doctr deploy "docs-$TRAVIS_BRANCH" --no-require-master ; + doctr deploy "docs-$TRAVIS_BRANCH" ; fi; fi @@ -48,3 +48,5 @@ doctr: key-path : ci/github_deploy_key.enc deploy-repo : taurus-org/taurus-doc require-master: false + sync: true + built-docs: build/sphinx/html/ From f941cabdaa4dc5af5036b6b6b4cbbffd61c475b8 Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 14 Sep 2017 09:09:57 +0200 Subject: [PATCH 044/288] Change docs deploy dir Use docs/v- instead of docs- --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index eff18e511..c075efe24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,11 +36,9 @@ script: pip install doctr ; docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then - echo "deploying to docs" ; doctr deploy docs ; else - echo "deploying to docs-$TRAVIS_BRANCH" ; - doctr deploy "docs-$TRAVIS_BRANCH" ; + doctr deploy "docs/v-$TRAVIS_BRANCH" ; fi; fi From ff69f5256e2d606cf2c0a32fd6efd3f980fa3f07 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 14 Sep 2017 09:52:35 +0200 Subject: [PATCH 045/288] Deploy to root of gh-branches instead of docs dir --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c075efe24..b48d8e3c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,9 +36,9 @@ script: pip install doctr ; docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then - doctr deploy docs ; + doctr deploy . ; else - doctr deploy "docs/v-$TRAVIS_BRANCH" ; + doctr deploy "v-$TRAVIS_BRANCH" ; fi; fi From 44293156a3671702530dc6ca4a4698930185743d Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 14 Sep 2017 12:07:48 +0200 Subject: [PATCH 046/288] Remove set -x from travis yaml --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b48d8e3c4..58d922353 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,6 @@ before_install: script: - set -e - - set -x - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py install" - docker exec taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" From a470252ac55332f3a0aa7dff42faef831e00c984 Mon Sep 17 00:00:00 2001 From: Jan Kotanski Date: Thu, 21 Sep 2017 15:37:56 +0200 Subject: [PATCH 047/288] remove zombies on start new ExternalApp --- lib/taurus/qt/qtgui/util/taurusaction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/taurus/qt/qtgui/util/taurusaction.py b/lib/taurus/qt/qtgui/util/taurusaction.py index 6df3e6d66..cad0d5158 100644 --- a/lib/taurus/qt/qtgui/util/taurusaction.py +++ b/lib/taurus/qt/qtgui/util/taurusaction.py @@ -125,6 +125,7 @@ def actionTriggered(self, args=None): if any(args): #Qt.QMessageBox.warning(self.parentWidget(),'Warning','In ExternalAppAction(%s)'%args) self._process.append(subprocess.Popen(args)) + self._process = [p for p in self._process if p.poll() is None] return True else: return False From cb35fd832fd0edef3458496b832f93e54b99e77d Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 26 Sep 2017 09:18:11 +0200 Subject: [PATCH 048/288] Taurus reports false warnings Attributes that are not quantities report unitless warning. Fix #579 --- lib/taurus/core/tango/tangoattribute.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 045a92836..01480ab31 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -905,7 +905,11 @@ def _decodeAttrInfoEx(self, pytango_attrinfoex=None): ############################################################### # changed in taurus4: range, alarm and warning now return # quantities if appropriate - units = self._unit_from_tango(i.unit) + if self.isNumeric(True): + units = self._unit_from_tango(i.unit) + else: + units = UR.parse_units(None) + if PyTango.is_numerical_type(i.data_type, inc_array=True): Q_ = partial(quantity_from_tango_str, units=units, dtype=i.data_type) From f18cc03693157e714bc056dc52bf03a39a3ecdf6 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 26 Sep 2017 10:37:37 +0200 Subject: [PATCH 049/288] [doc] [minor] Correct outdated text --- doc/source/devel/core_tutorial.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/devel/core_tutorial.rst b/doc/source/devel/core_tutorial.rst index 4398e1c9b..83e0d4e03 100644 --- a/doc/source/devel/core_tutorial.rst +++ b/doc/source/devel/core_tutorial.rst @@ -136,8 +136,8 @@ model name: - :func:`taurus.Attribute` - :func:`taurus.Object` -The first four helpers require you to know which type of Element (e.g., -Attribute, Device,...) is represented by the model name. If you do not know that +The first three helpers require you to know which type of Element (i.e., +Attribute, Device or Authority) is represented by the model name. If you do not know that beforehand, you can use :meth:`taurus.Object` which will automatically find the type and provide you with the corresponding model object (but of course this is slightly less efficient than using one of the first three helpers). @@ -237,4 +237,4 @@ the most important ones. .. _Tango: http://www.tango-controls.org/ .. _PyTango: http://packages.python.org/PyTango/ .. _EPICS: http://www.aps.anl.gov/epics/ -.. _RFC3986: https://tools.ietf.org/html/rfc3986 \ No newline at end of file +.. _RFC3986: https://tools.ietf.org/html/rfc3986 From bfd14ee54db7095e333b8f942f44a29d5529d135 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 28 Sep 2017 11:21:22 +0200 Subject: [PATCH 050/288] Some TaurusButton do not show icons TaurusDevButton and TaurusImageButton do not show the icons. Fix them. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 671ae1565..c028a8e88 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -27,9 +27,10 @@ taurusvalue.py: """ -__all__ = ["TaurusValue", "TaurusValuesFrame", "DefaultTaurusValueCheckBox", "DefaultLabelWidget", +__all__ = ["TaurusValue", "TaurusValuesFrame", "DefaultTaurusValueCheckBox", "DefaultUnitsWidget", "TaurusPlotButton", "TaurusArrayEditorButton", - "TaurusValuesTableButton", "TaurusValuesTableButton_W", "TaurusDevButton"] + "TaurusValuesTableButton", "TaurusValuesTableButton_W", + "DefaultLabelWidget", "TaurusDevButton", "TaurusImageButton"] __docformat__ = 'restructuredtext' @@ -256,7 +257,7 @@ class TaurusArrayEditorButton(_AbstractTaurusValueButton): class TaurusImageButton(_AbstractTaurusValueButton): '''A button that launches a TaurusPlot''' _widgetClassName = 'TaurusImageDialog' - _icon = ':/mimetypes/image-x-generic.svg' + _icon = 'mimetypes:image-x-generic.svg' class TaurusValuesTableButton(_AbstractTaurusValueButton): @@ -275,7 +276,7 @@ class TaurusValuesTableButton_W(TaurusValuesTableButton): class TaurusDevButton(_AbstractTaurusValueButton): '''A button that launches a TaurusAttrForm''' _widgetClassName = 'TaurusDevicePanel' - _icon = ':/places/folder-remote.svg' + _icon = 'places:folder-remote.svg' _text = 'Show Device' From 9fbde5a61b76c4ba42fea737eef0ec9a4019b77b Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 28 Sep 2017 16:09:08 +0200 Subject: [PATCH 051/288] [doc] [minor] Fix copy-paste error in docstring --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index c028a8e88..6a68e9f0d 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -255,7 +255,7 @@ class TaurusArrayEditorButton(_AbstractTaurusValueButton): class TaurusImageButton(_AbstractTaurusValueButton): - '''A button that launches a TaurusPlot''' + '''A button that launches an ImageDialog''' _widgetClassName = 'TaurusImageDialog' _icon = 'mimetypes:image-x-generic.svg' From 8757ac6ffb7481bf2f2db56764c8df0d6c0f73ae Mon Sep 17 00:00:00 2001 From: cpascual Date: Sat, 7 Oct 2017 15:00:12 +0200 Subject: [PATCH 052/288] Enable unit test for write/read epics attribute The epics.AttributeTestCase.write_read_attr was disabled because epics was not working in the CI test docker images. Reactivate it because epics seems to work well now. --- lib/taurus/core/epics/test/test_epicsattribute.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/taurus/core/epics/test/test_epicsattribute.py b/lib/taurus/core/epics/test/test_epicsattribute.py index 463037976..06caa0cc1 100755 --- a/lib/taurus/core/epics/test/test_epicsattribute.py +++ b/lib/taurus/core/epics/test/test_epicsattribute.py @@ -54,9 +54,7 @@ wvalue=Quantity('1m'), quality=AttrQuality.ATTR_VALID, error=None, - ), - test_skip='There are troubles in the docker container. ' - 'This test will be skipped till we fix it' + ) ) @unittest.skipIf(sys.modules.has_key('epics') is False, "epics module is not available") From 85a7bd34f5dcc1894867e637f7ab6fab0640e3a9 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Sat, 7 Oct 2017 18:14:52 +0200 Subject: [PATCH 053/288] Add -t to docker exec command Attempt to get rid of lock after running the testsuite in the docker --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 58d922353..ea9a02698 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ before_install: script: - set -e - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py install" - - docker exec taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" + - docker exec -t taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" # deploy sphinx-built docs to taurus-doc repo - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then From fe3ae00ae6511aac212ccd616619745fcf8c0a4a Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 11 Oct 2017 12:40:41 +0200 Subject: [PATCH 054/288] Use FQDN in the tangovalidator testsuite The test cases do not use the TANGO_HOST in a fully qualified domain name mode. Fix it. --- .../core/tango/test/test_tangovalidator.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/taurus/core/tango/test/test_tangovalidator.py b/lib/taurus/core/tango/test/test_tangovalidator.py index 3115859f0..78ad2b09b 100644 --- a/lib/taurus/core/tango/test/test_tangovalidator.py +++ b/lib/taurus/core/tango/test/test_tangovalidator.py @@ -38,8 +38,11 @@ TangoAttributeNameValidator) import PyTango -__GETENV = PyTango.ApiUtil.get_env_var +import socket +__PY_TANGO_HOST = PyTango.ApiUtil.get_env_var("TANGO_HOST") +host, port = __PY_TANGO_HOST.split(':') +__TANGO_HOST = "{0}:{1}".format(socket.getfqdn(host), port) #========================================================================= # Tests for Tango Authority name validation @@ -80,11 +83,11 @@ class TangoAuthValidatorTestCase(AbstractNameValidatorTestCase, @names(name='tango://foo:123/a/b/c', out=('tango://foo:123/a/b/c', '//foo:123/a/b/c', 'a/b/c')) @names(name='tango:sys/tg_test/1', - out=('tango://%s/sys/tg_test/1' % __GETENV("TANGO_HOST"), + out=('tango://%s/sys/tg_test/1' % __TANGO_HOST, 'sys/tg_test/1', 'sys/tg_test/1')) @names(name='tango:alias', out=(None, None, 'alias')) # @names(name = 'tango:mot49', # commented out because it assumes mot49 exists -# out=('tango://%s/motor/motctrl13/1'% __GETENV("TANGO_HOST"), +# out=('tango://%s/motor/motctrl13/1'% __TANGO_HOST, # 'motor/motctrl13/1', 'mot49')) class TangoDevValidatorTestCase(AbstractNameValidatorTestCase, unittest.TestCase): @@ -112,10 +115,10 @@ class TangoDevValidatorTestCase(AbstractNameValidatorTestCase, @names(name='tango://foo:123/a/b/c/d', out=('tango://foo:123/a/b/c/d', '//foo:123/a/b/c/d', 'd')) @names(name='tango:sys/tg_test/1/float_scalar', - out=('tango://%s/sys/tg_test/1/float_scalar' % __GETENV("TANGO_HOST"), + out=('tango://%s/sys/tg_test/1/float_scalar' % __TANGO_HOST, 'sys/tg_test/1/float_scalar', 'float_scalar')) # @names(name = 'tango:mot49/position', # commented out because it assumes mot49 -# out=('tango://%s/motor/motctrl13/1/position'% __GETENV("TANGO_HOST"), +# out=('tango://%s/motor/motctrl13/1/position'% __TANGO_HOST, # 'motor/motctrl13/1/position', 'position')) #========================================================================= # Tests for validation of Attribute name with fragment @@ -147,8 +150,7 @@ class TangoDevValidatorTestCase(AbstractNameValidatorTestCase, out=('tango://foo:123/a/b/c/d', '//foo:123/a/b/c/d', 'd', 'label')) @names(name='tango:sys/tg_test/1/float_scalar#', - out=('tango://%s/sys/tg_test/1/float_scalar' % - __GETENV("TANGO_HOST"), + out=('tango://%s/sys/tg_test/1/float_scalar' % __TANGO_HOST, 'sys/tg_test/1/float_scalar', 'float_scalar', '')) @names(name='tango://foo:123/a/b/c/d?configuration=label', out=('tango://foo:123/a/b/c/d', From dfa972947d5f1438b487fd1298491444bc33e0cd Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 11 Oct 2017 12:42:52 +0200 Subject: [PATCH 055/288] Add new back. comp. TangoValidator tests Increase the back. comp. TangoValidator test cases --- lib/taurus/core/tango/test/test_tangovalidator.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/taurus/core/tango/test/test_tangovalidator.py b/lib/taurus/core/tango/test/test_tangovalidator.py index 78ad2b09b..e4a1fadc5 100644 --- a/lib/taurus/core/tango/test/test_tangovalidator.py +++ b/lib/taurus/core/tango/test/test_tangovalidator.py @@ -78,6 +78,12 @@ class TangoAuthValidatorTestCase(AbstractNameValidatorTestCase, @valid(name='tango:a/b/ c', groups={'devname': 'a/b/ c'}) @invalid(name='tango:/a/b/c?') @valid(name='tango://a/b/c', strict=False) +@valid(name='tango:alias', strict=False) +@valid(name='tango://a/b/c', strict=False) +@invalid(name='tango:foo:1234/alias', strict=False) +@invalid(name='tango:foo:1234/a/b/c', strict=False) +@invalid(name='foo:1234/alias', strict=False) +@valid(name='foo:1234/a/b/c', strict=False) @invalid(name='tango://a/b/c', strict=True) @invalid(name='tango://devalias') @names(name='tango://foo:123/a/b/c', @@ -97,6 +103,8 @@ class TangoDevValidatorTestCase(AbstractNameValidatorTestCase, #========================================================================= # Tests for Tango Attribute name validation (without fragment) #========================================================================= +@valid(name='foo:10000/a/b/c/d', strict=False) +@valid(name='mot/position', strict=False) @valid(name='tango:a/b/c/d', groups={'devname': 'a/b/c', 'attrname': 'a/b/c/d', '_shortattrname': 'd'}) From 5406fb17a7475b0f24b757e456e6fa9bf81e7b4a Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 11 Oct 2017 12:45:13 +0200 Subject: [PATCH 056/288] Fix strange behavior of Tango validator Some back. comp. URIs do not work. The Tango validator does not return the group when the strict mode is set to False. Fix #586 --- lib/taurus/core/tango/tangovalidator.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/taurus/core/tango/tangovalidator.py b/lib/taurus/core/tango/tangovalidator.py index 71cdab2e4..fcd8f0acc 100644 --- a/lib/taurus/core/tango/tangovalidator.py +++ b/lib/taurus/core/tango/tangovalidator.py @@ -176,7 +176,19 @@ def nonStrictNamePattern(self): '''In non-strict mode, allow double-slash even if there is no Authority. (e.g., "tango://a/b/c" passes this non-strict form) ''' - return self.namePattern.replace('tango):(', 'tango)://(') + pattern = r'^((?P%(scheme)s)://)?' + \ + r'((?P%(authority)s)(?=/))?' + \ + r'(?P%(path)s)' + \ + r'(\?(?P%(query)s))?' + \ + r'(#%(fragment)s)?$' + authority = '(?P([\w\-_]+\.)*[\w\-_]+):(?P\d{1,5})' + path = '/?(?P((?P<_devslashname>[^/?#:]+/[^/?#:]+/[^/?#:]+)))' + + return pattern % dict(scheme=self.scheme, + authority=authority, + path=path, + query='(?!)', + fragment='(?!)') class TangoAttributeNameValidator(TaurusAttributeNameValidator): @@ -250,14 +262,16 @@ def nonStrictNamePattern(self): """ # allow for *optional* double-slashes and *optional* ?configuration... - pattern = r'^(?P%(scheme)s):(//)?' + \ + pattern = r'^((?P%(scheme)s)://)?' + \ r'((?P%(authority)s)(?=/))?' + \ r'(?P%(path)s)' + \ r'(\?(?P%(query)s))?' + \ r'(#%(fragment)s)?$' + authority = '(?P([\w\-_]+\.)*[\w\-_]+):(?P\d{1,5})' + query = 'configuration(=(?P(?P[^# ]+)))?' return pattern % dict(scheme=self.scheme, - authority='(?P([\w\-_]+\.)*[\w\-_]+):(?P\d{1,5})', + authority=authority, path=self.path, - query='configuration(=(?P(?P[^# ]+)))?', + query=query, fragment='(?!)') From 970f18104318485d0c08f14ad949e15d8df87c8a Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 13 Oct 2017 11:11:56 +0200 Subject: [PATCH 057/288] Remove obsolete test and update import test test_tauruscoreindependent test is not reallty working. Remove it and add a exclude pattern to test_import to make it useful when no tango or epics are importable --- lib/taurus/core/test/modulemanager.py | 150 ------------------ .../core/test/test_tauruscoreindependent.py | 108 ------------- lib/taurus/test/test_import.py | 17 +- 3 files changed, 14 insertions(+), 261 deletions(-) delete mode 100644 lib/taurus/core/test/modulemanager.py delete mode 100644 lib/taurus/core/test/test_tauruscoreindependent.py diff --git a/lib/taurus/core/test/modulemanager.py b/lib/taurus/core/test/modulemanager.py deleted file mode 100644 index cdcb770cd..000000000 --- a/lib/taurus/core/test/modulemanager.py +++ /dev/null @@ -1,150 +0,0 @@ -import imp -import importlib -from sys import modules -from taurus.core.util.singleton import Singleton - - -class ModuleManager(Singleton): - '''ModuleManager class is an helper to manager the python modules. - This class has methods to import, reload, and block python modules. - ''' - - def __init__(self): - self._modules = {} - - def _deleteModule(self, modname): - ''' Remove the given module name from the exported python modules - - :param modname: Module name - :type modname: str - ''' - try: - thismod = modules[modname] - except KeyError: - # This module is not imported - raise ValueError(modname) - these_symbols = dir(thismod) - - del modules[modname] - for mod in modules.values(): - try: - delattr(mod, modname) - except AttributeError: - pass - - def blockModule(self, modname): - ''' Remplace the given module name by None. inhibits the module. - - :param modname: Module name - :type modname: str - ''' - if self._modules.get(modname) is None: - self.importModule(modname) - - if modules[modname] is not None: - _mod = {} - _mod['mod'] = modules[modname] - _mod['submod'] = [] - # Delete and block submodules - for mod in modules.keys(): - if mod.find(modname) != -1: - _mod['submod'].append((mod, modules[mod])) - self._deleteModule(mod) - modules[mod] = None - # Delete and block the module - self._deleteModule(modname) - modules[modname] = None - self._modules[modname] = _mod - - def reloadModule(self, modname): - '''Reload the given module name. - - :param modname: Module name - :type modname: str - ''' - if self._modules.get(modname) is None: - msg = 'ModuleManager: Trying to reload a not imported module, %s'\ - % (modname) - print msg - return - # Reload the submodules - for subname, submod in self._modules[modname]['submod']: - modules[subname] = submod - # Reload the module - modules[modname] = self._modules[modname]['mod'] - imp.reload(self._modules[modname]['mod']) - - def importModule(self, modname): - ''' Import the given module name. - - :param modname: Module name - :type modname: str - ''' - try: - mod = __import__(modname) - modules[modname] = mod - _mod = {} - _mod['mod'] = mod - _mod['submod'] = [] - self._modules[modname] = _mod - except ImportError: - print 'Imposible to import the module %s' % (modname) - - def _getModuleDict(self, modname): - ''' Return a dictionary with the given module name and its submodels - if exists or None. - - :param modname: Module name - :type modname: str - - :return dictionary - ''' - return self._modules.get(modname) - - def getModule(self, modname): - ''' Return a module of the given module name if exists or None. - - :param modname: Module name - :type modname: str - - :return python module - ''' - d = self._getModuleDict(modname) - if d is None: - return None - return d['mod'] - -#---------------------Just 4 Test----------------------------------------- - - -def blockPyTango(modmanager): - import taurus - from taurus.core.taurusmanager import TaurusManager - tm = TaurusManager() - print '\n-*-*-*-*-*- Block PyTango' - modmanager.blockModule('PyTango') - taurus.check_dependencies() - print '\n -- Taurus plugins' - print tm.buildPlugins() - - -def reloadPyTango(modmanager): - import taurus - from taurus.core.taurusmanager import TaurusManager - tm = TaurusManager() - print '\n-*-*-*-*-*- Reload PyTango' - modmanager.reloadModule('PyTango') - taurus.check_dependencies() - print '\n -- Taurus plugins' - print tm.buildPlugins() - - -def test(): - modmanager = ModuleManager() - blockPyTango(modmanager) - reloadPyTango(modmanager) - blockPyTango(modmanager) - - -if __name__ == '__main__': - test() diff --git a/lib/taurus/core/test/test_tauruscoreindependent.py b/lib/taurus/core/test/test_tauruscoreindependent.py deleted file mode 100644 index 8e755a215..000000000 --- a/lib/taurus/core/test/test_tauruscoreindependent.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python - -############################################################################# -## -# 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 . -## -############################################################################# - -"""Test for taurus.core being PyTango Independent""" - -__docformat__ = 'restructuredtext' - -from taurus.external import unittest -# import functools -# from taurus.test import insertTest -# from modulemanager import ModuleManager -# -# # Decorator -# isTangoAvailable = functools.partial(insertTest, helper_name='isTangoAvailable', -# blockPytango=False) -# -# -# *TODO -#@isTangoAvailable(blockPytango=True) -#@isTangoAvailable() # Default False -#@isTangoAvailable(blockPytango=True) - - -class CoreTangoIndependentTestCase(unittest.TestCase): - '''Test the Tango-independent core. As part of the SEP3 specification - Taurus's core must be functional without PyTango. - This test checks that you can import taurus without PyTango. - ''' - - def _importTaurus(self): - ''' Helper method''' - try: - import taurus - except ImportError: - raise ImportError("Cannot import Taurus") - - def test_basicImport(self): - '''Check if Taurus can be imported without PyTango - ''' - # skip if PyTango is available - try: - import PyTango - msg = 'Cannot test Tango-independence if PyTango can be imported' - self.skipTest(msg) - except ImportError: - pass - # check that taurus can be imported - self.assertRaises(ImportError, self._importTaurus()) - -# _modmanager = ModuleManager() -# def _basicImportWithoutPyTango(self): -# '''Basic test just try to import taurus (without PyTango) -# ''' -# # TODO: -# # _basicImportWithoutPyTango -> test_basicImportWithoutPyTango -# # We have problems blocking modules so this test failed -# self._modmanager.blockModule('PyTango') -# self.assertRaises(ImportError, self._importTaurus()) -# -# def isTangoAvailable(self, blockPytango=False): -# '''Test to check is Tango module is available for Taurus -# ''' -# # TODO: -# # We have problems blocking modules so this test failed because that -# if blockPytango: -# self._modmanager.blockModule('PyTango') -# -# from taurus.core.taurusmanager import TaurusManager -# plugins = TaurusManager().buildPlugins() -# -# if blockPytango: -# print '\t***', plugins -# msg = 'Taurus is using Tango scheme, but you are blocking PyTango' -# self.assertFalse('tango' in plugins, msg) -# else: -# msg = 'Taurus can not use Tango scheme, maybe you have not' +\ -# ' installed PyTango' -# self.assertTrue('tango' in plugins, msg) -# -# -# def tearDown(self): -# ''' Restore the original PyTango module''' -# # *TODO -# #self._modmanager.reloadModule('PyTango') -# pass -# diff --git a/lib/taurus/test/test_import.py b/lib/taurus/test/test_import.py index 215d4b59e..9f575a4a6 100644 --- a/lib/taurus/test/test_import.py +++ b/lib/taurus/test/test_import.py @@ -30,9 +30,9 @@ class TaurusImportTestCase(unittest.TestCase): - ''' + """ Test if all the submodules can be imported - ''' + """ def setUp(self): """Preconditions: moduleexplorer utility has to be available """ @@ -46,7 +46,18 @@ def testImportSubmodules(self): Expected Results: It is expected to get no warning message on module importing """ - exclude_patterns = (r'taurus.qt.qtgui.extra_.*',) + exclude_patterns = [r'taurus\.qt\.qtgui\.extra_.*'] + + try: + import PyTango + except ImportError: + exclude_patterns.append(r'taurus\.core\.tango') + try: + import epics + except ImportError: + exclude_patterns.append(r'taurus\.core\.epics') + + moduleinfo, wrn = self.explore('taurus', verbose=False, exclude_patterns=exclude_patterns) msg = None From f5be8e15be955d8acde66c6f50b5773de8750cf9 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 13 Oct 2017 11:52:06 +0200 Subject: [PATCH 058/288] Add new test cases --- lib/taurus/core/tango/test/test_tangovalidator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/test/test_tangovalidator.py b/lib/taurus/core/tango/test/test_tangovalidator.py index e4a1fadc5..817b03986 100644 --- a/lib/taurus/core/tango/test/test_tangovalidator.py +++ b/lib/taurus/core/tango/test/test_tangovalidator.py @@ -78,7 +78,9 @@ class TangoAuthValidatorTestCase(AbstractNameValidatorTestCase, @valid(name='tango:a/b/ c', groups={'devname': 'a/b/ c'}) @invalid(name='tango:/a/b/c?') @valid(name='tango://a/b/c', strict=False) -@valid(name='tango:alias', strict=False) +@valid(name='tango:alias') +# @invalid(name='tango:alias', strict=False) # It matches with strict=True +@valid(name='tango://alias', strict=False) @valid(name='tango://a/b/c', strict=False) @invalid(name='tango:foo:1234/alias', strict=False) @invalid(name='tango:foo:1234/a/b/c', strict=False) From aadad029150fe169b1c294f895524df0c5778b3c Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 13 Oct 2017 11:52:31 +0200 Subject: [PATCH 059/288] Fix TangoDeviceNameValidator back. comp. URI Devices with URI like tango://devalias fails. Fix it. --- lib/taurus/core/tango/tangovalidator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangovalidator.py b/lib/taurus/core/tango/tangovalidator.py index fcd8f0acc..7eee4b5b9 100644 --- a/lib/taurus/core/tango/tangovalidator.py +++ b/lib/taurus/core/tango/tangovalidator.py @@ -182,7 +182,8 @@ def nonStrictNamePattern(self): r'(\?(?P%(query)s))?' + \ r'(#%(fragment)s)?$' authority = '(?P([\w\-_]+\.)*[\w\-_]+):(?P\d{1,5})' - path = '/?(?P((?P<_devslashname>[^/?#:]+/[^/?#:]+/[^/?#:]+)))' + path = '/?(?P((?P<_devalias>(?<=//)([^/?#:]+))|' + \ + '(?P<_devslashname>[^/?#:]+/[^/?#:]+/[^/?#:]+)))' return pattern % dict(scheme=self.scheme, authority=authority, From eab576ef2c6b8f17120ade98588d989ec82d4ea2 Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 13 Oct 2017 16:26:31 +0200 Subject: [PATCH 060/288] Remove imports of PyTango in taurus.qt submodule Remove module-level imports of PyTango in taurus.qt submodule. In some cases, use taurus-equivalent code and in others delay the import and mark it as tango-centric. --- lib/taurus/qt/qtgui/button/taurusbutton.py | 6 +++--- lib/taurus/qt/qtgui/button/test/res/Timeout | 2 +- lib/taurus/qt/qtgui/dialog/taurusmessagebox.py | 6 +++--- lib/taurus/qt/qtgui/display/tauruslabel.py | 10 ++++------ lib/taurus/qt/qtgui/display/tauruslcd.py | 10 ++++------ lib/taurus/qt/qtgui/panel/taurusform.py | 10 ++++++---- lib/taurus/qt/qtgui/panel/taurusmessagepanel.py | 5 +---- lib/taurus/qt/qtgui/plot/taurustrend.py | 2 +- lib/taurus/qt/qtgui/tree/taurusdevicetree.py | 4 +++- 9 files changed, 26 insertions(+), 29 deletions(-) diff --git a/lib/taurus/qt/qtgui/button/taurusbutton.py b/lib/taurus/qt/qtgui/button/taurusbutton.py index 8d83d23e5..379e0e6ee 100644 --- a/lib/taurus/qt/qtgui/button/taurusbutton.py +++ b/lib/taurus/qt/qtgui/button/taurusbutton.py @@ -30,8 +30,6 @@ __docformat__ = 'restructuredtext' -import PyTango - from taurus.external.qt import Qt from taurus.core.taurusbasetypes import LockStatus, TaurusLockInfo from taurus.core.taurusdevice import TaurusDevice @@ -265,7 +263,7 @@ class TaurusCommandButton(Qt.QPushButton, TaurusBaseWidget): .. seealso:: :class:`TaurusCommandsForm` provides a good example of use of TaurusCommandButton (including managing the return value) ''' - + # TODO: tango-centric commandExecuted = Qt.pyqtSignal(object) def __init__(self, parent=None, designMode=False, command=None, @@ -372,6 +370,8 @@ def _castParameters(self, parameters=None, command=None, dev=None): :return: (sequence or scalar) a sequence of parameters (or a scalar if only one parameter) ''' + import PyTango + if parameters is None: parameters = self._parameters if command is None: diff --git a/lib/taurus/qt/qtgui/button/test/res/Timeout b/lib/taurus/qt/qtgui/button/test/res/Timeout index a4a421cfa..860b41ba7 100755 --- a/lib/taurus/qt/qtgui/button/test/res/Timeout +++ b/lib/taurus/qt/qtgui/button/test/res/Timeout @@ -40,7 +40,7 @@ The argument value is the time that will take to execute the command. """ -import PyTango +import PyTango # Tango-centric import sys import time diff --git a/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py b/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py index f0a227ed1..1d8142e73 100644 --- a/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py +++ b/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py @@ -306,7 +306,7 @@ def py_exc(): def tg_exc(): - import PyTango + import PyTango # TODO: tango-centric try: PyTango.Except.throw_exception('TangoException', 'A simple tango exception', 'right here') @@ -316,7 +316,7 @@ def tg_exc(): def tg_serv_exc(): - import PyTango + import PyTango # TODO: tango-centric import taurus dev = taurus.Device("sys/tg_test/1") try: @@ -330,7 +330,7 @@ def tg_serv_exc(): def py_tg_serv_exc(): - import PyTango + import PyTango # TODO: tango-centric try: PyTango.Except.throw_exception('TangoException', 'A simple tango exception', 'right here') diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 8e6e19a4f..2a8475d93 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -32,10 +32,8 @@ import operator import re -# shame of me for importing PyTango! -import PyTango - -from taurus.core.taurusbasetypes import TaurusElementType, TaurusEventType +from taurus.core.taurusbasetypes import (TaurusElementType, TaurusEventType, + AttrQuality, TaurusDevState) from taurus.external.qt import Qt from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.base import TaurusBaseController @@ -170,10 +168,10 @@ def w_value(self): return 0.0 def quality(self): - return PyTango.AttrQuality.ATTR_VALID + return AttrQuality.ATTR_VALID def state(self): - return PyTango.DevState.ON + return TaurusDevState.Ready def _updateToolTip(self, lcd): lcd.setToolTip("Some random value for design purposes only") diff --git a/lib/taurus/qt/qtgui/display/tauruslcd.py b/lib/taurus/qt/qtgui/display/tauruslcd.py index 501eed174..ffc744211 100644 --- a/lib/taurus/qt/qtgui/display/tauruslcd.py +++ b/lib/taurus/qt/qtgui/display/tauruslcd.py @@ -31,10 +31,8 @@ import operator -# shame of me for importing PyTango! -import PyTango - -from taurus.core.taurusbasetypes import TaurusElementType, TaurusEventType +from taurus.core.taurusbasetypes import (TaurusElementType, TaurusEventType, + AttrQuality, TaurusDevState) from taurus.external.qt import Qt from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.base import TaurusBaseController @@ -141,10 +139,10 @@ def w_value(self): return 0.0 def quality(self): - return PyTango.AttrQuality.ATTR_VALID + return AttrQuality.ATTR_VALID def state(self): - return PyTango.DevState.ON + return TaurusDevState.ON def _updateToolTip(self, lcd): lcd.setToolTip("Some random value for design purposes only") diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index d5cb9ee3d..b7d7c599d 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -32,10 +32,9 @@ from datetime import datetime from taurus.external.qt import Qt -import PyTango import taurus.core -from taurus.core import TaurusDevState +from taurus.core import TaurusDevState, DisplayLevel from taurus.qt.qtcore.mimetypes import (TAURUS_ATTR_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_MODEL_MIME_TYPE) @@ -170,6 +169,7 @@ def setCustomWidgetMap(self, cwmap): type strings (i.e. see :class:`PyTango.DeviceInfo`) and whose values are tuples of classname,args,kwargs ''' + # TODO: tango-centric self._customWidgetMap = cwmap def getCustomWidgetMap(self): @@ -179,6 +179,7 @@ def getCustomWidgetMap(self): type strings (i.e. see :class:`PyTango.DeviceInfo`) and whose values are tuples of classname,args,kwargs ''' + # TODO: tango-centric return self._customWidgetMap @Qt.pyqtSlot('QString', name='modelChanged') @@ -546,6 +547,7 @@ def getQtDesignerPluginInfo(cls): class TaurusCommandsForm(TaurusWidget): '''A form that shows commands available for a Device Server''' + # TODO: tango-centric def __init__(self, parent=None, designMode=False): TaurusWidget.__init__(self, parent, designMode) @@ -574,7 +576,7 @@ def __init__(self, parent=None, designMode=False): self._defaultParameters = [] self._sortKey = lambda x: x.cmd_name - self._operatorViewFilter = lambda x: x.disp_level == PyTango.DispLevel.OPERATOR + self._operatorViewFilter = lambda x: x.disp_level == DisplayLevel.OPERATOR # self.setLayout(Qt.QGridLayout()) self.modelChanged.connect(self._updateCommandWidgets) @@ -771,7 +773,7 @@ def __init__(self, parent=None, designMode=False): TaurusWidget.__init__(self, parent, designMode) self._viewFilters = [] - self._operatorViewFilter = lambda x: x.disp_level == PyTango.DispLevel.OPERATOR + self._operatorViewFilter = lambda x: x.disp_level == DisplayLevel.OPERATOR self.setLayout(Qt.QVBoxLayout()) diff --git a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py index e52851a3d..66e4e0096 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py @@ -42,10 +42,6 @@ except: pygments = None -# shame on me for importing PyTango! well not so much since this is designed to -# show PyTango exceptions -import PyTango - from taurus.core.util.report import TaurusMessageReportHandler from taurus.external.qt import Qt from taurus.qt.qtgui.util.ui import UILoadable @@ -94,6 +90,7 @@ def setError(self, err_type=None, err_value=None, err_traceback=None): class TangoMessageErrorHandler(TaurusMessageErrorHandler): """This class is designed to handle :class:`PyTango.DevFailed` error into a :class:`TaurusMessagePanel`""" + # TODO: tango-centric def setError(self, err_type=None, err_value=None, err_traceback=None): """Translates the given error object into an HTML string and places it diff --git a/lib/taurus/qt/qtgui/plot/taurustrend.py b/lib/taurus/qt/qtgui/plot/taurustrend.py index 75fbfb7a4..7db1e0a2d 100644 --- a/lib/taurus/qt/qtgui/plot/taurustrend.py +++ b/lib/taurus/qt/qtgui/plot/taurustrend.py @@ -45,7 +45,7 @@ def getArchivedTrendValues(*args, **kwargs): try: - import PyTangoArchiving + import PyTangoArchiving # TODO: tango-centric return PyTangoArchiving.getArchivedTrendValues(*args, **kwargs) except: return [] diff --git a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py b/lib/taurus/qt/qtgui/tree/taurusdevicetree.py index f1e5840e7..9dd343891 100644 --- a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py +++ b/lib/taurus/qt/qtgui/tree/taurusdevicetree.py @@ -39,7 +39,6 @@ import os import traceback from functools import partial -import PyTango # to change!! # @todo: icons_dev_tree is not an included or standard module. # Is anybody using it? If not, the following lines should be removed and @@ -310,6 +309,8 @@ class TaurusDevTree(TaurusTreeNodeContainer, Qt.QTreeWidget, TaurusBaseWidget): addModels merges the tree with new models setFilters clears previous models and adds new one ''' + + # TODO: tango-centric __properties__ = ( 'ModelInConfig', 'modifiableByUser', @@ -730,6 +731,7 @@ def addAttrToDev(self, my_device, expert=False, allow_types=None): @argin expert If False only PyTango.DispLevel.OPERATOR attributes are displayed @argin allow_types Only those types included in the list will be displayed (e.g. may be restricted to numeric types only) """ + import PyTango # TODO: tango-centric numeric_types = [PyTango.DevDouble, PyTango.DevFloat, PyTango.DevLong, PyTango.DevLong64, PyTango.DevULong, PyTango.DevShort, PyTango.DevUShort, PyTango.DevBoolean, PyTango.DevState] allow_types = allow_types or [PyTango.DevString] + numeric_types From 463794c14fb176178863852d88a6c61d150439ec Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 13 Oct 2017 17:00:51 +0200 Subject: [PATCH 061/288] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aea1cced1..886c91220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,11 @@ the master branch after [3.7.1] and maintained in parallel to the develop branch) won't be reflected in this file. ## Unreleased +### Deprecated +- taurus.core.tango.search ### Changed +- taurus.qt widgets can now be used without installing PyTango (#590) - Tango model name validators now always return FQDN instead of PQDN for the tango host (#488) - Improved generated PDF doc (#525, #546, #548) (thanks @PhilLAL !) From 916a3db867ebccd3e34f0cb745f9282c1fe8aba8 Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 13 Oct 2017 18:28:12 +0200 Subject: [PATCH 062/288] Remove imports of taurus.core.tango in taurus.qt submodule Remove module-level imports of taurus.core.tango in taurus.qt submodule. In some cases, use taurus-equivalent code and in others delay the import and mark it as tango-centric. --- CHANGELOG.md | 2 + lib/taurus/core/tango/search.py | 148 +--- lib/taurus/core/util/fandango_search.py | 148 ++++ .../qt/qtcore/model/taurusdatabasemodel.py | 22 +- lib/taurus/qt/qtcore/util/properties.py | 2 +- .../qt/qtgui/base/test/test_taurusbase.py | 2 +- .../qt/qtgui/button/test/test_taurusbutton.py | 15 +- lib/taurus/qt/qtgui/display/taurusled.py | 52 +- lib/taurus/qt/qtgui/graphic/taurusgraphic.py | 18 +- .../qt/qtgui/panel/taurusdevicepanel.py | 7 +- .../qt/qtgui/panel/taurusfilterpanel.py | 760 ------------------ .../qt/qtgui/panel/taurusmessagepanel.py | 6 +- .../qt/qtgui/panel/taurusmodelchooser.py | 5 +- lib/taurus/qt/qtgui/plot/curveprops.py | 51 +- lib/taurus/qt/qtgui/tree/taurusdevicetree.py | 13 +- 15 files changed, 254 insertions(+), 997 deletions(-) create mode 100644 lib/taurus/core/util/fandango_search.py delete mode 100644 lib/taurus/qt/qtgui/panel/taurusfilterpanel.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 886c91220..debcf586a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ develop branch) won't be reflected in this file. - Doc issues - Deprecation warnings in taurus.core.util.event (#550) +### Removed +- taurus.qt.qtgui.panel.taurusfilterpanel ## [4.1.1] - 2017-07-21 diff --git a/lib/taurus/core/tango/search.py b/lib/taurus/core/tango/search.py index 68522b834..8a677e7f3 100644 --- a/lib/taurus/core/tango/search.py +++ b/lib/taurus/core/tango/search.py @@ -1,146 +1,6 @@ -#!/usr/bin/env python +from taurus.core.util.log import deprecated as __deprecated -############################################################################# -## -# 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 . -## -############################################################################# +__deprecated(dep='taurus.core.tango.search', + alt='taurus.core.util.fandango_search', rel='4.1.2') -""" -search.py: methods for getting matching device/attribute/alias names from Tango database - -These methods have been borrowed from fandango modules. -""" - -import re -import taurus - -############################################################################### -# Utils - - -def searchCl(regexp, target): - return re.search(extend_regexp(regexp).lower(), target.lower()) - - -def matchCl(regexp, target): - return re.match(extend_regexp(regexp).lower(), target.lower()) - - -def is_regexp(s): - return any(c in s for c in '.*[]()+?') - - -def extend_regexp(s): - s = str(s).strip() - if '.*' not in s: - s = s.replace('*', '.*') - if '.*' not in s: - if ' ' in s: - s = s.replace(' ', '.*') - if '/' not in s: - s = '.*' + s + '.*' - else: - if not s.startswith('^'): - s = '^' + s - if not s.endswith('$'): - s = s + '$' - return s - - -def isString(s): - typ = s.__class__.__name__.lower() - return not hasattr(s, '__iter__') and 'str' in typ and 'list' not in typ - - -def isCallable(obj): - return hasattr(obj, '__call__') - - -def isMap(obj): - return hasattr(obj, 'has_key') or hasattr(obj, 'items') - - -def isDictionary(obj): - return isMap(obj) - - -def isSequence(obj): - typ = obj.__class__.__name__.lower() - return (hasattr(obj, '__iter__') or 'list' in typ) and not isString(obj) and not isMap(obj) - - -def split_model_list(modelNames): - '''convert str to list if needed (commas and whitespace are considered as separators)''' - if isString(modelNames): # isinstance(modelNames,(basestring,Qt.QString)): - modelNames = str(modelNames).replace(',', ' ') - modelNames = modelNames.split() - if isSequence(modelNames): # isinstance(modelNames,(list.Qt.QStringList)): - modelNames = [str(s) for s in modelNames] - return modelNames - - -def get_matching_devices(expressions, limit=0, exported=False): - """ - Searches for devices matching expressions, if exported is True only running devices are returned - """ - db = taurus.Authority() - all_devs = [s.lower() for s in db.get_device_name('*', '*')] - # This code is used to get data from multiples hosts - #if any(not fun.matchCl(rehost,expr) for expr in expressions): all_devs.extend(get_all_devices(exported)) - # for expr in expressions: - #m = fun.matchCl(rehost,expr) - # if m: - #host = m.groups()[0] - # print 'get_matching_devices(%s): getting %s devices ...'%(expr,host) - #odb = PyTango.Database(*host.split(':')) - #all_devs.extend('%s/%s'%(host,d) for d in odb.get_device_name('*','*')) - result = [e for e in expressions if e.lower() in all_devs] - expressions = [extend_regexp(e) for e in expressions if e not in result] - result.extend(filter(lambda d: any(matchCl(extend_regexp(e), d) - for e in expressions), all_devs)) - return result - - -def get_device_for_alias(alias): - db = taurus.Authority() - try: - return db.get_device_alias(alias) - except Exception, e: - if 'no device found' in str(e).lower(): - return None - return None # raise e - - -def get_alias_for_device(dev): - db = taurus.Authority() - try: - # .get_database_device().DbGetDeviceAlias(dev) - result = db.get_alias(dev) - return result - except Exception, e: - if 'no alias found' in str(e).lower(): - return None - return None # raise e - - -def get_alias_dict(exp='*'): - tango = taurus.Authority() - return dict((k, tango.get_device_alias(k)) for k in tango.get_device_alias_list(exp)) +from taurus.core.util.fandango_search import * diff --git a/lib/taurus/core/util/fandango_search.py b/lib/taurus/core/util/fandango_search.py new file mode 100644 index 000000000..3eb2c530f --- /dev/null +++ b/lib/taurus/core/util/fandango_search.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python + +############################################################################# +## +# 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 . +## +############################################################################# + +""" +fandango_search.py: methods for getting matching device/attribute/alias names +from Tango database + +These methods have been borrowed from fandango modules. +""" +# TODO: tango-centric + +import re +import taurus + +############################################################################### +# Utils + + +def searchCl(regexp, target): + return re.search(extend_regexp(regexp).lower(), target.lower()) + + +def matchCl(regexp, target): + return re.match(extend_regexp(regexp).lower(), target.lower()) + + +def is_regexp(s): + return any(c in s for c in '.*[]()+?') + + +def extend_regexp(s): + s = str(s).strip() + if '.*' not in s: + s = s.replace('*', '.*') + if '.*' not in s: + if ' ' in s: + s = s.replace(' ', '.*') + if '/' not in s: + s = '.*' + s + '.*' + else: + if not s.startswith('^'): + s = '^' + s + if not s.endswith('$'): + s = s + '$' + return s + + +def isString(s): + typ = s.__class__.__name__.lower() + return not hasattr(s, '__iter__') and 'str' in typ and 'list' not in typ + + +def isCallable(obj): + return hasattr(obj, '__call__') + + +def isMap(obj): + return hasattr(obj, 'has_key') or hasattr(obj, 'items') + + +def isDictionary(obj): + return isMap(obj) + + +def isSequence(obj): + typ = obj.__class__.__name__.lower() + return (hasattr(obj, '__iter__') or 'list' in typ) and not isString(obj) and not isMap(obj) + + +def split_model_list(modelNames): + '''convert str to list if needed (commas and whitespace are considered as separators)''' + if isString(modelNames): # isinstance(modelNames,(basestring,Qt.QString)): + modelNames = str(modelNames).replace(',', ' ') + modelNames = modelNames.split() + if isSequence(modelNames): # isinstance(modelNames,(list.Qt.QStringList)): + modelNames = [str(s) for s in modelNames] + return modelNames + + +def get_matching_devices(expressions, limit=0, exported=False): + """ + Searches for devices matching expressions, if exported is True only running devices are returned + """ + db = taurus.Authority() + all_devs = [s.lower() for s in db.get_device_name('*', '*')] + # This code is used to get data from multiples hosts + #if any(not fun.matchCl(rehost,expr) for expr in expressions): all_devs.extend(get_all_devices(exported)) + # for expr in expressions: + #m = fun.matchCl(rehost,expr) + # if m: + #host = m.groups()[0] + # print 'get_matching_devices(%s): getting %s devices ...'%(expr,host) + #odb = PyTango.Database(*host.split(':')) + #all_devs.extend('%s/%s'%(host,d) for d in odb.get_device_name('*','*')) + result = [e for e in expressions if e.lower() in all_devs] + expressions = [extend_regexp(e) for e in expressions if e not in result] + result.extend(filter(lambda d: any(matchCl(extend_regexp(e), d) + for e in expressions), all_devs)) + return result + + +def get_device_for_alias(alias): + db = taurus.Authority() + try: + return db.get_device_alias(alias) + except Exception, e: + if 'no device found' in str(e).lower(): + return None + return None # raise e + + +def get_alias_for_device(dev): + db = taurus.Authority() + try: + # .get_database_device().DbGetDeviceAlias(dev) + result = db.get_alias(dev) + return result + except Exception, e: + if 'no alias found' in str(e).lower(): + return None + return None # raise e + + +def get_alias_dict(exp='*'): + tango = taurus.Authority() + return dict((k, tango.get_device_alias(k)) for k in tango.get_device_alias_list(exp)) diff --git a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py index 19131323a..487988df0 100644 --- a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py +++ b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py @@ -24,6 +24,7 @@ ############################################################################# """This module provides widgets that display the database in a tree format""" +# TODO: tango-centric __all__ = ["TaurusTreeDevicePartItem", "TaurusTreeDeviceDomainItem", "TaurusTreeDeviceFamilyItem", "TaurusTreeDeviceMemberItem", "TaurusTreeSimpleDeviceItem", @@ -42,8 +43,6 @@ from taurus.core.taurusbasetypes import TaurusElementType, TaurusDevState import taurus.qt.qtcore.mimetypes -from taurus.core.tango.tangodatabase import TangoInfo, TangoDatabase - from .taurusmodel import TaurusBaseTreeItem, TaurusBaseModel, TaurusBaseProxyModel ElemType = TaurusElementType @@ -80,8 +79,12 @@ def getDevStateToolTip(*args, **kwargs): class TaurusTreeDbBaseItem(TaurusBaseTreeItem): - DisplayFunc = TangoInfo.name - + try: + # TODO: tango-centric + from taurus.core.tango.tangodatabase import TangoInfo + DisplayFunc = TangoInfo.name + except: + pass class TaurusTreeDevicePartItem(TaurusTreeDbBaseItem): """A node designed to represent a 'part' (or totality) of a device name""" @@ -458,6 +461,8 @@ def pyData(self, index, role): def setupModelData(self, data): if data is None: return + + from taurus.core.tango.tangodatabase import TangoDatabase if isinstance(data, TangoDatabase): data = data.cache() devices = data.devices() @@ -487,6 +492,8 @@ class TaurusDbSimpleDeviceAliasModel(TaurusDbBaseModel): def setupModelData(self, data): if data is None: return + + from taurus.core.tango.tangodatabase import TangoDatabase if isinstance(data, TangoDatabase): data = data.cache() devices = data.devices() @@ -510,6 +517,8 @@ class TaurusDbPlainDeviceModel(TaurusDbBaseModel): def setupModelData(self, data): if data is None: return + + from taurus.core.tango.tangodatabase import TangoDatabase if isinstance(data, TangoDatabase): data = data.cache() devices = data.devices() @@ -534,6 +543,8 @@ class TaurusDbDeviceModel(TaurusDbBaseModel): def setupModelData(self, data): if data is None: return + + from taurus.core.tango.tangodatabase import TangoDatabase if isinstance(data, TangoDatabase): data = data.deviceTree() @@ -564,6 +575,7 @@ def setupModelData(self, data): if data is None: return + from taurus.core.tango.tangodatabase import TangoDatabase if isinstance(data, TangoDatabase): data = data.cache() @@ -594,6 +606,7 @@ def setupModelData(self, data): if data is None: return + from taurus.core.tango.tangodatabase import TangoDatabase if isinstance(data, TangoDatabase): data = data.cache() @@ -645,6 +658,7 @@ def setupModelData(self, data): if data is None: return + from taurus.core.tango.tangodatabase import TangoDatabase if isinstance(data, TangoDatabase): data = data.cache() diff --git a/lib/taurus/qt/qtcore/util/properties.py b/lib/taurus/qt/qtcore/util/properties.py index 44cd52789..db292eed9 100644 --- a/lib/taurus/qt/qtcore/util/properties.py +++ b/lib/taurus/qt/qtcore/util/properties.py @@ -57,7 +57,7 @@ def resetFilters(self): from functools import partial from taurus.external.qt import Qt -from taurus.core.tango.search import * +from taurus.core.util.fandango_search import isSequence, isDictionary def join(*seqs): diff --git a/lib/taurus/qt/qtgui/base/test/test_taurusbase.py b/lib/taurus/qt/qtgui/base/test/test_taurusbase.py index 93b604030..ee49a9920 100644 --- a/lib/taurus/qt/qtgui/base/test/test_taurusbase.py +++ b/lib/taurus/qt/qtgui/base/test/test_taurusbase.py @@ -29,7 +29,7 @@ from taurus.external import unittest from taurus.test import insertTest from taurus.qt.qtgui.test import BaseWidgetTestCase -from taurus.core.tango.test import TangoSchemeTestLauncher +from taurus.core.tango.test import TangoSchemeTestLauncher # tango-centric from taurus.qt.qtgui.container import TaurusWidget DEV_NAME = TangoSchemeTestLauncher.DEV_NAME diff --git a/lib/taurus/qt/qtgui/button/test/test_taurusbutton.py b/lib/taurus/qt/qtgui/button/test/test_taurusbutton.py index 8fb95c0c0..dd9f542aa 100644 --- a/lib/taurus/qt/qtgui/button/test/test_taurusbutton.py +++ b/lib/taurus/qt/qtgui/button/test/test_taurusbutton.py @@ -33,10 +33,16 @@ from taurus.qt.qtgui.test import BaseWidgetTestCase, GenericWidgetTestCase from taurus.qt.qtgui.button import TaurusCommandButton -# The following are Tango-centric imports. -# TODO: change them if/when TaurusCommandbuttongets generalized -from PyTango import CommunicationFailed -from taurus.core.tango.starter import ProcessStarter +skip, skipmsg = False, None + +try: + # The following are Tango-centric imports. + # TODO: change them if/when TaurusCommandbuttongets generalized + from PyTango import CommunicationFailed + from taurus.core.tango.starter import ProcessStarter +except: + skip = True + skipmsg = "tango-dependent test" class TaurusCommandButtonTest(GenericWidgetTestCase, unittest.TestCase): @@ -44,6 +50,7 @@ class TaurusCommandButtonTest(GenericWidgetTestCase, unittest.TestCase): _modelnames = ['sys/tg_test/1', None, 'sys/database/2', ''] +@unittest.skipIf(skip, skipmsg) class TaurusCommandButtonTest2(BaseWidgetTestCase, unittest.TestCase): _klass = TaurusCommandButton diff --git a/lib/taurus/qt/qtgui/display/taurusled.py b/lib/taurus/qt/qtgui/display/taurusled.py index f49183952..8e84b56d4 100644 --- a/lib/taurus/qt/qtgui/display/taurusled.py +++ b/lib/taurus/qt/qtgui/display/taurusled.py @@ -36,7 +36,6 @@ from taurus.external.qt import Qt from taurus.core import DataFormat, AttrQuality, DataType -from taurus.core.tango import DevState from taurus.qt.qtgui.base import TaurusBaseWidget from qled import QLed @@ -148,29 +147,32 @@ def usePreferedColor(self, widget): # value. If representing the quality, use the quality map return widget.fgRole != 'quality' - -class _TaurusLedControllerState(_TaurusLedController): - - # key status, color, inTrouble - LedMap = {DevState.ON: (True, "green", False), - DevState.OFF: (False, "black", False), - DevState.CLOSE: (True, "white", False), - DevState.OPEN: (True, "green", False), - DevState.INSERT: (True, "green", False), - DevState.EXTRACT: (True, "green", False), - DevState.MOVING: (True, "blue", False), - DevState.STANDBY: (True, "yellow", False), - DevState.FAULT: (True, "red", False), - DevState.INIT: (True, "yellow", False), - DevState.RUNNING: (True, "blue", False), - DevState.ALARM: (True, "orange", False), - DevState.DISABLE: (True, "magenta", False), - DevState.UNKNOWN: (False, "black", False), - None: (False, "black", True)} - - def usePreferedColor(self, widget): - # never use prefered widget color. Use always the map - return False +try: + from taurus.core.tango import DevState # TODO: Tango-centric + class _TaurusLedControllerState(_TaurusLedController): + + # key status, color, inTrouble + LedMap = {DevState.ON: (True, "green", False), + DevState.OFF: (False, "black", False), + DevState.CLOSE: (True, "white", False), + DevState.OPEN: (True, "green", False), + DevState.INSERT: (True, "green", False), + DevState.EXTRACT: (True, "green", False), + DevState.MOVING: (True, "blue", False), + DevState.STANDBY: (True, "yellow", False), + DevState.FAULT: (True, "red", False), + DevState.INIT: (True, "yellow", False), + DevState.RUNNING: (True, "blue", False), + DevState.ALARM: (True, "orange", False), + DevState.DISABLE: (True, "magenta", False), + DevState.UNKNOWN: (False, "black", False), + None: (False, "black", True)} + + def usePreferedColor(self, widget): + # never use prefered widget color. Use always the map + return False +except: + pass class _TaurusLedControllerDesignMode(_TaurusLedController): @@ -226,7 +228,7 @@ def _calculate_controller_class(self): elif model.isBoolean(): klass = _TaurusLedControllerBool elif model.type == DataType.DevState: - klass = _TaurusLedControllerState + klass = _TaurusLedControllerState # TODO: tango-centric return klass def controller(self): diff --git a/lib/taurus/qt/qtgui/graphic/taurusgraphic.py b/lib/taurus/qt/qtgui/graphic/taurusgraphic.py index 27239959d..7373eac44 100755 --- a/lib/taurus/qt/qtgui/graphic/taurusgraphic.py +++ b/lib/taurus/qt/qtgui/graphic/taurusgraphic.py @@ -26,6 +26,8 @@ taurusgraphic.py: """ +# TODO: Tango-centric + __all__ = ['SynopticSelectionStyle', 'parseTangoUri', 'QEmitter', # TODO: QEmitter should probably be removed (kept priv) @@ -69,10 +71,6 @@ from taurus.core.taurusdevice import TaurusDevice from taurus.core.taurusattribute import TaurusAttribute from taurus.core.util.enumeration import Enumeration -# TODO: tango-centric! -from taurus.core.tango import DevState -from taurus.core.tango.tangovalidator import (TangoDeviceNameValidator, - TangoAttributeNameValidator) from taurus.external.qt import Qt from taurus.qt.qtgui.base import TaurusBaseComponent from taurus.qt.qtgui.util import (QT_ATTRIBUTE_QUALITY_PALETTE, QT_DEVICE_STATE_PALETTE, @@ -88,7 +86,11 @@ def parseTangoUri(name): + # TODO: Tango-centric from taurus.core import tango + from taurus.core.tango.tangovalidator import (TangoDeviceNameValidator, + TangoAttributeNameValidator) + validator = {tango.TangoDevice: TangoDeviceNameValidator, tango.TangoAttribute: TangoAttributeNameValidator} try: @@ -348,6 +350,11 @@ def getItemByName(self, item_name, strict=None): :return: (list) items """ + + # TODO: Tango-centric + from taurus.core.tango.tangovalidator import ( + TangoDeviceNameValidator, TangoAttributeNameValidator) + strict = ( not self.ANY_ATTRIBUTE_SELECTS_DEVICE) if strict is None else strict alnum = '(?:[a-zA-Z0-9-_\*]|(?:\.\*))(?:[a-zA-Z0-9-_\*]|(?:\.\*))*' @@ -1171,6 +1178,9 @@ def __init__(self, name=None, parent=None): self.call__init__(TaurusGraphicsItem, name, parent) def updateStyle(self): + + from taurus.core.tango import DevState # Tango-centric + v = self.getModelValueObj() self._currBrush = Qt.QBrush(Qt.Qt.NoBrush) diff --git a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py index 34e2d5fd4..56c3612f3 100644 --- a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py @@ -49,8 +49,6 @@ from taurus.qt.qtgui.util.ui import UILoadable from taurus.qt.qtgui.icon import getCachedPixmap -from taurus.core.tango.tangodatabase import TangoDevInfo # @todo: Tango-centric! - ############################################################################### # TaurusDevicePanel (from Vacca) @@ -490,6 +488,9 @@ def get_comms_form(self, device, form=None, parent=None): def filterNonExported(obj): + # TODO: Tango-centric + from taurus.core.tango.tangodatabase import TangoDevInfo + if not isinstance(obj, TangoDevInfo) or obj.exported(): return obj return None @@ -566,6 +567,8 @@ def setTangoHost(self, host): def onItemSelectionChanged(self, current, previous): itemData = current.itemData() + + from taurus.core.tango.tangodatabase import TangoDevInfo if isinstance(itemData, TangoDevInfo): # TODO: Tango-centric self.onDeviceSelected(itemData) diff --git a/lib/taurus/qt/qtgui/panel/taurusfilterpanel.py b/lib/taurus/qt/qtgui/panel/taurusfilterpanel.py deleted file mode 100644 index d792b9b96..000000000 --- a/lib/taurus/qt/qtgui/panel/taurusfilterpanel.py +++ /dev/null @@ -1,760 +0,0 @@ -#!/usr/bin/env python - -############################################################################# -## -# 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 widgets that display the authority in a tree format""" - -__docformat__ = 'restructuredtext' - -import os -import re - -from taurus.external.qt import Qt - -from taurus.core.taurusauthority import TaurusAuthority -from taurus.core.taurusbasetypes import TaurusElementType as ElemType -import taurus.qt.qtgui.base - -from taurus.core.tango.tangodatabase import (TangoAttrInfo, TangoDevInfo, - TangoServInfo) - -from taurus.qt.qtgui.icon import getElementTypeIcon - - - - -class BaseFilter(object): - - def __init__(self, re_expr): - if len(re_expr) == 0: - re_expr = ".*" - else: - if re_expr[0] != "^": - re_expr = "^" + re_expr - if re_expr[-1] != "$": - re_expr += "$" - self._re_expr = re.compile(re_expr, re.IGNORECASE) - - def __call__(self, obj): - return self.filter(obj) - - -class BaseElementFilter(BaseFilter): - - def __init__(self, re_expr, func=None): - super(BaseElementFilter, self).__init__(re_expr) - self._klass = func.im_class - self._func = func - - def filter(self, obj): - if not isinstance(obj, self._klass): - return obj - v = self._func(obj) - if self._re_expr.match(v): - return obj - - -class DeviceFilter(BaseElementFilter): - - def __init__(self, re_expr, func=TangoDevInfo.name): - super(DeviceFilter, self).__init__(re_expr, func=func) - - -class DeviceClassFilter(BaseElementFilter): - - def __init__(self, re_expr, func=TangoDevInfo.name): - super(DeviceClassFilter, self).__init__(re_expr, func=func) - - -class ServerFilter(BaseElementFilter): - - def __init__(self, re_expr, func=TangoServInfo.name): - super(ServerFilter, self).__init__(re_expr, func=func) - - -class AttributeFilter(BaseElementFilter): - - def __init__(self, re_expr, func=TangoAttrInfo.name): - super(AttributeFilter, self).__init__(re_expr, func=func) - - -class KlassFilter(BaseFilter): - - def __init__(self, klass): - # don't call super on purpose. We don't need/have a regular expression here! - #super(KlassFilter, self).__init__(re_expr) - self._klass = klass - - def filter(self, obj): - if isinstance(obj, self._klass): - return obj - - -def getFilter(type, re_expr=None): - if re_expr is None: - if type == ElemType.Device: - return KlassFilter(TangoDevInfo) - elif type == ElemType.Server: - return KlassFilter(TangoServInfo) - elif type == ElemType.DeviceClass: - return KlassFilter(TangoDevInfo) - return None - - if type == ElemType.Device: - return DeviceFilter(re_expr) - elif type == ElemType.Domain: - return DeviceFilter(re_expr, TangoDevInfo.domain) - elif type == ElemType.Family: - return DeviceFilter(re_expr, TangoDevInfo.family) - elif type == ElemType.Member: - return DeviceFilter(re_expr, TangoDevInfo.member) - elif type == ElemType.Server: - return ServerFilter(re_expr) - elif type == ElemType.ServerName: - return ServerFilter(re_expr, TangoServInfo.serverName) - elif type == ElemType.ServerInstance: - return ServerFilter(re_expr, TangoServInfo.serverInstance) - elif type == ElemType.DeviceClass: - return DeviceClassFilter(re_expr) - elif type == ElemType.Attribute: - return AttributeFilter(re_expr) - - -class TaurusFilterPanelOld1(Qt.QWidget, taurus.qt.qtgui.base.TaurusBaseWidget): - - def __init__(self, parent=None, designMode=False): - name = self.__class__.__name__ - self.call__init__wo_kw(Qt.QWidget, parent) - self.call__init__(taurus.qt.qtgui.base.TaurusBaseWidget, - name, designMode=designMode) - self.init() - - def init(self): - l = Qt.QBoxLayout(Qt.QBoxLayout.TopToBottom) - self.setLayout(l) - self.addFilterHeader() - self.insertFilterItem() - l.addStretch(1) - - def addFilterHeader(self): - label = Qt.QLabel("Type:") - comboBox = Qt.QComboBox() - comboBox.addItem(getElementTypeIcon(ElemType.Attribute), - "Attribute", ElemType.Attribute) - comboBox.addItem(getElementTypeIcon(ElemType.Device), - "Device", ElemType.Device) - comboBox.addItem(getElementTypeIcon(ElemType.DeviceClass), - "Device type", ElemType.DeviceClass) - comboBox.addItem(getElementTypeIcon(ElemType.Server), - "Server", ElemType.Server) - comboBox.addItem("Any") - previewButton = Qt.QPushButton("Preview") - previewButton.clicked.connect(self.onPreview) - field = Qt.QWidget() - l = Qt.QHBoxLayout() - field.setLayout(l) - l.addWidget(label) - l.addWidget(comboBox) - l.addWidget(previewButton) - l.addStretch(1) - self.layout().addWidget(field) - - def insertFilterItem(self, row=None): - - sl = self.layout() - - comboBox = Qt.QComboBox() - self._fillComboBox(comboBox) - comboBox.currentIndexChanged.connect(self.onFilterComboBoxItemSelected) - - edit = Qt.QLineEdit() - - addButton = Qt.QPushButton(Qt.QIcon(":/actions/list-add.svg"), "") - addButton.clicked.connect(self.onAddFilterButtonClicked) - - removeButton = Qt.QPushButton( - Qt.QIcon(":/actions/list-remove.svg"), "") - removeButton.clicked.connect(self.onRemoveFilterButtonClicked) - - field = Qt.QWidget() - l = Qt.QHBoxLayout() - field.setLayout(l) - l.addWidget(Qt.QLabel("Filter by")) - l.addWidget(comboBox) - l.addWidget(edit) - l.addWidget(addButton) - l.addWidget(removeButton) - - if row is None: - sl.addWidget(field) - else: - sl.insertWidget(row, field) - - def _fillComboBox(self, comboBox): - comboBox.addItem(getElementTypeIcon(ElemType.Attribute), - "Attribute", ElemType.Attribute) - comboBox.addItem(getElementTypeIcon(ElemType.Device), - "Device", ElemType.Device) - comboBox.addItem(getElementTypeIcon(ElemType.DeviceClass), - "Device type", ElemType.DeviceClass) - comboBox.addItem(getElementTypeIcon(ElemType.Domain), - "Domain", ElemType.Domain) - comboBox.addItem(getElementTypeIcon(ElemType.Family), - "Family", ElemType.Family) - comboBox.addItem(getElementTypeIcon(ElemType.Member), - "Member", ElemType.Member) - comboBox.addItem(getElementTypeIcon(ElemType.Server), - "Server", ElemType.Server) - comboBox.addItem(getElementTypeIcon(ElemType.ServerName), - "Server Name", ElemType.ServerName) - comboBox.addItem(getElementTypeIcon(ElemType.ServerInstance), - "Server Instance", ElemType.ServerInstance) - - def onFilterComboBoxItemSelected(self, index): - pass - - def onAddFilterButtonClicked(self): - button = self.sender() - if button is None: - return - field = button.parent() - index = self.layout().indexOf(field) - self.insertFilterItem(index + 1) - - def onRemoveFilterButtonClicked(self): - l = self.layout() - # there is a header row, at least one filter row and a stretch at the - # end, therefore, if there are only three rows, we don't allow to delete - # the only existing filter - if l.count() <= 3: - return - button = self.sender() - if button is None: - return - field = button.parent() - l.removeWidget(field) - field.setParent(None) - - def onPreview(self): - import trees - model = self.getModel() - dialog = Qt.QDialog() - dialog.setModal(True) - w = trees.TaurusTreeWidget(dialog, perspective=self.getHeaderType()) - w.setModel(model) - w.setFilters(self.calculate()) - dialog.exec_() - - def getHeaderType(self): - g_layout = self.layout() - header = g_layout.itemAt(0).widget() - headerCombo = header.layout().itemAt(1).widget() - return Qt.from_qvariant(headerCombo.itemData(headerCombo.currentIndex())) - - def calculate(self): - db = self.getModelObj() - if db is None: - return - - g_layout = self.layout() - - filters = [] - for i in xrange(1, g_layout.count() - 1): - field_layout = g_layout.itemAt(i).widget().layout() - comboBox = field_layout.itemAt(1).widget() - edit = field_layout.itemAt(2).widget() - - type = Qt.from_qvariant(comboBox.itemData(comboBox.currentIndex())) - expr = str(edit.text()) - f = getFilter(type, expr) - filters.append(f) - - finalType = self.getHeaderType() - filters.append(getFilter(finalType)) - return filters - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # TaurusBaseWidget overwriting - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def getModelClass(self): - return TaurusAuthority - - #: This property holds the unique URI string representing the model name - #: with which this widget will get its data from. The convention used for - #: the string can be found :ref:`here `. - #: - #: In case the property :attr:`useParentModel` is set to True, the model - #: text must start with a '/' followed by the attribute name. - #: - #: **Access functions:** - #: - #: * :meth:`TaurusBaseWidget.getModel` - #: * :meth:`TaurusBaseWidget.setModel` - #: * :meth:`TaurusBaseWidget.resetModel` - #: - #: .. seealso:: :ref:`model-concept` - model = Qt.pyqtProperty("QString", taurus.qt.qtgui.base.TaurusBaseWidget.getModel, - taurus.qt.qtgui.base.TaurusBaseWidget.setModel, - taurus.qt.qtgui.base.TaurusBaseWidget.resetModel) - - -class TaurusFilterPanelOld2(Qt.QWidget, taurus.qt.qtgui.base.TaurusBaseWidget): - - def __init__(self, parent=None, designMode=False): - name = self.__class__.__name__ - self.call__init__wo_kw(Qt.QWidget, parent) - self.call__init__(taurus.qt.qtgui.base.TaurusBaseWidget, - name, designMode=designMode) - self.init() - - def init(self): - l = Qt.QGridLayout() - l.setContentsMargins(0, 0, 0, 0) - self.setLayout(l) - - comboBox = Qt.QComboBox() - comboBox.addItem(getElementTypeIcon(ElemType.Attribute), - "Attribute", ElemType.Attribute) - comboBox.addItem(getElementTypeIcon(ElemType.Device), - "Device", ElemType.Device) - comboBox.addItem(getElementTypeIcon(ElemType.DeviceClass), - "Device type", ElemType.DeviceClass) - comboBox.addItem(getElementTypeIcon(ElemType.Server), - "Server", ElemType.Server) - - l.addWidget(Qt.QLabel("Filter for:"), 0, 0) - l.addWidget(comboBox, 0, 1) - - import trees - self._deviceEdit = Qt.QComboBox() - self._deviceEdit.setEditable(True) - self._deviceEdit.setMaxVisibleItems(10) - self._deviceEdit.setInsertPolicy(Qt.QComboBox.InsertAtTop) - self._deviceDomainEdit = Qt.QLineEdit() - self._deviceFamilyEdit = Qt.QLineEdit() - self._deviceMemberEdit = Qt.QLineEdit() - self._deviceClass = Qt.QLineEdit() - self._serverEdit = Qt.QLineEdit() - self._serverNameEdit = Qt.QLineEdit() - self._serverInstanceEdit = Qt.QLineEdit() - self._attributeEdit = Qt.QLineEdit() - - lbl = Qt.QLabel("Device type:") - l.addWidget(lbl, 1, 0) - l.setAlignment(lbl, Qt.Qt.AlignRight) - l.addWidget(self._deviceClass, 1, 1) - l.addWidget(Qt.QLabel("Device:"), 2, 0) - l.addWidget(self._deviceEdit, 2, 1) - l.addWidget(Qt.QLabel("Device domain:"), 3, 0) - l.addWidget(self._deviceDomainEdit, 3, 1) - l.addWidget(Qt.QLabel("Device family:"), 4, 0) - l.addWidget(self._deviceFamilyEdit, 4, 1) - l.addWidget(Qt.QLabel("Device member:"), 5, 0) - l.addWidget(self._deviceMemberEdit, 5, 1) - l.addWidget(Qt.QLabel("Server:"), 6, 0) - l.addWidget(self._serverEdit, 6, 1) - l.addWidget(Qt.QLabel("Server name:"), 7, 0) - l.addWidget(self._serverNameEdit, 7, 1) - l.addWidget(Qt.QLabel("Server instance:"), 8, 0) - l.addWidget(self._serverInstanceEdit, 8, 1) - l.addWidget(Qt.QLabel("Attribute:"), 9, 0) - l.addWidget(self._attributeEdit, 9, 1) - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # TaurusBaseWidget overwriting - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def getModelClass(self): - return TaurusAuthority - - def setModel(self, m): - taurus.qt.qtgui.base.TaurusBaseWidget.setModel(self, m) - db = self.getModelObj() - #model = self._deviceEdit.model() - # if model is None: return - # model.setDataSource(db) - self._deviceEdit.clear() - if db is not None: - deviceNames = db.cache().getDeviceNames() - deviceNames.sort() - #icon = taurus.core.icons.getElementTypeIcon(ElemType.Device) - self._deviceEdit.addItems(deviceNames) - - #: This property holds the unique URI string representing the model name - #: with which this widget will get its data from. The convention used for - #: the string can be found :ref:`here `. - #: - #: In case the property :attr:`useParentModel` is set to True, the model - #: text must start with a '/' followed by the attribute name. - #: - #: **Access functions:** - #: - #: * :meth:`TaurusBaseWidget.getModel` - #: * :meth:`TaurusBaseWidget.setModel` - #: * :meth:`TaurusBaseWidget.resetModel` - #: - #: .. seealso:: :ref:`model-concept` - model = Qt.pyqtProperty("QString", taurus.qt.qtgui.base.TaurusBaseWidget.getModel, - taurus.qt.qtgui.base.TaurusBaseWidget.setModel, - taurus.qt.qtgui.base.TaurusBaseWidget.resetModel) - - -class _MessageWidget(Qt.QWidget): - - def __init__(self, parent=None, pixmap=None): - Qt.QWidget.__init__(self, parent) - l = Qt.QHBoxLayout() - self.setLayout(l) - self._icon = Qt.QLabel() - if pixmap is None: - pixmap = Qt.QIcon.fromTheme("dialog-warning").pixmap(16, 16) - self._icon.setPixmap(pixmap) - self._label = Qt.QLabel() - l.addWidget(self._icon) - l.addWidget(self._label) - - def setText(self, text): - self._label.setText(text) - - -from taurus.external.qt.uic import loadUi - - -class TaurusFilterPanel(Qt.QWidget, taurus.qt.qtgui.base.TaurusBaseWidget): - - _Items = "server", "serverName", "serverInstance", \ - "deviceName", "deviceType", "deviceDomain", "deviceFamily", "deviceMember", \ - "attribute" - - def __init__(self, parent=None, designMode=False): - name = self.__class__.__name__ - self.call__init__wo_kw(Qt.QWidget, parent) - self.call__init__(taurus.qt.qtgui.base.TaurusBaseWidget, - name, designMode=designMode) - self.init() - - def init(self): - l = Qt.QVBoxLayout() - self.setLayout(l) - - panel = self._mainPanel = Qt.QWidget() - l.addWidget(panel, 1) - this_dir = os.path.dirname(os.path.abspath(__file__)) - ui_filename = os.path.join(this_dir, 'ui', 'TaurusFilterPanel.ui') - self.ui = ui = loadUi(ui_filename, baseinstance=panel) - - comboBox = ui.filterTypeCombo - comboBox.addItem(getElementTypeIcon(ElemType.Attribute), - "Attribute", ElemType.Attribute) - comboBox.addItem(getElementTypeIcon(ElemType.Device), - "Device", ElemType.Device) - comboBox.addItem(getElementTypeIcon(ElemType.DeviceClass), - "Device type", ElemType.DeviceClass) - comboBox.addItem(getElementTypeIcon(ElemType.Server), - "Server", ElemType.Server) - - clickedSig = Qt.SIGNAL("clicked()") - idxChangedSig = Qt.SIGNAL("currentIndexChanged(int)") - ui.serverNameCombo.currentIndexChanged.connect( - self._updateServerInstanceCombo) - ui.deviceDomainCombo.currentIndexChanged.connect( - self._updateDeviceFamilyCombo) - ui.deviceFamilyCombo.currentIndexChanged.connect( - self._updateDeviceMemberCombo) - - class clearSelection(object): - - def __init__(self, cb): - self._cb = cb - - def __call__(self): - self._cb.setCurrentIndex(-1) - - clear_icon = Qt.QIcon.fromTheme("edit-clear") - for combo, clearButton in zip(self.combos(), self.clearButtons()): - combo.currentIndexChanged.connect(self._updateStatusBar) - clearButton.clicked.connect(clearSelection(combo)) - clearButton.setIcon(clear_icon) - - sb = self._statusbar = Qt.QStatusBar() - sb.setSizeGripEnabled(False) - l.addWidget(sb) - sbWarningMsg = self._sbWarningMsg = _MessageWidget() - sbWarningMsg.setVisible(False) - sb.addWidget(sbWarningMsg) - - def combos(self): - if not hasattr(self, "_combos"): - f = self.ui - self._combos = [getattr(f, name + "Combo") for name in self._Items] - return self._combos - - def clearButtons(self): - if not hasattr(self, "_clearButtons"): - f = self.ui - self._clearButtons = [getattr(f, name + "ClearButton") - for name in self._Items] - return self._clearButtons - - def _db_cache(self): - db = self.getModelObj() - if db is None: - return - return db.cache() - - def _updateStatusBar(self, index=None): - form = self.ui - server = str(form.serverCombo.currentText()) - serverName = str(form.serverNameCombo.currentText()) - serverInstance = str(form.serverInstanceCombo.currentText()) - - msg = self._sbWarningMsg - msg.setVisible(False) - - if server and (serverName or serverInstance): - sb = self._statusbar - s = "Specifying name filter and type/instance filters at the same " \ - "time may result in an empty filter" - msg.setVisible(True) - msg.setText(s) - - deviceName = str(form.deviceNameCombo.currentText()) - deviceDomain = str(form.deviceDomainCombo.currentText()) - deviceFamily = str(form.deviceFamilyCombo.currentText()) - deviceMember = str(form.deviceMemberCombo.currentText()) - - if deviceName and (deviceDomain or deviceFamily or deviceMember): - sb = self._statusbar - s = "Specifying name filter and domain/family/member filters at the same " \ - "time may result in an empty filter" - msg.setVisible(True) - msg.setText(s) - - def _updateServerCombo(self, index=None): - combo = self.ui.serverCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - servers = db_cache.servers() - icon = getElementTypeIcon(ElemType.Server) - for serverName in sorted(servers): - serverInfo = servers[serverName] - combo.addItem(icon, serverName, serverInfo) - combo.setCurrentIndex(-1) - - def _updateServerNameCombo(self, index=None): - combo = self.ui.serverNameCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - servers = db_cache.servers() - serverNames = [] - for server in servers.values(): - name = server.serverName() - if name not in serverNames: - serverNames.append(name) - serverNames.sort() - icon = getElementTypeIcon(ElemType.ServerName) - for serverName in serverNames: - combo.addItem(icon, serverName) - combo.setCurrentIndex(-1) - - def _updateServerInstanceCombo(self, index=None): - combo = self.ui.serverInstanceCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - if index is None or index == -1: - return - serverName = str(self.sender().currentText()) - servers = db_cache.servers() - serverInstances = [] - for server in servers.values(): - if server.serverName() == serverName: - serverInstances.append(server.serverInstance()) - serverInstances.sort() - icon = getElementTypeIcon(ElemType.ServerInstance) - for serverInstance in serverInstances: - combo.addItem(icon, serverInstance) - combo.setCurrentIndex(-1) - - def _updateDeviceTypeCombo(self, index=None): - combo = self.ui.deviceTypeCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - deviceKlasses = db_cache.klasses() - icon = getElementTypeIcon(ElemType.DeviceClass) - for klassName in sorted(deviceKlasses): - klassInfo = deviceKlasses[klassName] - combo.addItem(icon, klassName, klassInfo) - combo.setCurrentIndex(-1) - - def _updateDeviceNameCombo(self, index=None): - combo = self.ui.deviceNameCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - devices = db_cache.devices() - icon = getElementTypeIcon(ElemType.Device) - for deviceName in sorted(devices): - deviceInfo = devices[deviceName] - combo.addItem(icon, deviceName, deviceInfo) - combo.setCurrentIndex(-1) - - def _updateDeviceDomainCombo(self, index=None): - combo = self.ui.deviceDomainCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - domains = db_cache.getDeviceDomainNames() - domains.sort() - icon = getElementTypeIcon(ElemType.Domain) - for domain in domains: - combo.addItem(icon, domain) - combo.setCurrentIndex(-1) - - def _updateDeviceFamilyCombo(self, index=None): - combo = self.ui.deviceFamilyCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - - deviceDomain = str(self.ui.deviceDomainCombo.currentText()) - if deviceDomain == "": - return - families = db_cache.getDeviceFamilyNames(deviceDomain) - families.sort() - icon = getElementTypeIcon(ElemType.Family) - for family in families: - combo.addItem(icon, family) - combo.setCurrentIndex(-1) - - def _updateDeviceMemberCombo(self, index=None): - combo = self.ui.deviceMemberCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - - deviceDomain = str(self.ui.deviceDomainCombo.currentText()) - if deviceDomain == "": - return - deviceFamily = str(self.ui.deviceFamilyCombo.currentText()) - if deviceFamily == "": - return - members = db_cache.getDeviceMemberNames(deviceDomain, deviceFamily) - members.sort() - icon = getElementTypeIcon(ElemType.Member) - for member in members: - combo.addItem(icon, member) - combo.setCurrentIndex(-1) - - def _updateAttributeCombo(self, index=None): - combo = self.ui.attributeCombo - combo.clear() - db_cache = self._db_cache() - if db_cache is None: - return - - def _fillItems(self): - self._updateServerCombo() - self._updateServerNameCombo() - self._updateServerInstanceCombo() - self._updateDeviceTypeCombo() - self._updateDeviceNameCombo() - self._updateDeviceDomainCombo() - self._updateDeviceFamilyCombo() - self._updateDeviceMemberCombo() - self._updateAttributeCombo() - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # TaurusBaseWidget overwriting - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def getModelClass(self): - return TaurusAuthority - - def setModel(self, m): - taurus.qt.qtgui.base.TaurusBaseWidget.setModel(self, m) - db = self.getModelObj() - #model = self._deviceEdit.model() - # if model is None: return - # model.setDataSource(db) - self.ui.deviceNameCombo.clear() - self._fillItems() - - #: This property holds the unique URI string representing the model name - #: with which this widget will get its data from. The convention used for - #: the string can be found :ref:`here `. - #: - #: In case the property :attr:`useParentModel` is set to True, the model - #: text must start with a '/' followed by the attribute name. - #: - #: **Access functions:** - #: - #: * :meth:`TaurusBaseWidget.getModel` - #: * :meth:`TaurusBaseWidget.setModel` - #: * :meth:`TaurusBaseWidget.resetModel` - #: - #: .. seealso:: :ref:`model-concept` - model = Qt.pyqtProperty("QString", taurus.qt.qtgui.base.TaurusBaseWidget.getModel, - taurus.qt.qtgui.base.TaurusBaseWidget.setModel, - taurus.qt.qtgui.base.TaurusBaseWidget.resetModel) - - -def main(): - from taurus.qt.qtgui.application import TaurusApplication - from taurus.core.util import argparse - import sys - - parser = argparse.get_taurus_parser() - parser.usage = "%prog [options] [hostname]" - - app = TaurusApplication(cmd_line_parser=parser) - args = app.get_command_line_args() - - if len(args) > 0: - host = args[0] - else: - host = taurus.Authority().getNormalName() - - w = TaurusFilterPanel() - w.setWindowIcon(Qt.QIcon(":/actions/system-shutdown.svg")) - w.setWindowTitle("A Taurus Filter Example") - w.setModel(host) - w.show() - - sys.exit(app.exec_()) - -if __name__ == "__main__": - main() diff --git a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py index 66e4e0096..338af7c20 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py @@ -485,7 +485,11 @@ def getError(self): :rtype: tuple""" return self._exc_info - ErrorHandlers = {PyTango.DevFailed: TangoMessageErrorHandler} + try: + import Pyango + ErrorHandlers = {PyTango.DevFailed: TangoMessageErrorHandler} + except: + ErrorHandlers = {} @classmethod def registerErrorHandler(klass, err_type, err_handler): diff --git a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py index d72b4aa16..e458eb1df 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py +++ b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py @@ -37,9 +37,6 @@ from taurus.core.util.containers import CaselessList from taurusmodellist import TaurusModelList -#@todo: tango-centric!! -from taurus.core.tango.tangodatabase import TangoDevInfo, TangoAttrInfo - class TaurusModelSelectorTree(TaurusWidget): @@ -108,6 +105,8 @@ def setButtonsPos(self, buttonsPos): def getSelectedModels(self): # todo: this method is tango-centric, but it could be fixed... + # @todo: tango-centric!! + from taurus.core.tango.tangodatabase import TangoDevInfo, TangoAttrInfo selected = [] for item in self._deviceTree.selectedItems(): nfo = item.itemData() diff --git a/lib/taurus/qt/qtgui/plot/curveprops.py b/lib/taurus/qt/qtgui/plot/curveprops.py index 229ae8c97..ecfaee425 100755 --- a/lib/taurus/qt/qtgui/plot/curveprops.py +++ b/lib/taurus/qt/qtgui/plot/curveprops.py @@ -36,6 +36,7 @@ from taurus.external.qt import Qt, Qwt5 import taurus import taurus.core +from taurus.core import TaurusElementType from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_ATTR_MIME_TYPE from taurus.qt.qtgui.util.ui import UILoadable @@ -45,26 +46,6 @@ NamedColors, CurveAppearanceProperties -# URI regexp including slices in fragment (adapted from http://www.ietf.org/rfc/rfc2396.txt (appendix B)) -# '^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*)(\[[0-9,:]*?\]))?' -# 12 3 4 5 6 7 8 9 A -# -# we base the nexus and ascii file regexps on this one, keeping the group numbers -# The different components of an URI can be obtained from this regexp using match.group(n1,n2,...), where: -# COMPONENT GROUP_number -# scheme 2 -# authority 4 -# path 5 -# query 7 -# fragment 9 -# slice 10 (0xA) - -NEXUS_SRC = re.compile( - r'^((nxfile|nxdata):)(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*)(\[[0-9,: ]*?\]))?') -ASCII_SRC = re.compile( - r'^((file):)(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*)(\[[0-9,: ]*?\]))?') - - # set some named constants # columns: NUMCOLS = 4 @@ -72,10 +53,7 @@ SRC_ROLE = Qt.Qt.UserRole + 1 PROPS_ROLE = Qt.Qt.UserRole + 2 -# TODO: Tango-centric (use agnostic validation) -from taurus.core.tango.tangovalidator import TangoAttributeNameValidator -ATTRNAMEVALIDATOR = TangoAttributeNameValidator() - +from taurus import isValidName class Component(object): @@ -84,7 +62,6 @@ def __init__(self, src): self.display = '' self.icon = Qt.QIcon() self.ok = True - self._attrnamevalidator = ATTRNAMEVALIDATOR self._dbCache = taurus.Authority() self.setSrc(src) @@ -104,27 +81,9 @@ def processSrc(self, src): if src.startswith('='): #@todo: evaluate/validate the expression return src, src[1:].strip(), Qt.QIcon.fromTheme('accessories-calculator'), True - # for tango attributes - if self._attrnamevalidator.isValid(src): - pars = self._attrnamevalidator.getUriGroups(src) - dev = self._dbCache.getDevice(pars['devname']) - if dev is None: - return src, src, Qt.QIcon.fromTheme('network-error'), False - attr = dev.getAttribute(pars['_shortattrname']) - if attr is None: - return src, pars['_shortattrname'], Qt.QIcon.fromTheme('network-error'), False - return src, attr.name(), Qt.QIcon.fromTheme('network-server'), True - # for nexus files - m = re.match(NEXUS_SRC, src) - if m is not None: - host, path, nxpath, slice = m.group(4, 5, 9, 10) - #@todo:open file and check the data is accessible - return src, nxpath, Qt.QIcon.fromTheme('x-office-spreadsheet'), True - # for ascii files - m = re.match(ASCII_SRC, src) - if m is not None: - host, path, = m.group(4, 5) - #@todo: open and check the file + # for attributes + if isValidName(src, etypes=[TaurusElementType.Attribute]): + return src, src, Qt.QIcon.fromTheme('network-server'), True # If nothing matches... return src, src, Qt.QIcon.fromTheme('dialog-warning'), False diff --git a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py b/lib/taurus/qt/qtgui/tree/taurusdevicetree.py index 9dd343891..864078e94 100644 --- a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py +++ b/lib/taurus/qt/qtgui/tree/taurusdevicetree.py @@ -37,6 +37,7 @@ import time import os +import re import traceback from functools import partial @@ -50,12 +51,19 @@ from taurus.external.qt import Qt +import taurus import taurus.core from taurus.core.util.colors import DEVICE_STATE_PALETTE, ATTRIBUTE_QUALITY_PALETTE from taurus.core.util.containers import CaselessDict -from taurus.core.tango.search import * # @TODO: Avoid implicit imports +from taurus.core.util.fandango_search import ( + isCallable, isString, split_model_list, isSequence, isMap, + get_matching_devices, matchCl, get_alias_for_device, extend_regexp) from taurus.qt.qtcore.util.emitter import SingletonWorker -from taurus.qt.qtcore.mimetypes import * # @TODO: Avoid implicit imports +from taurus.qt.qtcore.mimetypes import (TAURUS_MODEL_LIST_MIME_TYPE, + TAURUS_DEV_MIME_TYPE, + TAURUS_ATTR_MIME_TYPE, + TAURUS_MODEL_MIME_TYPE + ) from taurus.qt.qtcore.util import properties from taurus.qt.qtcore.util.properties import djoin from taurus.qt.qtgui.base import TaurusBaseComponent, TaurusBaseWidget @@ -211,6 +219,7 @@ def getNodeIcon(self, node=None): def getNodeDraggable(self, node=None): """ This method will return True only if the selected node belongs to a numeric Tango attribute """ + import PyTango # TODO: tango-centric numtypes = [PyTango.DevDouble, PyTango.DevFloat, PyTango.DevLong, PyTango.DevLong64, PyTango.DevULong, PyTango.DevShort, PyTango.DevUShort, PyTango.DevBoolean] if node is None: From 6812bf7600d13caba94856e2c9053340c68f9e9c Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 13 Oct 2017 18:59:31 +0200 Subject: [PATCH 063/288] Delay import of sardana Delay import of sardana to avoid import issues if PyTango or sardana are not installed --- lib/taurus/qt/qtgui/taurusgui/macrolistener.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/taurusgui/macrolistener.py b/lib/taurus/qt/qtgui/taurusgui/macrolistener.py index 4a90225ed..3e430d847 100644 --- a/lib/taurus/qt/qtgui/taurusgui/macrolistener.py +++ b/lib/taurus/qt/qtgui/taurusgui/macrolistener.py @@ -44,8 +44,6 @@ from taurus.core.util.containers import CaselessList from taurus.external.qt import Qt from taurus.qt.qtgui.base import TaurusBaseComponent -from sardana.taurus.core.tango.sardana import PlotType -from sardana.taurus.core.tango.sardana.pool import getChannelConfigs class ChannelFilter(object): @@ -126,6 +124,9 @@ def onExpConfChanged(self, expconf): QDoor.getExperimentDescription` for more details ''' + + from sardana.taurus.core.tango.sardana import PlotType + from sardana.taurus.core.tango.sardana.pool import getChannelConfigs activeMntGrp = expconf['ActiveMntGrp'] if activeMntGrp is None: return From 012287407ddddb833a18063a9976379bf348cb63 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 16 Oct 2017 14:01:42 +0200 Subject: [PATCH 064/288] Avoid to hide Taurus3 bug Taurus3 does not allow URIs like "tango://alias" The backwards compatibility layer should follow (if possible) the doc rather than the implementation. --- lib/taurus/core/tango/tangovalidator.py | 3 +-- lib/taurus/core/tango/test/test_tangovalidator.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/taurus/core/tango/tangovalidator.py b/lib/taurus/core/tango/tangovalidator.py index 7eee4b5b9..81d2eaece 100644 --- a/lib/taurus/core/tango/tangovalidator.py +++ b/lib/taurus/core/tango/tangovalidator.py @@ -182,7 +182,7 @@ def nonStrictNamePattern(self): r'(\?(?P%(query)s))?' + \ r'(#%(fragment)s)?$' authority = '(?P([\w\-_]+\.)*[\w\-_]+):(?P\d{1,5})' - path = '/?(?P((?P<_devalias>(?<=//)([^/?#:]+))|' + \ + path = '/?(?P((?P<_devalias>([^/?#:]+))|' + \ '(?P<_devslashname>[^/?#:]+/[^/?#:]+/[^/?#:]+)))' return pattern % dict(scheme=self.scheme, @@ -262,7 +262,6 @@ def nonStrictNamePattern(self): non-strict form, and the named group "fragment" will contain "label" """ - # allow for *optional* double-slashes and *optional* ?configuration... pattern = r'^((?P%(scheme)s)://)?' + \ r'((?P%(authority)s)(?=/))?' + \ r'(?P%(path)s)' + \ diff --git a/lib/taurus/core/tango/test/test_tangovalidator.py b/lib/taurus/core/tango/test/test_tangovalidator.py index 817b03986..3309b7bea 100644 --- a/lib/taurus/core/tango/test/test_tangovalidator.py +++ b/lib/taurus/core/tango/test/test_tangovalidator.py @@ -79,13 +79,12 @@ class TangoAuthValidatorTestCase(AbstractNameValidatorTestCase, @invalid(name='tango:/a/b/c?') @valid(name='tango://a/b/c', strict=False) @valid(name='tango:alias') -# @invalid(name='tango:alias', strict=False) # It matches with strict=True @valid(name='tango://alias', strict=False) @valid(name='tango://a/b/c', strict=False) @invalid(name='tango:foo:1234/alias', strict=False) @invalid(name='tango:foo:1234/a/b/c', strict=False) -@invalid(name='foo:1234/alias', strict=False) -@valid(name='foo:1234/a/b/c', strict=False) +@valid(name='foo:1234/alias', strict=False) # Implicit scheme +@valid(name='foo:1234/a/b/c', strict=False) # Implicit scheme @invalid(name='tango://a/b/c', strict=True) @invalid(name='tango://devalias') @names(name='tango://foo:123/a/b/c', From 691dab0ce6f0f9f2f7c1f1f4a3719a5a91559468 Mon Sep 17 00:00:00 2001 From: cpascual Date: Mon, 16 Oct 2017 14:48:56 +0200 Subject: [PATCH 065/288] Protect tango-centric implementation TaurusModelSelectorTree.getSelectedModels is implemented in a Tango-centric way. Protect that part of the code to avoid exceptions when tango is not installed --- lib/taurus/qt/qtgui/panel/taurusmodelchooser.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py index e458eb1df..9d2f3b21c 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py +++ b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py @@ -104,10 +104,13 @@ def setButtonsPos(self, buttonsPos): raise ValueError("Invalid buttons position") def getSelectedModels(self): - # todo: this method is tango-centric, but it could be fixed... - # @todo: tango-centric!! - from taurus.core.tango.tangodatabase import TangoDevInfo, TangoAttrInfo selected = [] + try: + from taurus.core.tango.tangodatabase import (TangoDevInfo, + TangoAttrInfo) + except: + return selected + # TODO: Tango-centric for item in self._deviceTree.selectedItems(): nfo = item.itemData() if isinstance(nfo, TangoDevInfo): From b6cccc5315f8d961bc8c3c8fa7b2fc49d6b98f40 Mon Sep 17 00:00:00 2001 From: cpascual Date: Mon, 16 Oct 2017 15:22:26 +0200 Subject: [PATCH 066/288] Protect tango-centric implementation TaurusDbDeviceModel.setupModelData is implemented in a Tango-centric way. Protect that part of the code to avoid exceptions when tango is not installed --- lib/taurus/qt/qtcore/model/taurusdatabasemodel.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py index 487988df0..23bcf46cf 100644 --- a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py +++ b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py @@ -543,8 +543,11 @@ class TaurusDbDeviceModel(TaurusDbBaseModel): def setupModelData(self, data): if data is None: return - - from taurus.core.tango.tangodatabase import TangoDatabase + try: + # TODO: Tango-centric + from taurus.core.tango.tangodatabase import TangoDatabase + except ImportError: + return if isinstance(data, TangoDatabase): data = data.deviceTree() From a3596817bb41b943ff8d54f2738b8e290d67d97f Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 17 Oct 2017 08:33:34 +0200 Subject: [PATCH 067/288] (minor) PEP8 --- lib/taurus/core/tango/test/test_tangovalidator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/tango/test/test_tangovalidator.py b/lib/taurus/core/tango/test/test_tangovalidator.py index 3309b7bea..919d9c1a3 100644 --- a/lib/taurus/core/tango/test/test_tangovalidator.py +++ b/lib/taurus/core/tango/test/test_tangovalidator.py @@ -83,8 +83,8 @@ class TangoAuthValidatorTestCase(AbstractNameValidatorTestCase, @valid(name='tango://a/b/c', strict=False) @invalid(name='tango:foo:1234/alias', strict=False) @invalid(name='tango:foo:1234/a/b/c', strict=False) -@valid(name='foo:1234/alias', strict=False) # Implicit scheme -@valid(name='foo:1234/a/b/c', strict=False) # Implicit scheme +@valid(name='foo:1234/alias', strict=False) # Implicit scheme +@valid(name='foo:1234/a/b/c', strict=False) # Implicit scheme @invalid(name='tango://a/b/c', strict=True) @invalid(name='tango://devalias') @names(name='tango://foo:123/a/b/c', From c106ad0cfd55d75c599b327b0a1a4015c06770a7 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 17 Oct 2017 09:23:56 +0200 Subject: [PATCH 068/288] Consider uchar attributes as non-numeric Modify the taurus type codification to considered the uchar attributes non-numeric ("bytes"). Adapt the test cases. The GUIs for the moment will represent these attributes using a numeric representation. --- lib/taurus/core/tango/tangoattribute.py | 12 +-- .../core/tango/test/test_tangoattribute.py | 74 +++++++++---------- lib/taurus/core/tango/util/tango_taurus.py | 4 +- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 01480ab31..295da2719 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -90,10 +90,8 @@ def __init__(self, attr=None, pytango_dev_attr=None, config=None): if self._attrRef is None: return - numerical = (PyTango.is_numerical_type(self._attrRef._tango_data_type, - inc_array=True) or - p.type == PyTango.CmdArgType.DevUChar - ) + numerical = PyTango.is_numerical_type(self._attrRef._tango_data_type, + inc_array=True) if p.has_failed: self.error = PyTango.DevFailed(*p.get_err_stack()) @@ -316,10 +314,6 @@ def getNewOperation(self, value): # PyTango connection #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - def isNumeric(self, inc_array=False): - tgtype = self._tango_data_type - return PyTango.is_numerical_type(tgtype, inc_array=inc_array) - def isInteger(self, inc_array=False): tgtype = self._tango_data_type return PyTango.is_int_type(tgtype, inc_array=inc_array) @@ -905,7 +899,7 @@ def _decodeAttrInfoEx(self, pytango_attrinfoex=None): ############################################################### # changed in taurus4: range, alarm and warning now return # quantities if appropriate - if self.isNumeric(True): + if self.isNumeric(): units = self._unit_from_tango(i.unit) else: units = UR.parse_units(None) diff --git a/lib/taurus/core/tango/test/test_tangoattribute.py b/lib/taurus/core/tango/test/test_tangoattribute.py index d5a1350fc..6580b355b 100644 --- a/lib/taurus/core/tango/test/test_tangoattribute.py +++ b/lib/taurus/core/tango/test/test_tangoattribute.py @@ -155,90 +155,90 @@ # Test encode-decode of strings, booleans and uchars @insertTest(helper_name='write_read_attr', attrname='uchar_image', - setvalue=Quantity(_UINT8_IMG, 'mm'), - expected=dict(rvalue=Quantity(_UINT8_IMG, 'mm'), - wvalue=Quantity(_UINT8_IMG, 'mm'), - type=DataType.Integer, + setvalue=_UINT8_IMG, + expected=dict(rvalue=_UINT8_IMG, + wvalue=_UINT8_IMG, + type=DataType.Bytes, label='uchar_image', writable=True, ), - expected_attrv=dict(rvalue=Quantity(_UINT8_IMG, 'mm'), + expected_attrv=dict(rvalue=_UINT8_IMG, value=_UINT8_IMG, - wvalue=Quantity(_UINT8_IMG, 'mm'), + wvalue=_UINT8_IMG, w_value=_UINT8_IMG, quality=AttrQuality.ATTR_VALID ) ) @insertTest(helper_name='write_read_attr', attrname='uchar_spectrum', - setvalue=Quantity(_UINT8_SPE, 'mm'), - expected=dict(rvalue=Quantity(_UINT8_SPE, 'mm'), - wvalue=Quantity(_UINT8_SPE, 'mm'), - type=DataType.Integer, + setvalue=_UINT8_SPE, + expected=dict(rvalue=_UINT8_SPE, + wvalue=_UINT8_SPE, + type=DataType.Bytes, writable=True, ), - expected_attrv=dict(rvalue=Quantity(_UINT8_SPE, 'mm'), + expected_attrv=dict(rvalue=_UINT8_SPE, value=_UINT8_SPE, - wvalue=Quantity(_UINT8_SPE, 'mm'), + wvalue=_UINT8_SPE, w_value=_UINT8_SPE, quality=AttrQuality.ATTR_VALID ) ) @insertTest(helper_name='write_read_attr', attrname='uchar_scalar', - setvalue=Quantity(12, 'mm'), - expected=dict(rvalue=Quantity(12, 'mm'), - wvalue=Quantity(12, 'mm'), - type=DataType.Integer, + setvalue=12, + expected=dict(rvalue=12, + wvalue=12, + type=DataType.Bytes, writable=True, range=[None, None], alarms=[None, None], warnings=[None, None] ), - expected_attrv=dict(rvalue=Quantity(12, 'mm'), + expected_attrv=dict(rvalue=12, value=12, - wvalue=Quantity(12, 'mm'), + wvalue=12, w_value=12, quality=AttrQuality.ATTR_VALID ) ) @insertTest(helper_name='write_read_attr', attrname='uchar_image', - setvalue=Quantity(_UINT8_IMG, 'mm'), - expected=dict(rvalue=Quantity(_UINT8_IMG, 'mm'), - wvalue=Quantity(_UINT8_IMG, 'mm'), - type=DataType.Integer, + setvalue=_UINT8_IMG, + expected=dict(rvalue=_UINT8_IMG, + wvalue=_UINT8_IMG, + type=DataType.Bytes, label='uchar_image', writable=True, ), - expected_attrv=dict(rvalue=Quantity(_UINT8_IMG, 'mm'), + expected_attrv=dict(rvalue=_UINT8_IMG, value=_UINT8_IMG, - wvalue=Quantity(_UINT8_IMG, 'mm'), + wvalue=_UINT8_IMG, w_value=_UINT8_IMG, quality=AttrQuality.ATTR_VALID ) ) @insertTest(helper_name='write_read_attr', attrname='uchar_spectrum', - setvalue=Quantity(_UINT8_SPE, 'mm'), - expected=dict(rvalue=Quantity(_UINT8_SPE, 'mm'), - wvalue=Quantity(_UINT8_SPE, 'mm'), - type=DataType.Integer, + setvalue=_UINT8_SPE, + expected=dict(rvalue=_UINT8_SPE, + wvalue=_UINT8_SPE, + type=DataType.Bytes, writable=True, ), - expected_attrv=dict(rvalue=Quantity(_UINT8_SPE, 'mm'), + expected_attrv=dict(rvalue=_UINT8_SPE, value=_UINT8_SPE, - wvalue=Quantity(_UINT8_SPE, 'mm'), + wvalue=_UINT8_SPE, w_value=_UINT8_SPE, quality=AttrQuality.ATTR_VALID ) ) @insertTest(helper_name='write_read_attr', attrname='uchar_scalar', - setvalue=Quantity(12, 'mm'), - expected=dict(rvalue=Quantity(12, 'mm'), - wvalue=Quantity(12, 'mm'), - type=DataType.Integer, + setvalue=12, + expected=dict(rvalue=12, + wvalue=12, + type=DataType.Bytes, writable=True, range=[None, None], alarms=[None, None], @@ -531,7 +531,7 @@ attrname='uchar_image_ro', expected=dict(rvalue=Quantity([[1] * 3] * 3, 'mm'), wvalue=None, - type=DataType.Integer + type=DataType.Bytes ), expected_attrv=dict(value=[[1] * 3] * 3, w_value=None, @@ -542,9 +542,9 @@ @insertTest(helper_name='write_read_attr', attrname='uchar_scalar_ro', - expected=dict(rvalue=Quantity(1, 'mm'), + expected=dict(rvalue=1, wvalue=None, - type=DataType.Integer, + type=DataType.Bytes, data_format=DataFormat._0D, writable=False, range=[None, None], diff --git a/lib/taurus/core/tango/util/tango_taurus.py b/lib/taurus/core/tango/util/tango_taurus.py index 3b8116a1f..e4d9e040e 100644 --- a/lib/taurus/core/tango/util/tango_taurus.py +++ b/lib/taurus/core/tango/util/tango_taurus.py @@ -47,7 +47,7 @@ PyTango.CmdArgType.DevUShort: DataType.Integer, PyTango.CmdArgType.DevULong: DataType.Integer, PyTango.CmdArgType.DevString: DataType.String, - PyTango.CmdArgType.DevVarCharArray: DataType.Integer, + PyTango.CmdArgType.DevVarCharArray: DataType.Bytes, PyTango.CmdArgType.DevVarShortArray: DataType.Integer, PyTango.CmdArgType.DevVarLongArray: DataType.Integer, PyTango.CmdArgType.DevVarFloatArray: DataType.Float, @@ -60,7 +60,7 @@ PyTango.CmdArgType.DevState: DataType.DevState, PyTango.CmdArgType.ConstDevString: DataType.String, PyTango.CmdArgType.DevVarBooleanArray: DataType.Boolean, - PyTango.CmdArgType.DevUChar: DataType.Integer, + PyTango.CmdArgType.DevUChar: DataType.Bytes, PyTango.CmdArgType.DevLong64: DataType.Integer, PyTango.CmdArgType.DevULong64: DataType.Integer, PyTango.CmdArgType.DevVarLong64Array: DataType.Integer, From 6517ad8047f490515d28f84790e9231dda59d1eb Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 17 Oct 2017 10:30:48 +0200 Subject: [PATCH 069/288] Improve doc of FILTER_OLD_TANGO_EVENTS --- lib/taurus/tauruscustomsettings.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/taurus/tauruscustomsettings.py b/lib/taurus/tauruscustomsettings.py index 1158c911d..71abb6852 100755 --- a/lib/taurus/tauruscustomsettings.py +++ b/lib/taurus/tauruscustomsettings.py @@ -65,9 +65,10 @@ DEFAULT_SCHEME = "tango" # Filter old tango events: -#Sometimes TangoAttribute can receive event from the past. See issue #216. -# True discard (Tango) event which its timestamp is older than cache data. -# False (or commented out) for backwards compatibility +# Sometimes TangoAttribute can receive an event with an older timestamp +# than its current one. See https://github.com/taurus-org/taurus/issues/216 +# True discards (Tango) events whose timestamp is older than the cached one. +# False (or commented out) for backwards (pre 4.1) compatibility FILTER_OLD_TANGO_EVENTS = True # Extra Taurus schemes. You can add a list of modules to be loaded for From 6932d0020a386d247c1230d90e1d0170fe2d600d Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 17 Oct 2017 14:33:49 +0200 Subject: [PATCH 070/288] (minor) fix typos --- doc/source/tep/TEP14.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/tep/TEP14.md b/doc/source/tep/TEP14.md index 918aa6797..d5713ec5b 100644 --- a/doc/source/tep/TEP14.md +++ b/doc/source/tep/TEP14.md @@ -70,12 +70,12 @@ DataType | _0D | _1D | _2D ------------------- | ------------ | ------------------- | ---------------------- Boolean | bool | `ndarray` | `ndarray` Integer, Float | Quantity | Quantity | Quantity -String | str | seq | `seq>` +String | str | `seq` | `seq>` Bytes | bytes | | Some important remarks: -- units is no longer a common member of the TaurusAttribute. Each each physical value, being represented as a Quantity object, has its own units (e.g. the rvalue, wvalue or the range values units are not necessarily identical, although they should be compatible among them) +- units is no longer a common member of the TaurusAttribute. Each physical value, being represented as a Quantity object, has its own units (e.g. the rvalue, wvalue or the range values units are not necessarily identical, although they should be compatible among them) - The use of Quantity objects blurs the difference between integer and float numbers because unit transformations may transform a quantity whose magnitude initially was an integer into a "float"-based quantity. But this is not essentially different from the implicit type conversion between int and float objects. - while numpy arrays are the only allowed types for representing non-scalar booleans (and the same for integers and float within Quantities), we do **not** require numpy arrays for arrays of strings because `numpy.ndarray` imposes fixed lengths of its items. Instead, for strings we recomend using (nested) lists of `str`. From d81561bf214e05c7c20c90a68679e550b3ba4232 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 18 Oct 2017 10:42:48 +0200 Subject: [PATCH 071/288] Fix PEP8 and add documentation --- lib/taurus/core/tango/tangoattribute.py | 42 +++++++++++++++---------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 77fd5fb3b..03c4c5256 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -442,21 +442,26 @@ def write(self, value, with_read=True): self.error("[Tango] write failed: %s" % str(e)) raise e - def poll(self, **kwargs): + def poll(self, single=True, value=None, time=None, error=None): """ Notify listeners when the attribute has been polled""" - single = kwargs.get('single', True) try: if single: self.read(cache=False) else: - value = self.decode(kwargs.get('value')) - self.__attr_err = kwargs.get('error') + value = self.decode(value) + self.__attr_err = error filter_old_event = getattr(tauruscustomsettings, 'FILTER_OLD_TANGO_EVENTS', False) - if self.__attr_value is not None and \ - self.__attr_err is None and \ - filter_old_event and \ - kwargs.get('time') < self.__attr_value.time.totime(): + + # Discard "valid" notifications (value is not None and error + # is None) if FILTER_OLD_TANGO_EVENTS is enabled + # and the given timestamp is older than the timestamp + # of the cache value + if (self.__attr_value is not None + and self.__attr_err is None + and filter_old_event + and time is not None + and time < self.__attr_value.time.totime()): return self.__attr_value = value @@ -518,12 +523,12 @@ def read(self, cache=True): if self.__attr_err is not None: raise self.__attr_err return self.__attr_value - + def getAttributeProxy(self): """Convenience method that creates and returns a PyTango.AttributeProxy object""" return PyTango.AttributeProxy(self.getFullName()) - + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # API for listeners #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- @@ -740,11 +745,16 @@ def _pushAttrEvent(self, event): attr_value = self.decode(event.attr_value) filter_old_event = getattr(tauruscustomsettings, 'FILTER_OLD_TANGO_EVENTS', False) - if self.__attr_value is not None and \ - filter_old_event and \ - event.attr_value.time.totime() < \ - self.__attr_value.time.totime(): - return None, None + time = event.attr_value.time.totime() + + # Discard "valid" events if the attribute value is not None + # and FILTER_OLD_TANGO_EVENTS is enabled + # and the given timestamp is older than the timestamp + # of the cache value + if (self.__attr_value is not None + and filter_old_event + and time < self.__attr_value.time.totime()): + return [None, None] self.__attr_value = attr_value self.__attr_err = None @@ -760,7 +770,7 @@ def _pushAttrEvent(self, event): event.errors[0].reason) self.__subscription_state = SubscriptionState.PendingSubscribe self._activatePolling() - return None, None + return [None, None] else: self.__attr_value, self.__attr_err = None, PyTango.DevFailed( From 78bba5a67d1a51c524d68756963f68a4f0c1a1ba Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 18 Oct 2017 17:43:05 +0200 Subject: [PATCH 072/288] Simplify logic for filtered events Rearrange the filter code to make it more efficient --- lib/taurus/core/tango/tangoattribute.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index b7d190b59..ba729ea71 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -444,23 +444,20 @@ def poll(self, single=True, value=None, time=None, error=None): else: value = self.decode(value) self.__attr_err = error + if self.__attr_err: + raise self.__attr_err + # Avoid "valid-but-outdated" notifications + # if FILTER_OLD_TANGO_EVENTS is enabled + # and the given timestamp is older than the timestamp + # of the cached value filter_old_event = getattr(tauruscustomsettings, 'FILTER_OLD_TANGO_EVENTS', False) - - # Discard "valid" notifications (value is not None and error - # is None) if FILTER_OLD_TANGO_EVENTS is enabled - # and the given timestamp is older than the timestamp - # of the cache value if (self.__attr_value is not None - and self.__attr_err is None and filter_old_event and time is not None and time < self.__attr_value.time.totime()): return - self.__attr_value = value - if self.__attr_err: - raise self.__attr_err except PyTango.DevFailed, df: self.__subscription_event.set() self.debug("Error polling: %s" % df[0].desc) From 591517b7ab6eadd6771838bce0d0013599e4e807 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 09:23:34 +0200 Subject: [PATCH 073/288] Adapt resetLabelConfig to the new format of fragments --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 713b5aad6..9a6e76d47 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -1216,7 +1216,7 @@ def setLabelConfig(self, config): self.updateLabelWidget() def resetLabelConfig(self): - self._labelConfig = '{attr.label}' + self._labelConfig = 'label' self.updateLabelWidget() def getSwitcherClass(self): From 67fd0d47a27dc7b1fef45c19ac54ef8cfe2153ff Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 13:53:22 +0200 Subject: [PATCH 074/288] Add permanentText feature to TaurusLabel PermanentText is a way of forcing TaurusLabel to always show a static text. Modify displayValue method to return the permanentText set by the user. --- lib/taurus/qt/qtgui/display/tauruslabel.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 8e6e19a4f..41203abaa 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -231,6 +231,7 @@ class TaurusLabel(Qt.QLabel, TaurusBaseWidget): def __init__(self, parent=None, designMode=False): self._prefix = self.DefaultPrefix self._suffix = self.DefaultSuffix + self._permanentText = None self._bgRole = self.DefaultBgRole self._fgRole = self.DefaultFgRole self._modelIndex = self.DefaultModelIndex @@ -398,6 +399,10 @@ def setSuffixText(self, suffix): def resetSuffixText(self): self.setSuffixText(self.DefaultSuffix) + def setPermanentText(self, text): + self.setText(text) + self._permanentText = text + def setAutoTrim(self, trim): self._autoTrim = trim self.controllerUpdate() @@ -423,6 +428,11 @@ def getAutoTrim(self): def resetAutoTrim(self): self.setAutoTrim(self.DefaultAutoTrim) + def displayValue(self, v): + if self._permanentText is not None: + return self._permanentText + return TaurusBaseWidget.displayValue(self, v) + @classmethod def getQtDesignerPluginInfo(cls): d = TaurusBaseWidget.getQtDesignerPluginInfo() From 7c502baf85aa21288ee83cea05490c8fb7d96726 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 13:55:42 +0200 Subject: [PATCH 075/288] Add setLabelText feature to TaurusValue Setting a text to the label widget used to work via the setLabelConfig method. But this method is foreseen for setting the model fragment configuration for the label widget. Add a dedicated method to set the label text. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 9a6e76d47..2d80737b5 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -345,6 +345,7 @@ def __init__(self, parent=None, designMode=False, customWidgetMap=None): self._allowWrite = True self._minimumHeight = None self._labelConfig = 'label' + self._labelText = None self.setModifiableByUser(False) if parent is not None: @@ -797,6 +798,9 @@ def updateLabelWidget(self): if hasattr(self._labelWidget, 'setModel'): self._labelWidget.setModel(self.getFullModelName()) + if self._labelText is not None: + self._labelWidget.setPermanentText(self._labelText) + def updateReadWidget(self): # get the class for the widget and replace it if necessary try: @@ -1219,6 +1223,10 @@ def resetLabelConfig(self): self._labelConfig = 'label' self.updateLabelWidget() + def setLabelText(self, text): + self._labelText = text + self._labelWidget.setPermanentText(text) + def getSwitcherClass(self): '''Returns the TaurusValue switcher class (used in compact mode). Override this method if you want to use a custom switcher in From 9c9988636c727fc2f2f325ad042d9f0a89ba6ca2 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 13:57:27 +0200 Subject: [PATCH 076/288] Add backwards compatibility to setLabelConfig setLabelConfig used to work for setting an arbitrary text to the label widget. Maintain this behavior with a deprecation warning. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 2d80737b5..a5d225a0b 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -1216,6 +1216,20 @@ def getLabelConfig(self): @Qt.pyqtSlot('QString') def setLabelConfig(self, config): + """Sets fragment configuration to the label widget. + + :param config: fragment + :type config: str + """ + # backwards compatibility: this method used to work for setting + # an arbitrary text to the label widget + try: + self.getModelFragmentObj(config) + except Exception: + msg = "Use setLabelText for setting an arbitrary label text" + self.deprecated(msg) + self.setLabelText(config) + return self._labelConfig = config self.updateLabelWidget() From 9fbd31c57418b87e31f447933f09ba32046f1a64 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 13:58:05 +0200 Subject: [PATCH 077/288] Revert to previous behavior of test_bug126 --- lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py index 0fd9d6463..1e48d3553 100644 --- a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py @@ -51,14 +51,15 @@ def test_bug126(self): '''Verify that case is not lost when customizing a label (bug#126)''' w = self._widget # self._widget.setModel('eval:1') - self._widget.setModel('tango:' + DEV_NAME + '/MIXEDcase') + self._widget.setModel('tango:' + DEV_NAME + '/double_scalar') label = 'MIXEDcase' + w.setLabelConfig(label) self.processEvents(repetitions=10, sleep=.1) shownLabel = str(w.labelWidget().text()) msg = 'Shown label ("%s") differs from set label ("%s")' % (shownLabel, label) self.assertEqual(label, shownLabel, msg) - self.assertMaxDeprecations(0) + self.assertMaxDeprecations(1) def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): '''Checks the texts for scalar attributes''' From f157266de5dc2784124a3f81b9a6ac7b45a5633d Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 13:58:34 +0200 Subject: [PATCH 078/288] Add test for checking the label case sensitivity --- lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py index 1e48d3553..3debf99c2 100644 --- a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py @@ -77,6 +77,20 @@ def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): self.assertEqual(got, expected, msg) self.assertMaxDeprecations(maxdepr) + def test_labelCaseSensitivity(self): + '''Verify that case is respected of in the label widget''' + '''Verify that case is not lost when customizing a label (bug#126)''' + w = self._widget + # self._widget.setModel('eval:1') + self._widget.setModel('tango:' + DEV_NAME + '/MIXEDcase') + label = 'MIXEDcase' + self.processEvents(repetitions=10, sleep=.1) + shownLabel = str(w.labelWidget().text()) + msg = 'Shown label ("%s") differs from set label ("%s")' % (shownLabel, + label) + self.assertEqual(label, shownLabel, msg) + self.assertMaxDeprecations(0) + def tearDown(self): '''Set Model to None''' self._widget.setModel(None) From 9ee5772a4d3358cbee364e5f1ff1d6f530f74a92 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 15:09:55 +0200 Subject: [PATCH 079/288] Remove duplicated docstring and change quotes --- lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py index 3debf99c2..3bbb173e0 100644 --- a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py @@ -78,8 +78,7 @@ def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): self.assertMaxDeprecations(maxdepr) def test_labelCaseSensitivity(self): - '''Verify that case is respected of in the label widget''' - '''Verify that case is not lost when customizing a label (bug#126)''' + """Verify that case is respected of in the label widget""" w = self._widget # self._widget.setModel('eval:1') self._widget.setModel('tango:' + DEV_NAME + '/MIXEDcase') From c4cf0cc7204e9c0e3bb2fe61dffb1298dee58bb5 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 15:14:19 +0200 Subject: [PATCH 080/288] Change docstring quotes according to PEP8 --- .../qt/qtgui/panel/test/test_taurusvalue.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py index 3bbb173e0..d4cf6d823 100644 --- a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py @@ -34,35 +34,35 @@ DEV_NAME = TangoSchemeTestLauncher.DEV_NAME -@insertTest(helper_name='texts', - model='tango:' + DEV_NAME + '/double_scalar', - expected=('double_scalar', '1.23', '0.00 mm', 'mm'), - # expected=('double_scalar', '1.23', '0.0', 'mm'), +@insertTest(helper_name="texts", + model="tango:" + DEV_NAME + "/double_scalar", + expected=("double_scalar", "1.23", "0.00 mm", "mm"), + # expected=("double_scalar", "1.23", "0.0", "mm"), # TODO: change taurusvalue's line edit to hide units ) class TaurusValueTest(TangoSchemeTestLauncher, BaseWidgetTestCase, unittest.TestCase): - ''' + """ Specific tests for TaurusValue - ''' + """ _klass = TaurusValue def test_bug126(self): - '''Verify that case is not lost when customizing a label (bug#126)''' + """Verify that case is not lost when customizing a label (bug#126)""" w = self._widget - # self._widget.setModel('eval:1') - self._widget.setModel('tango:' + DEV_NAME + '/double_scalar') - label = 'MIXEDcase' + # self._widget.setModel("eval:1") + self._widget.setModel("tango:" + DEV_NAME + "/double_scalar") + label = "MIXEDcase" w.setLabelConfig(label) self.processEvents(repetitions=10, sleep=.1) shownLabel = str(w.labelWidget().text()) - msg = 'Shown label ("%s") differs from set label ("%s")' % (shownLabel, + msg = "Shown label ("%s") differs from set label ("%s")" % (shownLabel, label) self.assertEqual(label, shownLabel, msg) self.assertMaxDeprecations(1) def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): - '''Checks the texts for scalar attributes''' + """Checks the texts for scalar attributes""" self._widget.setModel(model) if fgRole is not None: self._widget.setFgRole(fgRole) @@ -72,7 +72,7 @@ def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): str(self._widget.writeWidget().displayText()), str(self._widget.unitsWidget().text()), ) - msg = ('wrong text for "%s":\n expected: %s\n got: %s' % + msg = ("wrong text for "%s":\n expected: %s\n got: %s" % (model, expected, got)) self.assertEqual(got, expected, msg) self.assertMaxDeprecations(maxdepr) @@ -80,9 +80,9 @@ def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): def test_labelCaseSensitivity(self): """Verify that case is respected of in the label widget""" w = self._widget - # self._widget.setModel('eval:1') - self._widget.setModel('tango:' + DEV_NAME + '/MIXEDcase') - label = 'MIXEDcase' + # self._widget.setModel("eval:1") + self._widget.setModel("tango:" + DEV_NAME + "/MIXEDcase") + label = "MIXEDcase" self.processEvents(repetitions=10, sleep=.1) shownLabel = str(w.labelWidget().text()) msg = 'Shown label ("%s") differs from set label ("%s")' % (shownLabel, @@ -91,10 +91,10 @@ def test_labelCaseSensitivity(self): self.assertMaxDeprecations(0) def tearDown(self): - '''Set Model to None''' + """Set Model to None""" self._widget.setModel(None) TangoSchemeTestLauncher.tearDown(self) unittest.TestCase.tearDown(self) -if __name__ == '__main__': +if __name__ == "__main__": pass From 60bdb2b9b7c59de85d017bc18c8b80cb3551f079 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 15:15:21 +0200 Subject: [PATCH 081/288] Change docstring quotes according to PEP8 --- lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py index d4cf6d823..5728acf5d 100644 --- a/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/test/test_taurusvalue.py @@ -56,7 +56,7 @@ def test_bug126(self): w.setLabelConfig(label) self.processEvents(repetitions=10, sleep=.1) shownLabel = str(w.labelWidget().text()) - msg = "Shown label ("%s") differs from set label ("%s")" % (shownLabel, + msg = 'Shown label ("%s") differs from set label ("%s")' % (shownLabel, label) self.assertEqual(label, shownLabel, msg) self.assertMaxDeprecations(1) @@ -72,7 +72,7 @@ def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): str(self._widget.writeWidget().displayText()), str(self._widget.unitsWidget().text()), ) - msg = ("wrong text for "%s":\n expected: %s\n got: %s" % + msg = ('wrong text for "%s":\n expected: %s\n got: %s' % (model, expected, got)) self.assertEqual(got, expected, msg) self.assertMaxDeprecations(maxdepr) @@ -80,7 +80,6 @@ def texts(self, model=None, expected=None, fgRole=None, maxdepr=0): def test_labelCaseSensitivity(self): """Verify that case is respected of in the label widget""" w = self._widget - # self._widget.setModel("eval:1") self._widget.setModel("tango:" + DEV_NAME + "/MIXEDcase") label = "MIXEDcase" self.processEvents(repetitions=10, sleep=.1) From fee0219cfa73b9f480c4f233b930e3f02c6b5210 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 15:48:51 +0200 Subject: [PATCH 082/288] Register permanentText as config property of TaurusLabel --- lib/taurus/qt/qtgui/display/tauruslabel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 41203abaa..b10704cf8 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -252,6 +252,11 @@ def __init__(self, parent=None, designMode=False): if self._designMode: self.controllerUpdate() + # register configurable properties + self.registerConfigProperty( + self.getPermanentText, self.setPermanentText, "permanentText" + ) + def _calculate_controller_class(self): ctrl_map = _CONTROLLER_MAP if self._designMode: @@ -399,6 +404,9 @@ def setSuffixText(self, suffix): def resetSuffixText(self): self.setSuffixText(self.DefaultSuffix) + def getPermanentText(self): + return self._permanentText + def setPermanentText(self, text): self.setText(text) self._permanentText = text From 5d533e6e1187d429adef16dd763735862b373c31 Mon Sep 17 00:00:00 2001 From: zreszela Date: Thu, 19 Oct 2017 15:49:13 +0200 Subject: [PATCH 083/288] Register labelText as config property of TaurusValue --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index a5d225a0b..b6aea6f15 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -353,6 +353,8 @@ def __init__(self, parent=None, designMode=False, customWidgetMap=None): self.registerConfigProperty( self.getLabelConfig, self.setLabelConfig, 'labelConfig') + self.registerConfigProperty( + self.getLabelText, self.setLabelText, 'labelText') self.registerConfigProperty(self.isCompact, self.setCompact, 'compact') def setVisible(self, visible): @@ -1237,6 +1239,9 @@ def resetLabelConfig(self): self._labelConfig = 'label' self.updateLabelWidget() + def getLabelText(self): + return self._labelText + def setLabelText(self, text): self._labelText = text self._labelWidget.setPermanentText(text) From 866d7c89e3770f988d637f168ffbf35b060331ac Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 20 Oct 2017 13:05:57 +0200 Subject: [PATCH 084/288] Show the current formatter in the dialog The "Set formatter" dialog does not show the current formatter. Show it. --- lib/taurus/qt/qtgui/base/taurusbase.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index dc0c53cf2..93acc494a 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -1269,9 +1269,16 @@ def showFormatterDlg(self): :return: formatter: python fromat string or formatter callable (in string version) or None ''' + if isinstance(self.FORMAT, str): + current_format = self.FORMAT + else: + current_format = '{0}.{1}'.format(self.FORMAT.__module__, + self.FORMAT.__name__) + formatter, ok = Qt.QInputDialog.getText(self, "Set formatter", "Enter a formatter:", - Qt.QLineEdit.Normal, "") + Qt.QLineEdit.Normal, + current_format) if ok and formatter: return self._getFormatter(formatter) From 651d9619bb1b616b0970e99b5b687ba38a9557c6 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 20 Oct 2017 15:44:33 +0200 Subject: [PATCH 085/288] Fix formatter initialization The _setFormatter method does not exist. The refactoring during the implementation was not completed. Fix the implementation. --- lib/taurus/qt/qtgui/base/taurusbase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 93acc494a..0231da048 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -1246,7 +1246,8 @@ def __init__(self, name, parent=None, designMode=False): formatter = getattr(taurus.tauruscustomsettings, 'DEFAULT_FORMATTER', None) if formatter is not None: - self._setFormatter(formatter) + format = self._getFormatter(formatter) + self.setFormat(format) def _getFormatter(self, formatter): '''' Method to get custom formatter From 727ea3212b921cdefce3af380ab59a55dd078372 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 20 Oct 2017 15:52:25 +0200 Subject: [PATCH 086/288] Add "--default-formatter" arg parse Add --default-formatter option to allow to change the default formatter in runtime. --- lib/taurus/core/util/argparse/taurusargparse.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/taurus/core/util/argparse/taurusargparse.py b/lib/taurus/core/util/argparse/taurusargparse.py index eb7eea3bf..ea985fd67 100644 --- a/lib/taurus/core/util/argparse/taurusargparse.py +++ b/lib/taurus/core/util/argparse/taurusargparse.py @@ -108,6 +108,7 @@ def get_taurus_parser(parser=None): help_taurusserial = "taurus serialization mode. Allowed values are (case insensitive): "\ "serial, concurrent (default)" help_rcport = "enables remote debugging using the given port" + help_formatter = "Override the default formatter in runtime" group.add_option("--taurus-log-level", dest="taurus_log_level", metavar="LEVEL", help=help_tauruslog, type="str", default="info") group.add_option("--taurus-polling-period", dest="taurus_polling_period", metavar="MILLISEC", @@ -118,6 +119,9 @@ def get_taurus_parser(parser=None): help=help_tangohost, type="str", default=None) group.add_option("--remote-console-port", dest="remote_console_port", metavar="PORT", help=help_rcport, type="int", default=None) + group.add_option("--default-formatter", dest="default_formatter", + metavar="FORMATTER", help=help_formatter, type="str", + default=None) parser.add_option_group(group) return parser @@ -197,6 +201,12 @@ def init_taurus_args(parser=None, args=None, values=None): except Exception, e: taurus.warning("Cannot spawn debugger. Reason: %s", str(e)) + # initialize default formatter + if options.default_formatter is not None: + from taurus import tauruscustomsettings + setattr(tauruscustomsettings, 'DEFAULT_FORMATTER', + options.default_formatter) + return parser, options, args From 903ae5025ca87b632af1b4391d80dce6be1ec79a Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 20 Oct 2017 15:54:55 +0200 Subject: [PATCH 087/288] Add warning in DEFAULT_FORMATTER option --- lib/taurus/tauruscustomsettings.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/taurus/tauruscustomsettings.py b/lib/taurus/tauruscustomsettings.py index 14ced32de..697b58a63 100755 --- a/lib/taurus/tauruscustomsettings.py +++ b/lib/taurus/tauruscustomsettings.py @@ -72,10 +72,16 @@ # attribute type, but sometimes a custom formatter is needed. # The formatter can be a python format string or formatter callable # (in string version) -#e.g. +# e.g. # DEFAULT_FORMATTER = '{0}' # DEFAULT_FORMATTER = 'taurus.core.tango.util.formatter.tangoFormatter' +# WARNING! +# This option will affect to ALL widgets and is not recommended to use. +# If you are interested to modify it for a specific widget use the +# runtime option instead. +# e.g. taurusform MODEL --default-formatter='{:2.3f}' + # ---------------------------------------------------------------------------- # PLY (lex/yacc) optimization: 1=Active (default) , 0=disabled. # Set PLY_OPTIMIZE = 0 if you are getting yacc exceptions while loading From f2dc5579d5165c17a047e384547f5a55295cdbd8 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Fri, 20 Oct 2017 16:14:05 +0200 Subject: [PATCH 088/288] rephrase tauruscustomsettings doc --- lib/taurus/tauruscustomsettings.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/taurus/tauruscustomsettings.py b/lib/taurus/tauruscustomsettings.py index 697b58a63..e059288fa 100755 --- a/lib/taurus/tauruscustomsettings.py +++ b/lib/taurus/tauruscustomsettings.py @@ -68,19 +68,21 @@ # providing support to new schemes # EXTRA_SCHEME_MODULES = ['myownschememodule'] -# Set a custom formatter. Taurus widgets use a default formatter based on the +# Custom formatter. Taurus widgets use a default formatter based on the # attribute type, but sometimes a custom formatter is needed. -# The formatter can be a python format string or formatter callable -# (in string version) +# IMPORTANT: seting this option in this file will affect to ALL widgets +# of ALL applications (which is probably **not** what you want, since it +# may have unexpected effects in some applications). +# Consider using the API for modifying this on a per-widget or per-class +# basis at runtime, or using the related `--default-formatter` parameter +# from TaurusApplication, e.g.: +# $ taurusform MODEL --default-formatter='{:2.3f}' +# The formatter can be a python format string or the name of a formatter +# callable # e.g. # DEFAULT_FORMATTER = '{0}' # DEFAULT_FORMATTER = 'taurus.core.tango.util.formatter.tangoFormatter' -# WARNING! -# This option will affect to ALL widgets and is not recommended to use. -# If you are interested to modify it for a specific widget use the -# runtime option instead. -# e.g. taurusform MODEL --default-formatter='{:2.3f}' # ---------------------------------------------------------------------------- # PLY (lex/yacc) optimization: 1=Active (default) , 0=disabled. From 194196f92c8fa3a583d86a1212898ba4cf8adab5 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 12:14:59 +0200 Subject: [PATCH 089/288] Add set_disable_tango_events flag to TangoFactory and TangoAttribute --- lib/taurus/core/tango/tangoattribute.py | 36 +++++++++++++++++-------- lib/taurus/core/tango/tangofactory.py | 11 ++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index a45c88fc8..44d8d6601 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -284,7 +284,9 @@ def __init__(self, name, parent, **kwargs): # subscribe to configuration events (unsubscription done at cleanup) self.__cfg_evt_id = None - self._subscribeConfEvents() + self._ignore_events = self.factory().get_tango_events_disabled() + if not self._ignore_events: + self._subscribeConfEvents() def cleanUp(self): self.trace("[TangoAttribute] cleanUp") @@ -597,24 +599,36 @@ def _subscribeEvents(self): self.__subscription_event = threading.Event() attr_name = self.getSimpleName() + if self._ignore_events: + self.__chg_evt_id = -1 + self._activatePolling() + return + try: self.__subscription_state = SubscriptionState.Subscribing - self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( - attr_name, PyTango.EventType.CHANGE_EVENT, - self, []) # connects to self.push_event callback - + self._call_dev_hw_subscribe_event(False) except: self.__subscription_state = SubscriptionState.PendingSubscribe self._activatePolling() - self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( - attr_name, PyTango.EventType.CHANGE_EVENT, - self, [], True) # connects to self.push_event callback - + self._call_dev_hw_subscribe_event(True) + + def _call_dev_hw_subscribe_event(self,stateless=True): + try: + attr_name = self.getSimpleName() + cid = self.__dev_hw_obj.subscribe_event( + attr_name, PyTango.EventType.CHANGE_EVENT, + self, [], stateless) # connects to self.push_event callback + self.__chg_evt_id = cid + return cid + except: + self.error(traceback.format_exc()) + def _unsubscribeEvents(self): # Careful in this method: This is intended to be executed in the cleanUp # so we should not access external objects from the factory, like the # parent object - if self.__dev_hw_obj is not None and self.__chg_evt_id is not None: + + if self.__dev_hw_obj is not None and self.__chg_evt_id not in (None,-1): self.trace("Unsubscribing to change events (ID=%d)", self.__chg_evt_id) try: @@ -669,7 +683,7 @@ def _unsubscribeConfEvents(self): # Careful in this method: This is intended to be executed in the cleanUp # so we should not access external objects from the factory, like the # parent object - if self.__cfg_evt_id and not self.__dev_hw_obj is None: + if self.__cfg_evt_id is not None and not self.__dev_hw_obj is None: self.trace("Unsubscribing to configuration events (ID=%s)", str(self.__cfg_evt_id)) try: diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py index b90725daa..981575b99 100644 --- a/lib/taurus/core/tango/tangofactory.py +++ b/lib/taurus/core/tango/tangofactory.py @@ -109,6 +109,7 @@ def init(self, *args, **kwargs): def reInit(self): """Reinitialize the singleton""" self._default_tango_host = None + self._tango_events_disabled = False self.dft_db = None self.tango_db = CaselessWeakValueDict() self.tango_db_queries = CaselessWeakValueDict() @@ -182,6 +183,16 @@ def get_default_tango_host(self): """Retruns the current default tango host """ return self._default_tango_host + + def set_tango_events_disabled(self,value): + """ If True, disable event subscribing on TangoAttribute objects + """ + self._tango_events_disabled = value + + def get_tango_events_disabled(self): + """ Returns the current tango_events_disabled status + """ + return self._tango_events_disabled def registerAttributeClass(self, attr_name, attr_klass): """Registers a new attribute class for the attribute name. From b455ff6b1ce1de4ec247b740f7ff5df2ec7ab4f2 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 12:14:59 +0200 Subject: [PATCH 090/288] Add set_disable_tango_events flag to TangoFactory and TangoAttribute --- lib/taurus/core/tango/tangoattribute.py | 36 +++++++++++++++++-------- lib/taurus/core/tango/tangofactory.py | 11 ++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 295da2719..9d39633c1 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -287,7 +287,9 @@ def __init__(self, name, parent, **kwargs): # subscribe to configuration events (unsubscription done at cleanup) self.__cfg_evt_id = None - self._subscribeConfEvents() + self._ignore_events = self.factory().get_tango_events_disabled() + if not self._ignore_events: + self._subscribeConfEvents() def cleanUp(self): self.trace("[TangoAttribute] cleanUp") @@ -603,24 +605,36 @@ def _subscribeEvents(self): self.__subscription_event = threading.Event() attr_name = self.getSimpleName() + if self._ignore_events: + self.__chg_evt_id = -1 + self._activatePolling() + return + try: self.__subscription_state = SubscriptionState.Subscribing - self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( - attr_name, PyTango.EventType.CHANGE_EVENT, - self, []) # connects to self.push_event callback - + self._call_dev_hw_subscribe_event(False) except: self.__subscription_state = SubscriptionState.PendingSubscribe self._activatePolling() - self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( - attr_name, PyTango.EventType.CHANGE_EVENT, - self, [], True) # connects to self.push_event callback - + self._call_dev_hw_subscribe_event(True) + + def _call_dev_hw_subscribe_event(self,stateless=True): + try: + attr_name = self.getSimpleName() + cid = self.__dev_hw_obj.subscribe_event( + attr_name, PyTango.EventType.CHANGE_EVENT, + self, [], stateless) # connects to self.push_event callback + self.__chg_evt_id = cid + return cid + except: + self.error(traceback.format_exc()) + def _unsubscribeEvents(self): # Careful in this method: This is intended to be executed in the cleanUp # so we should not access external objects from the factory, like the # parent object - if self.__dev_hw_obj is not None and self.__chg_evt_id is not None: + + if self.__dev_hw_obj is not None and self.__chg_evt_id not in (None,-1): self.trace("Unsubscribing to change events (ID=%d)", self.__chg_evt_id) try: @@ -675,7 +689,7 @@ def _unsubscribeConfEvents(self): # Careful in this method: This is intended to be executed in the cleanUp # so we should not access external objects from the factory, like the # parent object - if self.__cfg_evt_id and not self.__dev_hw_obj is None: + if self.__cfg_evt_id is not None and not self.__dev_hw_obj is None: self.trace("Unsubscribing to configuration events (ID=%s)", str(self.__cfg_evt_id)) try: diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py index b90725daa..981575b99 100644 --- a/lib/taurus/core/tango/tangofactory.py +++ b/lib/taurus/core/tango/tangofactory.py @@ -109,6 +109,7 @@ def init(self, *args, **kwargs): def reInit(self): """Reinitialize the singleton""" self._default_tango_host = None + self._tango_events_disabled = False self.dft_db = None self.tango_db = CaselessWeakValueDict() self.tango_db_queries = CaselessWeakValueDict() @@ -182,6 +183,16 @@ def get_default_tango_host(self): """Retruns the current default tango host """ return self._default_tango_host + + def set_tango_events_disabled(self,value): + """ If True, disable event subscribing on TangoAttribute objects + """ + self._tango_events_disabled = value + + def get_tango_events_disabled(self): + """ Returns the current tango_events_disabled status + """ + return self._tango_events_disabled def registerAttributeClass(self, attr_name, attr_klass): """Registers a new attribute class for the attribute name. From 158a364311ec8572673e1a9654ea2d75f147ab86 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 24 Oct 2017 13:34:37 +0200 Subject: [PATCH 091/288] (minor) PEP8 --- lib/taurus/core/tango/tangoattribute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 9d39633c1..851f716a7 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -618,7 +618,7 @@ def _subscribeEvents(self): self._activatePolling() self._call_dev_hw_subscribe_event(True) - def _call_dev_hw_subscribe_event(self,stateless=True): + def _call_dev_hw_subscribe_event(self, stateless=True): try: attr_name = self.getSimpleName() cid = self.__dev_hw_obj.subscribe_event( From de3144be827aab1510996061904be3dfbc525ebb Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 24 Oct 2017 13:45:40 +0200 Subject: [PATCH 092/288] (minor) PEP8 --- lib/taurus/core/tango/tangoattribute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 851f716a7..8904b8f35 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -634,7 +634,7 @@ def _unsubscribeEvents(self): # so we should not access external objects from the factory, like the # parent object - if self.__dev_hw_obj is not None and self.__chg_evt_id not in (None,-1): + if self.__dev_hw_obj is not None and self.__chg_evt_id not in (None, -1): self.trace("Unsubscribing to change events (ID=%d)", self.__chg_evt_id) try: From 44d48b66e83f107395ab9f7004a28b0c0e0fee5d Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 14:53:49 +0200 Subject: [PATCH 093/288] rename tango_events_disabled to tango_subscribe_enabled --- lib/taurus/core/tango/tangofactory.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py index 981575b99..5a3180ae9 100644 --- a/lib/taurus/core/tango/tangofactory.py +++ b/lib/taurus/core/tango/tangofactory.py @@ -109,7 +109,7 @@ def init(self, *args, **kwargs): def reInit(self): """Reinitialize the singleton""" self._default_tango_host = None - self._tango_events_disabled = False + self._tango_subscribe_enabled = True self.dft_db = None self.tango_db = CaselessWeakValueDict() self.tango_db_queries = CaselessWeakValueDict() @@ -184,15 +184,15 @@ def get_default_tango_host(self): """ return self._default_tango_host - def set_tango_events_disabled(self,value): + def set_tango_subscribe_enabled(self,value): """ If True, disable event subscribing on TangoAttribute objects """ - self._tango_events_disabled = value + self._tango_subscribe_enabled = value - def get_tango_events_disabled(self): - """ Returns the current tango_events_disabled status + def is_tango_subscribe_enabled(self): + """ Returns the current tango_subscribe_enabled status """ - return self._tango_events_disabled + return self._tango_subscribe_enabled def registerAttributeClass(self, attr_name, attr_klass): """Registers a new attribute class for the attribute name. From 66049ba973891b0c99b1392da1c4066bf9f3fcdc Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 15:07:03 +0200 Subject: [PATCH 094/288] rename tango_events_disabled to tango_subscribe_enabled --- lib/taurus/core/tango/tangoattribute.py | 29 ++++++++++--------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 8904b8f35..a0f759f1b 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -287,8 +287,7 @@ def __init__(self, name, parent, **kwargs): # subscribe to configuration events (unsubscription done at cleanup) self.__cfg_evt_id = None - self._ignore_events = self.factory().get_tango_events_disabled() - if not self._ignore_events: + if self.factory().is_tango_subscribe_enabled(): self._subscribeConfEvents() def cleanUp(self): @@ -602,11 +601,7 @@ def _subscribeEvents(self): self.debug("failed to subscribe to chg events: HW is None") return - self.__subscription_event = threading.Event() - attr_name = self.getSimpleName() - - if self._ignore_events: - self.__chg_evt_id = -1 + if self.factory().is_tango_subscribe_enabled(): self._activatePolling() return @@ -619,22 +614,19 @@ def _subscribeEvents(self): self._call_dev_hw_subscribe_event(True) def _call_dev_hw_subscribe_event(self, stateless=True): - try: - attr_name = self.getSimpleName() - cid = self.__dev_hw_obj.subscribe_event( - attr_name, PyTango.EventType.CHANGE_EVENT, - self, [], stateless) # connects to self.push_event callback - self.__chg_evt_id = cid - return cid - except: - self.error(traceback.format_exc()) + attr_name = self.getSimpleName() + cid = self.__dev_hw_obj.subscribe_event( + attr_name, PyTango.EventType.CHANGE_EVENT, + self, [], stateless) # connects to self.push_event callback + self.__chg_evt_id = cid + return cid def _unsubscribeEvents(self): # Careful in this method: This is intended to be executed in the cleanUp # so we should not access external objects from the factory, like the # parent object - if self.__dev_hw_obj is not None and self.__chg_evt_id not in (None, -1): + if self.__dev_hw_obj is not None and self.__chg_evt_id is not None: self.trace("Unsubscribing to change events (ID=%d)", self.__chg_evt_id) try: @@ -689,7 +681,8 @@ def _unsubscribeConfEvents(self): # Careful in this method: This is intended to be executed in the cleanUp # so we should not access external objects from the factory, like the # parent object - if self.__cfg_evt_id is not None and not self.__dev_hw_obj is None: + + if self.__cfg_evt_id is not None and self.__dev_hw_obj is not None: self.trace("Unsubscribing to configuration events (ID=%s)", str(self.__cfg_evt_id)) try: From 18025b6b8a1719d4dbed284f15c073e8ec44c0aa Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 15:15:04 +0200 Subject: [PATCH 095/288] Amend tango_events_disabled check --- lib/taurus/core/tango/tangoattribute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index a0f759f1b..d4ac3e557 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -601,7 +601,7 @@ def _subscribeEvents(self): self.debug("failed to subscribe to chg events: HW is None") return - if self.factory().is_tango_subscribe_enabled(): + if not self.factory().is_tango_subscribe_enabled(): self._activatePolling() return From d7f3cb733c4ad7dea7ddce10d1da809245378cdc Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 15:21:40 +0200 Subject: [PATCH 096/288] Amend methods description --- lib/taurus/core/tango/tangoattribute.py | 10 ++++++---- lib/taurus/core/tango/tangofactory.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index d4ac3e557..90f41daeb 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -613,13 +613,15 @@ def _subscribeEvents(self): self._activatePolling() self._call_dev_hw_subscribe_event(True) - def _call_dev_hw_subscribe_event(self, stateless=True): + def _call_dev_hw_subscribe_event(self, stateless=True): + """ Executes event subscription on parent TangoDevice objectName + """ attr_name = self.getSimpleName() - cid = self.__dev_hw_obj.subscribe_event( + self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, self, [], stateless) # connects to self.push_event callback - self.__chg_evt_id = cid - return cid + + return self.__chg_evt_id def _unsubscribeEvents(self): # Careful in this method: This is intended to be executed in the cleanUp diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py index 5a3180ae9..fd2668a75 100644 --- a/lib/taurus/core/tango/tangofactory.py +++ b/lib/taurus/core/tango/tangofactory.py @@ -185,7 +185,7 @@ def get_default_tango_host(self): return self._default_tango_host def set_tango_subscribe_enabled(self,value): - """ If True, disable event subscribing on TangoAttribute objects + """ If True, enables event subscribing on TangoAttribute objects """ self._tango_subscribe_enabled = value From 8da4ffd7853b4f5fe80dc0a969585dd037e18cd0 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 24 Oct 2017 15:53:14 +0200 Subject: [PATCH 097/288] Fix Taurus splash screen is not shown Taurus splash screen icon is set in the old style, so the image is not shown. Fix #494 --- lib/taurus/qt/qtgui/container/taurusmainwindow.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/container/taurusmainwindow.py b/lib/taurus/qt/qtgui/container/taurusmainwindow.py index d456f877c..3e62414aa 100644 --- a/lib/taurus/qt/qtgui/container/taurusmainwindow.py +++ b/lib/taurus/qt/qtgui/container/taurusmainwindow.py @@ -189,7 +189,9 @@ class TaurusMainWindow(Qt.QMainWindow, TaurusBaseContainer): # Allows the user to change/create/delete perspectives _supportUserPerspectives = True _showLogger = True - _splashLogo = ":/TaurusSplash.png" # set to None for disabling splash screen + # + # set to None for disabling splash screen + _splashLogo = "large:TaurusSplash.png" _splashMessage = "Initializing Main window..." def __init__(self, parent=None, designMode=False, splash=None): @@ -1080,7 +1082,8 @@ class MyMainWindow(TaurusMainWindow): # Allows the user to change/create/delete perspectives _supportUserPerspectives = True _showLogger = True - _splashLogo = ":/TaurusSplash.png" # set to None for disabling splash screen + # set to None for disabling splash screen + _splashLogo = "large:TaurusSplash.png" _splashMessage = "Initializing Main window..." def __init__(self): From e85668d764e6b9b152c759faf73a5526f81e17f2 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 16:15:16 +0200 Subject: [PATCH 098/288] Avoid overriding of chg_evt_id (may cause deadlock on app exit) --- lib/taurus/core/tango/tangoattribute.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 90f41daeb..0465d2344 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -583,6 +583,9 @@ def setConfigEx(self, config): def isUsingEvents(self): return self.__subscription_state == SubscriptionState.Subscribed + + def getSubscriptionState(self): + return self.__subscription_state def _process_event_exception(self, ex): pass @@ -590,6 +593,10 @@ def _process_event_exception(self, ex): def _subscribeEvents(self): """ Enable subscription to the attribute events. If change events are not supported polling is activated """ + if self.__chg_evt_id is not None: + self.warning("chg events already subscribed (id=%s)" + %self.__chg_evt_id) + return if self.__dev_hw_obj is None: dev = self.getParentObj() From 3bbb77ec2e18fdf31a8255dbd1bbc3b45ac1994e Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 16:33:12 +0200 Subject: [PATCH 099/288] Avoid overriding of cfg_evt_id (may cause deadlock on app exit) --- lib/taurus/core/tango/tangoattribute.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 0465d2344..accf73137 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -593,6 +593,7 @@ def _process_event_exception(self, ex): def _subscribeEvents(self): """ Enable subscription to the attribute events. If change events are not supported polling is activated """ + if self.__chg_evt_id is not None: self.warning("chg events already subscribed (id=%s)" %self.__chg_evt_id) @@ -655,6 +656,12 @@ def _unsubscribeEvents(self): def _subscribeConfEvents(self): """ Enable subscription to the attribute configuration events.""" self.trace("Subscribing to configuration events...") + + if self.__cfg_evt_id is not None: + self.warning("cfg events already subscribed (id=%s)" + %self.__cfg_evt_id) + return + if self.__dev_hw_obj is None: dev = self.getParentObj() if dev is None: From 0a24290f6e62fca9d3a203f7a76d2e211ceadeb1 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 25 Oct 2017 16:01:24 +0200 Subject: [PATCH 100/288] Fix wrong plugin info for TaurusTrend2DDialog TaurusTrend2DDialog cannot be used in designer due to a wrong module info in the its designer plugin method. Fix it. --- lib/taurus/qt/qtgui/extra_guiqwt/taurustrend2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/taurustrend2d.py b/lib/taurus/qt/qtgui/extra_guiqwt/taurustrend2d.py index f24ac7d16..af5649057 100644 --- a/lib/taurus/qt/qtgui/extra_guiqwt/taurustrend2d.py +++ b/lib/taurus/qt/qtgui/extra_guiqwt/taurustrend2d.py @@ -279,7 +279,7 @@ def resetMaxDataBufferSize(self): def getQtDesignerPluginInfo(cls): """reimplemented from :class:`TaurusBaseWidget`""" ret = TaurusBaseWidget.getQtDesignerPluginInfo() - ret['module'] = 'taurus.qt.qtgui.plot' + ret['module'] = 'taurus.qt.qtgui.extra_guiqwt' ret['group'] = 'Taurus Display' ret['icon'] = 'designer:qwtplot.png' return ret From 4a1c315c2f1b0de0129a3470768161a6825d8170 Mon Sep 17 00:00:00 2001 From: zreszela Date: Wed, 25 Oct 2017 16:38:52 +0200 Subject: [PATCH 101/288] Fix icons path location in TaurusLockButton Change extra_icon to extra_icons where the icons are really located. --- lib/taurus/qt/qtgui/button/taurusbutton.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtgui/button/taurusbutton.py b/lib/taurus/qt/qtgui/button/taurusbutton.py index 8d83d23e5..9a2f97066 100644 --- a/lib/taurus/qt/qtgui/button/taurusbutton.py +++ b/lib/taurus/qt/qtgui/button/taurusbutton.py @@ -545,10 +545,10 @@ def getQtDesignerPluginInfo(cls): class TaurusLockButton(Qt.QPushButton, TaurusBaseWidget): - _LOCK_MAP = {LockStatus.Unlocked: "extra_icon:lock_unlocked.svg", - LockStatus.Locked: "extra_icon:lock_locked_unpreviledged.svg", - LockStatus.LockedMaster: "extra_icon:lock_locked.svg", - LockStatus.Unknown: "extra_icon:lock_unknown.svg"} + _LOCK_MAP = {LockStatus.Unlocked: "extra_icons:lock_unlocked.svg", + LockStatus.Locked: "extra_icons:lock_locked_unpreviledged.svg", + LockStatus.LockedMaster: "extra_icons:lock_locked.svg", + LockStatus.Unknown: "extra_icons:lock_unknown.svg"} def __init__(self, parent=None, designMode=False): self._lock_info = TaurusLockInfo() From 32de03ff9f697ddad55120cfd80b6312bb2d6fa6 Mon Sep 17 00:00:00 2001 From: Jan Kotanski Date: Wed, 25 Oct 2017 17:06:46 +0200 Subject: [PATCH 102/288] add taurus.external.ordereddict back --- lib/taurus/external/ordereddict/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/taurus/external/ordereddict/__init__.py diff --git a/lib/taurus/external/ordereddict/__init__.py b/lib/taurus/external/ordereddict/__init__.py new file mode 100644 index 000000000..9e4bb2d4a --- /dev/null +++ b/lib/taurus/external/ordereddict/__init__.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import + +try: + # ordereddict from python 2.7 or from ordereddict installed package? + from collections import OrderedDict +except ImportError: + # ordereddict from local import + import os From 45a823ed5f534c608c3e766cea09f5cbaeaa3cfb Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 25 Oct 2017 15:19:53 +0200 Subject: [PATCH 103/288] Simplify API - Merge set Format and setFormatter API. - Add getFormat method to get the string representation of the current format - Remove custom onSetFormatter implementations - Implement custom getFormat/setFormat for TaurusValue and TaurusPlot since they need to access to a specific elements. --- lib/taurus/qt/qtgui/base/taurusbase.py | 53 +++++++++++++----------- lib/taurus/qt/qtgui/panel/taurusform.py | 17 +++++--- lib/taurus/qt/qtgui/panel/taurusvalue.py | 11 +++-- lib/taurus/qt/qtgui/plot/taurusplot.py | 21 ++++------ 4 files changed, 55 insertions(+), 47 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 0231da048..768f67bb3 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -745,12 +745,35 @@ def setFormat(self, format): It also resets the internal format string, which will be recalculated in the next call to :method"`displayValue` - :param format: (str or callable) A format string or a callable - that returns it + :param format: (str or callable) A format string, + or a callable string representation, + or a calable that returns it """ + # Check if the format is a callable string representation + if isinstance(format, basestring): + try: + moduleName, formatterName = format.rsplit('.', 1) + __import__(moduleName) + module = sys.modules[moduleName] + format = getattr(module, formatterName) + except: + format = str(format) self.FORMAT = format self.resetFormat() + def getFormat(self): + """ Method to get the `FORMAT` attribute for this instance. + + :return: (str) a string of the current format. + It could be a python format string or a callable string representation. + """ + if isinstance(self.FORMAT, basestring): + formatter = self.FORMAT + else: + formatter = '{0}.{1}'.format(self.FORMAT.__module__, + self.FORMAT.__name__) + return formatter + def resetFormat(self): """Reset the internal format string. It forces a recalculation in the next call to :method:`displayValue`. @@ -1246,23 +1269,7 @@ def __init__(self, name, parent=None, designMode=False): formatter = getattr(taurus.tauruscustomsettings, 'DEFAULT_FORMATTER', None) if formatter is not None: - format = self._getFormatter(formatter) - self.setFormat(format) - - def _getFormatter(self, formatter): - '''' Method to get custom formatter - :return: formatter: python fromat string or formatter callable - (in string version) - ''' - try: - moduleName, formatterName = formatter.rsplit('.', 1) - __import__(moduleName) - module = sys.modules[moduleName] - formatter = getattr(module, formatterName) - except: - pass - finally: - return formatter + sefl.setFormat(formatter) def showFormatterDlg(self): ''' @@ -1270,18 +1277,14 @@ def showFormatterDlg(self): :return: formatter: python fromat string or formatter callable (in string version) or None ''' - if isinstance(self.FORMAT, str): - current_format = self.FORMAT - else: - current_format = '{0}.{1}'.format(self.FORMAT.__module__, - self.FORMAT.__name__) + current_format = self.getFormat() formatter, ok = Qt.QInputDialog.getText(self, "Set formatter", "Enter a formatter:", Qt.QLineEdit.Normal, current_format) if ok and formatter: - return self._getFormatter(formatter) + return formatter return None diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index f2f794bb9..294377f5f 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -365,11 +365,18 @@ def setWithButtons(self, trueFalse): def resetWithButtons(self): self.setWithButtons(True) - def onSetFormatter(self): - format = self.showFormatterDlg() - if format: - for item in self.getItems(): - item._readWidget.setFormat(format) + def getFormat(self): + """Reimplemented from TaurusBaseComponent""" + # Form delegates format to the taurusvalues + return None + + def setFormat(self, format): + """Reimplemented from TaurusBaseComponent""" + # Form delegates format to the taurusvalues + if format is None: + return + for item in self.getItems(): + item._readWidget.setFormat(format) def setCompact(self, compact): self._compact = compact diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 23faeace1..8fa705360 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -433,10 +433,13 @@ def setParent(self, parent): # do the base class stuff too Qt.QWidget.setParent(self, parent) - def onSetFormatter(self): - format = self.showFormatterDlg() - if format is not None: - self._readWidget.setFormat(format) + def getFormat(self): + """"Reimplemented to delegate to readWidget""" + return self._readWidget.getFormat() + + def setFormat(self, format): + """"Reimplemented to delegate to readWidget""" + self._readWidget.setFormat(format) def getAllowWrite(self): return self._allowWrite diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/plot/taurusplot.py index 031c3c4f3..6f8556ac0 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/plot/taurusplot.py @@ -1268,19 +1268,14 @@ def __initActions(self): # that gets the focus (the canvas instead of self) self.canvas().addAction(action) - def onSetFormatter(self): - ''' Method to be triggered on setFormatter action - ''' - format = self.showFormatterDlg() - if format is not None: - self.debug( - 'Default format has been changed to: {0}'.format(format)) - - targetCurveNames = self.curves.iterkeys() - for name in targetCurveNames: - curve = self.curves.get(name, None) - w = getattr(curve, 'owner', curve) - w.setFormat(format) + def setFormat(self, format): + """Reimplemented from TaurusBaseComponent""" + targetCurveNames = self.curves.iterkeys() + for name in targetCurveNames: + curve = self.curves.get(name, None) + w = getattr(curve, 'owner', curve) + w.setFormat(format) + TaurusBaseComponent.setFormat(self, format) def dropEvent(self, event): '''reimplemented to support dropping of modelnames in taurusplots''' From 12076619145b59571ad2a916f5481fc8cc9b196d Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 26 Oct 2017 08:38:00 +0200 Subject: [PATCH 104/288] Register formatter as a config property Register the formatter as configuration property at TaurusBaseComponent level. And adapt it for taurus plotting widgets. --- lib/taurus/qt/qtgui/base/taurusbase.py | 7 +++++-- lib/taurus/qt/qtgui/plot/taurusplot.py | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 768f67bb3..90e5e3b12 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -145,10 +145,13 @@ def __init__(self, name, parent=None, designMode=False): self._exception_listener = set([TaurusExceptionListener()]) # register configurable properties - self.registerConfigProperty( - self.isModifiableByUser, self.setModifiableByUser, "modifiableByUser") + self.registerConfigProperty(self.isModifiableByUser, + self.setModifiableByUser, + "modifiableByUser") self.registerConfigProperty( self.getModelInConfig, self.setModelInConfig, "ModelInConfig") + self.registerConfigProperty(self.getFormat, self.setFormat, + 'formatter') self.resetModelInConfig() @deprecation_decorator(rel='4.0') diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/plot/taurusplot.py index 6f8556ac0..524f39008 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/plot/taurusplot.py @@ -2319,7 +2319,8 @@ def _createMiscDict(self): miscdict = {'defaultCurvesTitle': self.getDefaultCurvesTitle(), 'canvasBackground': self.canvasBackground(), 'orderedCurveNames': self.getCurveNamesSorted(), - 'plotTitle': unicode(self.title().text())} + 'plotTitle': unicode(self.title().text()), + 'formatter': self.getFormat()} if self.isWindow(): miscdict["Geometry"] = self.saveGeometry() return miscdict @@ -2448,6 +2449,8 @@ def applyMiscConfig(self, miscdict): # set geometry (if this is a top level window) if self.isWindow() and 'Geometry' in miscdict: self.restoreGeometry(miscdict['Geometry']) + if "formatter" in miscdict: + self.setFormat(miscdict['formatter']) def applyAxesConfig(self, axes): '''sets the axes according to settings stored in the axes dict, From 7f3a4d96cbd95aac8562caa59e0e5482e67629fb Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 26 Oct 2017 08:46:34 +0200 Subject: [PATCH 105/288] Add shortcut to setup the tango formatter This is a Tango-centric solution that must be changed when the plugin system is implemented. --- lib/taurus/qt/qtgui/base/taurusbase.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 90e5e3b12..2958ec43e 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -1298,6 +1298,13 @@ def onSetFormatter(self): if format is not None: self.debug( 'Default format has been changed to: {0}'.format(format)) + # ----------------------------------------------------------------- + # TODO: Tango-centric (replace by agnostic entry point solution) + # shortcut to setup the tango formatter + if format == "tangoFormatter": + from taurus.core.tango.util.formatter import tangoFormatter + format = tangoFormatter + # ----------------------------------------------------------------- self.setFormat(format) # It makes the GUI to hang... If this needs implementing, we should From 9bfdfe6fbf1181337293615884a17b8259c81cad Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 26 Oct 2017 09:15:19 +0200 Subject: [PATCH 106/288] Removing unneeded import --- lib/taurus/qt/qtgui/tree/taurusdevicetree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py b/lib/taurus/qt/qtgui/tree/taurusdevicetree.py index 864078e94..9c57f0e92 100644 --- a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py +++ b/lib/taurus/qt/qtgui/tree/taurusdevicetree.py @@ -51,7 +51,6 @@ from taurus.external.qt import Qt -import taurus import taurus.core from taurus.core.util.colors import DEVICE_STATE_PALETTE, ATTRIBUTE_QUALITY_PALETTE from taurus.core.util.containers import CaselessDict From b8ac5554ae2a26f290978c3031427d2815758922 Mon Sep 17 00:00:00 2001 From: Jan Kotanski Date: Thu, 26 Oct 2017 09:21:36 +0200 Subject: [PATCH 107/288] add depreacated warning --- lib/taurus/external/ordereddict/__init__.py | 35 ++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/taurus/external/ordereddict/__init__.py b/lib/taurus/external/ordereddict/__init__.py index 9e4bb2d4a..e14238ff4 100644 --- a/lib/taurus/external/ordereddict/__init__.py +++ b/lib/taurus/external/ordereddict/__init__.py @@ -1,8 +1,29 @@ -from __future__ import absolute_import +# -*- coding: utf-8 -*- -try: - # ordereddict from python 2.7 or from ordereddict installed package? - from collections import OrderedDict -except ImportError: - # ordereddict from local import - import os +############################################################################## +## +## 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 collections import OrderedDict +from taurus.core.util import log as __log + +__log.deprecated(dep='taurus.external.ordereddict', rel='4.0.4') From ddaf8fe8b162bccd5bf91ced850f09a87bf22f05 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 26 Oct 2017 09:23:10 +0200 Subject: [PATCH 108/288] Add TODO The implementation of TaurusDbDeviceModel.setupModelData uses a try-except that is not used in other cases (e.g. TaurusDbPlainDeviceModel). Leave a TODO for checking which is the correct one (or if both are). --- lib/taurus/qt/qtcore/model/taurusdatabasemodel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py index 23bcf46cf..4347da3d3 100644 --- a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py +++ b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py @@ -531,7 +531,7 @@ def setupModelData(self, data): class TaurusDbDeviceModel(TaurusDbBaseModel): - """A Qt model that structures device elements is a 3 level tree organized + """A Qt model that structures device elements in a 3 level tree organized as: - @@ -545,6 +545,7 @@ def setupModelData(self, data): return try: # TODO: Tango-centric + # TODO: is this try needed? (not done in, e.g. TaurusDbPlainDeviceModel) from taurus.core.tango.tangodatabase import TangoDatabase except ImportError: return From 67662b8c31ca12245288f36c7954efebb1548b39 Mon Sep 17 00:00:00 2001 From: Jan Kotanski Date: Thu, 26 Oct 2017 09:25:30 +0200 Subject: [PATCH 109/288] update CHANGELOG --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aea1cced1..7ea6563bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,6 @@ develop branch) won't be reflected in this file. - tauruscurve (#514) ### Removed -- `taurus.external.ordereddict` (#223) - `taurus.qt.qtgui.Q*` modules (Qt, QtCore, QtGui, Qwt5,...) - `taurus.qt.qtgui.util.taurusropepatch` module - `taurusqt.qtgui.util.genwidget` From 9b793040f48b9b18d73af2e16212ce891dd4b1fe Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 26 Oct 2017 10:01:46 +0200 Subject: [PATCH 110/288] Try to avoid timeouts in Travis Travis is lately failing due to timeouts in the `docker run` command. For some reason downloading the docker is suddenly taking much longer than before. Try to work around it by using `travis_wait`. Also, use `-t` parameter for all the `docker exec commands` --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea9a02698..cada3ebe2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,14 +22,14 @@ matrix: - env: DOCKER_IMG=cpascual/taurus-test:debian-buster before_install: - - docker run -d --name=taurus-test -h taurus-test --volume=`pwd`:/taurus $DOCKER_IMG + - travis_wait docker run -d --name=taurus-test -h taurus-test --volume=`pwd`:/taurus $DOCKER_IMG - sleep 10 script: - set -e - - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py install" - - docker exec -t taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" - - docker exec taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" + - docker exec -t taurus-test /bin/bash -c "cd /taurus ; python setup.py install" + - travis_wait docker exec -t taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" + - docker exec -t taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" # deploy sphinx-built docs to taurus-doc repo - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then pip install doctr ; From 6317debcfc1b9024f875fc636a38d3e0e23dc36e Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 26 Oct 2017 11:16:07 +0200 Subject: [PATCH 111/288] revert wrong changelog modification Add a new entry in the current unreleased release instead of removing the entry in a previous release, --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ea6563bc..f8be77b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ develop branch) won't be reflected in this file. ## Unreleased +### Added +- Re-added `taurus.external.ordereddict` (#599) + ### Changed - Tango model name validators now always return FQDN instead of PQDN for the tango host (#488) @@ -55,6 +58,7 @@ develop branch) won't be reflected in this file. - tauruscurve (#514) ### Removed +- `taurus.external.ordereddict` (#223) - `taurus.qt.qtgui.Q*` modules (Qt, QtCore, QtGui, Qwt5,...) - `taurus.qt.qtgui.util.taurusropepatch` module - `taurusqt.qtgui.util.genwidget` From 2d9598744113dafd0d2a4b2ec21dcb905b8944cd Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 26 Oct 2017 11:19:48 +0200 Subject: [PATCH 112/288] Fix the release number in a deprecation warning The deprecation warning for taurus.external states that it was deprecated in 4.0.4, but according to the changelog, it was in 4.0.3. Fix it --- lib/taurus/external/ordereddict/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/external/ordereddict/__init__.py b/lib/taurus/external/ordereddict/__init__.py index e14238ff4..86cadbef4 100644 --- a/lib/taurus/external/ordereddict/__init__.py +++ b/lib/taurus/external/ordereddict/__init__.py @@ -26,4 +26,4 @@ from collections import OrderedDict from taurus.core.util import log as __log -__log.deprecated(dep='taurus.external.ordereddict', rel='4.0.4') +__log.deprecated(dep='taurus.external.ordereddict', rel='4.0.3') From 73c5b5790c68356645e57ecfea5f47183b22abd2 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 26 Oct 2017 11:22:38 +0200 Subject: [PATCH 113/288] Add alt='...' to deprecation warning Suggest the use of collections module for OrderedDict --- lib/taurus/external/ordereddict/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/external/ordereddict/__init__.py b/lib/taurus/external/ordereddict/__init__.py index 86cadbef4..02d40fa12 100644 --- a/lib/taurus/external/ordereddict/__init__.py +++ b/lib/taurus/external/ordereddict/__init__.py @@ -26,4 +26,5 @@ from collections import OrderedDict from taurus.core.util import log as __log -__log.deprecated(dep='taurus.external.ordereddict', rel='4.0.3') +__log.deprecated(dep='taurus.external.ordereddict', rel='4.0.3', + alt='collections.OrderedDict') From 86f74cf596ebc5d9f2e41f91b86a969be84c7cf9 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 26 Oct 2017 12:55:07 +0200 Subject: [PATCH 114/288] (minor) Rephrase of docstring --- lib/taurus/qt/qtgui/base/taurusbase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 2958ec43e..84cfafbc2 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -748,9 +748,9 @@ def setFormat(self, format): It also resets the internal format string, which will be recalculated in the next call to :method"`displayValue` - :param format: (str or callable) A format string, - or a callable string representation, - or a calable that returns it + :param format: (str or callable) A format string + or a formatter callable (or the callable name in + "full.module.callable" format) """ # Check if the format is a callable string representation if isinstance(format, basestring): From c76a18cd783ae0c18741f22cd83ee8f995bab946 Mon Sep 17 00:00:00 2001 From: mrosanes Date: Thu, 26 Oct 2017 14:13:41 +0200 Subject: [PATCH 115/288] Add deprecation warning TaurusMainWindow Change Tango Host action is Tango Centric and not functional. Deprecate it. --- CHANGELOG.md | 1 + lib/taurus/qt/qtgui/container/taurusmainwindow.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb20e36c..84897f098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ develop branch) won't be reflected in this file. ## Unreleased ### Deprecated - taurus.core.tango.search +- taurus.qt.qtgui.container.taurusmainwindow._onChangeTangoHostAction (#379) ### Added - Re-added `taurus.external.ordereddict` (#599) diff --git a/lib/taurus/qt/qtgui/container/taurusmainwindow.py b/lib/taurus/qt/qtgui/container/taurusmainwindow.py index 3e62414aa..ffe04824f 100644 --- a/lib/taurus/qt/qtgui/container/taurusmainwindow.py +++ b/lib/taurus/qt/qtgui/container/taurusmainwindow.py @@ -35,6 +35,7 @@ import sys from taurus import tauruscustomsettings +from taurus.core.util import deprecation_decorator from taurus.external.qt import Qt from taurusbasecontainer import TaurusBaseContainer @@ -877,6 +878,8 @@ def deleteExternalAppLauncher(self, action): self.unregisterConfigurableItem("_extApp[%s]" % str(action.text()), raiseOnError=False) + @deprecation_decorator(dbg_msg="Change Tango Host action is TangoCentric", + rel="4.1.1") def _onChangeTangoHostAction(self): ''' slot called when the Change Tango Host is triggered. It prompts for a From 05c0e8595bd6a621325a3dda0e40c80627a8bea6 Mon Sep 17 00:00:00 2001 From: mrosanes Date: Thu, 26 Oct 2017 15:02:28 +0200 Subject: [PATCH 116/288] Update version when the deprecation appears --- lib/taurus/qt/qtgui/container/taurusmainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/container/taurusmainwindow.py b/lib/taurus/qt/qtgui/container/taurusmainwindow.py index ffe04824f..0eeb676f7 100644 --- a/lib/taurus/qt/qtgui/container/taurusmainwindow.py +++ b/lib/taurus/qt/qtgui/container/taurusmainwindow.py @@ -879,7 +879,7 @@ def deleteExternalAppLauncher(self, action): raiseOnError=False) @deprecation_decorator(dbg_msg="Change Tango Host action is TangoCentric", - rel="4.1.1") + rel="4.1.2") def _onChangeTangoHostAction(self): ''' slot called when the Change Tango Host is triggered. It prompts for a From a9146960fa92a61177d34f75b350d48c0311899a Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 24 Oct 2017 15:39:50 +0200 Subject: [PATCH 117/288] Add TaurusEmitterThread.refreshTimer --- lib/taurus/qt/qtcore/util/emitter.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 0212de1fc..15bbd4cbc 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -142,7 +142,8 @@ def modelSetter(args): def __init__(self, parent = None, designMode = False): ... self.modelsQueue = Queue.Queue() - self.modelsThread = TaurusEmitterThread(parent=self,queue=self.modelsQueue,method=modelSetter ) + self.modelsThread = TaurusEmitterThread(parent=self, + queue=self.modelsQueue,method=modelSetter ) ... def build_widgets(...): ... @@ -160,9 +161,10 @@ def setModel(self,model): """ - def __init__(self, parent=None, name='', queue=None, method=None, cursor=None, sleep=5000): + def __init__(self, parent=None, name='', queue=None, method=None, + cursor=None, sleep=5000, period_ms=0): """ - Parent most not be None and must be a TaurusGraphicsScene! + Parent must be not None and must be a TaurusGraphicsScene! """ Qt.QThread.__init__(self, parent) self.name = name @@ -185,6 +187,12 @@ def __init__(self, parent=None, name='', queue=None, method=None, cursor=None, s # Moved to the end to prevent segfaults ... self.emitter.doSomething.connect(self._doSomething) self.emitter.somethingDone.connect(self.next) + + self.period = period_ms + + def onRefresh(self): + self.refreshTimer.setInterval(self.period) + self.next() def getQueue(self): if self.queue: @@ -260,11 +268,18 @@ def next(self): return def run(self): - Qt.QApplication.instance().thread().sleep(int(self.timewait / 1000) - if self.timewait > 10 else int(self.timewait)) # wait(self.sleep) + Qt.QApplication.instance().thread().sleep( + int(self.timewait / 1000) if self.timewait > 10 else int(self.timewait)) # wait(self.sleep) self.log.info('#' * 80) self.log.info('At TaurusEmitterThread.run()') self.next() + + if self.period: + self.refreshTimer = Qt.QTimer() + Qt.QObject.connect(self.refreshTimer, + Qt.SIGNAL("timeout()"), self.onRefresh) + self.refreshTimer.start(self.period) + while True: self.log.debug('At TaurusEmitterThread.run() loop.') item = self.todo.get(True) From 4945f8f871041161b24a820cbe1f55d67537c9c6 Mon Sep 17 00:00:00 2001 From: zreszela Date: Fri, 27 Oct 2017 12:25:33 +0200 Subject: [PATCH 118/288] Remove labelText property from TaurusValue PR #496 introduced a new assumption that a label widget of a TaurusValue must be a TaurusLabel or its subclass. But custom widgets were also accepted in the past e.g. Sardana PMTV. Change it since this compromises the API too much and remove a way of setting a label text using the TaurusValue. Instead a label text should be set directly on the label widget. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index fc7661820..3c80373cd 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -354,8 +354,6 @@ def __init__(self, parent=None, designMode=False, customWidgetMap=None): self.registerConfigProperty( self.getLabelConfig, self.setLabelConfig, 'labelConfig') - self.registerConfigProperty( - self.getLabelText, self.setLabelText, 'labelText') self.registerConfigProperty(self.isCompact, self.setCompact, 'compact') def setVisible(self, visible): @@ -1229,9 +1227,10 @@ def setLabelConfig(self, config): try: self.getModelFragmentObj(config) except Exception: - msg = "Use setLabelText for setting an arbitrary label text" - self.deprecated(msg) - self.setLabelText(config) + try: + self._labelWidget.setText(config) + except: + self.debug("Setting permanent text to the label widget failed") return self._labelConfig = config self.updateLabelWidget() @@ -1240,13 +1239,6 @@ def resetLabelConfig(self): self._labelConfig = 'label' self.updateLabelWidget() - def getLabelText(self): - return self._labelText - - def setLabelText(self, text): - self._labelText = text - self._labelWidget.setPermanentText(text) - def getSwitcherClass(self): '''Returns the TaurusValue switcher class (used in compact mode). Override this method if you want to use a custom switcher in From e7dfa50c57c04f48c7c1e34264e29cd413be611d Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Fri, 27 Oct 2017 14:13:09 +0200 Subject: [PATCH 119/288] Add DelayedSubsriber to emitter classes: - DelayedSubscriber to retry event subscription once Qt is already running. - In TangoAttribute, replace _activePolling by enablePolling(True) when tango events are not enabled - Add polling timer and a loop wait to TaurusEmitterThread, in order to manage cpu usage. - Reformat __doc__ for emitter classes --- lib/taurus/core/tango/tangoattribute.py | 6 +- lib/taurus/qt/qtcore/util/emitter.py | 207 +++++++++++++++++++----- 2 files changed, 166 insertions(+), 47 deletions(-) mode change 100644 => 100755 lib/taurus/qt/qtcore/util/emitter.py diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index accf73137..7a8ac72a4 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -222,7 +222,7 @@ def __fix_int(self, value): class TangoAttribute(TaurusAttribute): - + no_cfg_value = '-----' no_unit = 'No unit' no_standard_unit = 'No standard unit' @@ -610,8 +610,8 @@ def _subscribeEvents(self): return if not self.factory().is_tango_subscribe_enabled(): - self._activatePolling() - return + self.enablePolling(True) + return try: self.__subscription_state = SubscriptionState.Subscribing diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py old mode 100644 new mode 100755 index 15bbd4cbc..57f3d9faa --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -23,10 +23,20 @@ ########################################################################### """ -emitter.py: This module provides a task scheduler used by TaurusGrid and TaurusDevTree widgets +emitter.py: This module provides a task scheduler used by TaurusGrid and + TaurusDevTree widgets """ -import Queue +try: + import queue + import queue as Queue +except: + #Python 2.6 backwards compatibility + import Queue + import Queue as queue + + + import traceback from functools import partial from collections import Iterable @@ -36,7 +46,7 @@ from taurus.core.util.log import Logger from taurus.core.util.singleton import Singleton - +from taurus.core.taurusbasetypes import SubscriptionState ############################################################################### # Helping methods @@ -90,33 +100,44 @@ class TaurusEmitterThread(Qt.QThread): The TaurusEmitterThread Class ========================== - This object get items from a python Queue and performs a thread safe operation on them. + This object get items from a python Queue and performs a thread safe + operation on them. It is useful to serialize Qt tasks in a background thread. :param parent: a Qt/Taurus object :param name: identifies object logs - :param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used + :param queue: if None parent.getQueue() is used, if not then the queue + passed as argument is used :param method: the method to be executed using each queue item as argument - :param cursor: if True or QCursor a custom cursor is set while the Queue is not empty + :param cursor: if True or QCursor a custom cursor is set while + the Queue is not empty How TaurusEmitterThread works -------------------------- - TaurusEmitterThread is a worker thread that processes a queue of iterables passed as arguments to the specified method every time that ``doSomething()`` is called: + TaurusEmitterThread is a worker thread that processes a queue of iterables + passed as arguments to the specified method every time that + ``doSomething()`` is called: - * ``self.method(*item)`` will be called if TaurusEmitterThread.method has been initialized. - * ``item[0](item[1:])`` will be called if ``method`` is not initialized and the first element of the queued iterable is *callable*. + * ``self.method(*item)`` will be called if TaurusEmitterThread.method + has been initialized. + * ``item[0](item[1:])`` will be called if ``method`` is not initialized + and the first element of the queued iterable is *callable*. TaurusEmitterThread uses two queues: * ``self.queue`` manages the objects added externally: - + the ``next()`` method passes objects from ``self.queue`` to ``self.todo queue`` + + the ``next()`` method passes objects from ``self.queue`` + to ``self.todo queue`` + every time that a *somethingDone* signal arrives ``next()`` is called. - + ``next()`` can be called also externally to ensure that the main queue is being processed. + + ``next()`` can be called also externally to ensure that the main queue + is being processed. + the queue can be accessed externally using ``getQueue()`` + ``getQueue().qsize()`` returns the number of remaining objects in queue. - + while there are objects in queue the ``.next()`` method will override applications cursor. a call to ``next()`` with an empty queue will restore the original cursor. + + while there are objects in queue the ``.next()`` method will + override applications cursor. a call to ``next()`` with an empty queue + will restore the original cursor. * ``self.todo`` is managed by the ``run()/start()`` method: @@ -147,13 +168,15 @@ def __init__(self, parent = None, designMode = False): ... def build_widgets(...): ... - previous,synoptic_value = synoptic_value,TaurusValue(cell_frame) + previous,synoptic_value = \ + synoptic_value,TaurusValue(cell_frame) #synoptic_value.setModel(model) self.modelsQueue.put((synoptic_value,model)) ... def setModel(self,model): ... - if hasattr(self,'modelsThread') and not self.modelsThread.isRunning(): + if hasattr(self,'modelsThread') and \ + not self.modelsThread.isRunning(): self.modelsThread.start() elif self.modelsQueue.qsize(): self.modelsThread.next() @@ -162,7 +185,7 @@ def setModel(self,model): """ def __init__(self, parent=None, name='', queue=None, method=None, - cursor=None, sleep=5000, period_ms=0): + cursor=None, sleep=5000, polling=0, loopwait=5): """ Parent must be not None and must be a TaurusGraphicsScene! """ @@ -177,6 +200,13 @@ def __init__(self, parent=None, name='', queue=None, method=None, Qt.Qt.WaitCursor) if cursor is True else cursor self._cursor = False self.timewait = sleep + self.polling = polling + self.loopwait = int(loopwait*1e-3) + if self.polling: + self.refreshTimer = Qt.QTimer() + self.refreshTimer.timeout.connect(self.onRefresh) + else: + self.refreshTimer = None self.emitter = QEmitter() self.emitter.moveToThread(Qt.QApplication.instance().thread()) @@ -186,13 +216,22 @@ def __init__(self, parent=None, name='', queue=None, method=None, self._done = 0 # Moved to the end to prevent segfaults ... self.emitter.doSomething.connect(self._doSomething) - self.emitter.somethingDone.connect(self.next) - self.period = period_ms + if not self.refreshTimer: + self.emitter.somethingDone.connect(self.next) + def onRefresh(self): - self.refreshTimer.setInterval(self.period) - self.next() + try: + self.refreshTimer.setInterval(self.polling) + size = self.getQueue().qsize() + if size: + self.log.info('onRefresh(%s)'%size) + self.next() + else: + self.log.debug('onRefresh()') + except: + self.log.warning(traceback.format_exc()) def getQueue(self): if self.queue: @@ -204,7 +243,8 @@ def getQueue(self): def getDone(self): """ Returns % of done tasks in 0-1 range """ - return self._done / (self._done + self.getQueue().qsize()) if self._done else 0. + return self._done / (self._done + self.getQueue().qsize()) \ + if self._done else 0. def clear(self): while not self.todo.empty(): @@ -237,20 +277,23 @@ def _doSomething(self, params): try: method(*args) except: - self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' % ( - map(str, args), traceback.format_exc())) + self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' + % (map(str, args), traceback.format_exc())) self.emitter.somethingDone.emit() self._done += 1 return def next(self): queue = self.getQueue() - msg = 'At TaurusEmitterThread.next(), %d items remaining.' % queue.qsize() - (queue.empty() and self.log.info or self.log.debug)(msg) + msg = 'At TaurusEmitterThread.next(), %d items remaining.' \ + % queue.qsize() + (queue.empty() and not self.polling and self.log.info + or self.log.debug)(msg) try: if not queue.empty(): if not self._cursor and self.cursor is not None: - Qt.QApplication.instance().setOverrideCursor(Qt.QCursor(self.cursor)) + Qt.QApplication.instance().setOverrideCursor( + Qt.QCursor(self.cursor)) self._cursor = True # A blocking get here would hang the GUIs!!! item = queue.get(False) @@ -269,16 +312,14 @@ def next(self): def run(self): Qt.QApplication.instance().thread().sleep( - int(self.timewait / 1000) if self.timewait > 10 else int(self.timewait)) # wait(self.sleep) + int(self.timewait / 1000) if self.timewait > 10 + else int(self.timewait)) self.log.info('#' * 80) self.log.info('At TaurusEmitterThread.run()') self.next() - if self.period: - self.refreshTimer = Qt.QTimer() - Qt.QObject.connect(self.refreshTimer, - Qt.SIGNAL("timeout()"), self.onRefresh) - self.refreshTimer.start(self.period) + if self.refreshTimer: + self.refreshTimer.start(self.polling) while True: self.log.debug('At TaurusEmitterThread.run() loop.') @@ -290,33 +331,108 @@ def run(self): continue self.log.debug('Emitting doSomething signal ...') self.emitter.doSomething.emit(item) + if self.loopwait: + self.sleep(self.loopwait) # End of while self.log.info( '#' * 80 + '\nOut of TaurusEmitterThread.run()' + '\n' + '#' * 80) # End of Thread - - -class SingletonWorker(): # Qt.QObject): + + +class DelayedSubscriber(Logger): + """ + DelayedSubscriber(schema) will use a TaurusEmitterThread to perform + a thread safe delayed subscribing on all Attributes of a given + Taurus Schema that has not been previously subscribed. + """ + + def __init__(self,schema,parent=None,sleep=10000,pause=5): + + self._schema = schema + self.call__init__(Logger, 'DelayedSubscriber(%s)'%self._schema, None) + self._factory = taurus.Factory(schema) + + self._modelsQueue = queue.Queue() + self._modelsThread = TaurusEmitterThread(parent=parent, + queue=self._modelsQueue, + method=self.modelSubscriber, + sleep=sleep,loopwait=pause) + + self._modelsQueue.put((self.addUnsubscribedAttributes,)) + self._modelsThread.start() + + def modelSubscriber(self,method,args=[]): + self.debug('modelSubscriber(%s,%s)'%(method,args)) + return method(*args) + + def getUnsubscribedAttributes(self): + attrs = [] + items = self._factory.getExistingAttributes().items() + for name,attr in items: + if attr is None: + continue + elif attr.hasListeners() and not attr.isUsingEvents(): + attrs.append(attr) + + return attrs + + def addUnsubscribedAttributes(self): + try: + items = self.getUnsubscribedAttributes() + if len(items): + self.info('addUnsubscribedAttributes([%d])'%len(items)) + for attr in items: + self.addModelObj(attr) + self._modelsThread.next() + self.info('Thread queue: [%d]'%(self._modelsQueue.qsize())) + except: + self.warning(traceback.format_exc()) + + def addModelObj(self,modelObj): + parent = modelObj.getParentObj() + if parent: + proxy = parent.getDeviceProxy() + if not proxy: + #self.debug('addModelObj(%s), proxy not available'%modelObj) + return + self._modelsQueue.put((modelObj._subscribeConfEvents,)) + modelObj.__subscription_state = SubscriptionState.PendingSubscribe + #modelObj._activatePolling() + self.debug('addModelObj(%s)'%str(modelObj)) + self._modelsQueue.put((modelObj._call_dev_hw_subscribe_event,(True,))) + + def cleanUp(self): + self.trace("[DelayedSubscriber] cleanUp") + self._modelsThread.stop() + Logger.cleanUp(self) + +class SingletonWorker(): """ - The SingletonWorker works - ========================= + SingletonWorker is used to manage TaurusEmitterThread as Singleton objects - The SingletonWorker class is constructed using the same arguments than the TaurusTreadEmitter class ; but instead of creating a QThread for each instance of the class it creates a single QThread for all instances. + SingletonWorker is constructed using the same arguments + than TaurusTreadEmitter ; but instead of creating a QThread for each + instance it creates a single QThread for all instances. - The Queue is still different for each of the instances; it is connected to the TaurusEmitterThread signals (*next()* and *somethingDone()*) and each Worker queue is used as a feed for the shared QThread. + The Queue is still different for each of the instances; it is connected + to the TaurusEmitterThread signals (*next()* and *somethingDone()*) + and each Worker queue is used as a feed for the shared QThread. This implementation reduced the cpu of vacca application in a 50% factor. :param parent: a Qt/Taurus object :param name: identifies object logs - :param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used + :param queue: if None parent.getQueue() is used, if not then the queue + passed as argument is used :param method: the method to be executed using each queue item as argument - :param cursor: if True or QCursor a custom cursor is set while the Queue is not empty - This class is used to manage TaurusEmitterThread as Singleton objects: + :param cursor: if True or QCursor a custom cursor is set while + the Queue is not empty + """ _thread = None - def __init__(self, parent=None, name='', queue=None, method=None, cursor=None, sleep=5000, log=Logger.Warning, start=True): + def __init__(self, parent=None, name='', queue=None, method=None, + cursor=None, sleep=5000, log=Logger.Warning, start=True): self.name = name self.log = Logger('SingletonWorker(%s)' % self.name) self.log.setLogLevel(log) @@ -343,7 +459,9 @@ def next(self, item=None): self.put(item) elif self.queue.empty(): return - msg = 'At SingletonWorker.next(), %d items not passed yet to Emitter.' % self.queue.qsize() + msg = 'At SingletonWorker.next(), '\ + '%d items not passed yet to Emitter.' \ + % self.queue.qsize() self.log.info(msg) #(queue.empty() and self.log.info or self.log.debug)(msg) try: @@ -390,7 +508,8 @@ def stop(self): def clear(self): """ This method will clear queue only if next() has not been called. - If you call self.thread.clear() it will clear objects for all workers!, be careful + If you call self.thread.clear() it will clear objects for all workers!, + be careful """ while not self.queue.empty(): self.queue.get() From 83f781f265f902639d373b25cbdb4f549bc8d54e Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 27 Oct 2017 15:35:31 +0200 Subject: [PATCH 120/288] Initialize the default formatter according to tauruscustomsettings Use the DEFAULT_FORMATTER variable from tauruscustomsettings when initializing the FORMAT class member of TaurusBaseComponent. This makes initialization in the init of TaurusBaseWidget redundant. --- lib/taurus/qt/qtgui/base/taurusbase.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 84cfafbc2..6dfc20599 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -95,7 +95,8 @@ class TaurusBaseComponent(TaurusListener, BaseConfigurableClass): _eventBufferPeriod = 0 # Python format string or Formatter callable - FORMAT = defaultFormatter + FORMAT = getattr(taurus.tauruscustomsettings, 'DEFAULT_FORMATTER', + defaultFormatter) # Dictionary mapping dtypes to format strings defaultFormatDict = {float: "{:.{bc.modelObj.precision}f}", @@ -746,7 +747,7 @@ def _updateFormat(self, dtype, **kwargs): def setFormat(self, format): """ Method to set the `FORMAT` attribute for this instance. It also resets the internal format string, which will be recalculated - in the next call to :method"`displayValue` + in the next call to :method:`displayValue` :param format: (str or callable) A format string or a formatter callable (or the callable name in @@ -1269,17 +1270,13 @@ def __init__(self, name, parent=None, designMode=False): self.call__init__(TaurusBaseComponent, name, parent=parent, designMode=designMode) self._setText = self._findSetTextMethod() - formatter = getattr(taurus.tauruscustomsettings, - 'DEFAULT_FORMATTER', None) - if formatter is not None: - sefl.setFormat(formatter) def showFormatterDlg(self): - ''' + """ showFormatterDlg show a dialog to get the formatter from the user. :return: formatter: python fromat string or formatter callable (in string version) or None - ''' + """ current_format = self.getFormat() formatter, ok = Qt.QInputDialog.getText(self, "Set formatter", @@ -1292,8 +1289,7 @@ def showFormatterDlg(self): return None def onSetFormatter(self): - ''' Method to be triggered on setFormatter action - ''' + """ Slot to be called by setFormatter action""" format = self.showFormatterDlg() if format is not None: self.debug( @@ -1301,7 +1297,7 @@ def onSetFormatter(self): # ----------------------------------------------------------------- # TODO: Tango-centric (replace by agnostic entry point solution) # shortcut to setup the tango formatter - if format == "tangoFormatter": + if format.strip() == "tangoFormatter": from taurus.core.tango.util.formatter import tangoFormatter format = tangoFormatter # ----------------------------------------------------------------- From 652cab98d6706b3fd30a9279f8ca0f4eb864ddc2 Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 27 Oct 2017 15:36:10 +0200 Subject: [PATCH 121/288] (minor) PEP8 and doc fixes --- lib/taurus/core/util/argparse/taurusargparse.py | 2 +- lib/taurus/qt/qtgui/panel/taurusform.py | 3 ++- lib/taurus/qt/qtgui/plot/taurusplot.py | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/taurus/core/util/argparse/taurusargparse.py b/lib/taurus/core/util/argparse/taurusargparse.py index ea985fd67..14f129c7d 100644 --- a/lib/taurus/core/util/argparse/taurusargparse.py +++ b/lib/taurus/core/util/argparse/taurusargparse.py @@ -108,7 +108,7 @@ def get_taurus_parser(parser=None): help_taurusserial = "taurus serialization mode. Allowed values are (case insensitive): "\ "serial, concurrent (default)" help_rcport = "enables remote debugging using the given port" - help_formatter = "Override the default formatter in runtime" + help_formatter = "Override the default formatter" group.add_option("--taurus-log-level", dest="taurus_log_level", metavar="LEVEL", help=help_tauruslog, type="str", default="info") group.add_option("--taurus-polling-period", dest="taurus_polling_period", metavar="MILLISEC", diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index ff2fe1360..dd73a0cde 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -145,6 +145,7 @@ def __init__(self, parent=None, TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, 'text/plain']) self.resetCompact() + # properties self.registerConfigProperty( self.isWithButtons, self.setWithButtons, 'withButtons') @@ -368,7 +369,7 @@ def resetWithButtons(self): def getFormat(self): """Reimplemented from TaurusBaseComponent""" - # Form delegates format to the taurusvalues + # Always return None (format delegated to the taurusvalues) return None def setFormat(self, format): diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/plot/taurusplot.py index 524f39008..95d1e48d9 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/plot/taurusplot.py @@ -1177,8 +1177,7 @@ def __initActions(self): self._dataInspectorAction.setChecked(self._pointPicker.isEnabled()) self._dataInspectorAction.toggled[bool].connect(self.toggleDataInspectorMode) - self._setFormatterAction = Qt.QAction( - "Set Formatter...", None) + self._setFormatterAction = Qt.QAction("Set Formatter...", None) self._setFormatterAction.triggered[()].connect(self.onSetFormatter) self._curveStatsAction = Qt.QAction("Calculate statistics", None) From bf49681808bd0722cdfd7f05043e858fe37351a5 Mon Sep 17 00:00:00 2001 From: cpascual Date: Mon, 30 Oct 2017 11:09:33 +0100 Subject: [PATCH 122/288] Fix issues with global setting of default format Using --default-format is not working properly due to uninitialized stuff when setFormat is called. Also, if used, the customization of FORMAT at a class level is lost. Fix this by ensuring the required variables are initialized before calling setFormat in the initialization of TaurusBaseComponent, and doing it *only* if FORMAT is not set at class level. Also, change TaurusValue to delegate onSetFormatter to the read widget, instead of delegating getFormat and setFormat --- lib/taurus/qt/qtgui/base/taurusbase.py | 11 +++++++++-- lib/taurus/qt/qtgui/panel/taurusform.py | 4 +++- lib/taurus/qt/qtgui/panel/taurusvalue.py | 13 ++++++------- lib/taurus/qt/qtgui/plot/taurusplot.py | 6 ++++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 6dfc20599..1e2cdc99b 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -95,8 +95,8 @@ class TaurusBaseComponent(TaurusListener, BaseConfigurableClass): _eventBufferPeriod = 0 # Python format string or Formatter callable - FORMAT = getattr(taurus.tauruscustomsettings, 'DEFAULT_FORMATTER', - defaultFormatter) + # (None means that the default formatter will be used) + FORMAT = None # Dictionary mapping dtypes to format strings defaultFormatDict = {float: "{:.{bc.modelObj.precision}f}", @@ -145,6 +145,12 @@ def __init__(self, name, parent=None, designMode=False): else: self._exception_listener = set([TaurusExceptionListener()]) + # Use default formatter if none has been set by the class + if self.FORMAT is None: + self.setFormat(getattr(taurus.tauruscustomsettings, + 'DEFAULT_FORMATTER', + defaultFormatter)) + # register configurable properties self.registerConfigProperty(self.isModifiableByUser, self.setModifiableByUser, @@ -1302,6 +1308,7 @@ def onSetFormatter(self): format = tangoFormatter # ----------------------------------------------------------------- self.setFormat(format) + return format # It makes the GUI to hang... If this needs implementing, we should # reimplement it using the Qt parent class, not QWidget... diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index dd73a0cde..d303a2083 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -88,6 +88,8 @@ def __init__(self, parent=None, buttons=None, withButtons=True, designMode=False): + + self._children = [] TaurusWidget.__init__(self, parent, designMode) if buttons is None: @@ -95,7 +97,7 @@ def __init__(self, parent=None, Qt.QDialogButtonBox.Reset self._customWidgetMap = {} self._model = [] - self._children = [] + # self._children = [] self.setFormWidget(formWidget) self.setLayout(Qt.QVBoxLayout()) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 08ba2590f..5dc74d8d0 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -435,13 +435,12 @@ def setParent(self, parent): # do the base class stuff too Qt.QWidget.setParent(self, parent) - def getFormat(self): - """"Reimplemented to delegate to readWidget""" - return self._readWidget.getFormat() - - def setFormat(self, format): - """"Reimplemented to delegate to readWidget""" - self._readWidget.setFormat(format) + def onSetFormatter(self): + """ + Reimplemented to call onSetFormatter of the read widget (if provided) + """ + if hasattr(self._readWidget, 'onSetFormatter'): + return self._readWidget.onSetFormatter() def getAllowWrite(self): return self._allowWrite diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/plot/taurusplot.py index 95d1e48d9..984cd9808 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/plot/taurusplot.py @@ -1031,8 +1031,12 @@ class TaurusPlot(Qwt5.QwtPlot, TaurusBaseWidget): def __init__(self, parent=None, designMode=False): name = "TaurusPlot" + # book-keeping of attached tauruscurves + self.curves = CaselessDict() # TODO: Tango-centric + Qwt5.QwtPlot.__init__(self, parent) TaurusBaseWidget.__init__(self, name) + self._designMode = designMode self._modelNames = [] self._useParentModel = False @@ -1066,8 +1070,6 @@ def __init__(self, parent=None, designMode=False): # enable dropping (see also dragEnterEvent and dropEvent methods) self.setAcceptDrops(True) - # book-keeping of attached tauruscurves - self.curves = CaselessDict() # TODO: Tango-centric #self.curves_lock = threading.RLock() self.curves_lock = DummyLock() From 89a3b22397293c9669df5e09e14ef5583b8a0ebb Mon Sep 17 00:00:00 2001 From: cpascual Date: Mon, 30 Oct 2017 11:10:57 +0100 Subject: [PATCH 123/288] (minor) Fix wrong spelling in debug messages --- lib/taurus/qt/qtgui/plot/taurustrend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/plot/taurustrend.py b/lib/taurus/qt/qtgui/plot/taurustrend.py index 7db1e0a2d..f9e07976b 100644 --- a/lib/taurus/qt/qtgui/plot/taurustrend.py +++ b/lib/taurus/qt/qtgui/plot/taurustrend.py @@ -491,14 +491,14 @@ def _onDroppedEvent(self, reason='Unknown'): mustwarn = False if self.droppedEventsCount == self.droppedEventsWarning: mustwarn = True - msg = ('At least %i events from model "%s" have being dropped. This attribute may have problems\n' + + msg = ('At least %i events from model "%s" have been dropped. This attribute may have problems\n' + 'Future occurrences will be silently ignored') % (self.droppedEventsWarning, self.modelName) # disable the consecutive Dropped events warning (we do not want it # if we got this one) self.consecutiveDroppedEventsWarning = -1 if self.consecutiveDroppedEventsCount == self.consecutiveDroppedEventsWarning: mustwarn = True - msg = ('At least %i consecutive events from model "%s" have being dropped. This attribute may have problems\n' + + msg = ('At least %i consecutive events from model "%s" have been dropped. This attribute may have problems\n' + 'Future occurrences will be silently ignored') % (self.consecutiveDroppedEventsWarning, self.modelName) # disable the consecutive Dropped events warning self.consecutiveDroppedEventsWarning = -1 From e4e4e48a2d3ef8c04778b7c071439a3a6ae13959 Mon Sep 17 00:00:00 2001 From: cpascual Date: Mon, 30 Oct 2017 11:52:05 +0100 Subject: [PATCH 124/288] Delegate onSetFormatter in TaurusForm Reimplement TaurusForm.onSetFormatter instead of getFormat and setFormat for coherence with what was done in TaurusValue --- lib/taurus/qt/qtgui/panel/taurusform.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index d303a2083..02b86fea5 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -369,18 +369,16 @@ def setWithButtons(self, trueFalse): def resetWithButtons(self): self.setWithButtons(True) - def getFormat(self): - """Reimplemented from TaurusBaseComponent""" - # Always return None (format delegated to the taurusvalues) - return None - - def setFormat(self, format): - """Reimplemented from TaurusBaseComponent""" - # Form delegates format to the taurusvalues - if format is None: - return - for item in self.getItems(): - item._readWidget.setFormat(format) + def onSetFormatter(self): + """Reimplemented from TaurusBaseWidget""" + # Form delegates se to the taurusvalues + format = TaurusWidget.onSetFormatter(self) + if format is not None: + for item in self.getItems(): + rw = item.readWidget() + if hasattr(rw, 'setFormat'): + rw.setFormat(format) + return format def setCompact(self, compact): self._compact = compact From 72974a40f5fb8dfa8a6f00ada36dbfd52d5d6408 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 30 Oct 2017 12:49:41 +0100 Subject: [PATCH 125/288] Rewrite deprecation notice in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84897f098..688b135b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ develop branch) won't be reflected in this file. ## Unreleased ### Deprecated - taurus.core.tango.search -- taurus.qt.qtgui.container.taurusmainwindow._onChangeTangoHostAction (#379) +- TaurusMainWindow's "Change Tango Host" action (#379) ### Added - Re-added `taurus.external.ordereddict` (#599) From c847e7105de03ae4078cd2c6aca96322672d710d Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 15:19:38 +0100 Subject: [PATCH 126/288] revert commit e7dfa50c --- lib/taurus/core/tango/tangoattribute.py | 6 +- lib/taurus/qt/qtcore/util/emitter.py | 207 +++++------------------- 2 files changed, 47 insertions(+), 166 deletions(-) mode change 100755 => 100644 lib/taurus/qt/qtcore/util/emitter.py diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 7a8ac72a4..accf73137 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -222,7 +222,7 @@ def __fix_int(self, value): class TangoAttribute(TaurusAttribute): - + no_cfg_value = '-----' no_unit = 'No unit' no_standard_unit = 'No standard unit' @@ -610,8 +610,8 @@ def _subscribeEvents(self): return if not self.factory().is_tango_subscribe_enabled(): - self.enablePolling(True) - return + self._activatePolling() + return try: self.__subscription_state = SubscriptionState.Subscribing diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py old mode 100755 new mode 100644 index 57f3d9faa..15bbd4cbc --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -23,20 +23,10 @@ ########################################################################### """ -emitter.py: This module provides a task scheduler used by TaurusGrid and - TaurusDevTree widgets +emitter.py: This module provides a task scheduler used by TaurusGrid and TaurusDevTree widgets """ -try: - import queue - import queue as Queue -except: - #Python 2.6 backwards compatibility - import Queue - import Queue as queue - - - +import Queue import traceback from functools import partial from collections import Iterable @@ -46,7 +36,7 @@ from taurus.core.util.log import Logger from taurus.core.util.singleton import Singleton -from taurus.core.taurusbasetypes import SubscriptionState + ############################################################################### # Helping methods @@ -100,44 +90,33 @@ class TaurusEmitterThread(Qt.QThread): The TaurusEmitterThread Class ========================== - This object get items from a python Queue and performs a thread safe - operation on them. + This object get items from a python Queue and performs a thread safe operation on them. It is useful to serialize Qt tasks in a background thread. :param parent: a Qt/Taurus object :param name: identifies object logs - :param queue: if None parent.getQueue() is used, if not then the queue - passed as argument is used + :param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used :param method: the method to be executed using each queue item as argument - :param cursor: if True or QCursor a custom cursor is set while - the Queue is not empty + :param cursor: if True or QCursor a custom cursor is set while the Queue is not empty How TaurusEmitterThread works -------------------------- - TaurusEmitterThread is a worker thread that processes a queue of iterables - passed as arguments to the specified method every time that - ``doSomething()`` is called: + TaurusEmitterThread is a worker thread that processes a queue of iterables passed as arguments to the specified method every time that ``doSomething()`` is called: - * ``self.method(*item)`` will be called if TaurusEmitterThread.method - has been initialized. - * ``item[0](item[1:])`` will be called if ``method`` is not initialized - and the first element of the queued iterable is *callable*. + * ``self.method(*item)`` will be called if TaurusEmitterThread.method has been initialized. + * ``item[0](item[1:])`` will be called if ``method`` is not initialized and the first element of the queued iterable is *callable*. TaurusEmitterThread uses two queues: * ``self.queue`` manages the objects added externally: - + the ``next()`` method passes objects from ``self.queue`` - to ``self.todo queue`` + + the ``next()`` method passes objects from ``self.queue`` to ``self.todo queue`` + every time that a *somethingDone* signal arrives ``next()`` is called. - + ``next()`` can be called also externally to ensure that the main queue - is being processed. + + ``next()`` can be called also externally to ensure that the main queue is being processed. + the queue can be accessed externally using ``getQueue()`` + ``getQueue().qsize()`` returns the number of remaining objects in queue. - + while there are objects in queue the ``.next()`` method will - override applications cursor. a call to ``next()`` with an empty queue - will restore the original cursor. + + while there are objects in queue the ``.next()`` method will override applications cursor. a call to ``next()`` with an empty queue will restore the original cursor. * ``self.todo`` is managed by the ``run()/start()`` method: @@ -168,15 +147,13 @@ def __init__(self, parent = None, designMode = False): ... def build_widgets(...): ... - previous,synoptic_value = \ - synoptic_value,TaurusValue(cell_frame) + previous,synoptic_value = synoptic_value,TaurusValue(cell_frame) #synoptic_value.setModel(model) self.modelsQueue.put((synoptic_value,model)) ... def setModel(self,model): ... - if hasattr(self,'modelsThread') and \ - not self.modelsThread.isRunning(): + if hasattr(self,'modelsThread') and not self.modelsThread.isRunning(): self.modelsThread.start() elif self.modelsQueue.qsize(): self.modelsThread.next() @@ -185,7 +162,7 @@ def setModel(self,model): """ def __init__(self, parent=None, name='', queue=None, method=None, - cursor=None, sleep=5000, polling=0, loopwait=5): + cursor=None, sleep=5000, period_ms=0): """ Parent must be not None and must be a TaurusGraphicsScene! """ @@ -200,13 +177,6 @@ def __init__(self, parent=None, name='', queue=None, method=None, Qt.Qt.WaitCursor) if cursor is True else cursor self._cursor = False self.timewait = sleep - self.polling = polling - self.loopwait = int(loopwait*1e-3) - if self.polling: - self.refreshTimer = Qt.QTimer() - self.refreshTimer.timeout.connect(self.onRefresh) - else: - self.refreshTimer = None self.emitter = QEmitter() self.emitter.moveToThread(Qt.QApplication.instance().thread()) @@ -216,22 +186,13 @@ def __init__(self, parent=None, name='', queue=None, method=None, self._done = 0 # Moved to the end to prevent segfaults ... self.emitter.doSomething.connect(self._doSomething) + self.emitter.somethingDone.connect(self.next) - if not self.refreshTimer: - self.emitter.somethingDone.connect(self.next) - + self.period = period_ms def onRefresh(self): - try: - self.refreshTimer.setInterval(self.polling) - size = self.getQueue().qsize() - if size: - self.log.info('onRefresh(%s)'%size) - self.next() - else: - self.log.debug('onRefresh()') - except: - self.log.warning(traceback.format_exc()) + self.refreshTimer.setInterval(self.period) + self.next() def getQueue(self): if self.queue: @@ -243,8 +204,7 @@ def getQueue(self): def getDone(self): """ Returns % of done tasks in 0-1 range """ - return self._done / (self._done + self.getQueue().qsize()) \ - if self._done else 0. + return self._done / (self._done + self.getQueue().qsize()) if self._done else 0. def clear(self): while not self.todo.empty(): @@ -277,23 +237,20 @@ def _doSomething(self, params): try: method(*args) except: - self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' - % (map(str, args), traceback.format_exc())) + self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' % ( + map(str, args), traceback.format_exc())) self.emitter.somethingDone.emit() self._done += 1 return def next(self): queue = self.getQueue() - msg = 'At TaurusEmitterThread.next(), %d items remaining.' \ - % queue.qsize() - (queue.empty() and not self.polling and self.log.info - or self.log.debug)(msg) + msg = 'At TaurusEmitterThread.next(), %d items remaining.' % queue.qsize() + (queue.empty() and self.log.info or self.log.debug)(msg) try: if not queue.empty(): if not self._cursor and self.cursor is not None: - Qt.QApplication.instance().setOverrideCursor( - Qt.QCursor(self.cursor)) + Qt.QApplication.instance().setOverrideCursor(Qt.QCursor(self.cursor)) self._cursor = True # A blocking get here would hang the GUIs!!! item = queue.get(False) @@ -312,14 +269,16 @@ def next(self): def run(self): Qt.QApplication.instance().thread().sleep( - int(self.timewait / 1000) if self.timewait > 10 - else int(self.timewait)) + int(self.timewait / 1000) if self.timewait > 10 else int(self.timewait)) # wait(self.sleep) self.log.info('#' * 80) self.log.info('At TaurusEmitterThread.run()') self.next() - if self.refreshTimer: - self.refreshTimer.start(self.polling) + if self.period: + self.refreshTimer = Qt.QTimer() + Qt.QObject.connect(self.refreshTimer, + Qt.SIGNAL("timeout()"), self.onRefresh) + self.refreshTimer.start(self.period) while True: self.log.debug('At TaurusEmitterThread.run() loop.') @@ -331,108 +290,33 @@ def run(self): continue self.log.debug('Emitting doSomething signal ...') self.emitter.doSomething.emit(item) - if self.loopwait: - self.sleep(self.loopwait) # End of while self.log.info( '#' * 80 + '\nOut of TaurusEmitterThread.run()' + '\n' + '#' * 80) # End of Thread - - -class DelayedSubscriber(Logger): - """ - DelayedSubscriber(schema) will use a TaurusEmitterThread to perform - a thread safe delayed subscribing on all Attributes of a given - Taurus Schema that has not been previously subscribed. - """ - - def __init__(self,schema,parent=None,sleep=10000,pause=5): - - self._schema = schema - self.call__init__(Logger, 'DelayedSubscriber(%s)'%self._schema, None) - self._factory = taurus.Factory(schema) - - self._modelsQueue = queue.Queue() - self._modelsThread = TaurusEmitterThread(parent=parent, - queue=self._modelsQueue, - method=self.modelSubscriber, - sleep=sleep,loopwait=pause) - - self._modelsQueue.put((self.addUnsubscribedAttributes,)) - self._modelsThread.start() - - def modelSubscriber(self,method,args=[]): - self.debug('modelSubscriber(%s,%s)'%(method,args)) - return method(*args) - - def getUnsubscribedAttributes(self): - attrs = [] - items = self._factory.getExistingAttributes().items() - for name,attr in items: - if attr is None: - continue - elif attr.hasListeners() and not attr.isUsingEvents(): - attrs.append(attr) - - return attrs - - def addUnsubscribedAttributes(self): - try: - items = self.getUnsubscribedAttributes() - if len(items): - self.info('addUnsubscribedAttributes([%d])'%len(items)) - for attr in items: - self.addModelObj(attr) - self._modelsThread.next() - self.info('Thread queue: [%d]'%(self._modelsQueue.qsize())) - except: - self.warning(traceback.format_exc()) - - def addModelObj(self,modelObj): - parent = modelObj.getParentObj() - if parent: - proxy = parent.getDeviceProxy() - if not proxy: - #self.debug('addModelObj(%s), proxy not available'%modelObj) - return - self._modelsQueue.put((modelObj._subscribeConfEvents,)) - modelObj.__subscription_state = SubscriptionState.PendingSubscribe - #modelObj._activatePolling() - self.debug('addModelObj(%s)'%str(modelObj)) - self._modelsQueue.put((modelObj._call_dev_hw_subscribe_event,(True,))) - - def cleanUp(self): - self.trace("[DelayedSubscriber] cleanUp") - self._modelsThread.stop() - Logger.cleanUp(self) - -class SingletonWorker(): + + +class SingletonWorker(): # Qt.QObject): """ - SingletonWorker is used to manage TaurusEmitterThread as Singleton objects + The SingletonWorker works + ========================= - SingletonWorker is constructed using the same arguments - than TaurusTreadEmitter ; but instead of creating a QThread for each - instance it creates a single QThread for all instances. + The SingletonWorker class is constructed using the same arguments than the TaurusTreadEmitter class ; but instead of creating a QThread for each instance of the class it creates a single QThread for all instances. - The Queue is still different for each of the instances; it is connected - to the TaurusEmitterThread signals (*next()* and *somethingDone()*) - and each Worker queue is used as a feed for the shared QThread. + The Queue is still different for each of the instances; it is connected to the TaurusEmitterThread signals (*next()* and *somethingDone()*) and each Worker queue is used as a feed for the shared QThread. This implementation reduced the cpu of vacca application in a 50% factor. :param parent: a Qt/Taurus object :param name: identifies object logs - :param queue: if None parent.getQueue() is used, if not then the queue - passed as argument is used + :param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used :param method: the method to be executed using each queue item as argument - :param cursor: if True or QCursor a custom cursor is set while - the Queue is not empty - + :param cursor: if True or QCursor a custom cursor is set while the Queue is not empty + This class is used to manage TaurusEmitterThread as Singleton objects: """ _thread = None - def __init__(self, parent=None, name='', queue=None, method=None, - cursor=None, sleep=5000, log=Logger.Warning, start=True): + def __init__(self, parent=None, name='', queue=None, method=None, cursor=None, sleep=5000, log=Logger.Warning, start=True): self.name = name self.log = Logger('SingletonWorker(%s)' % self.name) self.log.setLogLevel(log) @@ -459,9 +343,7 @@ def next(self, item=None): self.put(item) elif self.queue.empty(): return - msg = 'At SingletonWorker.next(), '\ - '%d items not passed yet to Emitter.' \ - % self.queue.qsize() + msg = 'At SingletonWorker.next(), %d items not passed yet to Emitter.' % self.queue.qsize() self.log.info(msg) #(queue.empty() and self.log.info or self.log.debug)(msg) try: @@ -508,8 +390,7 @@ def stop(self): def clear(self): """ This method will clear queue only if next() has not been called. - If you call self.thread.clear() it will clear objects for all workers!, - be careful + If you call self.thread.clear() it will clear objects for all workers!, be careful """ while not self.queue.empty(): self.queue.get() From ce73ad786e79f1952691e878ca4028f7e5dc0202 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 15:21:16 +0100 Subject: [PATCH 127/288] Use enablePolling(True) if tango events are not Enabled --- lib/taurus/core/tango/tangoattribute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index accf73137..62f226021 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -610,8 +610,8 @@ def _subscribeEvents(self): return if not self.factory().is_tango_subscribe_enabled(): - self._activatePolling() - return + self.enablePolling(True) + return try: self.__subscription_state = SubscriptionState.Subscribing From 49298d5135a00a9b3d37515774a3548c05e8374d Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 15:31:38 +0100 Subject: [PATCH 128/288] PEP8 --- lib/taurus/qt/qtcore/util/emitter.py | 90 ++++++++++++++++++---------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 15bbd4cbc..9f7a95d07 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -23,7 +23,8 @@ ########################################################################### """ -emitter.py: This module provides a task scheduler used by TaurusGrid and TaurusDevTree widgets +emitter.py: This module provides a task scheduler used by TaurusGrid and + TaurusDevTree widgets """ import Queue @@ -90,33 +91,44 @@ class TaurusEmitterThread(Qt.QThread): The TaurusEmitterThread Class ========================== - This object get items from a python Queue and performs a thread safe operation on them. + This object get items from a python Queue and performs a thread safe + operation on them. It is useful to serialize Qt tasks in a background thread. :param parent: a Qt/Taurus object :param name: identifies object logs - :param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used + :param queue: if None parent.getQueue() is used, if not then the queue + passed as argument is used :param method: the method to be executed using each queue item as argument - :param cursor: if True or QCursor a custom cursor is set while the Queue is not empty + :param cursor: if True or QCursor a custom cursor is set while + the Queue is not empty How TaurusEmitterThread works -------------------------- - TaurusEmitterThread is a worker thread that processes a queue of iterables passed as arguments to the specified method every time that ``doSomething()`` is called: + TaurusEmitterThread is a worker thread that processes a queue of iterables + passed as arguments to the specified method every time that + ``doSomething()`` is called: - * ``self.method(*item)`` will be called if TaurusEmitterThread.method has been initialized. - * ``item[0](item[1:])`` will be called if ``method`` is not initialized and the first element of the queued iterable is *callable*. + * ``self.method(*item)`` will be called if TaurusEmitterThread.method + has been initialized. + * ``item[0](item[1:])`` will be called if ``method`` is not initialized + and the first element of the queued iterable is *callable*. TaurusEmitterThread uses two queues: * ``self.queue`` manages the objects added externally: - + the ``next()`` method passes objects from ``self.queue`` to ``self.todo queue`` + + the ``next()`` method passes objects from ``self.queue`` + to ``self.todo queue`` + every time that a *somethingDone* signal arrives ``next()`` is called. - + ``next()`` can be called also externally to ensure that the main queue is being processed. + + ``next()`` can be called also externally to ensure that the main queue + is being processed. + the queue can be accessed externally using ``getQueue()`` + ``getQueue().qsize()`` returns the number of remaining objects in queue. - + while there are objects in queue the ``.next()`` method will override applications cursor. a call to ``next()`` with an empty queue will restore the original cursor. + + while there are objects in queue the ``.next()`` method will + override applications cursor. a call to ``next()`` with an empty queue + will restore the original cursor. * ``self.todo`` is managed by the ``run()/start()`` method: @@ -147,13 +159,15 @@ def __init__(self, parent = None, designMode = False): ... def build_widgets(...): ... - previous,synoptic_value = synoptic_value,TaurusValue(cell_frame) + previous,synoptic_value = \ + synoptic_value,TaurusValue(cell_frame) #synoptic_value.setModel(model) self.modelsQueue.put((synoptic_value,model)) ... def setModel(self,model): ... - if hasattr(self,'modelsThread') and not self.modelsThread.isRunning(): + if hasattr(self,'modelsThread') and \ + not self.modelsThread.isRunning(): self.modelsThread.start() elif self.modelsQueue.qsize(): self.modelsThread.next() @@ -204,7 +218,8 @@ def getQueue(self): def getDone(self): """ Returns % of done tasks in 0-1 range """ - return self._done / (self._done + self.getQueue().qsize()) if self._done else 0. + pending = self.getQueue().qsize() + return float(self._done) / (self._done + pending) def clear(self): while not self.todo.empty(): @@ -237,20 +252,25 @@ def _doSomething(self, params): try: method(*args) except: - self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' % ( - map(str, args), traceback.format_exc())) + self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' + % (map(str, args), traceback.format_exc())) self.emitter.somethingDone.emit() self._done += 1 return def next(self): queue = self.getQueue() - msg = 'At TaurusEmitterThread.next(), %d items remaining.' % queue.qsize() - (queue.empty() and self.log.info or self.log.debug)(msg) + msg = ('At TaurusEmitterThread.next(), %d items remaining.' + % queue.qsize()) + if (queue.empty() and not self.period): + self.log.info(msg) + else: + self.log.debug(msg) try: if not queue.empty(): if not self._cursor and self.cursor is not None: - Qt.QApplication.instance().setOverrideCursor(Qt.QCursor(self.cursor)) + Qt.QApplication.instance().setOverrideCursor( + Qt.QCursor(self.cursor)) self._cursor = True # A blocking get here would hang the GUIs!!! item = queue.get(False) @@ -269,7 +289,8 @@ def next(self): def run(self): Qt.QApplication.instance().thread().sleep( - int(self.timewait / 1000) if self.timewait > 10 else int(self.timewait)) # wait(self.sleep) + int(self.timewait / 1000) if self.timewait > 10 + else int(self.timewait)) self.log.info('#' * 80) self.log.info('At TaurusEmitterThread.run()') self.next() @@ -296,27 +317,33 @@ def run(self): # End of Thread -class SingletonWorker(): # Qt.QObject): +class SingletonWorker(): """ - The SingletonWorker works - ========================= + SingletonWorker is used to manage TaurusEmitterThread as Singleton objects - The SingletonWorker class is constructed using the same arguments than the TaurusTreadEmitter class ; but instead of creating a QThread for each instance of the class it creates a single QThread for all instances. + SingletonWorker is constructed using the same arguments + than TaurusTreadEmitter ; but instead of creating a QThread for each + instance it creates a single QThread for all instances. - The Queue is still different for each of the instances; it is connected to the TaurusEmitterThread signals (*next()* and *somethingDone()*) and each Worker queue is used as a feed for the shared QThread. + The Queue is still different for each of the instances; it is connected + to the TaurusEmitterThread signals (*next()* and *somethingDone()*) + and each Worker queue is used as a feed for the shared QThread. This implementation reduced the cpu of vacca application in a 50% factor. :param parent: a Qt/Taurus object :param name: identifies object logs - :param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used + :param queue: if None parent.getQueue() is used, if not then the queue + passed as argument is used :param method: the method to be executed using each queue item as argument - :param cursor: if True or QCursor a custom cursor is set while the Queue is not empty - This class is used to manage TaurusEmitterThread as Singleton objects: + :param cursor: if True or QCursor a custom cursor is set while + the Queue is not empty + """ _thread = None - def __init__(self, parent=None, name='', queue=None, method=None, cursor=None, sleep=5000, log=Logger.Warning, start=True): + def __init__(self, parent=None, name='', queue=None, method=None, + cursor=None, sleep=5000, log=Logger.Warning, start=True): self.name = name self.log = Logger('SingletonWorker(%s)' % self.name) self.log.setLogLevel(log) @@ -343,7 +370,9 @@ def next(self, item=None): self.put(item) elif self.queue.empty(): return - msg = 'At SingletonWorker.next(), %d items not passed yet to Emitter.' % self.queue.qsize() + msg = ('At SingletonWorker.next(), ' + '%d items not passed yet to Emitter.' + % self.queue.qsize()) self.log.info(msg) #(queue.empty() and self.log.info or self.log.debug)(msg) try: @@ -390,7 +419,8 @@ def stop(self): def clear(self): """ This method will clear queue only if next() has not been called. - If you call self.thread.clear() it will clear objects for all workers!, be careful + If you call self.thread.clear() it will clear objects for all workers!, + be careful """ while not self.queue.empty(): self.queue.get() From 8c2e03d93ad4e0a18175a4a1650c85759fdcc9ee Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 15:35:36 +0100 Subject: [PATCH 129/288] deprecate Queue by queue --- lib/taurus/qt/qtcore/util/emitter.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 9f7a95d07..dbd74d4d5 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -27,7 +27,7 @@ TaurusDevTree widgets """ -import Queue +import queue import traceback from functools import partial from collections import Iterable @@ -142,7 +142,7 @@ class TaurusEmitterThread(Qt.QThread): .. code-block:: python #Applying TaurusEmitterThread to an existing class: - import Queue + import queue from functools import partial def modelSetter(args): @@ -153,7 +153,7 @@ def modelSetter(args): ... def __init__(self, parent = None, designMode = False): ... - self.modelsQueue = Queue.Queue() + self.modelsQueue = queue.Queue() self.modelsThread = TaurusEmitterThread(parent=self, queue=self.modelsQueue,method=modelSetter ) ... @@ -184,8 +184,8 @@ def __init__(self, parent=None, name='', queue=None, method=None, self.name = name self.log = Logger('TaurusEmitterThread(%s)' % self.name) self.log.setLogLevel(self.log.Info) - self.queue = queue or Queue.Queue() - self.todo = Queue.Queue() + self.queue = queue or queue.Queue() + self.todo = queue.Queue() self.method = method self.cursor = Qt.QCursor( Qt.Qt.WaitCursor) if cursor is True else cursor @@ -229,7 +229,7 @@ def clear(self): self._done += 1 def purge(obj): - nqueue = Queue.Queue() + nqueue = queue.Queue() while not self.todo.empty(): i = self.todo.get() if obj not in i: @@ -280,7 +280,7 @@ def next(self): Qt.QApplication.instance().restoreOverrideCursor() self._cursor = False - except Queue.Empty: + except queue.Empty: self.log.warning(traceback.format_exc()) pass except: @@ -355,7 +355,7 @@ def __init__(self, parent=None, name='', queue=None, method=None, SingletonWorker._thread = TaurusEmitterThread( parent, name='SingletonWorker', cursor=cursor, sleep=sleep) self.thread = SingletonWorker._thread - self.queue = queue or Queue.Queue() + self.queue = queue or queue.Queue() if start: self.start() @@ -387,7 +387,7 @@ def next(self, item=None): i += 1 self.log.info('%d Items added to emitter queue' % i) self.thread.emitter.newQueue.emit() - except Queue.Empty: + except queue.Empty: self.log.warning(traceback.format_exc()) except: self.log.warning(traceback.format_exc()) @@ -427,7 +427,7 @@ def clear(self): # self.thread.clear() def purge(obj): - nqueue = Queue.Queue() + nqueue = queue.Queue() while not self.queue.empty(): i = self.queue.get() if obj not in i: From 03dfb06fe2b9421b34074d2881e9fb3bdf9d2d85 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 15:42:08 +0100 Subject: [PATCH 130/288] Separate polling and event modes, add loopwait(ms) --- lib/taurus/qt/qtcore/util/emitter.py | 44 +++++++++++++++++++++------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index dbd74d4d5..682729986 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -176,9 +176,17 @@ def setModel(self,model): """ def __init__(self, parent=None, name='', queue=None, method=None, - cursor=None, sleep=5000, period_ms=0): + cursor=None, sleep=5000, polling=0, loopwait=5000): """ Parent must be not None and must be a TaurusGraphicsScene! + + :param queue: pass an external action queue (optional) + :param method: action processor (e.g. modelSetter) + :param cursor: QCursor during process (optional) + :param delay: delay in ms before thread start + :param polling: process actions at fix period (milliseconds) + :param loopwait: wait N milliseconds between actions + """ Qt.QThread.__init__(self, parent) self.name = name @@ -191,6 +199,13 @@ def __init__(self, parent=None, name='', queue=None, method=None, Qt.Qt.WaitCursor) if cursor is True else cursor self._cursor = False self.timewait = sleep + self.polling = polling + self.loopwait = int(loopwait*1e-3) + if self.polling: + self.refreshTimer = Qt.QTimer() + self.refreshTimer.timeout.connect(self.onRefresh) + else: + self.refreshTimer = None self.emitter = QEmitter() self.emitter.moveToThread(Qt.QApplication.instance().thread()) @@ -200,13 +215,21 @@ def __init__(self, parent=None, name='', queue=None, method=None, self._done = 0 # Moved to the end to prevent segfaults ... self.emitter.doSomething.connect(self._doSomething) - self.emitter.somethingDone.connect(self.next) - self.period = period_ms + if not self.refreshTimer: + self.emitter.somethingDone.connect(self.next) + def onRefresh(self): - self.refreshTimer.setInterval(self.period) - self.next() + try: + size = self.getQueue().qsize() + if size: + self.log.info('onRefresh(%s)'%size) + self.next() + else: + self.log.debug('onRefresh()') + except: + self.log.warning(traceback.format_exc()) def getQueue(self): if self.queue: @@ -262,7 +285,7 @@ def next(self): queue = self.getQueue() msg = ('At TaurusEmitterThread.next(), %d items remaining.' % queue.qsize()) - if (queue.empty() and not self.period): + if (queue.empty() and not self.polling): self.log.info(msg) else: self.log.debug(msg) @@ -295,11 +318,8 @@ def run(self): self.log.info('At TaurusEmitterThread.run()') self.next() - if self.period: - self.refreshTimer = Qt.QTimer() - Qt.QObject.connect(self.refreshTimer, - Qt.SIGNAL("timeout()"), self.onRefresh) - self.refreshTimer.start(self.period) + if self.refreshTimer: + self.refreshTimer.start(self.polling) while True: self.log.debug('At TaurusEmitterThread.run() loop.') @@ -311,6 +331,8 @@ def run(self): continue self.log.debug('Emitting doSomething signal ...') self.emitter.doSomething.emit(item) + if self.loopwait: + self.sleep(self.loopwait) # End of while self.log.info( '#' * 80 + '\nOut of TaurusEmitterThread.run()' + '\n' + '#' * 80) From 726e7e077b8406092c72228b8503a13e39768f1a Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 15:44:16 +0100 Subject: [PATCH 131/288] Add DelayedSubscriber class for thread-safe delayed event subscribing --- lib/taurus/qt/qtcore/util/emitter.py | 71 +++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 682729986..852771599 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -337,8 +337,75 @@ def run(self): self.log.info( '#' * 80 + '\nOut of TaurusEmitterThread.run()' + '\n' + '#' * 80) # End of Thread - - + + +class DelayedSubscriber(Logger): + """ + DelayedSubscriber(schema) will use a TaurusEmitterThread to perform + a thread safe delayed subscribing on all Attributes of a given + Taurus Schema that has not been previously subscribed. + """ + + def __init__(self,schema,parent=None,sleep=10000,pause=5): + + self._schema = schema + self.call__init__(Logger, 'DelayedSubscriber(%s)'%self._schema, None) + self._factory = taurus.Factory(schema) + + self._modelsQueue = queue.Queue() + self._modelsThread = TaurusEmitterThread(parent=parent, + queue=self._modelsQueue, + method=self.modelSubscriber, + sleep=sleep,loopwait=pause) + + self._modelsQueue.put((self.addUnsubscribedAttributes,)) + self._modelsThread.start() + + def modelSubscriber(self,method,args=[]): + self.debug('modelSubscriber(%s,%s)'%(method,args)) + return method(*args) + + def getUnsubscribedAttributes(self): + attrs = [] + items = self._factory.getExistingAttributes().items() + for name,attr in items: + if attr is None: + continue + elif attr.hasListeners() and not attr.isUsingEvents(): + attrs.append(attr) + + return attrs + + def addUnsubscribedAttributes(self): + try: + items = self.getUnsubscribedAttributes() + if len(items): + self.info('addUnsubscribedAttributes([%d])'%len(items)) + for attr in items: + self.addModelObj(attr) + self._modelsThread.next() + self.info('Thread queue: [%d]'%(self._modelsQueue.qsize())) + except: + self.warning(traceback.format_exc()) + + def addModelObj(self,modelObj): + parent = modelObj.getParentObj() + if parent: + proxy = parent.getDeviceProxy() + if not proxy: + #self.debug('addModelObj(%s), proxy not available'%modelObj) + return + self._modelsQueue.put((modelObj._subscribeConfEvents,)) + modelObj.__subscription_state = SubscriptionState.PendingSubscribe + #modelObj._activatePolling() + self.debug('addModelObj(%s)'%str(modelObj)) + self._modelsQueue.put((modelObj._call_dev_hw_subscribe_event,(True,))) + + def cleanUp(self): + self.trace("[DelayedSubscriber] cleanUp") + self._modelsThread.stop() + Logger.cleanUp(self) + class SingletonWorker(): """ SingletonWorker is used to manage TaurusEmitterThread as Singleton objects From c6124d9f2af7eb6eba80a7c7241d71ef1a81fe0b Mon Sep 17 00:00:00 2001 From: cpascual Date: Mon, 30 Oct 2017 15:51:36 +0100 Subject: [PATCH 132/288] Fix formatter tests The formatter tests were broken when merging #563. Fix them. --- .../qt/qtgui/display/test/test_tauruslabel.py | 108 ++++++++++++------ 1 file changed, 73 insertions(+), 35 deletions(-) diff --git a/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py b/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py index 317ff3638..9d51f001a 100644 --- a/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py @@ -178,36 +178,79 @@ def baseFormatter1(dtype, **kwargs): def baseFormatter2(dtype, **kwargs): return dtype.__name__ - -@insertTest(helper_name='checkFormat', +# ------------------------------------------------------------------------- +# Class formatter tests +@insertTest(helper_name='checkClassFormat', + model='eval:1.2345', + formatter='>>{}<<', + expected=">>1.2345<<") +@insertTest(helper_name='checkClassFormat', + model='eval:Q(5)#rvalue.magnitude', + formatter=baseFormatter2, + expected="int") +@insertTest(helper_name='checkClassFormat', + model='eval:Q("5m")#rvalue.units', + formatter=baseFormatter2, + expected="Unit") +@insertTest(helper_name='checkClassFormat', + model='eval:1.2345', + formatter=baseFormatter1, + expected="1.2") +@insertTest(helper_name='checkClassFormat', + model='eval:"hello"', + formatter=baseFormatter1, + expected="hello") +@insertTest(helper_name='checkClassFormat', + model='eval:"hello"', + formatter=baseFormatter2, + expected="str") +@insertTest(helper_name='checkClassFormat', + model='eval:"hello"', + formatter=None, + expected="hello") +@insertTest(helper_name='checkClassFormat', + model='eval:1.2345', + formatter='{:~.3f}', + expected="1.234") +@insertTest(helper_name='checkClassFormat', + model='eval:1.2345', + formatter='{:.3f}', + expected="1.234 dimensionless") +# ------------------------------------------------------------------------- +# Instance formatter tests +@insertTest(helper_name='checkInstanceFormat', + model='eval:1.2345', + formatter='>>{}<<', + expected=">>1.2345<<") +@insertTest(helper_name='checkInstanceFormat', model='eval:Q(5)#rvalue.magnitude', formatter=baseFormatter2, expected="int") -@insertTest(helper_name='checkFormat', +@insertTest(helper_name='checkInstanceFormat', model='eval:Q("5m")#rvalue.units', formatter=baseFormatter2, expected="Unit") -@insertTest(helper_name='checkFormat', +@insertTest(helper_name='checkInstanceFormat', model='eval:1.2345', formatter=baseFormatter1, expected="1.2") -@insertTest(helper_name='checkFormat', +@insertTest(helper_name='checkInstanceFormat', model='eval:"hello"', formatter=baseFormatter1, expected="hello") -@insertTest(helper_name='checkFormat', +@insertTest(helper_name='checkInstanceFormat', model='eval:"hello"', formatter=baseFormatter2, expected="str") -@insertTest(helper_name='checkFormat', +@insertTest(helper_name='checkInstanceFormat', model='eval:"hello"', formatter=None, expected="hello") -@insertTest(helper_name='checkFormat', +@insertTest(helper_name='checkInstanceFormat', model='eval:1.2345', formatter='{:~.3f}', expected="1.234") -@insertTest(helper_name='checkFormat', +@insertTest(helper_name='checkInstanceFormat', model='eval:1.2345', formatter='{:.3f}', expected="1.234 dimensionless") @@ -219,11 +262,18 @@ class TaurusLabelFormatTest(BaseWidgetTestCase, unittest.TestCase): _klass = TaurusLabel - def checkFormat(self, model, formatter, expected): + def setUp(self): + BaseWidgetTestCase.setUp(self) + # store original class format + self._origFormatter = self._klass.FORMAT + + def tearDown(self): + # restore original class format + self._klass.FORMAT = self._origFormatter + + def checkInstanceFormat(self, model, formatter, expected): if formatter is not None: self._widget.setFormat(formatter) - # self._widget.FORMAT = formatter - # self._widget.updateFormat() self._widget.setModel(model) self.processEvents(repetitions=50, sleep=.1) @@ -232,30 +282,18 @@ def checkFormat(self, model, formatter, expected): (model, expected, got)) self.assertEqual(got, expected, msg) + def checkClassFormat(self, model, formatter, expected): + self._klass.FORMAT = formatter + # self._widget was already created by BaseWidgetTestCase.setUp(self) + # but we need to re-create it to use the class formatter + self._widget = self._klass(*self.initargs, **self.initkwargs) + self._widget.setModel(model) + self.processEvents(repetitions=50, sleep=.1) - -@insertTest(helper_name='checkFormat', - model='eval:1.2345', - formatter='{:.3f}', - expected="1.234 dimensionless") -@insertTest(helper_name='checkFormat', - model='eval:1.2345', - formatter=None, - expected="1.2") -class TaurusLabelFormatClassTest(TaurusLabelFormatTest): - """ - Specific tests for testing the Formatting API with TaurusLabel - """ - - _klass = TaurusLabel - - def setUp(self): - TaurusLabelFormatTest.setUp(self) - self._defaultFormatter = TaurusLabel.FORMAT - TaurusLabel.FORMAT = baseFormatter1 - - def tearDown(self): - TaurusLabel.FORMAT = self._defaultFormatter + got = self._widget.text() + msg = ('wrong text for "%s":\n expected: %s\n got: %s' % + (model, expected, got)) + self.assertEqual(got, expected, msg) # From 64c31f5b40151feed420dff91eb155129954d02d Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 16:10:58 +0100 Subject: [PATCH 133/288] Solve queue renaming issues --- lib/taurus/qt/qtcore/util/emitter.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 852771599..f2ba47f49 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -27,7 +27,7 @@ TaurusDevTree widgets """ -import queue +from queue import Queue, Empty import traceback from functools import partial from collections import Iterable @@ -153,7 +153,7 @@ def modelSetter(args): ... def __init__(self, parent = None, designMode = False): ... - self.modelsQueue = queue.Queue() + self.modelsQueue = Queue() self.modelsThread = TaurusEmitterThread(parent=self, queue=self.modelsQueue,method=modelSetter ) ... @@ -192,8 +192,8 @@ def __init__(self, parent=None, name='', queue=None, method=None, self.name = name self.log = Logger('TaurusEmitterThread(%s)' % self.name) self.log.setLogLevel(self.log.Info) - self.queue = queue or queue.Queue() - self.todo = queue.Queue() + self.queue = queue or Queue() + self.todo = Queue() self.method = method self.cursor = Qt.QCursor( Qt.Qt.WaitCursor) if cursor is True else cursor @@ -252,7 +252,7 @@ def clear(self): self._done += 1 def purge(obj): - nqueue = queue.Queue() + nqueue = Queue() while not self.todo.empty(): i = self.todo.get() if obj not in i: @@ -303,7 +303,7 @@ def next(self): Qt.QApplication.instance().restoreOverrideCursor() self._cursor = False - except queue.Empty: + except Empty: self.log.warning(traceback.format_exc()) pass except: @@ -352,7 +352,7 @@ def __init__(self,schema,parent=None,sleep=10000,pause=5): self.call__init__(Logger, 'DelayedSubscriber(%s)'%self._schema, None) self._factory = taurus.Factory(schema) - self._modelsQueue = queue.Queue() + self._modelsQueue = Queue() self._modelsThread = TaurusEmitterThread(parent=parent, queue=self._modelsQueue, method=self.modelSubscriber, @@ -444,7 +444,7 @@ def __init__(self, parent=None, name='', queue=None, method=None, SingletonWorker._thread = TaurusEmitterThread( parent, name='SingletonWorker', cursor=cursor, sleep=sleep) self.thread = SingletonWorker._thread - self.queue = queue or queue.Queue() + self.queue = queue or Queue() if start: self.start() @@ -476,7 +476,7 @@ def next(self, item=None): i += 1 self.log.info('%d Items added to emitter queue' % i) self.thread.emitter.newQueue.emit() - except queue.Empty: + except Empty: self.log.warning(traceback.format_exc()) except: self.log.warning(traceback.format_exc()) @@ -516,7 +516,7 @@ def clear(self): # self.thread.clear() def purge(obj): - nqueue = queue.Queue() + nqueue = Queue() while not self.queue.empty(): i = self.queue.get() if obj not in i: From f4e4bc77dd55e1b104093dc9b7017619b240d6cb Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 16:11:28 +0100 Subject: [PATCH 134/288] Add SubscriptionState missing import --- lib/taurus/qt/qtcore/util/emitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index f2ba47f49..e56f2e8e2 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -37,7 +37,7 @@ from taurus.core.util.log import Logger from taurus.core.util.singleton import Singleton - +from taurus.core.taurusbasetypes import SubscriptionState ############################################################################### # Helping methods From 0e337db39a3a11e8724b693ae5d78f3f24db359c Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 16:25:34 +0100 Subject: [PATCH 135/288] Solve loopwait units mess --- lib/taurus/qt/qtcore/util/emitter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index e56f2e8e2..921639399 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -176,7 +176,7 @@ def setModel(self,model): """ def __init__(self, parent=None, name='', queue=None, method=None, - cursor=None, sleep=5000, polling=0, loopwait=5000): + cursor=None, sleep=5000, polling=0, loopwait=5): """ Parent must be not None and must be a TaurusGraphicsScene! @@ -200,7 +200,7 @@ def __init__(self, parent=None, name='', queue=None, method=None, self._cursor = False self.timewait = sleep self.polling = polling - self.loopwait = int(loopwait*1e-3) + self.loopwait = int(loopwait) if self.polling: self.refreshTimer = Qt.QTimer() self.refreshTimer.timeout.connect(self.onRefresh) @@ -331,8 +331,8 @@ def run(self): continue self.log.debug('Emitting doSomething signal ...') self.emitter.doSomething.emit(item) - if self.loopwait: - self.sleep(self.loopwait) + if self.loopwait: + self.msleep(self.loopwait) # End of while self.log.info( '#' * 80 + '\nOut of TaurusEmitterThread.run()' + '\n' + '#' * 80) From 1f73f2abbf38f9135e4830a822c8047ce58cd6b5 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 30 Oct 2017 16:30:59 +0100 Subject: [PATCH 136/288] Add period as DelayedSubscriber argument --- lib/taurus/qt/qtcore/util/emitter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 921639399..48c605267 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -346,7 +346,7 @@ class DelayedSubscriber(Logger): Taurus Schema that has not been previously subscribed. """ - def __init__(self,schema,parent=None,sleep=10000,pause=5): + def __init__(self,schema,parent=None,sleep=10000,pause=5,period=0): self._schema = schema self.call__init__(Logger, 'DelayedSubscriber(%s)'%self._schema, None) @@ -356,7 +356,7 @@ def __init__(self,schema,parent=None,sleep=10000,pause=5): self._modelsThread = TaurusEmitterThread(parent=parent, queue=self._modelsQueue, method=self.modelSubscriber, - sleep=sleep,loopwait=pause) + sleep=sleep,loopwait=pause,polling=period) self._modelsQueue.put((self.addUnsubscribedAttributes,)) self._modelsThread.start() From 4ca68f7b553c65330f103d90f8f8c983ab3aff1c Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 31 Oct 2017 17:14:01 +0100 Subject: [PATCH 137/288] Fix sleep argument to accept milliseconds only --- lib/taurus/qt/qtcore/util/emitter.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 48c605267..398a49b1c 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -183,7 +183,7 @@ def __init__(self, parent=None, name='', queue=None, method=None, :param queue: pass an external action queue (optional) :param method: action processor (e.g. modelSetter) :param cursor: QCursor during process (optional) - :param delay: delay in ms before thread start + :param sleep: delay in ms before thread start :param polling: process actions at fix period (milliseconds) :param loopwait: wait N milliseconds between actions @@ -198,8 +198,8 @@ def __init__(self, parent=None, name='', queue=None, method=None, self.cursor = Qt.QCursor( Qt.Qt.WaitCursor) if cursor is True else cursor self._cursor = False - self.timewait = sleep - self.polling = polling + self.timewait = int(sleep) + self.polling = int(polling) self.loopwait = int(loopwait) if self.polling: self.refreshTimer = Qt.QTimer() @@ -311,9 +311,7 @@ def next(self): return def run(self): - Qt.QApplication.instance().thread().sleep( - int(self.timewait / 1000) if self.timewait > 10 - else int(self.timewait)) + Qt.QApplication.instance().thread().msleep(self.timewait) self.log.info('#' * 80) self.log.info('At TaurusEmitterThread.run()') self.next() From d22d5dc4c1169489b36642dcc2c796174b4499cf Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 31 Oct 2017 17:17:06 +0100 Subject: [PATCH 138/288] fix comment --- lib/taurus/qt/qtcore/util/emitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 398a49b1c..c63532ab5 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -142,7 +142,7 @@ class TaurusEmitterThread(Qt.QThread): .. code-block:: python #Applying TaurusEmitterThread to an existing class: - import queue + from queue import Queue from functools import partial def modelSetter(args): From 62d091ed34c22075536837d54f18fe48e610cef1 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 2 Nov 2017 10:54:38 +0100 Subject: [PATCH 139/288] Fix Taurus may emit events with outdated data In the Tango scheme there are three occassions that that may update the attribute ( __attr_value, __attr_err). These methods are: - read (executed in the main thread) - push_event (executed in the Tango event consumer thread) - poll (executed in the Taurus polling thread) Current implementation may lead to emitting events with the outdated values. The polling mechanism reads the Tango attributes asynchronously (first drop the read requests and later on collect the replies). The more up to date event value may get overriden by an older value corresponding to the polling request dropped prior to the event reception. Add a RLock to protect the critical section (update the attribute). Fix #216 --- lib/taurus/core/tango/tangoattribute.py | 148 +++++++++++++----------- 1 file changed, 81 insertions(+), 67 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index ba729ea71..90fdc5f45 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -245,13 +245,15 @@ class TangoAttribute(TaurusAttribute): _description = 'A Tango Attribute' def __init__(self, name, parent, **kwargs): - # the last attribute value self.__attr_value = None # the last attribute error self.__attr_err = None + # Lock for protect the critical region (__attr_value and __attr_err) + self.__lock = threading.RLock() + # the change event identifier self.__chg_evt_id = None @@ -290,6 +292,7 @@ def __init__(self, name, parent, **kwargs): self.__cfg_evt_id = None self._subscribeConfEvents() + def cleanUp(self): self.trace("[TangoAttribute] cleanUp") self._unsubscribeConfEvents() @@ -438,38 +441,40 @@ def write(self, value, with_read=True): def poll(self, single=True, value=None, time=None, error=None): """ Notify listeners when the attribute has been polled""" - try: - if single: - self.read(cache=False) + with self.__lock: + try: + if single: + self.read(cache=False) + else: + value = self.decode(value) + self.__attr_err = error + if self.__attr_err: + raise self.__attr_err + # Avoid "valid-but-outdated" notifications + # if FILTER_OLD_TANGO_EVENTS is enabled + # and the given timestamp is older than the timestamp + # of the cached value + filter_old_event = getattr(tauruscustomsettings, + 'FILTER_OLD_TANGO_EVENTS', + False) + if (self.__attr_value is not None + and filter_old_event + and time is not None + and time < self.__attr_value.time.totime()): + return + self.__attr_value = value + except PyTango.DevFailed, df: + self.__subscription_event.set() + self.debug("Error polling: %s" % df[0].desc) + self.traceback() + self.fireEvent(TaurusEventType.Error, self.__attr_err) + except Exception, e: + self.__subscription_event.set() + self.debug("Error polling: %s" % str(e)) + self.fireEvent(TaurusEventType.Error, self.__attr_err) else: - value = self.decode(value) - self.__attr_err = error - if self.__attr_err: - raise self.__attr_err - # Avoid "valid-but-outdated" notifications - # if FILTER_OLD_TANGO_EVENTS is enabled - # and the given timestamp is older than the timestamp - # of the cached value - filter_old_event = getattr(tauruscustomsettings, - 'FILTER_OLD_TANGO_EVENTS', False) - if (self.__attr_value is not None - and filter_old_event - and time is not None - and time < self.__attr_value.time.totime()): - return - self.__attr_value = value - except PyTango.DevFailed, df: - self.__subscription_event.set() - self.debug("Error polling: %s" % df[0].desc) - self.traceback() - self.fireEvent(TaurusEventType.Error, self.__attr_err) - except Exception, e: - self.__subscription_event.set() - self.debug("Error polling: %s" % str(e)) - self.fireEvent(TaurusEventType.Error, self.__attr_err) - else: - self.__subscription_event.set() - self.fireEvent(TaurusEventType.Periodic, self.__attr_value) + self.__subscription_event.set() + self.fireEvent(TaurusEventType.Periodic, self.__attr_value) def read(self, cache=True): """ Returns the current value of the attribute. @@ -477,7 +482,6 @@ def read(self, cache=True): active then it will return the local cached value. Otherwise it will read the attribute value from the tango device.""" curr_time = time.time() - if cache: try: attr_timestamp = self.__attr_value.time.totime() @@ -492,29 +496,38 @@ def read(self, cache=True): self.fullname, self.__attr_err) raise self.__attr_err - if not cache or (self.__subscription_state in (SubscriptionState.PendingSubscribe, SubscriptionState.Unsubscribed) and not self.isPollingActive()): - try: - dev = self.getParentObj() - v = dev.read_attribute(self.getSimpleName()) - self.__attr_value, self.__attr_err = self.decode(v), None - return self.__attr_value - except PyTango.DevFailed, df: - self.__attr_value, self.__attr_err = None, df - err = df[0] - self.debug("[Tango] read failed (%s): %s", - err.reason, err.desc) - raise df - except Exception, e: - self.__attr_value, self.__attr_err = None, e - self.debug("[Tango] read failed: %s", e) - raise e - elif self.__subscription_state in (SubscriptionState.Subscribing, SubscriptionState.PendingSubscribe): + if not cache or (self.__subscription_state in ( + SubscriptionState.PendingSubscribe, + SubscriptionState.Unsubscribed) + and not self.isPollingActive()): + with self.__lock: + try: + dev = self.getParentObj() + v = dev.read_attribute(self.getSimpleName()) + self.__attr_value = self.decode(v) + self.__attr_err = None + return self.__attr_value + except PyTango.DevFailed, df: + self.__attr_value = None + self.__attr_err = df + err = df[0] + self.debug("[Tango] read failed (%s): %s", + err.reason, err.desc) + raise df + except Exception, e: + self.__attr_value = None + self.__attr_err = e + self.debug("[Tango] read failed: %s", e) + raise e + elif self.__subscription_state in (SubscriptionState.Subscribing, + SubscriptionState.PendingSubscribe): self.__subscription_event.wait() if self.__attr_err is not None: raise self.__attr_err return self.__attr_value + def getAttributeProxy(self): """Convenience method that creates and returns a PyTango.AttributeProxy object""" @@ -702,24 +715,25 @@ def push_event(self, event): It propagates the event to listeners and delegates other tasks to specific handlers for different event types. """ - # if it is a configuration event - if isinstance(event, PyTango.AttrConfEventData): - etype, evalue = self._pushConfEvent(event) - # if it is an attribute event - else: - etype, evalue = self._pushAttrEvent(event) + with self.__lock: + # if it is a configuration event + if isinstance(event, PyTango.AttrConfEventData): + etype, evalue = self._pushConfEvent(event) + # if it is an attribute event + else: + etype, evalue = self._pushAttrEvent(event) - # notify the listeners if required (i.e, if etype is not None) - if etype is None: - return - manager = Manager() - sm = self.getSerializationMode() - listeners = tuple(self._listeners) - if sm == TaurusSerializationMode.Concurrent: - manager.addJob(self.fireEvent, None, etype, evalue, - listeners=listeners) - else: - self.fireEvent(etype, evalue, listeners=listeners) + # notify the listeners if required (i.e, if etype is not None) + if etype is None: + return + manager = Manager() + sm = self.getSerializationMode() + listeners = tuple(self._listeners) + if sm == TaurusSerializationMode.Concurrent: + manager.addJob(self.fireEvent, None, etype, evalue, + listeners=listeners) + else: + self.fireEvent(etype, evalue, listeners=listeners) def _pushAttrEvent(self, event): """Handler of (non-configuration) events from the PyTango layer. From 18f9c333e00910ebc0a48c00444ab16a130fd3c5 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Fri, 3 Nov 2017 16:39:54 +0100 Subject: [PATCH 140/288] Remove PyTango dependency in taurusform.py --- lib/taurus/qt/qtgui/panel/taurusform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index 02b86fea5..d7a1bdab4 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -659,7 +659,7 @@ def _updateCommandWidgets(self, *args): self._cmdWidgets.append(button) button.commandExecuted.connect(self._onCommandExecuted) - if c.in_type != PyTango.CmdArgType.DevVoid: + if c.in_type: self.debug('Adding arguments for command %s' % c.cmd_name) pwidget = ParameterCB() if c.cmd_name.lower() in self._defaultParameters: From 8c0a4cd4767f3ed514256c6d5b8265809eceba5b Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Fri, 3 Nov 2017 17:09:42 +0100 Subject: [PATCH 141/288] Migrate TaurusGrid.itemClicked signal to API 2 (bugfix) --- lib/taurus/qt/qtgui/table/taurusgrid.py | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/taurus/qt/qtgui/table/taurusgrid.py b/lib/taurus/qt/qtgui/table/taurusgrid.py index 6fbbdb4fc..369fcf67b 100644 --- a/lib/taurus/qt/qtgui/table/taurusgrid.py +++ b/lib/taurus/qt/qtgui/table/taurusgrid.py @@ -194,6 +194,18 @@ def get_readwrite_models(expressions, limit=1000): models = models[:limit] return models +class TaurusGridCell(Qt.QFrame): + + itemClicked = Qt.pyqtSignal('QString') + + # Done in this way as TaurusValue.mousePressEvent is never called + def mousePressEvent(self, event): + # print 'In cell clicked' + targets = set(str(child.getModelName()) for child in self.children() + if hasattr(child, 'underMouse') and child.underMouse() + and hasattr(child, 'getModelName')) + for t in targets: + self.itemClicked.emit(t) class TaurusGrid(QtGui.QFrame, TaurusBaseWidget): """ TaurusGrid is a Taurus widget designed to represent a set of attributes distributed in columns and rows. @@ -214,7 +226,7 @@ class TaurusGrid(QtGui.QFrame, TaurusBaseWidget): # itemSelected = Qt.pyqtSignal('QString') - itemClicked = Qt.pyqtSignal() + itemClicked = Qt.pyqtSignal('QString') _TAGS = ['DOMAIN', 'FAMILY', 'HOST', 'LEVEL', 'CLASS', 'ATTRIBUTE', 'DEVICE'] @@ -668,7 +680,7 @@ def create_widgets_dict(self, models): def create_frame_with_gridlayout(self): """ Just a 'macro' to create the layouts that seem to fit better. """ - frame = QtGui.QFrame() + frame = TaurusGridCell() frame.setLayout(QtGui.QGridLayout()) frame.layout().setContentsMargins(2, 2, 2, 2) frame.layout().setSpacing(0) @@ -875,6 +887,7 @@ def build_widgets(self, values, show_labels=False, width=240, height=20, value_w widgets_row = [] for cell in row: cell_frame = self.create_frame_with_gridlayout() + cell_frame.itemClicked.connect(self.itemClickedHook) count = 0 for synoptic in sorted(cell): self.debug("processing synoptic %s" % synoptic) @@ -883,7 +896,6 @@ def build_widgets(self, values, show_labels=False, width=240, height=20, value_w self.debug('Creating TaurusValue with model = %s' % model) synoptic_value = TaurusValue(cell_frame) self.modelsQueue.put((synoptic_value, model)) - synoptic_value.itemClicked.connect(self.itemClicked) if self.hideLabels: synoptic_value.setLabelWidgetClass(None) @@ -894,23 +906,11 @@ def build_widgets(self, values, show_labels=False, width=240, height=20, value_w self._widgets_list.append(synoptic_value) count += 1 - # Done in this way as TauValue.mousePressEvent are never called - def mousePressEvent(event, obj): - # print 'In cell clicked' - targets = set(str(child.getModelName()) for child in obj.children() - if hasattr(child, 'underMouse') and child.underMouse() and hasattr(child, 'getModelName')) - [obj.itemClicked.emit(t) - for t in targets] - - cell_frame.mousePressEvent = partial( - mousePressEvent, obj=cell_frame) - cell_frame.itemClicked.connect(self.itemClicked) - widgets_row.append(cell_frame) widgets_matrix.append(widgets_row) return widgets_matrix - def itemClicked(self, item_name): + def itemClickedHook(self, item_name): self.trace('In TaurusGrid.itemClicked(%s)' % item_name) self.setItemSelected(item_name) self.itemClicked.emit(str(item_name)) From 5e952184f110f4fc29cfb70f91f3aca5138f27a9 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 6 Nov 2017 10:13:04 +0100 Subject: [PATCH 142/288] (minor) Rename __lock --> __read_lock --- lib/taurus/core/tango/tangoattribute.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 90fdc5f45..c34dcda71 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -251,8 +251,9 @@ def __init__(self, name, parent, **kwargs): # the last attribute error self.__attr_err = None - # Lock for protect the critical region (__attr_value and __attr_err) - self.__lock = threading.RLock() + # Lock for protecting the critical read region + # where __attr_value and __attr_err are updated + self.__read_lock = threading.RLock() # the change event identifier self.__chg_evt_id = None @@ -441,7 +442,7 @@ def write(self, value, with_read=True): def poll(self, single=True, value=None, time=None, error=None): """ Notify listeners when the attribute has been polled""" - with self.__lock: + with self.__read_lock: try: if single: self.read(cache=False) @@ -500,7 +501,7 @@ def read(self, cache=True): SubscriptionState.PendingSubscribe, SubscriptionState.Unsubscribed) and not self.isPollingActive()): - with self.__lock: + with self.__read_lock: try: dev = self.getParentObj() v = dev.read_attribute(self.getSimpleName()) @@ -715,7 +716,7 @@ def push_event(self, event): It propagates the event to listeners and delegates other tasks to specific handlers for different event types. """ - with self.__lock: + with self.__read_lock: # if it is a configuration event if isinstance(event, PyTango.AttrConfEventData): etype, evalue = self._pushConfEvent(event) From 5abce1a2971af30fb02b637205d5689afbeab76f Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 6 Nov 2017 10:43:53 +0100 Subject: [PATCH 143/288] Check command in_type using taurus.core.tango --- lib/taurus/qt/qtgui/panel/taurusform.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index d7a1bdab4..88557bd9d 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -658,8 +658,11 @@ def _updateCommandWidgets(self, *args): button.setUseParentModel(True) self._cmdWidgets.append(button) button.commandExecuted.connect(self._onCommandExecuted) + + import taurus.core.tango.util.tango_taurus as tango_taurus + in_type = tango_taurus.FROM_TANGO_TO_TAURUS_TYPE[c.in_type] - if c.in_type: + if in_type is not None: self.debug('Adding arguments for command %s' % c.cmd_name) pwidget = ParameterCB() if c.cmd_name.lower() in self._defaultParameters: From 2cb1491974049913eb329d02991cd9f99a159ad2 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Mon, 6 Nov 2017 11:01:02 +0100 Subject: [PATCH 144/288] rename itemClickedHook to onItemClicked --- lib/taurus/qt/qtgui/table/taurusgrid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/table/taurusgrid.py b/lib/taurus/qt/qtgui/table/taurusgrid.py index 369fcf67b..7b80acb54 100644 --- a/lib/taurus/qt/qtgui/table/taurusgrid.py +++ b/lib/taurus/qt/qtgui/table/taurusgrid.py @@ -887,7 +887,7 @@ def build_widgets(self, values, show_labels=False, width=240, height=20, value_w widgets_row = [] for cell in row: cell_frame = self.create_frame_with_gridlayout() - cell_frame.itemClicked.connect(self.itemClickedHook) + cell_frame.itemClicked.connect(self.onItemClicked) count = 0 for synoptic in sorted(cell): self.debug("processing synoptic %s" % synoptic) @@ -910,7 +910,7 @@ def build_widgets(self, values, show_labels=False, width=240, height=20, value_w widgets_matrix.append(widgets_row) return widgets_matrix - def itemClickedHook(self, item_name): + def onItemClicked(self, item_name): self.trace('In TaurusGrid.itemClicked(%s)' % item_name) self.setItemSelected(item_name) self.itemClicked.emit(str(item_name)) From cd221f3c3ac1b5a3031119f676567b7dce86de8e Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 9 Nov 2017 16:01:54 +0100 Subject: [PATCH 145/288] Move TaurusGridCell into a protected class of TaurusGrid --- lib/taurus/qt/qtgui/table/taurusgrid.py | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/taurus/qt/qtgui/table/taurusgrid.py b/lib/taurus/qt/qtgui/table/taurusgrid.py index 7b80acb54..726c39132 100644 --- a/lib/taurus/qt/qtgui/table/taurusgrid.py +++ b/lib/taurus/qt/qtgui/table/taurusgrid.py @@ -194,19 +194,6 @@ def get_readwrite_models(expressions, limit=1000): models = models[:limit] return models -class TaurusGridCell(Qt.QFrame): - - itemClicked = Qt.pyqtSignal('QString') - - # Done in this way as TaurusValue.mousePressEvent is never called - def mousePressEvent(self, event): - # print 'In cell clicked' - targets = set(str(child.getModelName()) for child in self.children() - if hasattr(child, 'underMouse') and child.underMouse() - and hasattr(child, 'getModelName')) - for t in targets: - self.itemClicked.emit(t) - class TaurusGrid(QtGui.QFrame, TaurusBaseWidget): """ TaurusGrid is a Taurus widget designed to represent a set of attributes distributed in columns and rows. The Model will be a list with attributes or device names (for devices the State attribute will be shown). @@ -230,6 +217,19 @@ class TaurusGrid(QtGui.QFrame, TaurusBaseWidget): _TAGS = ['DOMAIN', 'FAMILY', 'HOST', 'LEVEL', 'CLASS', 'ATTRIBUTE', 'DEVICE'] + + class _TaurusGridCell(Qt.QFrame): + + itemClicked = Qt.pyqtSignal('QString') + + # Done in this way as TaurusValue.mousePressEvent is never called + def mousePressEvent(self, event): + # print 'In cell clicked' + targets = set(str(child.getModelName()) for child in self.children() + if hasattr(child, 'underMouse') and child.underMouse() + and hasattr(child, 'getModelName')) + for t in targets: + self.itemClicked.emit(t) def __init__(self, parent=None, designMode=False): name = self.__class__.__name__ @@ -680,7 +680,7 @@ def create_widgets_dict(self, models): def create_frame_with_gridlayout(self): """ Just a 'macro' to create the layouts that seem to fit better. """ - frame = TaurusGridCell() + frame = TaurusGrid._TaurusGridCell() frame.setLayout(QtGui.QGridLayout()) frame.layout().setContentsMargins(2, 2, 2, 2) frame.layout().setSpacing(0) From f74e2b815738e06c044171a33332718310d00311 Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 14 Nov 2017 09:29:10 +0100 Subject: [PATCH 146/288] Fix #612: ignore extras info when checking requirements Under some circumstances (e.g. when installing taurus with pip from a tar.gz created with the sdist command), the `pkg_resources.Requirement` objects returned when interrogating taurus for its requirements include `.marker` information, whereas in other it does not. This mreaks the logic of `taurus.check_requirements()`. As a quick workaround, remove the markers info from the requirement string. --- lib/taurus/core/taurushelper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/taurushelper.py b/lib/taurus/core/taurushelper.py index 9c7e1f6d2..78170fe92 100644 --- a/lib/taurus/core/taurushelper.py +++ b/lib/taurus/core/taurushelper.py @@ -90,7 +90,8 @@ def check_dependencies(): # requirements from PyPI for r in d.requires(extras=[extra]): try: - pkg_resources.require(str(r)) + r = str(r).split(';')[0] # remove marker if present (see #612) + pkg_resources.require(r) print '\t[*]', except Exception: print '\t[ ]', From 00b6feb9340cbabb385e5270f9deee454b741063 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 15 Nov 2017 17:43:58 +0100 Subject: [PATCH 147/288] PEP 8 --- lib/taurus/qt/qtcore/util/emitter.py | 86 ++++++++++++++-------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index c63532ab5..26537a102 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -39,6 +39,7 @@ from taurus.core.taurusbasetypes import SubscriptionState + ############################################################################### # Helping methods @@ -215,16 +216,15 @@ def __init__(self, parent=None, name='', queue=None, method=None, self._done = 0 # Moved to the end to prevent segfaults ... self.emitter.doSomething.connect(self._doSomething) - + if not self.refreshTimer: self.emitter.somethingDone.connect(self.next) - - + def onRefresh(self): try: size = self.getQueue().qsize() if size: - self.log.info('onRefresh(%s)'%size) + self.log.info('onRefresh(%s)' % size) self.next() else: self.log.debug('onRefresh()') @@ -275,8 +275,8 @@ def _doSomething(self, params): try: method(*args) except: - self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' - % (map(str, args), traceback.format_exc())) + self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' + % (map(str, args), traceback.format_exc())) self.emitter.somethingDone.emit() self._done += 1 return @@ -284,7 +284,7 @@ def _doSomething(self, params): def next(self): queue = self.getQueue() msg = ('At TaurusEmitterThread.next(), %d items remaining.' - % queue.qsize()) + % queue.qsize()) if (queue.empty() and not self.polling): self.log.info(msg) else: @@ -315,10 +315,10 @@ def run(self): self.log.info('#' * 80) self.log.info('At TaurusEmitterThread.run()') self.next() - + if self.refreshTimer: self.refreshTimer.start(self.polling) - + while True: self.log.debug('At TaurusEmitterThread.run() loop.') item = self.todo.get(True) @@ -331,79 +331,81 @@ def run(self): self.emitter.doSomething.emit(item) if self.loopwait: self.msleep(self.loopwait) - # End of while + # End of while self.log.info( '#' * 80 + '\nOut of TaurusEmitterThread.run()' + '\n' + '#' * 80) # End of Thread - - + + class DelayedSubscriber(Logger): """ DelayedSubscriber(schema) will use a TaurusEmitterThread to perform a thread safe delayed subscribing on all Attributes of a given Taurus Schema that has not been previously subscribed. """ - - def __init__(self,schema,parent=None,sleep=10000,pause=5,period=0): - + + def __init__(self, schema, parent=None, sleep=10000, pause=5, period=0): + self._schema = schema - self.call__init__(Logger, 'DelayedSubscriber(%s)'%self._schema, None) + self.call__init__(Logger, 'DelayedSubscriber(%s)' % self._schema, None) self._factory = taurus.Factory(schema) self._modelsQueue = Queue() self._modelsThread = TaurusEmitterThread(parent=parent, - queue=self._modelsQueue, - method=self.modelSubscriber, - sleep=sleep,loopwait=pause,polling=period) + queue=self._modelsQueue, + method=self.modelSubscriber, + sleep=sleep, loopwait=pause, + polling=period) self._modelsQueue.put((self.addUnsubscribedAttributes,)) self._modelsThread.start() - - def modelSubscriber(self,method,args=[]): - self.debug('modelSubscriber(%s,%s)'%(method,args)) + + def modelSubscriber(self, method, args=[]): + self.debug('modelSubscriber(%s,%s)' % (method, args)) return method(*args) - + def getUnsubscribedAttributes(self): attrs = [] items = self._factory.getExistingAttributes().items() - for name,attr in items: - if attr is None: + for name, attr in items: + if attr is None: continue elif attr.hasListeners() and not attr.isUsingEvents(): - attrs.append(attr) + attrs.append(attr) return attrs - + def addUnsubscribedAttributes(self): try: - items = self.getUnsubscribedAttributes() + items = self.getUnsubscribedAttributes() if len(items): - self.info('addUnsubscribedAttributes([%d])'%len(items)) + self.info('addUnsubscribedAttributes([%d])' % len(items)) for attr in items: self.addModelObj(attr) self._modelsThread.next() - self.info('Thread queue: [%d]'%(self._modelsQueue.qsize())) + self.info('Thread queue: [%d]' % (self._modelsQueue.qsize())) except: self.warning(traceback.format_exc()) - - def addModelObj(self,modelObj): + + def addModelObj(self, modelObj): parent = modelObj.getParentObj() if parent: proxy = parent.getDeviceProxy() if not proxy: - #self.debug('addModelObj(%s), proxy not available'%modelObj) + # self.debug('addModelObj(%s), proxy not available'%modelObj) return self._modelsQueue.put((modelObj._subscribeConfEvents,)) modelObj.__subscription_state = SubscriptionState.PendingSubscribe - #modelObj._activatePolling() - self.debug('addModelObj(%s)'%str(modelObj)) - self._modelsQueue.put((modelObj._call_dev_hw_subscribe_event,(True,))) + # modelObj._activatePolling() + self.debug('addModelObj(%s)' % str(modelObj)) + self._modelsQueue.put((modelObj._call_dev_hw_subscribe_event, (True,))) def cleanUp(self): self.trace("[DelayedSubscriber] cleanUp") self._modelsThread.stop() Logger.cleanUp(self) - + + class SingletonWorker(): """ SingletonWorker is used to manage TaurusEmitterThread as Singleton objects @@ -429,7 +431,7 @@ class SingletonWorker(): """ _thread = None - def __init__(self, parent=None, name='', queue=None, method=None, + def __init__(self, parent=None, name='', queue=None, method=None, cursor=None, sleep=5000, log=Logger.Warning, start=True): self.name = name self.log = Logger('SingletonWorker(%s)' % self.name) @@ -458,10 +460,10 @@ def next(self, item=None): elif self.queue.empty(): return msg = ('At SingletonWorker.next(), ' - '%d items not passed yet to Emitter.' - % self.queue.qsize()) + '%d items not passed yet to Emitter.' + % self.queue.qsize()) self.log.info(msg) - #(queue.empty() and self.log.info or self.log.debug)(msg) + # (queue.empty() and self.log.info or self.log.debug)(msg) try: i = 0 while not self.queue.empty(): @@ -511,7 +513,7 @@ def clear(self): """ while not self.queue.empty(): self.queue.get() - # self.thread.clear() + # self.thread.clear() def purge(obj): nqueue = Queue() From d89d19df23feeba472d0298b068d3afdd6ea1a20 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 15 Nov 2017 18:37:51 +0100 Subject: [PATCH 148/288] Add TODOs for removing possible garbage code --- lib/taurus/qt/qtcore/util/emitter.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 26537a102..430bd8cf8 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -252,6 +252,10 @@ def clear(self): self._done += 1 def purge(obj): + # TODO: remove this method? + # This method seems buggy (it uses unreferenced `self`) + # AFAIK it is not called from anywere in Taurus and it is not part of + # a Qt API nqueue = Queue() while not self.todo.empty(): i = self.todo.get() @@ -516,6 +520,10 @@ def clear(self): # self.thread.clear() def purge(obj): + # TODO: remove this method? + # This method seems buggy (it uses unreferenced `self`) + # AFAIK it is not called from anywere in Taurus and it is not part of + # a Qt API nqueue = Queue() while not self.queue.empty(): i = self.queue.get() From 865fe1c5ea3e4c52d1f0d34410b4739c89e1805b Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 15 Nov 2017 18:59:29 +0100 Subject: [PATCH 149/288] (minor) PEP8 --- lib/taurus/qt/qtgui/table/taurusgrid.py | 190 ++++++++++++++---------- 1 file changed, 110 insertions(+), 80 deletions(-) diff --git a/lib/taurus/qt/qtgui/table/taurusgrid.py b/lib/taurus/qt/qtgui/table/taurusgrid.py index 726c39132..888ac60c3 100644 --- a/lib/taurus/qt/qtgui/table/taurusgrid.py +++ b/lib/taurus/qt/qtgui/table/taurusgrid.py @@ -46,7 +46,8 @@ from taurus.external.qt import Qt, QtGui, QtCore import taurus -from taurus.qt.qtcore.util.emitter import modelSetter, TaurusEmitterThread, SingletonWorker, MethodModel +from taurus.qt.qtcore.util.emitter import (modelSetter, TaurusEmitterThread, + SingletonWorker, MethodModel) from taurus.core.taurusmanager import TaurusManager from taurus.core.util.log import Logger from taurus.qt.qtgui.base import TaurusBaseWidget @@ -71,31 +72,32 @@ def get_all_models(expressions, limit=1000): It practically equals to fandango.get_matching_attributes; check which is better! Move this method to taurus.core.tango.search ''' - #print( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) + # print( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) if isinstance(expressions, str): # if any(re.match(s,expressions) for s in ('\{.*\}','\(.*\)','\[.*\]')): - ##self.debug( 'evaluating expressions ....') - #expressions = list(eval(expressions)) + ##self.debug( 'evaluating expressions ....') + # expressions = list(eval(expressions)) # else: - ##self.debug( 'expressions as string separated by commas ...') + ##self.debug( 'expressions as string separated by commas ...') expressions = expressions.split(',') - elif any(isinstance(expressions, klass) for klass in (QtCore.QStringList, list, tuple, dict)): - #self.debug( 'expressions converted from list ...') + elif any(isinstance(expressions, klass) for klass in + (QtCore.QStringList, list, tuple, dict)): + # self.debug( 'expressions converted from list ...') expressions = list(str(e) for e in expressions) - #self.debug( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) + # self.debug( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) taurus_db = taurus.Authority() - #taurus_db = taurus.Authority(os.environ['TANGO_HOST']) + # taurus_db = taurus.Authority(os.environ['TANGO_HOST']) # WHAAAAAAT????? Someone should get beaten for this line if 'SimulationAuthority' in str(type(taurus_db)): - #self.trace( 'Using a simulated database ...') + # self.trace( 'Using a simulated database ...') models = expressions else: all_devs = taurus_db.get_device_exported('*') models = [] for exp in expressions: - #self.trace( 'evaluating exp = "%s"' % exp) + # self.trace( 'evaluating exp = "%s"' % exp) exp = str(exp) devs = [] targets = [] @@ -111,21 +113,23 @@ def get_all_models(expressions, limit=1000): else: devs = [device] - #self.trace( 'TaurusGrid.get_all_models(): devices matched by %s / %s are %d:' % (device,attribute,len(devs))) - #self.debug( '%s' % (devs)) + # self.trace( 'TaurusGrid.get_all_models(): devices matched by %s / %s are %d:' % (device,attribute,len(devs))) + # self.debug( '%s' % (devs)) for dev in devs: if any(c in attribute for c in '.*[]()+?'): if '*' in attribute and '.*' not in attribute: attribute = attribute.replace('*', '.*') try: - #taurus_dp = taurus.core.taurusdevice.TaurusDevice(dev) - taurus_dp = taurus.core.taurusmanager.TaurusManager().getFactory()().getDevice(dev) - #self.debug( "taurus_dp = %s"%taurus_dp.getFullName()) - attrs = [att.name for att in taurus_dp.attribute_list_query( - ) if re_match_low(attribute, att.name)] + # taurus_dp = taurus.core.taurusdevice.TaurusDevice(dev) + taurus_dp = taurus.core.taurusmanager.TaurusManager().getFactory()().getDevice( + dev) + # self.debug( "taurus_dp = %s"%taurus_dp.getFullName()) + attrs = [att.name for att in + taurus_dp.attribute_list_query( + ) if re_match_low(attribute, att.name)] targets.extend(dev + '/' + att for att in attrs) except Exception, e: - #self.warning( 'ERROR! TaurusGrid.get_all_models(): Unable to get attributes for device %s: %s' % (dev,str(e))) + # self.warning( 'ERROR! TaurusGrid.get_all_models(): Unable to get attributes for device %s: %s' % (dev,str(e))) pass else: targets.append(dev + '/' + attribute) @@ -133,7 +137,7 @@ def get_all_models(expressions, limit=1000): # % (exp,targets) models.extend(targets) models = models[:limit] - #print( 'Out of TaurusGrid.get_all_models(...)') + # print( 'Out of TaurusGrid.get_all_models(...)') return models @@ -142,16 +146,18 @@ def get_readwrite_models(expressions, limit=1000): All devices matching expressions must be obtained. For each device only the good attributes are read. ''' - #self.debug( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) + # self.debug( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) if isinstance(expressions, str): - if any(re.match(s, expressions) for s in ('\{.*\}', '\(.*\)', '\[.*\]')): - #self.trace( 'evaluating expressions ....') + if any(re.match(s, expressions) for s in + ('\{.*\}', '\(.*\)', '\[.*\]')): + # self.trace( 'evaluating expressions ....') expressions = list(eval(expressions)) else: - #self.trace( 'expressions as string separated by commas ...') + # self.trace( 'expressions as string separated by commas ...') expressions = expressions.split(',') - elif any(isinstance(expressions, klass) for klass in (QtCore.QStringList, list, tuple, dict)): + elif any(isinstance(expressions, klass) for klass in + (QtCore.QStringList, list, tuple, dict)): expressions = list(str(e) for e in expressions) taurus_db = taurus.Authority() @@ -182,9 +188,12 @@ def get_readwrite_models(expressions, limit=1000): if '*' in attribute and '.*' not in attribute: attribute = attribute.replace('*', '.*') try: - taurus_dp = taurus.core.taurusmanager.TaurusManager().getFactory()().getDevice(dev) - attrs = [att.name for att in taurus_dp.attribute_list_query( - ) if re_match_low(attribute, att.name) and att.isReadOnly()] + taurus_dp = taurus.core.taurusmanager.TaurusManager().getFactory()().getDevice( + dev) + attrs = [att.name for att in + taurus_dp.attribute_list_query( + ) if re_match_low(attribute, + att.name) and att.isReadOnly()] targets.extend(dev + '/' + att for att in attrs) except Exception, e: pass @@ -194,6 +203,7 @@ def get_readwrite_models(expressions, limit=1000): models = models[:limit] return models + class TaurusGrid(QtGui.QFrame, TaurusBaseWidget): """ TaurusGrid is a Taurus widget designed to represent a set of attributes distributed in columns and rows. The Model will be a list with attributes or device names (for devices the State attribute will be shown). @@ -208,7 +218,7 @@ class TaurusGrid(QtGui.QFrame, TaurusBaseWidget): @todo _TAGS property should allow to change row/columns meaning and also add new Custom tags based on regexp """ - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Write your own code here to define the signals generated by this widget # @@ -217,25 +227,29 @@ class TaurusGrid(QtGui.QFrame, TaurusBaseWidget): _TAGS = ['DOMAIN', 'FAMILY', 'HOST', 'LEVEL', 'CLASS', 'ATTRIBUTE', 'DEVICE'] - + class _TaurusGridCell(Qt.QFrame): itemClicked = Qt.pyqtSignal('QString') - + # Done in this way as TaurusValue.mousePressEvent is never called def mousePressEvent(self, event): # print 'In cell clicked' - targets = set(str(child.getModelName()) for child in self.children() - if hasattr(child, 'underMouse') and child.underMouse() - and hasattr(child, 'getModelName')) + targets = set(str(child.getModelName()) + for child in self.children() + if hasattr(child, 'underMouse') + and child.underMouse() + and hasattr(child, 'getModelName') + ) for t in targets: - self.itemClicked.emit(t) + self.itemClicked.emit(t) def __init__(self, parent=None, designMode=False): name = self.__class__.__name__ self.call__init__wo_kw(QtGui.QFrame, parent) - #self.call__init__(TaurusBaseWidget, name, parent, designMode=designMode) + # self.call__init__(TaurusBaseWidget, name, parent, + # designMode=designMode) # It was needed to avoid exceptions in TaurusDesigner! if isinstance(parent, TaurusBaseWidget): self.call__init__(TaurusBaseWidget, name, @@ -280,7 +294,8 @@ def save(self, filename): d = { 'model': self.filter, 'row_labels': self.row_labels, 'column_labels': self.column_labels, - 'frames': self._show_row_frame or self._show_column_frame, 'labels': self._show_attr_labels, + 'frames': self._show_row_frame or self._show_column_frame, + 'labels': self._show_attr_labels, 'units': self._show_attr_units, 'others': self._show_others } f = open(filename, 'w') @@ -311,7 +326,7 @@ def load(self, filename, delayed=False): def defineStyle(self): """ Defines the initial style for the widget """ - #---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # Write your own code here to set the initial style of your widget # self.setLayout(QtGui.QGridLayout()) @@ -324,12 +339,12 @@ def sizeHint(self): def minimumSizeHint(self): return QtGui.QFrame.minimumSizeHint(self) - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # TaurusBaseWidget over writing - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def getModelClass(self): - #---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # [MANDATORY] # Replace your own code here # ex.: return taurus.core.taurusattribute.Attribute @@ -342,7 +357,7 @@ def attach(self): if self.isAttached(): return True - #---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # Write your own code here before attaching widget to attribute connect # the proper signal so that the first event is correctly received by the # widget @@ -362,7 +377,7 @@ def detach(self): TaurusBaseWidget.detach(self) - #---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # Write your own code here after detaching the widget from the model # # Typical code is: @@ -374,7 +389,7 @@ def detach(self): # by default disable widget when dettached self.setEnabled(False) - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # [MANDATORY] # Uncomment the following method if your superclass does not provide with a # isReadOnly() method or if you need to change its behavior @@ -383,7 +398,7 @@ def detach(self): # return True def updateStyle(self): - #---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # Write your own code here to update your widget style self.trace('@' * 80) self.trace( @@ -391,7 +406,7 @@ def updateStyle(self): self.trace('@' * 80) # It was showing an annoying "True" in the widget - #value = self.getShowText() or '' + # value = self.getShowText() or '' # if self._setText: self._setText(value) ##It must be included # update tooltip @@ -406,10 +421,11 @@ def updateStyle(self): self.update() - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Write your own code here for your own widget properties - def setModel(self, model, devsInRows=False, delayed=False, append=False, load=True): + def setModel(self, model, devsInRows=False, delayed=False, append=False, + load=True): '''The model can be initialized as a list of devices or hosts or dictionary or ...''' # self.setModelCheck(model) ##It must be included # differenciate if the model is a RegExp @@ -443,22 +459,28 @@ def setModel(self, model, devsInRows=False, delayed=False, append=False, load=Tr (self._modelNames))[:100] + '...') if load: - self.trace('In TaurusGrid.setModel(%s,load=True): modelNames are %d' % ( - str(model)[:100] + '...', len(self._modelNames))) # ,self._modelNames)) + self.trace( + 'In TaurusGrid.setModel(%s,load=True): modelNames are %d' % ( + str(model)[:100] + '...', + len(self._modelNames))) # ,self._modelNames)) if devsInRows: self.setRowLabels( - ','.join(set(d.rsplit('/', 1)[0] for d in self._modelNames))) + ','.join( + set(d.rsplit('/', 1)[0] for d in self._modelNames))) self.create_widgets_table(self._modelNames) self.modelsQueue.put( (MethodModel(self.showRowFrame), self._show_row_frame)) self.modelsQueue.put( - (MethodModel(self.showColumnFrame), self._show_column_frame)) + ( + MethodModel(self.showColumnFrame), self._show_column_frame)) self.modelsQueue.put( (MethodModel(self.showOthers), self._show_others)) self.modelsQueue.put( - (MethodModel(self.showAttributeLabels), self._show_attr_labels)) + (MethodModel(self.showAttributeLabels), + self._show_attr_labels)) self.modelsQueue.put( - (MethodModel(self.showAttributeUnits), self._show_attr_units)) + (MethodModel(self.showAttributeUnits), + self._show_attr_units)) self.updateStyle() if not self.delayed: @@ -499,7 +521,8 @@ def setTitle(self, title): self.title_widget.hide() def parse_labels(self, text): - if any(text.startswith(c[0]) and text.endswith(c[1]) for c in [('{', '}'), ('(', ')'), ('[', ']')]): + if any(text.startswith(c[0]) and text.endswith(c[1]) for c in + [('{', '}'), ('(', ')'), ('[', ']')]): try: labels = eval(text) return labels @@ -525,7 +548,7 @@ def setRowLabels(self, rows): i, QtGui.QTableWidgetItem(section)) except Exception, e: self.debug("setRowLabels(): Exception! %s" % e) - # self.create_widgets_table(self._columnsNames) + # self.create_widgets_table(self._columnsNames) def getRowLabels(self): return ','.join(':'.join(c) for c in self.row_labels) @@ -546,7 +569,7 @@ def setColumnLabels(self, columns): i, QtGui.QTableWidgetItem(equipment)) except Exception, e: self.debug("setColumnLabels(): Exception! %s" % e) - # self.create_widgets_table(self._columnsNames) + # self.create_widgets_table(self._columnsNames) def getColumnLabels(self): return ','.join(':'.join(c) for c in self.column_labels) @@ -601,9 +624,9 @@ def showAttributeUnits(self, boolean): pass return self._show_attr_units - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # QT properties - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- model = QtCore.pyqtProperty("QStringList", getModel, setModel, @@ -622,30 +645,32 @@ def showAttributeUnits(self, boolean): TaurusBaseWidget.setUseParentModel, TaurusBaseWidget.resetUseParentModel) - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Write your own code here for your own widget properties def create_widgets_dict(self, models): from collections import defaultdict # recursive dictionary with 2 levels values = defaultdict(lambda: defaultdict(list)) - #domains = list(set(m.split('/')[0].upper())) - #families = list(set(m.split('/')[1].upper())) + # domains = list(set(m.split('/')[0].upper())) + # families = list(set(m.split('/')[1].upper())) if not self.row_labels: # Domains used by default self.row_labels = sorted( - list(set(m.split('/')[0].upper() for m in models if m.count('/') >= 2))) + list(set(m.split('/')[0].upper() for m in models if + m.count('/') >= 2))) self.row_labels = zip(self.row_labels, self.row_labels) if not self.column_labels: # Families used by default self.column_labels = sorted( - list(set(m.split('/')[1].upper() for m in models if m.count('/') >= 2))) + list(set(m.split('/')[1].upper() for m in models if + m.count('/') >= 2))) self.column_labels = zip(self.column_labels, self.column_labels) - # for m in models: + # for m in models: # if m.count('/')<2: - #self.warning('Wrong model cannot be added: %s'%m) + # self.warning('Wrong model cannot be added: %s'%m) # else: - #domain,family = m.upper().split('/')[:2] + # domain,family = m.upper().split('/')[:2] # values[domain][family].append(m) row_not_found, col_not_found = False, False @@ -705,7 +730,7 @@ def create_widgets_table(self, models): for row in self.rows: line = [] for col in self.columns: - #line.append(dct[row][col] if col in dct[row] else []) + # line.append(dct[row][col] if col in dct[row] else []) if col in dct[row]: line.append(dct[row][col]) else: @@ -728,13 +753,13 @@ def create_widgets_table(self, models): self.table.setVerticalHeaderItem( i, QtGui.QTableWidgetItem(section)) -# table.setAutoScroll(True) -# resize_mode = QtGui.QHeaderView.Stretch -# table.horizontalHeader().setSectionResizeMode(resize_mode) + # table.setAutoScroll(True) + # resize_mode = QtGui.QHeaderView.Stretch + # table.horizontalHeader().setSectionResizeMode(resize_mode) # table.verticalHeader().setSectionResizeMode(resize_mode) -# for row in range(len(self.rows)): -# table.setRowHeight(row,5+25*sum(len(dct[self.rows[row]][col]) for col in self.columns)) + # for row in range(len(self.rows)): + # table.setRowHeight(row,5+25*sum(len(dct[self.rows[row]][col]) for col in self.columns)) # for col in range(len(self.columns)): # table.setColumnWidth(col,300) @@ -746,7 +771,7 @@ def create_widgets_table(self, models): else: self.layout().addWidget(self.table, 1, 0) - #---------------------------------------------------------------------- + # ---------------------------------------------------------------------- # SECTION CHECKBOXES self.checkboxes_frame = self.create_frame_with_gridlayout() @@ -796,7 +821,8 @@ def create_widgets_table(self, models): checkbox.hide() else: checkbox.setChecked(True) - self.columns_frame.layout().addWidget(checkbox, layout_row, layout_col) + self.columns_frame.layout().addWidget(checkbox, layout_row, + layout_col) layout_col += 1 if layout_col == 3: layout_col = 0 @@ -835,14 +861,16 @@ def showOthers(self, boolean): self._show_others = boolean if hasattr(self, 'rows_frame'): for checkbox in self.rows_frame.children(): - if isinstance(checkbox, QtGui.QCheckBox) and checkbox.text() == 'Others': + if isinstance(checkbox, + QtGui.QCheckBox) and checkbox.text() == 'Others': if self._show_others: checkbox.show() else: checkbox.hide() if hasattr(self, 'columns_frame'): for checkbox in self.columns_frame.children(): - if isinstance(checkbox, QtGui.QCheckBox) and checkbox.text() == 'Others': + if isinstance(checkbox, + QtGui.QCheckBox) and checkbox.text() == 'Others': if self._show_others: checkbox.show() else: @@ -877,11 +905,13 @@ def build_table(self, values): table.horizontalHeader().setSectionResizeMode(QtGui.QHeaderView.Stretch) # table.verticalHeader().setSectionResizeMode(QtGui.QHeaderView.Stretch) # table.horizontalHeader().setSectionResizeMode(QtGui.QHeaderView.ResizeToContents) - table.verticalHeader().setSectionResizeMode(QtGui.QHeaderView.ResizeToContents) + table.verticalHeader().setSectionResizeMode( + QtGui.QHeaderView.ResizeToContents) return table - def build_widgets(self, values, show_labels=False, width=240, height=20, value_width=120): + def build_widgets(self, values, show_labels=False, width=240, height=20, + value_width=120): widgets_matrix = [] for row in values: widgets_row = [] @@ -962,7 +992,6 @@ def getQtDesignerPluginInfo(cls): class Delegate(QtGui.QItemDelegate): - def __init__(self, parent=None): QtGui.QItemDelegate.__init__(self, parent) @@ -990,6 +1019,7 @@ def sysargs_to_dict(defaults=[]): if __name__ == '__main__': import sys + if len(sys.argv) < 2: print "The format of the call is something like:" print '\t/usr/bin/python taurusgrid.py grid.pickle.file' From d077b289f5b12bb45c59145a5696910b6aac0f3d Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 15 Nov 2017 19:05:27 +0100 Subject: [PATCH 150/288] (minor) Use TaurusApplication instead of QApplication --- lib/taurus/qt/qtgui/table/taurusgrid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/table/taurusgrid.py b/lib/taurus/qt/qtgui/table/taurusgrid.py index 888ac60c3..6a4c5c0b2 100644 --- a/lib/taurus/qt/qtgui/table/taurusgrid.py +++ b/lib/taurus/qt/qtgui/table/taurusgrid.py @@ -1019,6 +1019,7 @@ def sysargs_to_dict(defaults=[]): if __name__ == '__main__': import sys + from taurus.qt.qtgui.application import TaurusApplication if len(sys.argv) < 2: print "The format of the call is something like:" @@ -1026,7 +1027,7 @@ def sysargs_to_dict(defaults=[]): print '\t/usr/bin/python taurusgrid.py "model=lt.*/VC.*/.*/((C*)|(P*)|(I*))" cols=IP,CCG,PNV rows=LT01,LT02 others=False rowframe=True colframe=False' exit() - app = QtGui.QApplication(sys.argv[0:1]) + app = TaurusApplication(sys.argv[0:1]) gui = TaurusGrid() try: # first try if argument is a file to be opened From 225c2c7a34bc975de8a1380a31bca2f79d7b0c1b Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 15 Nov 2017 19:14:12 +0100 Subject: [PATCH 151/288] (minor) PEP8 --- lib/taurus/core/tango/tangoattribute.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index c34dcda71..9e07e9f55 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -459,9 +459,10 @@ def poll(self, single=True, value=None, time=None, error=None): 'FILTER_OLD_TANGO_EVENTS', False) if (self.__attr_value is not None - and filter_old_event - and time is not None - and time < self.__attr_value.time.totime()): + and filter_old_event + and time is not None + and time < self.__attr_value.time.totime() + ): return self.__attr_value = value except PyTango.DevFailed, df: From 3b252f22a1bc5a1822b15fa086225d7345aeb23b Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 15 Nov 2017 22:43:54 +0100 Subject: [PATCH 152/288] Remove unwanted debug log --- lib/taurus/core/tango/tangoattribute.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index cea91180f..8f5158a1f 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -149,7 +149,6 @@ def __getattr__(self, name): @taurus4_deprecation(alt='.rvalue') def _get_value(self): """for backwards compat with taurus < 4""" - debug(repr(self)) try: return self.__fix_int(self.rvalue.magnitude) except AttributeError: From 5bf1a7feed6222b0ddc2f4a7755b66eef2adcd09 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 16 Nov 2017 09:21:04 +0100 Subject: [PATCH 153/288] Update CHANGELOG.md --- CHANGELOG.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 688b135b7..f6e872f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,17 +13,28 @@ develop branch) won't be reflected in this file. - TaurusMainWindow's "Change Tango Host" action (#379) ### Added +- User Interface to set custom formatters (#564) - Re-added `taurus.external.ordereddict` (#599) +- Option to ignore outdated Tango events (#559) +- Travis-built docs (not yet replacing the RTD ones) (#572) ### Changed - taurus.qt widgets can now be used without installing PyTango (#590) - Tango model name validators now always return FQDN instead of PQDN for the tango host (#488) -- Improved generated PDF doc (#525, #546, #548) (thanks @PhilLAL !) +- Improved docs (#525, #540, #546, #548) (thanks @PhilLAL !) +- Make spyder dependency optional (#556) ### Fixed -- Doc issues -- Deprecation warnings in taurus.core.util.event (#550) +- Wrong "missing units" warnings for non-numerical attributes (#580) +- Taurus3 backwards compatibility issues (#496, #550) +- False positives in taurus.check_dependencies (#612) +- Main Window Splash screen not showing (#595) +- TaurusTrend2DDialog not usable from designer (#597) +- TaurusLockButton icons (#598) +- Exception in TaurusCommandForm (#608) +- Exception in TaurusGrid (#609) +- [Many other issues](https://github.com/taurus-org/taurus/issues?utf8=%E2%9C%93&q=milestone%3AJan18%20label%3Abug%20) ### Removed - taurus.qt.qtgui.panel.taurusfilterpanel From 82d9a6001b23c461e25e06a09dcb85fa5c219ebc Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 16 Nov 2017 11:01:18 +0100 Subject: [PATCH 154/288] Solve bug in purge methods, document them --- lib/taurus/qt/qtcore/util/emitter.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 430bd8cf8..c3645006e 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -251,11 +251,10 @@ def clear(self): self.getQueue().get() self._done += 1 - def purge(obj): - # TODO: remove this method? - # This method seems buggy (it uses unreferenced `self`) - # AFAIK it is not called from anywere in Taurus and it is not part of - # a Qt API + def purge(self,obj): + """ + Remove a given object from all queues + """ nqueue = Queue() while not self.todo.empty(): i = self.todo.get() @@ -519,11 +518,10 @@ def clear(self): self.queue.get() # self.thread.clear() - def purge(obj): - # TODO: remove this method? - # This method seems buggy (it uses unreferenced `self`) - # AFAIK it is not called from anywere in Taurus and it is not part of - # a Qt API + def purge(self,obj): + """ + Remove a given object from all queues + """ nqueue = Queue() while not self.queue.empty(): i = self.queue.get() @@ -531,6 +529,7 @@ def purge(obj): nqueue.put(i) while not nqueue.empty(): self.queue.put(nqueue.get()) + self.next() def isRunning(self): return self._running From 3be238f506d3676fdc1dd568fe15a3c08aa08ccf Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 16 Nov 2017 11:01:56 +0100 Subject: [PATCH 155/288] Move SubscriptionState update to TangoAttribute --- lib/taurus/core/tango/tangoattribute.py | 11 ++++++++++- lib/taurus/qt/qtcore/util/emitter.py | 5 ++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 62f226021..78e0c39ec 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -481,7 +481,11 @@ def read(self, cache=True): self.fullname, self.__attr_err) raise self.__attr_err - if not cache or (self.__subscription_state in (SubscriptionState.PendingSubscribe, SubscriptionState.Unsubscribed) and not self.isPollingActive()): + if not cache or ( + self.__subscription_state in + (SubscriptionState.PendingSubscribe, + SubscriptionState.Unsubscribed) + and not self.isPollingActive()): try: dev = self.getParentObj() v = dev.read_attribute(self.getSimpleName()) @@ -625,6 +629,11 @@ def _call_dev_hw_subscribe_event(self, stateless=True): """ Executes event subscription on parent TangoDevice objectName """ attr_name = self.getSimpleName() + if stateless and (self.__subscription_state == + SubscriptionState.Unsubscribed + or self.isPollingActive()): + self.__subscription_state = SubscriptionState.PendingSubscribe + self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, self, [], stateless) # connects to self.push_event callback diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index c3645006e..d1ee5c1a8 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -395,11 +395,10 @@ def addModelObj(self, modelObj): if parent: proxy = parent.getDeviceProxy() if not proxy: - # self.debug('addModelObj(%s), proxy not available'%modelObj) + self.debug('addModelObj(%s), proxy not available'%modelObj) return + self._modelsQueue.put((modelObj._subscribeConfEvents,)) - modelObj.__subscription_state = SubscriptionState.PendingSubscribe - # modelObj._activatePolling() self.debug('addModelObj(%s)' % str(modelObj)) self._modelsQueue.put((modelObj._call_dev_hw_subscribe_event, (True,))) From 170c42834e1624e0cfee92045b4230f2462dcb09 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 16 Nov 2017 12:21:04 +0100 Subject: [PATCH 156/288] pep8 on qtcore.util.emitter --- lib/taurus/qt/qtcore/util/emitter.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index d1ee5c1a8..8940cbd79 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -126,7 +126,7 @@ class TaurusEmitterThread(Qt.QThread): + ``next()`` can be called also externally to ensure that the main queue is being processed. + the queue can be accessed externally using ``getQueue()`` - + ``getQueue().qsize()`` returns the number of remaining objects in queue. + + ``getQueue().qsize()`` returns number of remaining objects in queue. + while there are objects in queue the ``.next()`` method will override applications cursor. a call to ``next()`` with an empty queue will restore the original cursor. @@ -187,7 +187,6 @@ def __init__(self, parent=None, name='', queue=None, method=None, :param sleep: delay in ms before thread start :param polling: process actions at fix period (milliseconds) :param loopwait: wait N milliseconds between actions - """ Qt.QThread.__init__(self, parent) self.name = name @@ -251,7 +250,7 @@ def clear(self): self.getQueue().get() self._done += 1 - def purge(self,obj): + def purge(self, obj): """ Remove a given object from all queues """ @@ -395,7 +394,7 @@ def addModelObj(self, modelObj): if parent: proxy = parent.getDeviceProxy() if not proxy: - self.debug('addModelObj(%s), proxy not available'%modelObj) + self.debug('addModelObj(%s), proxy not available' % modelObj) return self._modelsQueue.put((modelObj._subscribeConfEvents,)) @@ -429,7 +428,6 @@ class SingletonWorker(): :param method: the method to be executed using each queue item as argument :param cursor: if True or QCursor a custom cursor is set while the Queue is not empty - """ _thread = None @@ -517,7 +515,7 @@ def clear(self): self.queue.get() # self.thread.clear() - def purge(self,obj): + def purge(self, obj): """ Remove a given object from all queues """ From 94a7d465f58dee41c8081c1585cef14a67385aac Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 16 Nov 2017 12:22:27 +0100 Subject: [PATCH 157/288] (minor) PEP8 --- lib/taurus/core/tango/tangoattribute.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 608cfc02e..5141be054 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -498,9 +498,9 @@ def read(self, cache=True): self.fullname, self.__attr_err) raise self.__attr_err - if not cache or (self.__subscription_state in ( - SubscriptionState.PendingSubscribe, - SubscriptionState.Unsubscribed) + if not cache or (self.__subscription_state in + (SubscriptionState.PendingSubscribe, + SubscriptionState.Unsubscribed) and not self.isPollingActive()): with self.__read_lock: try: From db0c27d2caac2686bba8272918e0c0aa469811c2 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 16 Nov 2017 12:55:14 +0100 Subject: [PATCH 158/288] Create subscribePendingEvents method This method allows to not use protected methods from DelayedSubscriber. --- lib/taurus/core/tango/tangoattribute.py | 17 +++++++++++------ lib/taurus/qt/qtcore/util/emitter.py | 15 +++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 608cfc02e..ce71cc8ec 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -651,11 +651,7 @@ def _call_dev_hw_subscribe_event(self, stateless=True): """ Executes event subscription on parent TangoDevice objectName """ attr_name = self.getSimpleName() - if stateless and (self.__subscription_state == - SubscriptionState.Unsubscribed - or self.isPollingActive()): - self.__subscription_state = SubscriptionState.PendingSubscribe - + self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, self, [], stateless) # connects to self.push_event callback @@ -723,7 +719,7 @@ def _subscribeConfEvents(self): except: self.debug("Error getting attribute configuration") self.traceback() - + def _unsubscribeConfEvents(self): # Careful in this method: This is intended to be executed in the cleanUp # so we should not access external objects from the factory, like the @@ -738,6 +734,15 @@ def _unsubscribeConfEvents(self): except PyTango.DevFailed, e: self.debug("Error trying to unsubscribe configuration events") self.trace(str(e)) + + def subscribePendingEvents(self): + """ Execute delayed event subscription + """ + if (self.__subscription_state == SubscriptionState.Unsubscribed + or self.isPollingActive()): + self.__subscription_state = SubscriptionState.PendingSubscribe + self._subscribeConfEvents() + self._call_dev_hw_subscribe_event(True) def push_event(self, event): """Method invoked by the PyTango layer when an event occurs. diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 8940cbd79..0326f01b4 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -355,18 +355,20 @@ def __init__(self, schema, parent=None, sleep=10000, pause=5, period=0): self._modelsQueue = Queue() self._modelsThread = TaurusEmitterThread(parent=parent, queue=self._modelsQueue, - method=self.modelSubscriber, + method=self._modelSubscriber, sleep=sleep, loopwait=pause, polling=period) self._modelsQueue.put((self.addUnsubscribedAttributes,)) self._modelsThread.start() - def modelSubscriber(self, method, args=[]): + def _modelSubscriber(self, method, args=[]): self.debug('modelSubscriber(%s,%s)' % (method, args)) return method(*args) def getUnsubscribedAttributes(self): + """Check all pending subscriptions in the current factory + """ attrs = [] items = self._factory.getExistingAttributes().items() for name, attr in items: @@ -378,18 +380,20 @@ def getUnsubscribedAttributes(self): return attrs def addUnsubscribedAttributes(self): + """Schedule subscription for all pending attributes + """ try: items = self.getUnsubscribedAttributes() if len(items): self.info('addUnsubscribedAttributes([%d])' % len(items)) for attr in items: - self.addModelObj(attr) + self._addModelObj(attr) self._modelsThread.next() self.info('Thread queue: [%d]' % (self._modelsQueue.qsize())) except: self.warning(traceback.format_exc()) - def addModelObj(self, modelObj): + def _addModelObj(self, modelObj): parent = modelObj.getParentObj() if parent: proxy = parent.getDeviceProxy() @@ -397,9 +401,8 @@ def addModelObj(self, modelObj): self.debug('addModelObj(%s), proxy not available' % modelObj) return - self._modelsQueue.put((modelObj._subscribeConfEvents,)) + self._modelsQueue.put((modelObj.subscribePendingEvents,)) self.debug('addModelObj(%s)' % str(modelObj)) - self._modelsQueue.put((modelObj._call_dev_hw_subscribe_event, (True,))) def cleanUp(self): self.trace("[DelayedSubscriber] cleanUp") From eb17e2ed33195bf08aa57673fe0f40016ffdeb87 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 16 Nov 2017 16:13:49 +0100 Subject: [PATCH 159/288] Cast to bool the value in _TaurusLedController TaurusLed in taurus 3 allows to use not boolean models, if its values are '0' or '1'. e.g. eval:1 or eval:0 Improve the behaviour of the TaurusLed casting the value to bool for non boolean models. --- lib/taurus/qt/qtgui/display/taurusled.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/display/taurusled.py b/lib/taurus/qt/qtgui/display/taurusled.py index 8e84b56d4..e28459a32 100644 --- a/lib/taurus/qt/qtgui/display/taurusled.py +++ b/lib/taurus/qt/qtgui/display/taurusled.py @@ -76,9 +76,9 @@ def value(self): fgRole = widget.fgRole value = None if fgRole == 'rvalue': - value = obj.rvalue + value = bool(obj.rvalue) elif fgRole == 'wvalue': - value = obj.wvalue + value = bool(obj.wvalue) elif fgRole == 'quality': return obj.quality From 7b38208f0f7f6d500b054653409f2d492be14614 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 16 Nov 2017 17:41:24 +0100 Subject: [PATCH 160/288] Catch disconnect exception in TaurusTrend --- lib/taurus/qt/qtgui/plot/taurustrend.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/plot/taurustrend.py b/lib/taurus/qt/qtgui/plot/taurustrend.py index f9e07976b..e40b0327c 100644 --- a/lib/taurus/qt/qtgui/plot/taurustrend.py +++ b/lib/taurus/qt/qtgui/plot/taurustrend.py @@ -1626,7 +1626,12 @@ def showArchivingWarning(self): # stop the scale change notification temporally (to avoid duplicate # warnings) self.setUseArchiving(False) - self.axisWidget(self.xBottom).scaleDivChanged.disconnect(self._scaleChangeWarning) + try: + self.axisWidget(self.xBottom).scaleDivChanged.disconnect( + self._scaleChangeWarning) + except: + self.warning('Failed to disconnect ScaleChangeWarning dialog') + # show a dialog dlg = Qt.QDialog(self) dlg.setModal(True) From efccba3234f72779a9f9c758fcfa301ce31bd7ef Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 17 Nov 2017 08:27:32 +0100 Subject: [PATCH 161/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6e872f73..1f9350e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ develop branch) won't be reflected in this file. - Re-added `taurus.external.ordereddict` (#599) - Option to ignore outdated Tango events (#559) - Travis-built docs (not yet replacing the RTD ones) (#572) +- TaurusLed now supports non-boolean attributes (#617) ### Changed - taurus.qt widgets can now be used without installing PyTango (#590) From bdb432cfbfff0b8e8b6686dc9d5b188a49d187ac Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Fri, 17 Nov 2017 15:14:05 +0100 Subject: [PATCH 162/288] Solve Command+ComboBox bug,\nsetParameters expected string but received int instead --- lib/taurus/qt/qtgui/panel/taurusform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index 88557bd9d..45cc4a641 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -676,7 +676,7 @@ def _updateCommandWidgets(self, *args): button.setParameters(self._defaultParameters[ c.cmd_name.lower()][0]) pwidget.editTextChanged.connect(button.setParameters) - pwidget.currentIndexChanged.connect(button.setParameters) + pwidget.currentIndexChanged['QString'].connect(button.setParameters) pwidget.activated.connect(button.setFocus) button.commandExecuted.connect(pwidget.rememberCurrentText) layout.addWidget(pwidget, row, 1) From 71581465d4dd1ced1f5822070dca5cca711e3443 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 21 Nov 2017 08:18:30 +0100 Subject: [PATCH 163/288] Improve TangoSchemeTest Add command to change the device state. --- .../core/tango/test/res/TangoSchemeTest | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest index 1a9730d00..6baf74e9e 100755 --- a/lib/taurus/core/tango/test/res/TangoSchemeTest +++ b/lib/taurus/core/tango/test/res/TangoSchemeTest @@ -453,6 +453,26 @@ class TangoSchemeTest(Device): self.set_state(DevState.ON) self._quality = AttrQuality.ATTR_VALID + + @command(dtype_in=str) + def ChangeState(self, state): + tango_state = {"alarm": DevState.ALARM, + "fault": DevState.FAULT, + "off": DevState.OFF, + "standby": DevState.STANDBY, + "close": DevState.CLOSE, + "init": DevState.INIT, + "on": DevState.ON, + "unknown": DevState.UNKNOWN, + "disable": DevState.DISABLE, + "insert": DevState.INSERT, + "open": DevState.OPEN, + "extract": DevState.EXTRACT, + "moving": DevState.MOVING, + "running": DevState.RUNNING + } + self.set_state(tango_state.get(state.lower(), DevState.UNKNOWN)) + @command(dtype_in=str) def ChangeShortScalarROQuality(self, quality): quality_states = {"valid": AttrQuality.ATTR_VALID, From 85cbf7f1ca377e7fb056c34b3b4393963074f80b Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 21 Nov 2017 11:18:34 +0100 Subject: [PATCH 164/288] Fix regression in TaurusLed (with state attrs) PR #617 introduced an unexpected issue when TaurusLed is given a state attribute as model (the state palette is disabled). Fix it by providing an specialized value method in the State controller. --- lib/taurus/qt/qtgui/display/taurusled.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/taurus/qt/qtgui/display/taurusled.py b/lib/taurus/qt/qtgui/display/taurusled.py index e28459a32..7ed559add 100644 --- a/lib/taurus/qt/qtgui/display/taurusled.py +++ b/lib/taurus/qt/qtgui/display/taurusled.py @@ -168,6 +168,18 @@ class _TaurusLedControllerState(_TaurusLedController): DevState.UNKNOWN: (False, "black", False), None: (False, "black", True)} + def value(self): + widget, obj = self.widget(), self.modelObj() + fgRole = widget.fgRole + value = None + if fgRole == 'rvalue': + value = obj.rvalue + elif fgRole == 'wvalue': + value = obj.wvalue + elif fgRole == 'quality': + value = obj.quality + return value + def usePreferedColor(self, widget): # never use prefered widget color. Use always the map return False From 67d2c2ce88f5b8c502be29f5f8252cc59b55e22d Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 21 Nov 2017 11:50:24 +0100 Subject: [PATCH 165/288] Extend bgRole API to allow arbitrary keys Fixes #626 by allowing to set bgRole to rvalue for state attributes (since the dev state palette already supports Tango states if Tango is installed) --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 12 ++++++++++++ lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py | 1 + 2 files changed, 13 insertions(+) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 195b9ba9c..6802885f5 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -320,6 +320,12 @@ def updateLabelBackground(ctrl, widget): bgItem = ctrl.state() elif bgRole == 'value': bgItem = ctrl.value() + else: + modelObj = widget.getModelObj() + try: + bgItem = modelObj.getFragmentObj(bgRole) + except: + widget.warning('Invalid bgRole "%s"', bgRole) bgBrush, fgBrush = palette.qbrush(bgItem) _updatePaletteColors(widget, bgBrush, fgBrush, frameBrush) else: @@ -334,6 +340,12 @@ def updateLabelBackground(ctrl, widget): bgItem = ctrl.state() elif bgRole == 'value': bgItem = ctrl.value() + else: + modelObj = widget.getModelObj() + try: + bgItem = modelObj.getFragmentObj(bgRole) + except: + widget.warning('Invalid bgRole "%s"', bgRole) color_ss = palette.qtStyleSheet(bgItem) ss = StyleSheetTemplate.format("rgba(255,255,255,128)", color_ss) widget.setStyleSheet(ss) diff --git a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py index a9ce1d76c..44683f2fc 100644 --- a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py +++ b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py @@ -104,6 +104,7 @@ def __init__(self, parent=None): fg_widget.setCurrentIndex(0) fg_widget.setEditable(True) bg_widget.setCurrentIndex(0) + bg_widget.setEditable(True) self.w_label = w self.w_model = model_widget From d38ff55f4f1f43fab2280304045455082bfe435d Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 21 Nov 2017 12:04:14 +0100 Subject: [PATCH 166/288] Remove state led and show state in label TaurusDevicePanel added a state led next to the state label as a workaround when transitioning from taurus3 to taurus4 (in order to show Tango state colors). This is no longer necessary after extending the bgRole API. Make the state label bgRole show Tango states and remove the LED. --- lib/taurus/qt/qtgui/panel/taurusdevicepanel.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py index 56c3612f3..ac3da8a4a 100644 --- a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py @@ -217,11 +217,8 @@ def __init__(self, parent=None, model=None, palette=None, bound=True): self._stateframe.layout().addWidget(Qt.QLabel('State'), 0, 0, Qt.Qt.AlignCenter) self._statelabel = TaurusLabel(self._stateframe) self._statelabel.setMinimumWidth(100) - self._statelabel.setBgRole('state') + self._statelabel.setBgRole('rvalue') self._stateframe.layout().addWidget(self._statelabel, 0, 1, Qt.Qt.AlignCenter) - self._state = TaurusLed(self._stateframe) - self._state.setShowQuality(False) - self._stateframe.layout().addWidget(self._state, 0, 2, Qt.Qt.AlignCenter) self._statusframe = Qt.QScrollArea(self) self._status = TaurusLabel(self._statusframe) @@ -342,7 +339,6 @@ def setModel(self, model, pixmap=None): qpixmap = getCachedPixmap(logo) self._image.setPixmap(qpixmap) - self._state.setModel(model + '/state') # TODO: Tango-centric if hasattr(self, '_statelabel'): self._statelabel.setModel( model + '/state') # TODO: Tango-centric @@ -411,7 +407,6 @@ def detach_recursive(obj): detach_recursive(self) try: self._label.setText('') - self._state.setModel('') if hasattr(self, '_statelabel'): self._statelabel.setModel('') self._status.setModel('') From 5448d31c3e7eee3dcd1df5372057880af6185ad0 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 21 Nov 2017 15:18:01 +0100 Subject: [PATCH 167/288] Update CHANGELOG.md --- CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f9350e26..52883a1d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,10 @@ develop branch) won't be reflected in this file. - TaurusTrend2DDialog not usable from designer (#597) - TaurusLockButton icons (#598) - Exception in TaurusCommandForm (#608) -- Exception in TaurusGrid (#609) +- Taurus4 regressions in: + - QComboBox (#623) + - TaurusTrend (#618) + - TaurusGrid (#609) - [Many other issues](https://github.com/taurus-org/taurus/issues?utf8=%E2%9C%93&q=milestone%3AJan18%20label%3Abug%20) ### Removed @@ -101,7 +104,7 @@ For a full log of commits since Jul16, run (in your git repo): `git log 4.0.1..4.0.3` ### Added -- Generic Attribute, Device and Authority getters in TaurusFactory` +- Generic Attribute, Device and Authority getters in TaurusFactory - spyder >=3 support (#343) - bumpversion support (for maintainers) (#347) - Contribution policy explicited in CONTRIBUTING.md @@ -127,7 +130,7 @@ For a full log of commits since Jul16, run (in your git repo): - Macrolistener (affects sardana) (#373) - Synoptics (#363) - TaurusValueLineEdit (#265) - - taurusgui.macrolistener` (#260) + - taurusgui.macrolistener (#260) - TaurusEditor (#343) - Bug causing high CPU usage in TaurusForms (#247) - Deprecation warnings in `TaurusWheelEdit` (#337) From f1e112a5273dd45801cde4d24fafc0f148c51c83 Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 21 Nov 2017 15:26:58 +0100 Subject: [PATCH 168/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f9350e26..071e53fbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ develop branch) won't be reflected in this file. - Option to ignore outdated Tango events (#559) - Travis-built docs (not yet replacing the RTD ones) (#572) - TaurusLed now supports non-boolean attributes (#617) +- Support for arbitrary bgRole in labels (#629) ### Changed - taurus.qt widgets can now be used without installing PyTango (#590) From 56692f35ff03428a618ab63640fb3a22884451cd Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 21 Nov 2017 16:08:45 +0100 Subject: [PATCH 169/288] Document TaurusLabel.setBgRole --- lib/taurus/qt/qtgui/display/tauruslabel.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 4fa4baf05..e015a7777 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -361,7 +361,20 @@ def getBgRole(self): return self._bgRole def setBgRole(self, bgRole): - self._bgRole = str(bgRole).lower() + """ + Set the background role. The label background will be set according + to the current palette and the role. Valid roles are: + - 'none' : no background + - 'state' a color depending on the device state + - 'quality' a color depending on the attribute quality + - 'value' a color depending on the rvalue of the attribute + - a color based on the value of an arbitrary + member of the model object (warning: experimental feature!) + + .. warning:: the support is still experimental + and its API may change in future versions + """ + self._bgRole = str(bgRole) self.controllerUpdate() def resetBgRole(self): From cf1cad7b87f77519a3be6f5ebf90f2d0d84a6f3d Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 21 Nov 2017 16:08:58 +0100 Subject: [PATCH 170/288] Document TaurusLabel.setBgRole --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 6802885f5..ba218af6c 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -306,7 +306,7 @@ def updateLabelBackground(ctrl, widget): if ctrl.usePalette(): widget.setAutoFillBackground(True) - if bgRole in ('', 'none'): + if bgRole in ('', 'none', 'None'): transparentBrush = Qt.QBrush(Qt.Qt.transparent) frameBrush = transparentBrush bgBrush, fgBrush = transparentBrush, Qt.QBrush(Qt.Qt.black) @@ -329,7 +329,7 @@ def updateLabelBackground(ctrl, widget): bgBrush, fgBrush = palette.qbrush(bgItem) _updatePaletteColors(widget, bgBrush, fgBrush, frameBrush) else: - if bgRole in ('', 'none'): + if bgRole in ('', 'none', 'None'): ss = StyleSheetTemplate.format("rgba(0,0,0,0)", "") else: bgItem, palette = None, QT_DEVICE_STATE_PALETTE From d218369cb63ee29481e6a043dfc7d3ea62c346ae Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 21 Nov 2017 16:19:13 +0100 Subject: [PATCH 171/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52883a1d5..f90133ab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ develop branch) won't be reflected in this file. - Option to ignore outdated Tango events (#559) - Travis-built docs (not yet replacing the RTD ones) (#572) - TaurusLed now supports non-boolean attributes (#617) +- `ChangeState` command in TangoSchemeTest DS (#628) ### Changed - taurus.qt widgets can now be used without installing PyTango (#590) From 8617bbd1b24355f3738eccbcff23c30f5b66af9f Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 21 Nov 2017 17:11:25 +0100 Subject: [PATCH 172/288] Avoid to import queue from python3 The Queue module has been renamed to queue in Python 3. The emitter module imports the "Queue" module from python 3, since taurus uses python 2.7 this module should be imported from python 2 version. Change the import. --- lib/taurus/qt/qtcore/util/emitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 0326f01b4..8c691973a 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -27,7 +27,7 @@ TaurusDevTree widgets """ -from queue import Queue, Empty +from Queue import Queue, Empty import traceback from functools import partial from collections import Iterable From 162993627070fd10d5285857940c3af176db1a42 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 22 Nov 2017 09:07:21 +0100 Subject: [PATCH 173/288] (minor) (doc) Change queue import in docstring example --- lib/taurus/qt/qtcore/util/emitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index 8c691973a..d107cd029 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -143,7 +143,7 @@ class TaurusEmitterThread(Qt.QThread): .. code-block:: python #Applying TaurusEmitterThread to an existing class: - from queue import Queue + from Queue import Queue from functools import partial def modelSetter(args): From d2ae128fae0defdd79bccd05fb7afc511df71bdf Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 22 Nov 2017 09:33:24 +0100 Subject: [PATCH 174/288] Add "value" entry to bgRole Comobobox The setBgRole API allows "value" as a bgRole, but the combobox in the demo does not include it. Add this option. --- lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py index 44683f2fc..4daa3570b 100644 --- a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py +++ b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py @@ -91,7 +91,7 @@ def __init__(self, parent=None): fg_widget.addItems(["", "rvalue", "rvalue.magnitude", "rvalue.units", "wvalue", "wvalue.magnitude", "wvalue.units", "state", "quality", "none"]) - bg_widget.addItems(["quality", "state", "none"]) + bg_widget.addItems(["quality", "state", "value", "none"]) model_widget.textChanged.connect(w.setModel) fg_widget.currentIndexChanged[str].connect(w.setFgRole) From 10e44e39c200e7cdeb5e3bb4400cd81771fcfd50 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 22 Nov 2017 09:43:41 +0100 Subject: [PATCH 175/288] Add TODO informing of experimental status of the new API --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index ba218af6c..e0aeb2be1 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -321,6 +321,8 @@ def updateLabelBackground(ctrl, widget): elif bgRole == 'value': bgItem = ctrl.value() else: + # TODO: this is an *experimental* extension of the bgRole API + # added in v 4.1.2-alpha. It may change in future versions modelObj = widget.getModelObj() try: bgItem = modelObj.getFragmentObj(bgRole) @@ -341,6 +343,8 @@ def updateLabelBackground(ctrl, widget): elif bgRole == 'value': bgItem = ctrl.value() else: + # TODO: this is an *experimental* extension of the bgRole API + # added in v 4.1.2-alpha. It may change in future versions modelObj = widget.getModelObj() try: bgItem = modelObj.getFragmentObj(bgRole) From cd795f1f9d4a01c26eff0e7e67813ecb55be3728 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 22 Nov 2017 09:49:20 +0100 Subject: [PATCH 176/288] Use old API instead of the experimental one Using 'rvalue' implies using the new experimental API for bgRole, which in this case is not needed since we can use the existing one. Use the "stable" API --- lib/taurus/qt/qtgui/panel/taurusdevicepanel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py index ac3da8a4a..bd19f7b76 100644 --- a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py @@ -217,7 +217,7 @@ def __init__(self, parent=None, model=None, palette=None, bound=True): self._stateframe.layout().addWidget(Qt.QLabel('State'), 0, 0, Qt.Qt.AlignCenter) self._statelabel = TaurusLabel(self._stateframe) self._statelabel.setMinimumWidth(100) - self._statelabel.setBgRole('rvalue') + self._statelabel.setBgRole('value') self._stateframe.layout().addWidget(self._statelabel, 0, 1, Qt.Qt.AlignCenter) self._statusframe = Qt.QScrollArea(self) From 0a8f5dc3b8909f18181fb563091bb285051a2246 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Wed, 22 Nov 2017 16:33:05 +0100 Subject: [PATCH 177/288] Add import ascii file option to taurusplot --- lib/taurus/qt/qtgui/plot/taurusplot.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/plot/taurusplot.py index 984cd9808..fb48d799a 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/plot/taurusplot.py @@ -3690,6 +3690,8 @@ def main(): help="interprete X values as either timestamps (t) or numbers (n). Accepted values: t|n (e is also accepted as a synonim of n)") parser.add_option("--config", "--config-file", dest="config_file", default=None, help="use the given config file for initialization") + parser.add_option("--import", "--import-file", dest="import_file", default=None, + help="import the given ascii file into the plot") parser.add_option("--export", "--export-file", dest="export_file", default=None, help="use the given file to as output instead of showing the plot") parser.add_option("--window-name", dest="window_name", @@ -3712,8 +3714,12 @@ def main(): if options.config_file is not None: w.loadConfig(options.config_file) + if options.import_file is not None: + w.importAscii([options.import_file],xcol=0) + if models: w.setModel(models) + if options.export_file is not None: curves = dict.fromkeys(w.trendSets.keys(), 0) @@ -3737,7 +3743,7 @@ def exportIfAllCurves(curve, trend=w, counters=curves): # show the widget w.show() # if no models are passed, show the data import dialog - if len(models) == 0 and options.config_file is None: + if len(models) == 0 and options.config_file is None and options.import_file is None: w.showDataImportDlg() sys.exit(app.exec_()) From 15fa116f0aab94afb85927b58b2283b52c492fc2 Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 23 Nov 2017 10:30:49 +0100 Subject: [PATCH 178/288] Rename import ascii file option of taurusplot - Rename `--import-file` to `--import-ascii` - Remove `--import` alias of `--import-ascii` - PEP8 changes --- lib/taurus/qt/qtgui/plot/taurusplot.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/plot/taurusplot.py index fb48d799a..7163a5ddf 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/plot/taurusplot.py @@ -3690,7 +3690,7 @@ def main(): help="interprete X values as either timestamps (t) or numbers (n). Accepted values: t|n (e is also accepted as a synonim of n)") parser.add_option("--config", "--config-file", dest="config_file", default=None, help="use the given config file for initialization") - parser.add_option("--import", "--import-file", dest="import_file", default=None, + parser.add_option("--import-ascii", dest="import_ascii", default=None, help="import the given ascii file into the plot") parser.add_option("--export", "--export-file", dest="export_file", default=None, help="use the given file to as output instead of showing the plot") @@ -3714,8 +3714,8 @@ def main(): if options.config_file is not None: w.loadConfig(options.config_file) - if options.import_file is not None: - w.importAscii([options.import_file],xcol=0) + if options.import_ascii is not None: + w.importAscii([options.import_ascii], xcol=0) if models: w.setModel(models) @@ -3743,7 +3743,10 @@ def exportIfAllCurves(curve, trend=w, counters=curves): # show the widget w.show() # if no models are passed, show the data import dialog - if len(models) == 0 and options.config_file is None and options.import_file is None: + if (len(models) == 0 + and options.config_file is None + and options.import_ascii is None + ): w.showDataImportDlg() sys.exit(app.exec_()) From 59ee889e93ae41fd74417ebc053b7fee873a519f Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 23 Nov 2017 12:07:01 +0100 Subject: [PATCH 179/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a6966f4d..10e1f1bf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ develop branch) won't be reflected in this file. - Travis-built docs (not yet replacing the RTD ones) (#572) - TaurusLed now supports non-boolean attributes (#617) - Support for arbitrary bgRole in labels (#629) +- `--import-ascii` option in `taurusplot` launcher (#632) - `ChangeState` command in TangoSchemeTest DS (#628) ### Changed From ea5286a75230fbe24fb53c7dcbd4dcd8bd0a1bfd Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 23 Nov 2017 14:26:29 +0100 Subject: [PATCH 180/288] Bump version 4.1.2-alpha to 4.2.0-alpha --- .bumpversion.cfg | 2 +- lib/taurus/core/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 8a4497824..e697e7cd7 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 4.1.2-alpha +current_version = 4.2.0-alpha parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index a45e9db55..7bb23d3a5 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -48,7 +48,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.1.2-alpha' +version = '4.2.0-alpha' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: From 54c48a32d307148d4ebb848c59ea9b7749c2ba40 Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 23 Nov 2017 14:26:43 +0100 Subject: [PATCH 181/288] Bump version 4.2.0-alpha to 4.2.1-alpha --- .bumpversion.cfg | 2 +- lib/taurus/core/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e697e7cd7..4a4c32441 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 4.2.0-alpha +current_version = 4.2.1-alpha parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index 7bb23d3a5..1880b04a7 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -48,7 +48,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.2.0-alpha' +version = '4.2.1-alpha' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: From 4bdfe38e447bfb60d4bad2d136c23192a185255e Mon Sep 17 00:00:00 2001 From: cpascual Date: Fri, 24 Nov 2017 12:34:34 +0100 Subject: [PATCH 182/288] (doc) Add tip about the new panel dlg UI Add tip about the "other..." button in the "new panel" dialog --- doc/source/users/ui/taurusgui.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/users/ui/taurusgui.rst b/doc/source/users/ui/taurusgui.rst index bd31d19f3..e75cea59f 100644 --- a/doc/source/users/ui/taurusgui.rst +++ b/doc/source/users/ui/taurusgui.rst @@ -155,6 +155,10 @@ bar (or selecting `Panels->New Panel...`). This will open a dialog offering a catalog of different panel types and options for your new panel. Once accepted, the new panel will appear floating, ready to be docked to the main window. +.. tip:: if you are interested in creating a panel different from those offered in + the catalog, you can do so by using the "other..." button and manually + selecting the module and widget class of your choice. + .. figure:: /_static/taurusgui-newpanel01.png :align: center From a9d67e7d33a56ff0fadd6a8fbfb93ed0bfb0bdc1 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 29 Nov 2017 13:25:37 +0100 Subject: [PATCH 183/288] Deploy docs only if on taurus-org/taurus repo Travis tests are failing on forks because of the doc deployment step which requires an auth token. Fix by deploying only makes when building from the official repo. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cada3ebe2..368b7805d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ script: - travis_wait docker exec -t taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" - docker exec -t taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" # deploy sphinx-built docs to taurus-doc repo - - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" ]]; then + - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" && "$TRAVIS_REPO_SLUG" == "taurus-org/taurus" ]]; then pip install doctr ; docker exec taurus-test /bin/bash -c "touch /taurus/build/sphinx/html/.nojekyll" ; if [[ "${TRAVIS_BRANCH}" == "develop" ]]; then From 312883f767ffaf0d76883de7bc8e273472ab472a Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 12 Dec 2017 14:13:05 +0100 Subject: [PATCH 184/288] Add full model name to attribute description The attribute description is used by widgets to generate the tooltip. Add full model name info to the description object --- lib/taurus/core/taurusattribute.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/taurusattribute.py b/lib/taurus/core/taurusattribute.py index 9ca970276..c59aeb968 100644 --- a/lib/taurus/core/taurusattribute.py +++ b/lib/taurus/core/taurusattribute.py @@ -290,7 +290,8 @@ def getDisplayDescription(self, cache=True): def getDisplayDescrObj(self, cache=True): name = self.getLabel(cache=cache) - obj = [('name', name)] + obj = [('name', name), + ('model', self.getFullName() or '')] descr = self.description if descr: _descr = descr.replace("<", "<").replace(">", ">") From 3dbe3e53a2d96ead4575c7060f409c99bfdc16f4 Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 12 Dec 2017 14:17:45 +0100 Subject: [PATCH 185/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10e1f1bf7..653932977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ develop branch) won't be reflected in this file. - Support for arbitrary bgRole in labels (#629) - `--import-ascii` option in `taurusplot` launcher (#632) - `ChangeState` command in TangoSchemeTest DS (#628) +- Model info in widget tooltips (#640) ### Changed - taurus.qt widgets can now be used without installing PyTango (#590) From 72495f6e2bfa286988d4ad92ee390538dff6b715 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 13 Dec 2017 12:13:41 +0100 Subject: [PATCH 186/288] Convert gui_scripts to console_scripts (Fixes #541) Launchers do not show output in windows if they are implemented as gui_scripts. Change them to console_scripts. --- setup.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 4b9590a6a..804201dd4 100644 --- a/setup.py +++ b/setup.py @@ -81,10 +81,6 @@ def get_release_info(): console_scripts = [ 'taurustestsuite = taurus.test.testsuite:main', - # TODO: taurusdoc, -] - -gui_scripts = [ 'taurusconfigbrowser = taurus.qt.qtgui.panel.taurusconfigeditor:main', 'taurusplot = taurus.qt.qtgui.plot.taurusplot:main', 'taurustrend = taurus.qt.qtgui.plot.taurustrend:main', @@ -99,11 +95,11 @@ def get_release_info(): 'taurustrend2d = taurus.qt.qtgui.extra_guiqwt.taurustrend2d:taurusTrend2DMain', 'taurusiconcatalog = taurus.qt.qtgui.icon.catalog:main', 'taurusdemo = taurus.qt.qtgui.panel.taurusdemo:main', + # TODO: taurusdoc, ] entry_points = {'console_scripts': console_scripts, - 'gui_scripts': gui_scripts, - } +} classifiers = [ 'Development Status :: 3 - Alpha', From ca72fc107a33876ff9847aec2ccab6758ce68393 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 13 Dec 2017 17:01:45 +0100 Subject: [PATCH 187/288] Fix: Taurysform does not allow to change the label The PR #496 added a regresion in the taurusform change label feature. Fix it by: - In DefaultLabelWidget class: - revert the changes in _BCK_COMPAT_TAGS - fix getDisplayValue - Fix taurusValue onChangeLabelConfig method. - Reimplement taurusLabel setText to use setPemanentText. - Fix displayValue to allow format strings Fix #641 --- lib/taurus/qt/qtgui/display/tauruslabel.py | 22 +++++++++++++--- lib/taurus/qt/qtgui/panel/taurusvalue.py | 29 +++++++++++++--------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index e015a7777..538c510f5 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -98,7 +98,7 @@ def _updateForeground(self, label): self._trimmedText = self._shouldTrim(label, text) if self._trimmedText: text = "..." - label.setText(text) + label.setText_(text) def _shouldTrim(self, label, text): if not label.autoTrim: @@ -419,9 +419,17 @@ def getPermanentText(self): return self._permanentText def setPermanentText(self, text): - self.setText(text) + self.setText_(text) self._permanentText = text + def setText_(self, text): + """Method to expose QLabel.setText""" + Qt.QLabel.setText(self, text) + + def setText(self, text): + """Reimplementation of setText to set permanentText""" + self.setPermanentText(text) + def setAutoTrim(self, trim): self._autoTrim = trim self.controllerUpdate() @@ -448,9 +456,15 @@ def resetAutoTrim(self): self.setAutoTrim(self.DefaultAutoTrim) def displayValue(self, v): + """Reimplementation of displayValue for TaurusLabel""" if self._permanentText is not None: - return self._permanentText - return TaurusBaseWidget.displayValue(self, v) + value = self._permanentText + else: + value = TaurusBaseWidget.displayValue(self, v) + + attr = self.getModelObj() + dev = attr.getParent() + return value.format(dev=dev, attr=attr) @classmethod def getQtDesignerPluginInfo(cls): diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 24dce5877..5262028f4 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -91,7 +91,7 @@ def setModel(self, model): try: config = self.taurusValueBuddy().getLabelConfig() except Exception: - config = 'label' + config = '{attr.label}' elementtype = self.taurusValueBuddy().getModelType() fullname = self.taurusValueBuddy().getModelObj().getFullName() if elementtype == TaurusElementType.Attribute: @@ -107,14 +107,15 @@ def setModel(self, model): TaurusLabel.setModel(self, None) self.setText(devName) - _BCK_COMPAT_TAGS = {'': 'name', - '': 'fullname', - '': 'parentObj.name', - '': 'parentObj.name', - '': 'parentObj.fullname', + _BCK_COMPAT_TAGS = {'': '{attr.name}', + '': '{attr.fullname}', + '': '{dev.name}', + '': '{dev.name}', + '': '{dev.fullname}', } def getDisplayValue(self, cache=True, fragmentName=None): + """Reimplementation of getDisplayValue""" if self.modelObj is None or fragmentName is None: return self.getNoneValue() # support bck-compat tags @@ -122,7 +123,8 @@ def getDisplayValue(self, cache=True, fragmentName=None): new = self._BCK_COMPAT_TAGS.get(old, '{attr.%s}' % old) self.deprecated(dep=old, alt=new) fragmentName = fragmentName.replace(old, new) - return TaurusLabel.getDisplayValue(self, cache, fragmentName) + + return TaurusLabel.displayValue(self, fragmentName) def sizeHint(self): return Qt.QSize(Qt.QLabel.sizeHint(self).width(), 18) @@ -347,7 +349,7 @@ def __init__(self, parent=None, designMode=False, customWidgetMap=None): self._allowWrite = True self._minimumHeight = None - self._labelConfig = 'label' + self._labelConfig = '{attr.label}' self._labelText = None self.setModifiableByUser(False) @@ -636,6 +638,7 @@ def getCustomWidgetMap(self): def onChangeLabelConfig(self): keys = ['{attr.label}', '{attr.name}', '{attr.fullname}', '{dev.name}', '{dev.fullname}'] + try: current = keys.index(self.labelConfig) except: @@ -650,7 +653,7 @@ def onChangeLabelConfig(self): labelConfig, ok = Qt.QInputDialog.getItem(self, 'Change Label', msg, keys, current, True) if ok: - self.labelConfig = str(labelConfig) + self.labelConfig = labelConfig def onChangeReadWidget(self): classnames = ['None', 'Auto'] + \ @@ -809,7 +812,7 @@ def updateLabelWidget(self): self._labelWidget.setModel(self.getFullModelName()) if self._labelText is not None: - self._labelWidget.setPermanentText(self._labelText) + self._labelWidget.setText(self._labelText) def updateReadWidget(self): # get the class for the widget and replace it if necessary @@ -1233,19 +1236,21 @@ def setLabelConfig(self, config): """ # backwards compatibility: this method used to work for setting # an arbitrary text to the label widget + self._labelConfig = config try: self.getModelFragmentObj(config) + self._labelWidget._permanentText = None except Exception: try: self._labelWidget.setText(config) except: self.debug("Setting permanent text to the label widget failed") return - self._labelConfig = config + self.updateLabelWidget() def resetLabelConfig(self): - self._labelConfig = 'label' + self._labelConfig = '{attr.label}' self.updateLabelWidget() def getSwitcherClass(self): From 9d6f3d688ac5d2ee221d60652f04917c1035823b Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 14 Dec 2017 15:52:35 +0100 Subject: [PATCH 188/288] Protect code against exceptions Protect TaurusLabel displayValue for any exception during the set format. --- lib/taurus/qt/qtgui/display/tauruslabel.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 538c510f5..812bacd4a 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -457,14 +457,22 @@ def resetAutoTrim(self): def displayValue(self, v): """Reimplementation of displayValue for TaurusLabel""" - if self._permanentText is not None: - value = self._permanentText - else: + if self._permanentText is None: value = TaurusBaseWidget.displayValue(self, v) + else: + value = self._permanentText attr = self.getModelObj() dev = attr.getParent() - return value.format(dev=dev, attr=attr) + + try: + v = value.format(dev=dev, attr=attr) + except Exception as e: + self.warning( + "Error formatting display (%r). Reverting to raw string", e) + v = value + + return v @classmethod def getQtDesignerPluginInfo(cls): From d8e16f1c731570266e8f566d1e47dd7ef0c13970 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 19 Dec 2017 16:28:37 +0100 Subject: [PATCH 189/288] Reload wvalue if no previous value was read --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 7552e40ea..1866d21d3 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -122,6 +122,12 @@ def updateStyle(self): TaurusBaseWritableWidget.updateStyle(self) value = self.getValue() + if value is None and self.hasPendingOperations(): + try: + value = self.getModelValueObj().wvalue + self.setValue(value) + except: + value = None if value is None or not self.isTextValid(): # invalid value From d8c4d4c3e179a9eef182912996756318c0d1663f Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Tue, 19 Dec 2017 16:56:41 +0100 Subject: [PATCH 190/288] Solve bug in traces --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 1866d21d3..5a05c5267 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -237,7 +237,7 @@ def getValue(self): elif model_type == DataType.Bytes: return bytes(text) else: - raise TypeError('Unsupported model type "%s"', model_type) + raise TypeError('Unsupported model type "%s"'%model_type) except Exception, e: self.warning('Cannot return value for "%s". Reason: %r', text, e) return None From 4d158f5f88a963ced9ad791de2434f6011807453 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 20 Dec 2017 10:01:29 +0100 Subject: [PATCH 191/288] PEP8 --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 5a05c5267..c06c5e796 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -237,7 +237,7 @@ def getValue(self): elif model_type == DataType.Bytes: return bytes(text) else: - raise TypeError('Unsupported model type "%s"'%model_type) + raise TypeError('Unsupported model type "%s"' % model_type) except Exception, e: self.warning('Cannot return value for "%s". Reason: %r', text, e) return None From a2c67880a4ff0f67026fc39e4ca13993592874d5 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Wed, 20 Dec 2017 23:04:34 -0800 Subject: [PATCH 192/288] travis workaround --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 368b7805d..306dfe8e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ before_install: - sleep 10 script: + - python -c "import fcntl; fcntl.fcntl(1, fcntl.F_SETFL, 0)" - set -e - docker exec -t taurus-test /bin/bash -c "cd /taurus ; python setup.py install" - travis_wait docker exec -t taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" From 300cf903b8db540b615cbe9a93187fb4058d0d26 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 21 Dec 2017 09:53:00 +0100 Subject: [PATCH 193/288] Improve TangoSchemeTest device server Add two new attributes, "float_scalar_poll" and "float_scalar_evt", the first one has been configured to emit Period events (the tango polling has been enabled), and the second one has been configured to emit Change events. There are two new commands to configure the periods of these events: "SetEventPeriod" to set fixed change events, and "ChangePollingPeriod" to change the polling period of "float_scalar_poll" attribute. --- .../core/tango/test/res/TangoSchemeTest | 80 ++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest index 6baf74e9e..735582c1c 100755 --- a/lib/taurus/core/tango/test/res/TangoSchemeTest +++ b/lib/taurus/core/tango/test/res/TangoSchemeTest @@ -23,11 +23,13 @@ ## ############################################################################# import numpy -from time import time +from time import (time, sleep) from PyTango import DeviceProxy, AttrWriteType, DevState, AttrQuality from PyTango.server import Device, DeviceMeta, attribute, run, command +from threading import Thread + class TangoSchemeTest(Device): """ @@ -107,6 +109,10 @@ class TangoSchemeTest(Device): 'short_scalar_nu': dict(dtype=numpy.int16), 'float_scalar': dict(unit=default_unit["float"], dtype=numpy.float32), + 'float_scalar_poll': dict(unit=default_unit["float"], + dtype=numpy.float32, + polling_period=500), + 'double_scalar': dict(unit=default_unit["float"], dtype=numpy.float64), 'string_scalar': dict(dtype=str), @@ -254,8 +260,36 @@ class TangoSchemeTest(Device): uchar_image = attribute(access=AttrWriteType.READ_WRITE, **attrs['uchar_image']) + + # EVENTS + float_scalar_poll = attribute(access=AttrWriteType.READ, + **attrs['float_scalar_poll']) + float_scalar_evt = attribute(access=AttrWriteType.READ_WRITE, + **attrs['float_scalar']) + + + _float_scalar_evt = numpy.random.random() * 100 + # READ ONLY METHODS # SCALARS + def read_float_scalar_poll(self): + return numpy.random.random() * 100 + + def read_float_scalar_evt(self): + return self._float_scalar_evt + + def write_float_scalar_evt(self, value): + self._float_scalar_evt = value + self.push_change_event('float_scalar_evt', self._float_scalar_evt) + + @command(dtype_in=numpy.float32) + def PushEvent(self, data): + # push_change_event (self, attr_name, data, time_stamp, quality, + # dim_x = 1, dim_y = 0) + self.push_change_event('float_scalar_evt', data, time.time(), + PyTango.AttrQuality.ATTR_VALID, 1, 0) + + def read_boolean_scalar_ro(self): return self.default_rvalue['bool'] @@ -447,12 +481,54 @@ class TangoSchemeTest(Device): def write_uchar_image(self, v): self._uchar_image = v + def _update_loop(self): + while True: + sleep(0.1) + try: + if self.use_fixed_time: + sleeptime = self.sleeptime + else: + sleeptime = numpy.random.random()*10 + + self._float_scalar_evt = numpy.random.random() * 100 + + self.push_change_event('float_scalar_evt', + self._float_scalar_evt, time(), + AttrQuality.ATTR_VALID) + sleep(sleeptime) + except Exception as e: + print 'Exception in update loop', e + sleep(1) + def init_device(self): # To setUp the state Device.init_device(self) self.set_state(DevState.ON) self._quality = AttrQuality.ATTR_VALID - + self.set_change_event('float_scalar_evt', True, False) + # event thread + self.use_fixed_time = False + self.sleeptime = None + self.t = Thread(target=self._update_loop) + self.t.setDaemon(True) + self.t.start() + + @command(dtype_in=int) + def ChangePollingPeriod(self, period): + """ Change the polling period of float_scalar_poll attribute""" + self.poll_attribute('float_scalar_poll', period) + + + @command(dtype_in=float) + def SetEventPeriod(self, period): + """ Set a fixed event period for float_scalar_evt attribute. + If period is 0, the event period will be random. + """ + if period == 0: + self.use_fixed_time = False + else: + self.use_fixed_time = True + self.sleeptime = period @command(dtype_in=str) def ChangeState(self, state): From a96fe63df4ad19b0bafb53d9b0bd7c15ffcac3a8 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 21 Dec 2017 16:43:31 +0100 Subject: [PATCH 194/288] Correct indentation --- lib/taurus/core/tango/tangoattribute.py | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 563624647..1134488e2 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -447,24 +447,24 @@ def poll(self, single=True, value=None, time=None, error=None): if single: self.read(cache=False) else: - value = self.decode(value) - self.__attr_err = error - if self.__attr_err: - raise self.__attr_err - # Avoid "valid-but-outdated" notifications - # if FILTER_OLD_TANGO_EVENTS is enabled - # and the given timestamp is older than the timestamp - # of the cached value - filter_old_event = getattr(tauruscustomsettings, - 'FILTER_OLD_TANGO_EVENTS', - False) - if (self.__attr_value is not None - and filter_old_event - and time is not None - and time < self.__attr_value.time.totime() - ): - return - self.__attr_value = value + value = self.decode(value) + self.__attr_err = error + if self.__attr_err: + raise self.__attr_err + # Avoid "valid-but-outdated" notifications + # if FILTER_OLD_TANGO_EVENTS is enabled + # and the given timestamp is older than the timestamp + # of the cached value + filter_old_event = getattr(tauruscustomsettings, + 'FILTER_OLD_TANGO_EVENTS', + False) + if (self.__attr_value is not None + and filter_old_event + and time is not None + and time < self.__attr_value.time.totime() + ): + return + self.__attr_value = value except PyTango.DevFailed, df: self.__subscription_event.set() self.debug("Error polling: %s" % df[0].desc) From 0904f1c16a1a0036aba39a4f2696f4aadb0a6946 Mon Sep 17 00:00:00 2001 From: cpascual Date: Thu, 21 Dec 2017 17:09:20 +0100 Subject: [PATCH 195/288] Avoid using random values in TaurusSchemeTest - Change the behaviour of float_scalar_poll and float_scalar_evt attrs to flip between +value and -value instead of returning random values - Use a predictable sequence of sleep times instead of random ones when use_fixed_time = False --- .../core/tango/test/res/TangoSchemeTest | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest index 735582c1c..9337aafee 100755 --- a/lib/taurus/core/tango/test/res/TangoSchemeTest +++ b/lib/taurus/core/tango/test/res/TangoSchemeTest @@ -268,12 +268,13 @@ class TangoSchemeTest(Device): **attrs['float_scalar']) - _float_scalar_evt = numpy.random.random() * 100 + _float_scalar_evt = default_rvalue['float'] + _float_scalar_poll = default_rvalue['float'] # READ ONLY METHODS # SCALARS def read_float_scalar_poll(self): - return numpy.random.random() * 100 + self._float_scalar_poll *= -1 def read_float_scalar_evt(self): return self._float_scalar_evt @@ -485,17 +486,17 @@ class TangoSchemeTest(Device): while True: sleep(0.1) try: - if self.use_fixed_time: - sleeptime = self.sleeptime - else: - sleeptime = numpy.random.random()*10 + if not self.use_fixed_time: + self.sleeptime *= 2 + if self.sleeptime > 5: + self.sleeptime = 0.1 - self._float_scalar_evt = numpy.random.random() * 100 + self._float_scalar_evt *= -1 self.push_change_event('float_scalar_evt', self._float_scalar_evt, time(), AttrQuality.ATTR_VALID) - sleep(sleeptime) + sleep(self.sleeptime) except Exception as e: print 'Exception in update loop', e sleep(1) @@ -522,7 +523,7 @@ class TangoSchemeTest(Device): @command(dtype_in=float) def SetEventPeriod(self, period): """ Set a fixed event period for float_scalar_evt attribute. - If period is 0, the event period will be random. + If period is 0, the event period will vary from 0.1 to 5 """ if period == 0: self.use_fixed_time = False From 2d62ca4b3b1ef6197c2de2793fd22c1de6890582 Mon Sep 17 00:00:00 2001 From: mkomel Date: Tue, 9 Jan 2018 15:24:42 +0100 Subject: [PATCH 196/288] Fix for issue #660: added bolded text when displayed value differs from applied value in a TaurusValueComboBox --- lib/taurus/qt/qtgui/input/tauruscombobox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/input/tauruscombobox.py b/lib/taurus/qt/qtgui/input/tauruscombobox.py index f74216515..1cb82e372 100644 --- a/lib/taurus/qt/qtgui/input/tauruscombobox.py +++ b/lib/taurus/qt/qtgui/input/tauruscombobox.py @@ -124,7 +124,7 @@ def setValue(self, value): def updateStyle(self): '''reimplemented from :class:`TaurusBaseWritableWidget`''' if self.hasPendingOperations(): - self.setStyleSheet('TaurusValueComboBox {color: blue; }') + self.setStyleSheet('TaurusValueComboBox {color: blue; font-weight: bold;}') else: self.setStyleSheet('TaurusValueComboBox {}') super(TaurusValueComboBox, self).updateStyle() From 63cb2365567cadd53e0b662fc07e6c0bcff349fc Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 9 Jan 2018 15:25:50 +0100 Subject: [PATCH 197/288] Avoid warnings on non-specified tango units Consider "No unit" as a synonim of PyTango.constants.UnitNotSpec, and silently consider the corresponding value as unitless (note that this may potentially hide a configuration problem where an attribute that should define units has left them undefined). --- lib/taurus/core/tango/tangoattribute.py | 7 +++++-- lib/taurus/core/tango/util/tango_taurus.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 1134488e2..5675d4282 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -1025,13 +1025,16 @@ def _tango_data_type(self): return self._pytango_attrinfoex.data_type def _unit_from_tango(self, unit): - if unit == PyTango.constants.UnitNotSpec: + # silently treat unit-not-defined as unitless + # TODO: consider logging that unit-not-defined is treated as unitless + # TODO: See https://github.com/taurus-org/taurus/issues/584 + if unit == PyTango.constants.UnitNotSpec or unit == "No unit": unit = None try: return UR.parse_units(unit) except Exception as e: # TODO: Maybe we could dynamically register the unit in the UR - msg = 'Unknown unit "%s (will be treated as unitless)"' + msg = 'Unknown unit "%s" (will be treated as unitless)' if self.__already_warned_unit == unit: self.debug(msg, unit) else: diff --git a/lib/taurus/core/tango/util/tango_taurus.py b/lib/taurus/core/tango/util/tango_taurus.py index e4d9e040e..5dcf17913 100644 --- a/lib/taurus/core/tango/util/tango_taurus.py +++ b/lib/taurus/core/tango/util/tango_taurus.py @@ -117,14 +117,14 @@ def unit_from_tango(unit): from taurus import deprecated deprecated(dep='unit_from_tango', rel='4.0.4', alt="pint's parse_units") - if unit == PyTango.constants.UnitNotSpec: + if unit == PyTango.constants.UnitNotSpec or unit == "No unit": unit = None try: return UR.parse_units(unit) except (UndefinedUnitError, UnicodeDecodeError): # TODO: Maybe we could dynamically register the unit in the UR from taurus import warning - warning('Unknown unit "%s (will be treated as unitless)"', unit) + warning('Unknown unit "%s" (will be treated as unitless)', unit) return UR.parse_units(None) From 1a166989cd0d094ce3a12373645bbebabd7224ba Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 9 Jan 2018 16:03:40 +0100 Subject: [PATCH 198/288] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 653932977..3f458b067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ develop branch) won't be reflected in this file. - Model info in widget tooltips (#640) ### Changed +- Treat unit="No unit" as unitless in Tango attributes (#662) - taurus.qt widgets can now be used without installing PyTango (#590) - Tango model name validators now always return FQDN instead of PQDN for the tango host (#488) From 2f94c31fc500bf15f7bb5d2466effb5ffe4277f3 Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 9 Jan 2018 16:08:04 +0100 Subject: [PATCH 199/288] PEP8 --- lib/taurus/qt/qtgui/input/tauruscombobox.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/input/tauruscombobox.py b/lib/taurus/qt/qtgui/input/tauruscombobox.py index 1cb82e372..f6d7a7992 100644 --- a/lib/taurus/qt/qtgui/input/tauruscombobox.py +++ b/lib/taurus/qt/qtgui/input/tauruscombobox.py @@ -124,7 +124,8 @@ def setValue(self, value): def updateStyle(self): '''reimplemented from :class:`TaurusBaseWritableWidget`''' if self.hasPendingOperations(): - self.setStyleSheet('TaurusValueComboBox {color: blue; font-weight: bold;}') + self.setStyleSheet( + 'TaurusValueComboBox {color: blue; font-weight: bold;}') else: self.setStyleSheet('TaurusValueComboBox {}') super(TaurusValueComboBox, self).updateStyle() From 008c3d8562f029d1d9802c1dfb602b1d44db8bfd Mon Sep 17 00:00:00 2001 From: cpascual Date: Tue, 9 Jan 2018 16:20:41 +0100 Subject: [PATCH 200/288] Improve _taurusValueComboboxTest Use 2 comboboxes instead of one for better demo of pending operations. --- lib/taurus/qt/qtgui/input/tauruscombobox.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruscombobox.py b/lib/taurus/qt/qtgui/input/tauruscombobox.py index f6d7a7992..0a639266b 100644 --- a/lib/taurus/qt/qtgui/input/tauruscombobox.py +++ b/lib/taurus/qt/qtgui/input/tauruscombobox.py @@ -388,15 +388,22 @@ def _taurusValueComboboxTest(): ('name3', 3) ] a = TaurusApplication() - w = TaurusValueComboBox() - w.setModel(model) - w.addValueNames(names) - #w.autoApply = True + w = Qt.QWidget() + w.setLayout(Qt.QVBoxLayout()) + + cs = [] + for c in range(2): + c = TaurusValueComboBox() + c.setModel(model) + c.addValueNames(names) + w.layout().addWidget(c) + cs.append(c) + #c.autoApply = True w.show() return a.exec_() if __name__ == '__main__': import sys - # main = _taurusValueComboboxTest #uncomment to test TaurusValueCombobox - main = _taurusAttrListTest # uncomment to testtaurusAttrList + main = _taurusValueComboboxTest #uncomment to test TaurusValueCombobox + # main = _taurusAttrListTest # uncomment to testtaurusAttrList sys.exit(main()) From 93c1f413ecd06f22f7de7b9c34640419e93ed797 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 10 Jan 2018 09:10:32 +0100 Subject: [PATCH 201/288] minor PEP8 and syntax corrections --- .../core/tango/test/res/TangoSchemeTest | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest index 9337aafee..e5d8e0a8a 100755 --- a/lib/taurus/core/tango/test/res/TangoSchemeTest +++ b/lib/taurus/core/tango/test/res/TangoSchemeTest @@ -81,7 +81,6 @@ class TangoSchemeTest(Device): 'string': 'hello world', 'uchar': 1, } - default_unit = {'int': "mm", 'float': "mm"} # x10 default_rvalue @@ -102,7 +101,6 @@ class TangoSchemeTest(Device): 'float': [-default_rvalue["float"] * 3, default_rvalue["float"] * 3] } - attrs = {'bool_scalar': dict(dtype=bool), 'short_scalar': dict(unit=default_unit["int"], dtype=numpy.int16), @@ -112,7 +110,6 @@ class TangoSchemeTest(Device): 'float_scalar_poll': dict(unit=default_unit["float"], dtype=numpy.float32, polling_period=500), - 'double_scalar': dict(unit=default_unit["float"], dtype=numpy.float64), 'string_scalar': dict(dtype=str), @@ -150,7 +147,6 @@ class TangoSchemeTest(Device): max_dim_x=MAXDIMX, max_dim_y=MAXDIMY), 'MIXEDcase': dict(dtype=str) } - extra_cfg = {'short_scalar': dict(min_value=default_ranges['int'][0], max_value=default_ranges['int'][1], min_alarm=default_alarms['int'][0], @@ -188,7 +184,8 @@ class TangoSchemeTest(Device): **attrs['string_scalar']) uchar_scalar_ro = attribute(access=AttrWriteType.READ, **attrs['uchar_scalar']) - # SPECTRUMS + + # SPECTRA boolean_spectrum_ro = attribute(access=AttrWriteType.READ, **attrs['bool_spectrum']) short_spectrum_ro = attribute(access=AttrWriteType.READ, @@ -197,6 +194,7 @@ class TangoSchemeTest(Device): **attrs['float_spectrum']) string_spectrum_ro = attribute(access=AttrWriteType.READ, **attrs['string_spectrum']) + # IMAGES uchar_image_ro = attribute(access=AttrWriteType.READ, **attrs['uchar_image']) @@ -233,7 +231,8 @@ class TangoSchemeTest(Device): **attrs['string_scalar']) uchar_scalar = attribute(access=AttrWriteType.READ_WRITE, **attrs['uchar_scalar']) - # SPECTRUM + + # SPECTRA boolean_spectrum = attribute(access=AttrWriteType.READ_WRITE, **attrs['bool_spectrum']) short_spectrum = attribute(access=AttrWriteType.READ_WRITE, @@ -246,6 +245,7 @@ class TangoSchemeTest(Device): **attrs['string_spectrum']) uchar_spectrum = attribute(access=AttrWriteType.READ_WRITE, **attrs['uchar_spectrum']) + # IMAGES boolean_image = attribute(access=AttrWriteType.READ_WRITE, **attrs['bool_image']) @@ -260,14 +260,12 @@ class TangoSchemeTest(Device): uchar_image = attribute(access=AttrWriteType.READ_WRITE, **attrs['uchar_image']) - # EVENTS float_scalar_poll = attribute(access=AttrWriteType.READ, **attrs['float_scalar_poll']) float_scalar_evt = attribute(access=AttrWriteType.READ_WRITE, **attrs['float_scalar']) - _float_scalar_evt = default_rvalue['float'] _float_scalar_poll = default_rvalue['float'] @@ -290,7 +288,6 @@ class TangoSchemeTest(Device): self.push_change_event('float_scalar_evt', data, time.time(), PyTango.AttrQuality.ATTR_VALID, 1, 0) - def read_boolean_scalar_ro(self): return self.default_rvalue['bool'] @@ -312,7 +309,7 @@ class TangoSchemeTest(Device): def read_uchar_scalar_ro(self): return self.default_rvalue['uchar'] - # SPECTRUMS + # SPECTRA def read_boolean_spectrum_ro(self): return [self.default_rvalue['bool']] * self.DIMX @@ -394,7 +391,7 @@ class TangoSchemeTest(Device): def write_uchar_scalar(self, v): self._uchar_scalar = v - # SPECTRUMS + # SPECTRA def read_boolean_spectrum(self): return getattr(self, '_boolean_spectrum', [self.default_rvalue['bool']] * self.DIMX) @@ -519,7 +516,6 @@ class TangoSchemeTest(Device): """ Change the polling period of float_scalar_poll attribute""" self.poll_attribute('float_scalar_poll', period) - @command(dtype_in=float) def SetEventPeriod(self, period): """ Set a fixed event period for float_scalar_evt attribute. From 473a242919dd65bfc6c50a33061847c26a15d871 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 10 Jan 2018 09:10:58 +0100 Subject: [PATCH 202/288] Fix import error PyTango is referenced in PusEvent method without being imported. Fix it --- lib/taurus/core/tango/test/res/TangoSchemeTest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest index e5d8e0a8a..8deda674a 100755 --- a/lib/taurus/core/tango/test/res/TangoSchemeTest +++ b/lib/taurus/core/tango/test/res/TangoSchemeTest @@ -286,7 +286,7 @@ class TangoSchemeTest(Device): # push_change_event (self, attr_name, data, time_stamp, quality, # dim_x = 1, dim_y = 0) self.push_change_event('float_scalar_evt', data, time.time(), - PyTango.AttrQuality.ATTR_VALID, 1, 0) + AttrQuality.ATTR_VALID, 1, 0) def read_boolean_scalar_ro(self): return self.default_rvalue['bool'] From f7da5ea776863f3ab9048d4d780a59cfd7395fac Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 10 Jan 2018 09:14:30 +0100 Subject: [PATCH 203/288] Update Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 653932977..d10a1ce17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ develop branch) won't be reflected in this file. - TaurusLed now supports non-boolean attributes (#617) - Support for arbitrary bgRole in labels (#629) - `--import-ascii` option in `taurusplot` launcher (#632) -- `ChangeState` command in TangoSchemeTest DS (#628) +- State and event support in TangoSchemeTest DS (#628, #655) - Model info in widget tooltips (#640) ### Changed From 09d3343331ad6acbb41fd4429e019b83f2be9c67 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 10 Jan 2018 12:01:24 +0100 Subject: [PATCH 204/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d10a1ce17..3a483dfd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ develop branch) won't be reflected in this file. - TaurusTrend2DDialog not usable from designer (#597) - TaurusLockButton icons (#598) - Exception in TaurusCommandForm (#608) +- Launchers not showing output on MS Windows (#644) - Taurus4 regressions in: - QComboBox (#623) - TaurusTrend (#618) From 97b6cb29aaf94fb7ce8c7201ce0fa98663c840a5 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 10 Jan 2018 12:50:47 +0100 Subject: [PATCH 205/288] Fix infinite recursion problem in TaurusValueCombobox TaurusValueCombobox connects its own applied signal to the writeValue method, which in turn emits the applied signal, causing an infinite loop. Break the loop by not doing the connection (and calling writeValue directly when needed). Fixes #651 --- lib/taurus/qt/qtgui/input/tauruscombobox.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruscombobox.py b/lib/taurus/qt/qtgui/input/tauruscombobox.py index 0a639266b..9baebcbca 100644 --- a/lib/taurus/qt/qtgui/input/tauruscombobox.py +++ b/lib/taurus/qt/qtgui/input/tauruscombobox.py @@ -70,14 +70,12 @@ def preAttach(self): '''reimplemented from :class:`TaurusBaseWritableWidget`''' TaurusBaseWritableWidget.preAttach(self) self.currentIndexChanged.connect(self.writeIndexValue) - self.applied.connect(self.writeValue) def postDetach(self): '''reimplemented from :class:`TaurusBaseWritableWidget`''' TaurusBaseWritableWidget.postDetach(self) try: self.currentIndexChanged.disconnect(self.writeIndexValue) - self.applied.disconnect(self.writeValue) except TypeError: # In new style-signal if a signal is disconnected without # previously was connected it, it raises a TypeError @@ -136,16 +134,19 @@ def updateStyle(self): @Qt.pyqtSlot(int, name='currentIndexChanged') def writeIndexValue(self, index): - '''slot called to emit a valueChanged signal when the currentIndex is changed''' + """slot called to emit a valueChanged signal when the currentIndex is + changed (and trigger a write if AutoApply is enabled) + """ self.emitValueChanged() if self.getAutoApply(): - self.applied.emit() + self.writeValue() def keyPressEvent(self, event): - '''reimplemented to emit an 'applied()' signal when Enter (or Return) - key is pressed''' + """reimplemented to trigger a write when Enter (or Return) key is + pressed + """ if event.key() in [Qt.Qt.Key_Return, Qt.Qt.Key_Enter]: - self.applied.emit() + self.writeValue() event.accept() else: return Qt.QComboBox.keyPressEvent(self, event) @@ -380,7 +381,7 @@ def _taurusValueComboboxTest(): from taurus.qt.qtgui.application import TaurusApplication """tests TaurusValueCombobox """ # model = sys.argv[1] - model = 'sys/tg_test/1/short_scalar' + names = [ ('name0', 0), ('name1', 1), @@ -392,13 +393,13 @@ def _taurusValueComboboxTest(): w.setLayout(Qt.QVBoxLayout()) cs = [] - for c in range(2): + for model in ['sys/tg_test/1/short_scalar'] * 2: c = TaurusValueComboBox() c.setModel(model) c.addValueNames(names) w.layout().addWidget(c) cs.append(c) - #c.autoApply = True + # c.autoApply = True w.show() return a.exec_() From 37bf8634dd24d1dc91dea074b709a6233200ab03 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 10 Jan 2018 17:28:30 +0100 Subject: [PATCH 206/288] Comment in the code why comparing to "No unit" --- lib/taurus/core/tango/tangoattribute.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 5675d4282..da0d2b074 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -1027,7 +1027,10 @@ def _tango_data_type(self): def _unit_from_tango(self, unit): # silently treat unit-not-defined as unitless # TODO: consider logging that unit-not-defined is treated as unitless - # TODO: See https://github.com/taurus-org/taurus/issues/584 + # TODO: See https://github.com/taurus-org/taurus/issues/584 and + # https://github.com/taurus-org/taurus/pull/662 + # The extra comparison to "No unit" is necessary where + # server/database runs Tango 7 or 8 and client runs higher versions. if unit == PyTango.constants.UnitNotSpec or unit == "No unit": unit = None try: From ff07f4b92f62b95e0905b5bab6818a15ecba7bf4 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 11 Jan 2018 11:05:14 +0100 Subject: [PATCH 207/288] Mark the Delayed Subcription API as "experimental" --- lib/taurus/core/tango/tangofactory.py | 12 +++++++++++- lib/taurus/qt/qtcore/util/emitter.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py index fd2668a75..e3fa9c994 100644 --- a/lib/taurus/core/tango/tangofactory.py +++ b/lib/taurus/core/tango/tangofactory.py @@ -185,12 +185,22 @@ def get_default_tango_host(self): return self._default_tango_host def set_tango_subscribe_enabled(self,value): - """ If True, enables event subscribing on TangoAttribute objects + """ If True, enables event subscribing on TangoAttribute objects + + .. warning:: This method belongs to a "Delayed Event Subscription" API + added in v.4.2.1-alpha as an *experimental* feature. This API + may not be stable and/or it may be removed in a future release + (even on a minor version change) """ self._tango_subscribe_enabled = value def is_tango_subscribe_enabled(self): """ Returns the current tango_subscribe_enabled status + + .. warning:: This method belongs to a "Delayed Event Subscription" API + added in v.4.2.1-alpha as an *experimental* feature. This API + may not be stable and/or it may be removed in a future release + (even on a minor version change) """ return self._tango_subscribe_enabled diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index d107cd029..bf63142fc 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -344,6 +344,11 @@ class DelayedSubscriber(Logger): DelayedSubscriber(schema) will use a TaurusEmitterThread to perform a thread safe delayed subscribing on all Attributes of a given Taurus Schema that has not been previously subscribed. + + .. warning:: This class belongs to a "Delayed Event Subscription" API added + in v.4.2.1-alpha as an *experimental* feature. This API may + not be stable and/or it may be removed in a future release + (even on a minor version change) """ def __init__(self, schema, parent=None, sleep=10000, pause=5, period=0): From 0bfd80975f62b8e4cbd0da1a951074db6d6eb113 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 11 Jan 2018 11:07:45 +0100 Subject: [PATCH 208/288] Update CHANGELOG.md Review all changes since Jul17 up to now. --- CHANGELOG.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f4d1b1dc..a81b23982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,13 +22,14 @@ develop branch) won't be reflected in this file. - `--import-ascii` option in `taurusplot` launcher (#632) - State and event support in TangoSchemeTest DS (#628, #655) - Model info in widget tooltips (#640) +- (experimental) Delayed event subscription API (#605, #593) ### Changed - Treat unit="No unit" as unitless in Tango attributes (#662) - taurus.qt widgets can now be used without installing PyTango (#590) - Tango model name validators now always return FQDN instead of PQDN - for the tango host (#488) -- Improved docs (#525, #540, #546, #548) (thanks @PhilLAL !) + for the tango host (#488, #589) +- Improved docs (#525, #540, #546, #548, #636) (thanks @PhilLAL !) - Make spyder dependency optional (#556) ### Fixed @@ -37,13 +38,14 @@ develop branch) won't be reflected in this file. - False positives in taurus.check_dependencies (#612) - Main Window Splash screen not showing (#595) - TaurusTrend2DDialog not usable from designer (#597) -- TaurusLockButton icons (#598) +- Missing icons in buttons (#583, #598) - Exception in TaurusCommandForm (#608) - Launchers not showing output on MS Windows (#644) -- Taurus4 regressions in: - - QComboBox (#623) +- Various issues with input widgets (#623, #661, #650) +- Regressions in: - TaurusTrend (#618) - TaurusGrid (#609) + - TaurusGUI edit with `taurusgui --new-gui` (#532) - [Many other issues](https://github.com/taurus-org/taurus/issues?utf8=%E2%9C%93&q=milestone%3AJan18%20label%3Abug%20) ### Removed From ed04ae5c3bda55a53f875b6f30c6f619bce3dd66 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 11 Jan 2018 11:19:16 +0100 Subject: [PATCH 209/288] Bump version 4.2.1-alpha to 4.3.0-alpha --- .bumpversion.cfg | 2 +- lib/taurus/core/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 4a4c32441..5db361996 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 4.2.1-alpha +current_version = 4.3.0-alpha parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index 1880b04a7..45709596a 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -48,7 +48,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.2.1-alpha' +version = '4.3.0-alpha' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: From 921ec6202def445135c1c9c1aa3dde2ab2bad54e Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 15 Jan 2018 08:50:54 +0100 Subject: [PATCH 210/288] Improve tauruscustomsettings docs --- doc/source/devel/tauruscustomsettings.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/devel/tauruscustomsettings.rst b/doc/source/devel/tauruscustomsettings.rst index 274d0cbaf..8247a8c7c 100644 --- a/doc/source/devel/tauruscustomsettings.rst +++ b/doc/source/devel/tauruscustomsettings.rst @@ -7,7 +7,10 @@ Taurus custom settings Taurus provides a module located at its root directory called -`tauruscustomsettings`. +`tauruscustomsettings` which stores global configuration options. +It can be modified permanently so that it affects all applications +(use with care), or accessed at run time for setting options for +the current execution. .. automodule:: taurus.tauruscustomsettings :members: From 7f43b4d33f52baee31f7444049a1e2748b2c0193 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 15 Jan 2018 10:29:09 +0100 Subject: [PATCH 211/288] Improve documentation of Formatter API --- lib/taurus/qt/qtgui/base/taurusbase.py | 31 ++++++++++++++++++-------- lib/taurus/tauruscustomsettings.py | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 1e2cdc99b..99dbed449 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -680,20 +680,24 @@ def displayValue(self, v): :function:`defaultFormatter`, which makes use of :attribute:`defaultFormatDict`. - In order to customize the formatting behaviour, one can change - :attribute:`defaultFormatDict` or :attribute:`FORMAT` directly - at class level, or use :method:`setFormat` to alter the - format string of an specific instance + In order to customize the formatting behaviour, one can + use :method:`setFormat` to alter the formatter of an specific instance + (recommended) or change :attribute:`defaultFormatDict` or + :attribute:`FORMAT` directly at class level. - `FORMAT` can be set to a python format string [1] or a callable + The formatter can be set to a python format string [1] or a callable that returns a python format string. If a callable is used, it will be called with the following keyword arguments: - dtype: the data type of the value to be formatted - - basecomponent: the affected widget + - basecomponent: the affected widget The following are some examples for customizing the formatting: + - Change the format for widget instance `foo`: + + foo.setFormat("{:.2e}") + - Change FORMAT for all widgets (using a string): TaurusBaseComponent.FORMAT = "{:.2e}" @@ -705,9 +709,13 @@ def baseFormatter(dtype=None, basecomponent=None, **kwargs): TaurusLabel.FORMAT = baseFormatter - - Use the defaultFormatter but modify the format string for dtype=str: + - Use the defaultFormatDict but modify the format string for dtype=str: + + TaurusLabel.defaultFormatDict.update({"str": "{!r}"}) - TaurusBaseComponent.defaultFormatDict.update({"str": "{!r}"}) + .. seealso:: :attribute:`tauruscustomsettings.DEFAULT_FORMATTER`, + `--default-formatter` option in :class:`TaurusApplication`, + :meth:`TaurusBaseWidget.onSetFormatter` [1] https://docs.python.org/2/library/string.html @@ -1295,7 +1303,12 @@ def showFormatterDlg(self): return None def onSetFormatter(self): - """ Slot to be called by setFormatter action""" + """Slot to allow interactive setting of the Formatter. + + .. seealso:: :meth:`TaurusBaseWidget.showFormatterDlg`, + :meth:`TaurusBaseComponent.displayValue`, + :attribute:`tauruscustomsettings.DEFAULT_FORMATTER` + """ format = self.showFormatterDlg() if format is not None: self.debug( diff --git a/lib/taurus/tauruscustomsettings.py b/lib/taurus/tauruscustomsettings.py index f0803ad17..20af7d416 100755 --- a/lib/taurus/tauruscustomsettings.py +++ b/lib/taurus/tauruscustomsettings.py @@ -77,7 +77,7 @@ # Custom formatter. Taurus widgets use a default formatter based on the # attribute type, but sometimes a custom formatter is needed. -# IMPORTANT: seting this option in this file will affect to ALL widgets +# IMPORTANT: setting this option in this file will affect ALL widgets # of ALL applications (which is probably **not** what you want, since it # may have unexpected effects in some applications). # Consider using the API for modifying this on a per-widget or per-class From f5dda43fd8161e9b561e504e23916f932c06c2ab Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 16 Jan 2018 10:32:42 +0100 Subject: [PATCH 212/288] Fix wrong license header in emitter.py The header of emitter.py is outdated and licenses it as GPL. Change to LGPL to be coherent with the rest of Taurus code released by ALBA. --- lib/taurus/qt/qtcore/util/emitter.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index bf63142fc..df6d4c235 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -4,23 +4,24 @@ ## # This file is part of Taurus ## -# http://www.tango-controls.org/static/tau/latest/doc/html/index.html +# http://taurus-scada.org ## -# (copyleft) CELLS / ALBA Synchrotron, Bellaterra, Spain +# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain ## -# This 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 3 of the License, or +# 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. ## -# This software is distributed in the hope that it will be useful, +# 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 General Public License for more details. +# GNU Lesser General Public License for more details. ## -# You should have received a copy of the GNU General Public License -# along with this program; if not, see . -########################################################################### +# You should have received a copy of the GNU Lesser General Public License +# along with Taurus. If not, see . +## +############################################################################# """ emitter.py: This module provides a task scheduler used by TaurusGrid and From d42089ee4cd207f922685a5ae61bc561b2b7176c Mon Sep 17 00:00:00 2001 From: reszelaz Date: Wed, 17 Jan 2018 16:02:25 +0100 Subject: [PATCH 213/288] Cosinder DevVoid in Tango type to string translation DevVoid is a special type in Tango. It may indicate no input/output arguments of a command or be simply the default data type of the AttributeInfoEx class. Add it to the translation dictionary of Tango to other types. --- lib/taurus/core/tango/enums.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/taurus/core/tango/enums.py b/lib/taurus/core/tango/enums.py index 1d2755319..4f3e43376 100644 --- a/lib/taurus/core/tango/enums.py +++ b/lib/taurus/core/tango/enums.py @@ -70,6 +70,7 @@ # 'API_BadConfigurationProperty') FROM_TANGO_TO_NUMPY_TYPE = { + PyTango.DevVoid: None, PyTango.DevBoolean: numpy.bool8, PyTango.DevUChar: numpy.uint8, PyTango.DevShort: numpy.short, @@ -84,6 +85,7 @@ } FROM_TANGO_TO_STR_TYPE = { + PyTango.DevVoid: None, PyTango.DevBoolean: 'bool8', PyTango.DevUChar: 'uint8', PyTango.DevShort: 'short', From e6bba2fcb537ea7e3bc74dd7caf8de939c3cf591 Mon Sep 17 00:00:00 2001 From: Sergi Blanch-Torne Date: Thu, 18 Jan 2018 14:29:31 +0100 Subject: [PATCH 214/288] prepare the test that verifies the bug existence --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 70 ++++++++++++------ lib/taurus/qt/qtgui/input/taurusspinbox.py | 81 ++++++++++++++++----- 2 files changed, 110 insertions(+), 41 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index c06c5e796..4f08dc9fb 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -23,11 +23,9 @@ ## ############################################################################# -"""This module provides a set of basic taurus widgets based on QLineEdit""" - -__all__ = ["TaurusValueLineEdit"] - -__docformat__ = 'restructuredtext' +""" +This module provides a set of basic taurus widgets based on QLineEdit +""" import sys import numpy @@ -37,6 +35,10 @@ from taurus.qt.qtgui.util import PintValidator from taurus.core import DataType, DataFormat, TaurusEventType +__all__ = ["TaurusValueLineEdit"] + +__docformat__ = 'restructuredtext' + class TaurusValueLineEdit(Qt.QLineEdit, TaurusBaseWritableWidget): @@ -72,7 +74,8 @@ def _updateValidator(self, value): if units != val.units: val.setUnits(units) - # @TODO Other validators can be configured for other types (e.g. with string lengths, tango names,...) + # @TODO Other validators can be configured for other types + # (e.g. with string lengths, tango names,...) else: self.setValidator(None) self.debug("Validator disabled") @@ -93,9 +96,9 @@ def _onEditingFinished(self): if self._autoApply: self.writeValue() - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # TaurusBaseWritableWidget overwriting - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def notifyValueChanged(self, *args): '''reimplement to avoid autoapply on every partial edition''' self.emitValueChanged() @@ -140,7 +143,8 @@ def updateStyle(self): color, weight = 'black', 'normal' # also check alarms (if applicable) modelObj = self.getModelObj() - if modelObj and modelObj.type in [DataType.Integer, DataType.Float]: + if modelObj and modelObj.type in [DataType.Integer, + DataType.Float]: min_, max_ = modelObj.alarms if ((min_ is not None and value < min_) or (max_ is not None and value > max_)): @@ -258,23 +262,25 @@ def getQtDesignerPluginInfo(cls): ret['icon'] = "designer:lineedit.png" return ret - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # QT properties - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ model = Qt.pyqtProperty("QString", TaurusBaseWritableWidget.getModel, TaurusBaseWritableWidget.setModel, TaurusBaseWritableWidget.resetModel) - useParentModel = Qt.pyqtProperty("bool", TaurusBaseWritableWidget.getUseParentModel, - TaurusBaseWritableWidget.setUseParentModel, - TaurusBaseWritableWidget.resetUseParentModel) + useParentModel = \ + Qt.pyqtProperty("bool", TaurusBaseWritableWidget.getUseParentModel, + TaurusBaseWritableWidget.setUseParentModel, + TaurusBaseWritableWidget.resetUseParentModel) autoApply = Qt.pyqtProperty("bool", TaurusBaseWritableWidget.getAutoApply, TaurusBaseWritableWidget.setAutoApply, TaurusBaseWritableWidget.resetAutoApply) - forcedApply = Qt.pyqtProperty("bool", TaurusBaseWritableWidget.getForcedApply, + forcedApply = Qt.pyqtProperty("bool", + TaurusBaseWritableWidget.getForcedApply, TaurusBaseWritableWidget.setForcedApply, TaurusBaseWritableWidget.resetForcedApply) @@ -285,21 +291,41 @@ def getQtDesignerPluginInfo(cls): def main(): import sys - from taurus.qt.qtgui.application import TaurusApplication + import taurus.qt.qtgui.application + Application = taurus.qt.qtgui.application.TaurusApplication + + app = Application.instance() + owns_app = app is None - app = TaurusApplication() + if owns_app: + import taurus.core.util.argparse + parser = taurus.core.util.argparse.get_taurus_parser() + parser.usage = "%prog [options] " + app = Application(sys.argv, cmd_line_parser=parser, + app_name="Taurus lineedit demo", app_version="1.0", + org_domain="Taurus", org_name="Tango community") + + args = app.get_command_line_args() form = Qt.QWidget() layout = Qt.QVBoxLayout() form.setLayout(layout) - for m in ('sys/tg_test/1/double_scalar', - 'sys/tg_test/1/double_scalar' - ): + if len(args) == 0: + models = ['sys/tg_test/1/double_scalar', 'sys/tg_test/1/double_scalar'] + else: + models = args + for model in models: w = TaurusValueLineEdit() - w.setModel(m) + w.setModel(model) layout.addWidget(w) + form.resize(300, 50) form.show() - sys.exit(app.exec_()) + + if owns_app: + sys.exit(app.exec_()) + else: + return form + if __name__ == "__main__": sys.exit(main()) diff --git a/lib/taurus/qt/qtgui/input/taurusspinbox.py b/lib/taurus/qt/qtgui/input/taurusspinbox.py index 82ee026e6..078f00637 100644 --- a/lib/taurus/qt/qtgui/input/taurusspinbox.py +++ b/lib/taurus/qt/qtgui/input/taurusspinbox.py @@ -23,11 +23,9 @@ ## ############################################################################# -"""This module provides a set of basic taurus widgets based on QAbstractSpinBox""" - -__all__ = ["TaurusValueSpinBox", "TaurusValueSpinBoxEx"] - -__docformat__ = 'restructuredtext' +""" +This module provides a set of basic taurus widgets based on QAbstractSpinBox +""" from taurus.external.qt import Qt @@ -35,6 +33,10 @@ from taurus.qt.qtgui.icon import getStandardIcon from taurus.external.pint import Quantity +__all__ = ["TaurusValueSpinBox", "TaurusValueSpinBoxEx"] + +__docformat__ = 'restructuredtext' + class TaurusValueSpinBox(Qt.QAbstractSpinBox): @@ -64,9 +66,9 @@ def getValue(self): def keyPressEvent(self, evt): return self.lineEdit().keyPressEvent(evt) - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Mandatory overload from QAbstractSpinBox - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def stepBy(self, steps): self.setValue(self.getValue() + self._getSingleStepQuantity() * steps) @@ -99,9 +101,9 @@ def stepEnabled(self): ret |= Qt.QAbstractSpinBox.StepDownEnabled return ret - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Overload from QAbstractSpinBox - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def validate(self, input, pos): """Overloaded to use the current validator from the TaurusValueLineEdit, @@ -113,9 +115,9 @@ def validate(self, input, pos): return Qt.QAbstractSpinBox.validate(self, input, pos) return val.validate(input, pos) - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ # Model related methods - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def setModel(self, model): self.lineEdit().setModel(model) @@ -195,6 +197,7 @@ def getQtDesignerPluginInfo(cls): forcedApply = Qt.pyqtProperty("bool", getForcedApply, setForcedApply, resetForcedApply) + _S = """ QSpinBox::up-button { border-width: 0px; @@ -247,14 +250,54 @@ def __setattr__(self, name, value): setattr(self.spinBox, name, value) -if __name__ == "__main__": +def main(): import sys - from taurus.qt.qtgui.application import TaurusApplication - app = TaurusApplication() - - w = TaurusValueSpinBox() - w.setModel('sys/tg_test/1/double_scalar') - w.resize(300, 50) + import taurus.qt.qtgui.application + Application = taurus.qt.qtgui.application.TaurusApplication + + app = Application.instance() + owns_app = app is None + + if owns_app: + import taurus.core.util.argparse + parser = taurus.core.util.argparse.get_taurus_parser() + parser.usage = "%prog [options] " + app = Application(sys.argv, cmd_line_parser=parser, + app_name="Taurus spinbox demo", app_version="1.0", + org_domain="Taurus", org_name="Tango community") + + args = app.get_command_line_args() + + if len(args) == 0: + w = TaurusValueSpinBox() + w.setModel('sys/tg_test/1/double_scalar') + w.resize(300, 50) + else: + w = Qt.QWidget() + layout = Qt.QGridLayout() + w.setLayout(layout) + for model in args: + label = TaurusValueSpinBox() + label.setModel(model) + layout.addWidget(label) + w.resize(300, 50) w.show() - sys.exit(app.exec_()) \ No newline at end of file + if owns_app: + sys.exit(app.exec_()) + else: + return w + + +if __name__ == "__main__": + main() +# import sys +# from taurus.qt.qtgui.application import TaurusApplication +# app = TaurusApplication() + +# w = TaurusValueSpinBox() +# w.setModel('sys/tg_test/1/double_scalar') +# w.resize(300, 50) +# w.show() + +# sys.exit(app.exec_()) From 0aaa3e18104c1e0dadddb54c3579cf1da93d0938 Mon Sep 17 00:00:00 2001 From: Sergi Blanch-Torne Date: Thu, 18 Jan 2018 15:05:20 +0100 Subject: [PATCH 215/288] bugfix lineedit with units when model specifies, in fragments, to show only the magnitude --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 4f08dc9fb..4c3c1604f 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -206,7 +206,7 @@ def setValue(self, v): if model is None: v_str = str(v) else: - v_str = str(self.displayValue(v)) + v_str = str(self.getDisplayValue(v)) v_str = v_str.strip() self.setText(v_str) From dd36e1168dda55b8e4d298f022df0173b1f7b208 Mon Sep 17 00:00:00 2001 From: Sergi Blanch-Torne Date: Fri, 19 Jan 2018 11:22:17 +0100 Subject: [PATCH 216/288] fixing the behaviour when no fragment is setup for a LineEdit widget and tag its superclass from where all the writable widgets should inherit to be reviewed by the developers core. --- lib/taurus/qt/qtgui/base/taurusbase.py | 24 +++++++++++++++++++++ lib/taurus/qt/qtgui/input/tauruslineedit.py | 23 ++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 1e2cdc99b..dca1a3856 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -2061,6 +2061,30 @@ def getOperationCallbacks(self): ''' return [] + def getDisplayValue(self, cache=True, fragmentName=None): + """Returns a string representation of the model value associated with + this component. + + :param cache: (bool) (ignored, just for bck-compat). + :param fragmentName: (str or None) the returned value will correspond + to the given fragmentName. If None passed, + self.modelFragmentName will be used, and if None is + set, the defaultFragmentName of the model will be used + instead. + + :return: (str) a string representation of the model value. + """ + # @fixme: Even the widgets inheriting from this class interacts with + # writable models (then the fragmentName by default that one + # expects is 'wvalue' and not 'rvalue') it has been considered + # that perhaps this could be considered an API modification + # instead of a bugfix. + # The bugfix impact has been bounded only within the + # TaurusValueLineEdit. + return super(TaurusBaseWritableWidget, + self).getDisplayValue(cache=cache, + fragmentName=fragmentName) + def getValue(self): ''' This method must be implemented in derived classes to return diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 4c3c1604f..6c924b622 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -201,6 +201,29 @@ def _stepBy(self, v): value = self.getValue() self.setValue(value + Quantity(v, value.units)) + def getDisplayValue(self, cache=True, fragmentName=None): + """Returns a string representation of the model value associated with + this component. As this is a writable widget, if there is no fragment + specified, the default behaviour is to display the wvalue. + + :param cache: (bool) (ignored, just for bck-compat). + :param fragmentName: (str or None) the returned value will correspond + to the given fragmentName. If None passed, + self.modelFragmentName will be used, and if None is + set, the defaultFragmentName of the model will be used + instead. + + :return: (str) a string representation of the model value. + """ + if fragmentName is None and self.modelFragmentName is None: + return super(TaurusValueLineEdit, + self).getDisplayValue(cache=cache, + fragmentName='wvalue') + else: + return super(TaurusValueLineEdit, + self).getDisplayValue(cache=cache, + fragmentName=fragmentName) + def setValue(self, v): model = self.getModelObj() if model is None: From e86cd5d8ba8d221b29193a23786ec42c2249f2e8 Mon Sep 17 00:00:00 2001 From: Sergi Blanch-Torne Date: Mon, 22 Jan 2018 12:34:42 +0100 Subject: [PATCH 217/288] avoid super() due to diamond inheritance issues --- lib/taurus/qt/qtgui/base/taurusbase.py | 5 ++--- lib/taurus/qt/qtgui/input/tauruslineedit.py | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index dca1a3856..6a3460184 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -2081,9 +2081,8 @@ def getDisplayValue(self, cache=True, fragmentName=None): # instead of a bugfix. # The bugfix impact has been bounded only within the # TaurusValueLineEdit. - return super(TaurusBaseWritableWidget, - self).getDisplayValue(cache=cache, - fragmentName=fragmentName) + return TaurusBaseWidget.getDisplayValue(self, cache=cache, + fragmentName=fragmentName) def getValue(self): ''' diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 6c924b622..6d62fb33f 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -216,13 +216,11 @@ def getDisplayValue(self, cache=True, fragmentName=None): :return: (str) a string representation of the model value. """ if fragmentName is None and self.modelFragmentName is None: - return super(TaurusValueLineEdit, - self).getDisplayValue(cache=cache, - fragmentName='wvalue') + return TaurusBaseWritableWidget.\ + getDisplayValue(self, cache=cache, fragmentName='wvalue') else: - return super(TaurusValueLineEdit, - self).getDisplayValue(cache=cache, - fragmentName=fragmentName) + return TaurusBaseWritableWidget.\ + getDisplayValue(self, cache=cache, fragmentName=fragmentName) def setValue(self, v): model = self.getModelObj() From a4750e252f561ebfbac908b33955106285daa5c0 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 23 Jan 2018 09:26:05 +0100 Subject: [PATCH 218/288] (minor) PEP8 Avoid using "\" for line continuation --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 6d62fb33f..4f550d7c8 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -150,8 +150,8 @@ def updateStyle(self): (max_ is not None and value > max_)): color = 'orange' # apply style - style = 'TaurusValueLineEdit {color: %s; font-weight: %s}' %\ - (color, weight) + style = ('TaurusValueLineEdit {color: %s; font-weight: %s}' % + (color, weight)) self.setStyleSheet(style) def wheelEvent(self, evt): @@ -216,11 +216,11 @@ def getDisplayValue(self, cache=True, fragmentName=None): :return: (str) a string representation of the model value. """ if fragmentName is None and self.modelFragmentName is None: - return TaurusBaseWritableWidget.\ - getDisplayValue(self, cache=cache, fragmentName='wvalue') + return TaurusBaseWritableWidget.getDisplayValue( + self, cache=cache, fragmentName='wvalue') else: - return TaurusBaseWritableWidget.\ - getDisplayValue(self, cache=cache, fragmentName=fragmentName) + return TaurusBaseWritableWidget.getDisplayValue( + self, cache=cache, fragmentName=fragmentName) def setValue(self, v): model = self.getModelObj() @@ -291,10 +291,10 @@ def getQtDesignerPluginInfo(cls): TaurusBaseWritableWidget.setModel, TaurusBaseWritableWidget.resetModel) - useParentModel = \ - Qt.pyqtProperty("bool", TaurusBaseWritableWidget.getUseParentModel, - TaurusBaseWritableWidget.setUseParentModel, - TaurusBaseWritableWidget.resetUseParentModel) + useParentModel = Qt.pyqtProperty( + "bool", TaurusBaseWritableWidget.getUseParentModel, + TaurusBaseWritableWidget.setUseParentModel, + TaurusBaseWritableWidget.resetUseParentModel) autoApply = Qt.pyqtProperty("bool", TaurusBaseWritableWidget.getAutoApply, TaurusBaseWritableWidget.setAutoApply, From 8cd10d2896ad4dc603335f23d5dd702f607ea897 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 23 Jan 2018 09:42:41 +0100 Subject: [PATCH 219/288] (minor) rewrite TODO notice --- lib/taurus/qt/qtgui/base/taurusbase.py | 30 ++++++++++---------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 6a3460184..17ce25ca6 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -2062,27 +2062,19 @@ def getOperationCallbacks(self): return [] def getDisplayValue(self, cache=True, fragmentName=None): - """Returns a string representation of the model value associated with - this component. - - :param cache: (bool) (ignored, just for bck-compat). - :param fragmentName: (str or None) the returned value will correspond - to the given fragmentName. If None passed, - self.modelFragmentName will be used, and if None is - set, the defaultFragmentName of the model will be used - instead. - - :return: (str) a string representation of the model value. - """ - # @fixme: Even the widgets inheriting from this class interacts with - # writable models (then the fragmentName by default that one - # expects is 'wvalue' and not 'rvalue') it has been considered - # that perhaps this could be considered an API modification - # instead of a bugfix. - # The bugfix impact has been bounded only within the - # TaurusValueLineEdit. + """Reimplemented from class:`TaurusBaseWidget`""" + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # The widgets inheriting from this class interact with + # writable models and therefore the fragmentName should fall back to 'wvalue' + # instead of 'rvalue'. + # But changing it now is delicate due to risk of introducing API + # incompatibilities for widgets already assuming the current default. + # So instead of reimplementing it here, the fix was constrained to + # TaurusValueLineEdit.getDisplayValue() + # TODO: Consider reimplementing this to use wvalue by default return TaurusBaseWidget.getDisplayValue(self, cache=cache, fragmentName=fragmentName) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def getValue(self): ''' From 3cd8accc4359750014949d6d6dd2d0b4acdb1c6d Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 23 Jan 2018 10:01:19 +0100 Subject: [PATCH 220/288] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a81b23982..1b5aeede5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ develop branch) won't be reflected in this file. - State and event support in TangoSchemeTest DS (#628, #655) - Model info in widget tooltips (#640) - (experimental) Delayed event subscription API (#605, #593) +- Support DevVoid in Tango to numpy type translation dicts (#666) ### Changed - Treat unit="No unit" as unitless in Tango attributes (#662) @@ -41,7 +42,7 @@ develop branch) won't be reflected in this file. - Missing icons in buttons (#583, #598) - Exception in TaurusCommandForm (#608) - Launchers not showing output on MS Windows (#644) -- Various issues with input widgets (#623, #661, #650) +- Various issues with input widgets (#623, #661, #650, #669) - Regressions in: - TaurusTrend (#618) - TaurusGrid (#609) From fa4b3ad398760967d75c0eadb4fdd55c19574261 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 23 Jan 2018 17:09:09 +0100 Subject: [PATCH 221/288] (minor) PEP8 --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 4f550d7c8..61c569ecb 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -58,7 +58,7 @@ def __init__(self, qt_parent=None, designMode=False): self.editingFinished.connect(self._onEditingFinished) def _updateValidator(self, value): - '''This method sets a validator depending on the data type''' + """This method sets a validator depending on the data type""" if isinstance(value.wvalue, Quantity): val = self.validator() if not isinstance(val, PintValidator): @@ -81,8 +81,8 @@ def _updateValidator(self, value): self.debug("Validator disabled") def __decimalDigits(self, fmt): - '''returns the number of decimal digits from a format string - (or None if they are not defined)''' + """returns the number of decimal digits from a format string + (or None if they are not defined)""" try: if fmt[-1].lower() in ['f', 'g'] and '.' in fmt: return int(fmt[:-1].split('.')[-1]) @@ -92,7 +92,7 @@ def __decimalDigits(self, fmt): return None def _onEditingFinished(self): - '''slot for performing autoapply only when edition is finished''' + """slot for performing autoapply only when edition is finished""" if self._autoApply: self.writeValue() @@ -100,7 +100,7 @@ def _onEditingFinished(self): # TaurusBaseWritableWidget overwriting # ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ def notifyValueChanged(self, *args): - '''reimplement to avoid autoapply on every partial edition''' + """reimplement to avoid autoapply on every partial edition""" self.emitValueChanged() def handleEvent(self, evt_src, evt_type, evt_value): @@ -122,6 +122,7 @@ def isTextValid(self): return val.validate(str(self.text()), 0)[0] == val.Acceptable def updateStyle(self): + """Reimplemented from :class:`TaurusBaseWritableWidget`""" TaurusBaseWritableWidget.updateStyle(self) value = self.getValue() @@ -155,6 +156,7 @@ def updateStyle(self): self.setStyleSheet(style) def wheelEvent(self, evt): + """Wheel event handler""" if not self.getEnableWheelEvent() or Qt.QLineEdit.isReadOnly(self): return Qt.QLineEdit.wheelEvent(self, evt) model = self.getModelObj() @@ -172,6 +174,7 @@ def wheelEvent(self, evt): self._stepBy(numSteps) def keyPressEvent(self, evt): + """Key press event handler""" if evt.key() in (Qt.Qt.Key_Return, Qt.Qt.Key_Enter): Qt.QLineEdit.keyPressEvent(self, evt) evt.accept() From 81b8cafc28d8c8677fd924a825252500f7592c14 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 23 Jan 2018 17:57:09 +0100 Subject: [PATCH 222/288] Revert main changes from PR #650 The changes done in #650 introduce a problem when editing text in TaurusValueLineEdit widgets (see #667). Revert them. Fix #667 --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 61c569ecb..76b421265 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -126,12 +126,6 @@ def updateStyle(self): TaurusBaseWritableWidget.updateStyle(self) value = self.getValue() - if value is None and self.hasPendingOperations(): - try: - value = self.getModelValueObj().wvalue - self.setValue(value) - except: - value = None if value is None or not self.isTextValid(): # invalid value From 5e7b6385035bc37fd580a4ccdf357386703d2ee7 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 24 Jan 2018 11:23:53 +0100 Subject: [PATCH 223/288] Add workaround for bug in pint with leading zeroes The TaurusValueLineEdit is a ffected by a bug in pint (https://github.com/hgrecco/pint/issues/614). Work around it by stripping any leading zeroes from a value string to be passed to pint's Quantity constructor. --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 76b421265..160b62934 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -239,6 +239,10 @@ def getValue(self): model_format = model_obj.data_format if model_type in [DataType.Integer, DataType.Float]: try: + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # workaround for https://github.com/hgrecco/pint/issues/614 + text = text.lstrip('0') or '0' + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ q = Quantity(text) # allow implicit units (assume wvalue.units implicitly) if q.unitless: From 7e7dc6ef5f75aa4bc907e9a2938e8c6f585838d6 Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Wed, 24 Jan 2018 18:30:42 +0100 Subject: [PATCH 224/288] Save last value received for wvalue checking --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 4f550d7c8..f2132cf24 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -48,6 +48,7 @@ def __init__(self, qt_parent=None, designMode=False): self.call__init__(TaurusBaseWritableWidget, name, designMode=designMode) self._enableWheelEvent = False + self._value = None self.setAlignment(Qt.Qt.AlignRight) self.setValidator(None) @@ -124,14 +125,16 @@ def isTextValid(self): def updateStyle(self): TaurusBaseWritableWidget.updateStyle(self) - value = self.getValue() - if value is None and self.hasPendingOperations(): + if self._value is None and self.hasPendingOperations(): try: value = self.getModelValueObj().wvalue + self.info('Overwriting wvalue=None with %s' % (value)) self.setValue(value) except: value = None + value = self.getValue() + if value is None or not self.isTextValid(): # invalid value color, weight = 'gray', 'normal' @@ -229,6 +232,7 @@ def setValue(self, v): else: v_str = str(self.getDisplayValue(v)) v_str = v_str.strip() + self._value = v self.setText(v_str) def getValue(self): From 039e82f74ab3470a1a860fd083a2717022d98195 Mon Sep 17 00:00:00 2001 From: Sergi Date: Thu, 25 Jan 2018 06:28:26 +0100 Subject: [PATCH 225/288] Avoid overwriting event ID --- lib/taurus/core/tango/tangoattribute.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index da0d2b074..019c9443e 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -650,6 +650,12 @@ def _subscribeEvents(self): def _call_dev_hw_subscribe_event(self, stateless=True): """ Executes event subscription on parent TangoDevice objectName """ + + if self.__chg_evt_id is not None: + self.warning("chg events already subscribed (id=%s)" + %self.__chg_evt_id) + return + attr_name = self.getSimpleName() self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( From c7c90fc92e46fc9b8afc96d258688183e0d92cf6 Mon Sep 17 00:00:00 2001 From: Sergi Date: Thu, 25 Jan 2018 07:10:51 +0100 Subject: [PATCH 226/288] Add cache=False to getAttributeInfoEx\nReloading attribute config was needed to fully solve #650 --- lib/taurus/core/tango/tangoattribute.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 019c9443e..c8d1ca297 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -1162,7 +1162,16 @@ def getConfig(self): """Returns the current configuration of the attribute.""" return weakref.proxy(self) - def getAttributeInfoEx(self): + def getAttributeInfoEx(self, cache=True): + if not cache: + try: + attr_name = self.getSimpleName() + attrinfoex = self.__dev_hw_obj.attribute_query(attr_name) + self._decodeAttrInfoEx(attrinfoex) + except: + self.warning("Error getting attribute configuration") + self.traceback() + return self._pytango_attrinfoex @taurus4_deprecation(alt='.rvalue.units') From bc876ce38fdbede939a570529994aa8c49ca2db7 Mon Sep 17 00:00:00 2001 From: Sergi Date: Thu, 25 Jan 2018 07:17:06 +0100 Subject: [PATCH 227/288] reload attribute config on device reconnection\nfix jira CS-14995 --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index f2132cf24..52d88298b 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -127,6 +127,7 @@ def updateStyle(self): if self._value is None and self.hasPendingOperations(): try: + self.getModelObj().getAttributeInfoEx(cache = False) value = self.getModelValueObj().wvalue self.info('Overwriting wvalue=None with %s' % (value)) self.setValue(value) From 27390ad7735502dd06c3f4aa4ebc5c79eb059c21 Mon Sep 17 00:00:00 2001 From: Sergi Date: Thu, 25 Jan 2018 07:41:27 +0100 Subject: [PATCH 228/288] Fix #649, write widget not aware of errors --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 52d88298b..6e14ca3da 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -105,6 +105,7 @@ def notifyValueChanged(self, *args): self.emitValueChanged() def handleEvent(self, evt_src, evt_type, evt_value): + self.setEnabled(evt_type != TaurusEventType.Error) if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic): self._updateValidator(evt_value) TaurusBaseWritableWidget.handleEvent( @@ -131,6 +132,7 @@ def updateStyle(self): value = self.getModelValueObj().wvalue self.info('Overwriting wvalue=None with %s' % (value)) self.setValue(value) + self.setEnabled(value is not None) except: value = None From 95d38609e8f12770690a79fe1d8ec46ef9f6e7ef Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 25 Jan 2018 10:23:12 +0100 Subject: [PATCH 229/288] Update LineEdit^Ctyle in case of Error\nTaurusBaseWritableWidget just ignores errors --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 6e14ca3da..1039a7d4c 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -110,6 +110,8 @@ def handleEvent(self, evt_src, evt_type, evt_value): self._updateValidator(evt_value) TaurusBaseWritableWidget.handleEvent( self, evt_src, evt_type, evt_value) + if evt_type == TaurusEventType.Error: + self.updateStyle() def isTextValid(self): """ @@ -138,7 +140,7 @@ def updateStyle(self): value = self.getValue() - if value is None or not self.isTextValid(): + if value is None or not self.isTextValid() or not self.isEnabled(): # invalid value color, weight = 'gray', 'normal' else: From e0d9a20070859d3f550ea38e2ba8ed54cd94afdb Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 25 Jan 2018 13:42:08 +0100 Subject: [PATCH 230/288] Solve reconnection format issue in Controller based classes --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index e0aeb2be1..0cd613d39 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -99,6 +99,12 @@ def getDisplayValue(self, write=False): def handleEvent(self, evt_src, evt_type, evt_value): if evt_src == self.modelObj(): # update the "_last" values only if the event source is the model (it could be the background...) if evt_type == TaurusEventType.Change or evt_type == TaurusEventType.Periodic: + if self.widget() and self._last_value is None and \ + self._last_config_value is None: + # After a device is reconnected, attribute config must + # be reloaded if no config event has been received yet + self.widget().info('Reloading attr_config') + self.modelObj().getAttributeInfoEx(cache = False) self._last_value = evt_value elif evt_type == TaurusEventType.Config: # TODO: adapt to tep14 self._last_config_value = evt_value From a05ec55f22c8e73e37280e892b35dca2cddc5afb Mon Sep 17 00:00:00 2001 From: Sergi Rubio Date: Thu, 25 Jan 2018 13:43:43 +0100 Subject: [PATCH 231/288] Reduce traces, rename _value to _last_value to match controller syntax --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 1039a7d4c..b107d9f4b 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -48,7 +48,7 @@ def __init__(self, qt_parent=None, designMode=False): self.call__init__(TaurusBaseWritableWidget, name, designMode=designMode) self._enableWheelEvent = False - self._value = None + self._last_value = None self.setAlignment(Qt.Qt.AlignRight) self.setValidator(None) @@ -128,7 +128,9 @@ def isTextValid(self): def updateStyle(self): TaurusBaseWritableWidget.updateStyle(self) - if self._value is None and self.hasPendingOperations(): + if self._last_value is None and self.hasPendingOperations(): + # This check must be done at updateStyle() because pending + # operations list is not updated yet at handleEvent() stage try: self.getModelObj().getAttributeInfoEx(cache = False) value = self.getModelValueObj().wvalue @@ -237,7 +239,7 @@ def setValue(self, v): else: v_str = str(self.getDisplayValue(v)) v_str = v_str.strip() - self._value = v + self._last_value = v self.setText(v_str) def getValue(self): @@ -273,7 +275,11 @@ def getValue(self): else: raise TypeError('Unsupported model type "%s"' % model_type) except Exception, e: - self.warning('Cannot return value for "%s". Reason: %r', text, e) + msg = 'Cannot return value for "%s". Reason: %r' %(text, e) + if text not in (str(None),self.getNoneValue()): + self.warning(msg) + else: + self.debug(msg) return None def setEnableWheelEvent(self, b): From 0e3b41f1fa1fa5f932ee0d883df867aa0e8ae452 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 30 Jan 2018 16:02:02 +0100 Subject: [PATCH 232/288] (WIP) Mark tango-centric code with a TODO This should be fixed before merging this PR --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 0cd613d39..30f791b56 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -104,6 +104,7 @@ def handleEvent(self, evt_src, evt_type, evt_value): # After a device is reconnected, attribute config must # be reloaded if no config event has been received yet self.widget().info('Reloading attr_config') + # TODO: Tango-centric self.modelObj().getAttributeInfoEx(cache = False) self._last_value = evt_value elif evt_type == TaurusEventType.Config: # TODO: adapt to tep14 From 68d5978ea7adad45b6c4bf492d071d0f4e736e33 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 30 Jan 2018 16:03:27 +0100 Subject: [PATCH 233/288] (minor) PEP8 --- lib/taurus/core/tango/tangoattribute.py | 4 ++-- lib/taurus/qt/qtgui/base/tauruscontroller.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index c8d1ca297..bf9b62e45 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -652,8 +652,8 @@ def _call_dev_hw_subscribe_event(self, stateless=True): """ if self.__chg_evt_id is not None: - self.warning("chg events already subscribed (id=%s)" - %self.__chg_evt_id) + self.warning("chg events already subscribed (id=%s)", + self.__chg_evt_id) return attr_name = self.getSimpleName() diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 30f791b56..82ddbd3c9 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -99,8 +99,8 @@ def getDisplayValue(self, write=False): def handleEvent(self, evt_src, evt_type, evt_value): if evt_src == self.modelObj(): # update the "_last" values only if the event source is the model (it could be the background...) if evt_type == TaurusEventType.Change or evt_type == TaurusEventType.Periodic: - if self.widget() and self._last_value is None and \ - self._last_config_value is None: + if (self.widget() and self._last_value is None + and self._last_config_value is None): # After a device is reconnected, attribute config must # be reloaded if no config event has been received yet self.widget().info('Reloading attr_config') From 41680556b2221a460f8bee2d01ff1a3f98c726b9 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 31 Jan 2018 12:04:01 +0100 Subject: [PATCH 234/288] Make _pytango_attrinfoex None if uninitialized TangoAttribute._pytango_attrinfoex is set to an dummy PyTango.AttributeInfoEx() during initialization, preventing the identification of not-initialized cases. Make it remain as None instead. --- lib/taurus/core/tango/tangoattribute.py | 125 ++++++++++++------------ 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index bf9b62e45..58241126b 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -509,14 +509,14 @@ def read(self, cache=True): self.__attr_value = self.decode(v) self.__attr_err = None return self.__attr_value - except PyTango.DevFailed, df: + except PyTango.DevFailed as df: self.__attr_value = None self.__attr_err = df err = df[0] self.debug("[Tango] read failed (%s): %s", err.reason, err.desc) raise df - except Exception, e: + except Exception as e: self.__attr_value = None self.__attr_err = e self.debug("[Tango] read failed: %s", e) @@ -711,7 +711,7 @@ def _subscribeConfEvents(self): attr_name, PyTango.EventType.ATTR_CONF_EVENT, self, [], True) # connects to self.push_event callback - except PyTango.DevFailed, e: + except PyTango.DevFailed as e: self.debug("Error trying to subscribe to CONFIGURATION events.") self.traceback() # Subscription failed either because event mechanism is not available @@ -889,7 +889,7 @@ def setLimits(self, low, high): def setLabel(self, lbl): TaurusAttribute.setLabel(self, lbl) - infoex = self._pytango_attrinfoex + infoex = self._pytango_attrinfoex or PyTango.AttributeInfoEx() infoex.label = lbl self._applyConfig() @@ -904,7 +904,7 @@ def setRange(self, *limits): if high.unitless: high = Quantity(high.magnitude, self._units) TaurusAttribute.setRange(self, [low, high]) - infoex = self._pytango_attrinfoex + infoex = self._pytango_attrinfoex or PyTango.AttributeInfoEx() if low.magnitude != float('-inf'): infoex.min_value = str(low.to(self._units).magnitude) else: @@ -926,7 +926,7 @@ def setWarnings(self, *limits): if high.unitless: high = Quantity(high.magnitude, self._units) TaurusAttribute.setWarnings(self, [low, high]) - infoex = self._pytango_attrinfoex + infoex = self._pytango_attrinfoex or PyTango.AttributeInfoEx() if low.magnitude != float('-inf'): infoex.alarms.min_warning = str(low.to(self._units).magnitude) else: @@ -948,7 +948,7 @@ def setAlarms(self, *limits): if high.unitless: high = Quantity(high.magnitude, self._units) TaurusAttribute.setAlarms(self, [low, high]) - infoex = self._pytango_attrinfoex + infoex = self._pytango_attrinfoex or PyTango.AttributeInfoEx() if low.magnitude != float('-inf'): infoex.alarms.min_alarm = str(low.to(self._units).magnitude) else: @@ -960,75 +960,76 @@ def setAlarms(self, *limits): self._applyConfig() def _applyConfig(self): - config = self._pytango_attrinfoex + config = self._pytango_attrinfoex or PyTango.AttributeInfoEx() self.setConfigEx(config) def _decodeAttrInfoEx(self, pytango_attrinfoex=None): if pytango_attrinfoex is None: - self._pytango_attrinfoex = PyTango.AttributeInfoEx() + return + + self._pytango_attrinfoex = i = pytango_attrinfoex + + self.writable = i.writable != PyTango.AttrWriteType.READ + self._label = i.label + self.data_format = data_format_from_tango(i.data_format) + desc = description_from_tango(i.description) + if desc != "": + self._description = desc + self.type = data_type_from_tango(i.data_type) + ############################################################### + # changed in taurus4: range, alarm and warning now return + # quantities if appropriate + if self.isNumeric(): + units = self._unit_from_tango(i.unit) else: - self._pytango_attrinfoex = i = pytango_attrinfoex - - self.writable = i.writable != PyTango.AttrWriteType.READ - self._label = i.label - self.data_format = data_format_from_tango(i.data_format) - desc = description_from_tango(i.description) - if desc != "": - self._description = desc - self.type = data_type_from_tango(i.data_type) - ############################################################### - # changed in taurus4: range, alarm and warning now return - # quantities if appropriate - if self.isNumeric(): - units = self._unit_from_tango(i.unit) - else: - units = UR.parse_units(None) - - if PyTango.is_numerical_type(i.data_type, inc_array=True): - Q_ = partial(quantity_from_tango_str, units=units, - dtype=i.data_type) - ninf, inf = float('-inf'), float('inf') - min_value = Q_(i.min_value) or Quantity(ninf, units) - max_value = Q_(i.max_value) or Quantity(inf, units) - min_alarm = Q_(i.alarms.min_alarm) or Quantity(ninf, units) - max_alarm = Q_(i.alarms.max_alarm) or Quantity(inf, units) - min_warning = Q_(i.alarms.min_warning) or Quantity(ninf, units) - max_warning = Q_(i.alarms.max_warning) or Quantity(inf, units) - self._range = [min_value, max_value] - self._warning = [min_warning, max_warning] - self._alarm = [min_alarm, max_alarm] - - ############################################################### - # The following members will be accessed via __getattr__ - # self.standard_unit - # self.display_unit - # self.disp_level - ############################################################### - # Tango-specific extension of TaurusConfigValue - self.display_level = display_level_from_tango(i.disp_level) - self.tango_writable = i.writable - self.max_dim = i.max_dim_x, i.max_dim_y - ############################################################### - fmt = standard_display_format_from_tango(i.data_type, i.format) - self.format_spec = fmt.lstrip('%') # format specifier - match = re.search("[^\.]*\.(?P[0-9]+)[eEfFgG%]", fmt) - if match: - self.precision = int(match.group(1)) - # self._units and self._display_format is to be used by - # TangoAttrValue for performance reasons. Do not rely on it in other - # code - self._units = units + units = UR.parse_units(None) + + if PyTango.is_numerical_type(i.data_type, inc_array=True): + Q_ = partial(quantity_from_tango_str, units=units, + dtype=i.data_type) + ninf, inf = float('-inf'), float('inf') + min_value = Q_(i.min_value) or Quantity(ninf, units) + max_value = Q_(i.max_value) or Quantity(inf, units) + min_alarm = Q_(i.alarms.min_alarm) or Quantity(ninf, units) + max_alarm = Q_(i.alarms.max_alarm) or Quantity(inf, units) + min_warning = Q_(i.alarms.min_warning) or Quantity(ninf, units) + max_warning = Q_(i.alarms.max_warning) or Quantity(inf, units) + self._range = [min_value, max_value] + self._warning = [min_warning, max_warning] + self._alarm = [min_alarm, max_alarm] + + ############################################################### + # The following members will be accessed via __getattr__ + # self.standard_unit + # self.display_unit + # self.disp_level + ############################################################### + # Tango-specific extension of TaurusConfigValue + self.display_level = display_level_from_tango(i.disp_level) + self.tango_writable = i.writable + self.max_dim = i.max_dim_x, i.max_dim_y + ############################################################### + fmt = standard_display_format_from_tango(i.data_type, i.format) + self.format_spec = fmt.lstrip('%') # format specifier + match = re.search("[^\.]*\.(?P[0-9]+)[eEfFgG%]", fmt) + if match: + self.precision = int(match.group(1)) + # self._units and self._display_format is to be used by + # TangoAttrValue for performance reasons. Do not rely on it in other + # code + self._units = units @property @deprecation_decorator(alt='format_spec or precision', rel='4.0.4') def format(self): - i = self._pytango_attrinfoex + i = self._pytango_attrinfoex or PyTango.AttributeInfoEx() return standard_display_format_from_tango(i.data_type, i.format) @property def _tango_data_type(self): '''returns the *tango* (not Taurus) data type''' - return self._pytango_attrinfoex.data_type + i = self._pytango_attrinfoex or PyTango.AttributeInfoEx() + return i.data_type def _unit_from_tango(self, unit): # silently treat unit-not-defined as unitless From abc79e2fc1ccf9c550df62a76fcc7b4643355a5d Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 31 Jan 2018 15:15:46 +0100 Subject: [PATCH 235/288] Ensure attr info is decoded prior to decoding value Ensure that self._pytango_attrinfoex is initialized and decoded prior to decoding the attr value. --- lib/taurus/core/tango/tangoattribute.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 58241126b..7dde10491 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -399,7 +399,9 @@ def encode(self, value): def decode(self, attr_value): """Decodes a value that was received from PyTango into the expected representation""" - # TODO decode of the configuration + if self._pytango_attrinfoex is None: + self.getAttributeInfoEx(cache=False) + self._decodeAttrInfoEx() value = TangoAttrValue(pytango_dev_attr=attr_value, attr=self) return value @@ -1169,8 +1171,8 @@ def getAttributeInfoEx(self, cache=True): attr_name = self.getSimpleName() attrinfoex = self.__dev_hw_obj.attribute_query(attr_name) self._decodeAttrInfoEx(attrinfoex) - except: - self.warning("Error getting attribute configuration") + except Exception as e: + self.debug("Error getting attribute configuration: %s", e) self.traceback() return self._pytango_attrinfoex From 69c39a61b3ddb75b3774332ffc5667db84103145 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 31 Jan 2018 16:19:40 +0100 Subject: [PATCH 236/288] Ensure that TaurusValueLineEdit is properly initialized Ensure that the model object is initialized prior to handling other events --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 29 ++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 00f071279..075da632d 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -105,6 +105,19 @@ def notifyValueChanged(self, *args): self.emitValueChanged() def handleEvent(self, evt_src, evt_type, evt_value): + + # handle the case in which the line edit is not yet initialized + if self._last_value is None: + try: + self.getModelObj().read(cache=True) + frag_name = self.modelFragmentName or 'wvalue' + value = self.getModelFragmentObj(fragmentName=frag_name) + self.debug('Overwriting wvalue=None with %s' % (value)) + self.setValue(value) + self.setEnabled(value is not None) + except Exception as e: + self.debug('Failed attempt to initialize value: %r', e) + self.setEnabled(evt_type != TaurusEventType.Error) if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic): self._updateValidator(evt_value) @@ -129,20 +142,6 @@ def updateStyle(self): """Reimplemented from :class:`TaurusBaseWritableWidget`""" TaurusBaseWritableWidget.updateStyle(self) - # TODO: All this block should be moved to handleEvent - # if self._last_value is None and self.hasPendingOperations(): - # # This check must be done at updateStyle() because pending - # # operations list is not updated yet at handleEvent() stage - # try: - # # TODO: Tango-centric - # self.getModelObj().getAttributeInfoEx(cache = False) - # value = self.getModelValueObj().wvalue - # self.info('Overwriting wvalue=None with %s' % (value)) - # self.setValue(value) - # self.setEnabled(value is not None) - # except: - # value = None - value = self.getValue() if value is None or not self.isTextValid() or not self.isEnabled(): @@ -283,7 +282,7 @@ def getValue(self): return bytes(text) else: raise TypeError('Unsupported model type "%s"' % model_type) - except Exception, e: + except Exception as e: msg = 'Cannot return value for "%s". Reason: %r' if text in (str(None), self.getNoneValue()): self.debug(msg, text, e) From fb753c5255efbd95c6bb5148edf76e7ab0778e05 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 31 Jan 2018 16:43:41 +0100 Subject: [PATCH 237/288] Improve TaurusValue handling of (re)connected devices Allow TaurusValue recover from previous access errors without relying on config events. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 24dce5877..a7d21bfb4 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -38,7 +38,7 @@ import re from taurus.external.qt import Qt import taurus.core -from taurus.core import DataType, DataFormat +from taurus.core import DataType, DataFormat, TaurusEventType from taurus.core.taurusbasetypes import TaurusElementType from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_MODEL_MIME_TYPE @@ -310,6 +310,7 @@ def __init__(self, parent=None, designMode=False, customWidgetMap=None): self.call__init__wo_kw(Qt.QWidget, parent) self.call__init__(TaurusBaseWidget, name, designMode=designMode) + self.__error = False self.__modelClass = None self._designMode = designMode @@ -1165,7 +1166,10 @@ def handleEvent(self, evt_src, evt_type, evt_value): """Reimplemented from :meth:`TaurusBaseWidget.handleEvent` to update subwidgets on config events """ - if evt_type == taurus.core.taurusbasetypes.TaurusEventType.Config and not self._designMode: + if self._designMode: + return + + if self.__error or evt_type == TaurusEventType.Config: self.updateCustomWidget() self.updateLabelWidget() self.updateReadWidget() @@ -1173,6 +1177,9 @@ def handleEvent(self, evt_src, evt_type, evt_value): self.updateUnitsWidget() self.updateExtraWidget() + # set/unset the error flag + self.__error = (evt_type == TaurusEventType.Error) + def isValueChangedByUser(self): try: return self._writeWidget.isValueChangedByUser() From faa789b5ecf693f7cac6bff5754c0f98467d963f Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 31 Jan 2018 18:08:59 +0100 Subject: [PATCH 238/288] Improve TaurusLabel handling of (re)connected devices Allow TaurusLabel to recover from previous access errors without relying on config events. --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 82ddbd3c9..91ceab91f 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -97,18 +97,17 @@ def getDisplayValue(self, write=False): return self.widget().getDisplayValue() def handleEvent(self, evt_src, evt_type, evt_value): - if evt_src == self.modelObj(): # update the "_last" values only if the event source is the model (it could be the background...) - if evt_type == TaurusEventType.Change or evt_type == TaurusEventType.Periodic: - if (self.widget() and self._last_value is None - and self._last_config_value is None): - # After a device is reconnected, attribute config must - # be reloaded if no config event has been received yet - self.widget().info('Reloading attr_config') - # TODO: Tango-centric - self.modelObj().getAttributeInfoEx(cache = False) + # update the "_last" values only if the event source is the model + # (it could be the background...) + if evt_src == self.modelObj(): + if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic): + if self._last_value is None: + # reset the format so that it gets updated by displayValue + self.widget().resetFormat() self._last_value = evt_value elif evt_type == TaurusEventType.Config: # TODO: adapt to tep14 self._last_config_value = evt_value + self.widget().resetFormat() else: self._last_error_value = evt_value # In case of error, modify the last_value as well From d0dafecb84cacba0eb9b58eadf2dd533178f7236 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 7 Feb 2018 16:51:01 +0100 Subject: [PATCH 239/288] Deprecate onChangeLabelConfig in taurusvalue Mark as deprecated the onChangeLabelConfig method and do private the onChangeLabelText method. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 5262028f4..9da37c088 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -142,7 +142,7 @@ def contextMenuEvent(self, event): r_action.setEnabled(self.taurusValueBuddy().hasPendingOperations()) if self.taurusValueBuddy().isModifiableByUser(): menu.addAction("Change label", - self.taurusValueBuddy().onChangeLabelConfig) + self.taurusValueBuddy()._onChangeLabelText) menu.addAction("Change Read Widget", self.taurusValueBuddy().onChangeReadWidget) menu.addAction("Set Formatter", @@ -350,7 +350,6 @@ def __init__(self, parent=None, designMode=False, customWidgetMap=None): self._allowWrite = True self._minimumHeight = None self._labelConfig = '{attr.label}' - self._labelText = None self.setModifiableByUser(False) if parent is not None: @@ -636,6 +635,10 @@ def getCustomWidgetMap(self): return self._customWidgetMap def onChangeLabelConfig(self): + self.deprecated(msg="onChangeLabelConfig is deprecated", rel="Jan2018") + self._onChangeLabelText() + + def _onChangeLabelText(self): keys = ['{attr.label}', '{attr.name}', '{attr.fullname}', '{dev.name}', '{dev.fullname}'] @@ -811,9 +814,6 @@ def updateLabelWidget(self): if hasattr(self._labelWidget, 'setModel'): self._labelWidget.setModel(self.getFullModelName()) - if self._labelText is not None: - self._labelWidget.setText(self._labelText) - def updateReadWidget(self): # get the class for the widget and replace it if necessary try: @@ -1234,9 +1234,9 @@ def setLabelConfig(self, config): :param config: fragment :type config: str """ + self._labelConfig = config # backwards compatibility: this method used to work for setting # an arbitrary text to the label widget - self._labelConfig = config try: self.getModelFragmentObj(config) self._labelWidget._permanentText = None From fa3a4f02a62a90ff84d84fed98220a2863d6cfcd Mon Sep 17 00:00:00 2001 From: cfalcon Date: Wed, 7 Feb 2018 16:56:14 +0100 Subject: [PATCH 240/288] Avoid duplicate API Do private the setPermanentText method to avoid duplicate API to set text. --- lib/taurus/qt/qtgui/display/tauruslabel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 812bacd4a..7bf7af39c 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -252,7 +252,7 @@ def __init__(self, parent=None, designMode=False): # register configurable properties self.registerConfigProperty( - self.getPermanentText, self.setPermanentText, "permanentText" + self.getPermanentText, self._setPermanentText, "permanentText" ) def _calculate_controller_class(self): @@ -418,9 +418,10 @@ def resetSuffixText(self): def getPermanentText(self): return self._permanentText - def setPermanentText(self, text): - self.setText_(text) + def _setPermanentText(self, text): self._permanentText = text + if text is not None: + self.setText_(text) def setText_(self, text): """Method to expose QLabel.setText""" @@ -428,7 +429,7 @@ def setText_(self, text): def setText(self, text): """Reimplementation of setText to set permanentText""" - self.setPermanentText(text) + self._setPermanentText(text) def setAutoTrim(self, trim): self._autoTrim = trim From 741a3bfdb8f64609d35a13eb3cde1c118cebcb8b Mon Sep 17 00:00:00 2001 From: reszelaz Date: Thu, 8 Feb 2018 11:39:20 +0100 Subject: [PATCH 241/288] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b5aeede5..99967a35c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ develop branch) won't be reflected in this file. - Missing icons in buttons (#583, #598) - Exception in TaurusCommandForm (#608) - Launchers not showing output on MS Windows (#644) -- Various issues with input widgets (#623, #661, #650, #669) +- Various issues with input widgets (#623, #661, #650, #669, #651, #663) - Regressions in: - TaurusTrend (#618) - TaurusGrid (#609) From 0e40d86dae109d1b2b75c6e33cba4c47cc49f066 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 8 Feb 2018 14:38:28 +0100 Subject: [PATCH 242/288] Include back. comp. layer in setLabelConfig setLabelConfig fallback does not work with old style keys. e.g. Fix it. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 9da37c088..2e118b0b8 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -1242,7 +1242,8 @@ def setLabelConfig(self, config): self._labelWidget._permanentText = None except Exception: try: - self._labelWidget.setText(config) + self._labelWidget.setText(self._BCK_COMPAT_TAGS.get(config, + config)) except: self.debug("Setting permanent text to the label widget failed") return From 272e61b67688310fcb5d10e093fbb32e6dec1d5e Mon Sep 17 00:00:00 2001 From: cfalcon Date: Thu, 8 Feb 2018 15:04:23 +0100 Subject: [PATCH 243/288] Fix back. comp. layer The previous commit introduce a bug. Fix it. --- lib/taurus/qt/qtgui/panel/taurusvalue.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index 2e118b0b8..c8ae02b79 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -1242,8 +1242,12 @@ def setLabelConfig(self, config): self._labelWidget._permanentText = None except Exception: try: - self._labelWidget.setText(self._BCK_COMPAT_TAGS.get(config, - config)) + for old in re.findall('<.+?>', config): + new = self._BCK_COMPAT_TAGS.get(old, old) + self.deprecated(dep=old, alt=new) + config = config.replace(old, new) + + self._labelWidget.setText(config) except: self.debug("Setting permanent text to the label widget failed") return From 6ff23841ed091881cc6e14b71e9e56f0a219ce77 Mon Sep 17 00:00:00 2001 From: Matej Komel Date: Mon, 12 Feb 2018 15:52:12 +0100 Subject: [PATCH 244/288] Proposing fix for issue #648 (taurusLabel.setModelIndex() not working). --- lib/taurus/qt/qtgui/display/tauruslabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index e015a7777..cc2c34a13 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -90,7 +90,7 @@ def _updateForeground(self, label): elif fgRole.lower() in ('', 'none'): pass else: - value = label.getDisplayValue(fragmentName=fgRole) + value = self.getDisplayValue() self._text = text = label.prefixText + value + label.suffixText # Checks that the display fits in the widget and sets it to "..." if From cfd07129182b1033490d9146ddd2820a49edd269 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 12 Feb 2018 16:13:51 +0100 Subject: [PATCH 245/288] Improve BoundMethodWeakref class Allow arguments in "__call__" method Use '__self__' and '__func__' in place of 'im_self' and 'im_func' (Python < 2.6 style) --- lib/taurus/core/util/event.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index 5697e0895..c825d8920 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -48,8 +48,8 @@ class BoundMethodWeakref(object): def __init__(self, bound_method, del_cb=None): cb = (del_cb and self._deleted) - self.func_ref = weakref.ref(bound_method.im_func, cb) - self.obj_ref = weakref.ref(bound_method.im_self, cb) + self.func_ref = weakref.ref(bound_method.__func__, cb) + self.obj_ref = weakref.ref(bound_method.__self__, cb) if cb: self.del_cb = CallableRef(del_cb) self.already_deleted = 0 @@ -61,12 +61,12 @@ def _deleted(self, obj): del_cb(self) self.already_deleted = 1 - def __call__(self): + def __call__(self, *args, **kwargs): obj = self.obj_ref() if obj is not None: func = self.func_ref() if func is not None: - return func.__get__(obj) + return func(obj, *args, **kwargs) def __hash__(self): return id(self) From b65dc1c02bc95cdbb6e47b9185bb495acb8cc12c Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 12 Feb 2018 16:21:40 +0100 Subject: [PATCH 246/288] Avoid circular reference in TangoAttribute Break circular reference between TangoAttribute and event Subscriptions callbacks. Fix #511 --- lib/taurus/core/tango/tangoattribute.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index da0d2b074..17935854d 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -48,7 +48,7 @@ SubscriptionState, TaurusAttrValue, DataFormat, DataType) from taurus.core.taurusoperation import WriteAttrOperation -from taurus.core.util.event import EventListener +from taurus.core.util.event import (EventListener, BoundMethodWeakref) from taurus.core.util.log import (debug, taurus4_deprecation, deprecation_decorator) @@ -293,6 +293,8 @@ def __init__(self, name, parent, **kwargs): if self.factory().is_tango_subscribe_enabled(): self._subscribeConfEvents() + def __del__(self): + self.cleanUp() def cleanUp(self): self.trace("[TangoAttribute] cleanUp") @@ -652,9 +654,10 @@ def _call_dev_hw_subscribe_event(self, stateless=True): """ attr_name = self.getSimpleName() + # connects to self.push_event callback self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, - self, [], stateless) # connects to self.push_event callback + BoundMethodWeakref(self.push_event), [], stateless) return self.__chg_evt_id @@ -701,10 +704,11 @@ def _subscribeConfEvents(self): attr_name = self.getSimpleName() try: + # connects to self.push_event callback self.__cfg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.ATTR_CONF_EVENT, - self, [], True) # connects to self.push_event callback + BoundMethodWeakref(self.push_event), [], True) except PyTango.DevFailed, e: self.debug("Error trying to subscribe to CONFIGURATION events.") self.traceback() From 55d70b729e2c10867a62acf34d6e6f9c4a8314f7 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 12 Feb 2018 17:32:36 +0100 Subject: [PATCH 247/288] Replace dict by weak dict Tauruspollingtimer uses dict to keep indexed the taurus attributes and devices. This causes circular references when someone is destroyed. Fix it, replacing the dictionaries by "weak" dictionaries. --- lib/taurus/core/tauruspollingtimer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/taurus/core/tauruspollingtimer.py b/lib/taurus/core/tauruspollingtimer.py index 3cf50c150..c34b2976c 100644 --- a/lib/taurus/core/tauruspollingtimer.py +++ b/lib/taurus/core/tauruspollingtimer.py @@ -30,10 +30,11 @@ __docformat__ = "restructuredtext" import time +import weakref import threading from .util.log import Logger, DebugIt -from .util.containers import CaselessDict +from .util.containers import CaselessWeakValueDict from .util.timer import Timer @@ -97,9 +98,9 @@ def addAttribute(self, attribute, auto_start=True): attr_dict = self.dev_dict.get(dev) if attr_dict is None: if attribute.factory().caseSensitive: - self.dev_dict[dev] = attr_dict = {} + self.dev_dict[dev] = attr_dict = weakref.WeakValueDictionary() else: - self.dev_dict[dev] = attr_dict = CaselessDict() + self.dev_dict[dev] = attr_dict = CaselessWeakValueDict() if attr_name not in attr_dict: attr_dict[attr_name] = attribute self.attr_nb += 1 From a4bf01bd3d5ee40db2db01e685bae7b050862206 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 12 Feb 2018 17:42:41 +0100 Subject: [PATCH 248/288] Fix addListener circular reference addListener uses a class method as callback in a weak reference. The use of the class method break the weak reference. Use a BoundMethodWeakref of this method instead of it to avoid a circular reference. This commit and the previous one fix #512 --- lib/taurus/core/taurusmodel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/taurusmodel.py b/lib/taurus/core/taurusmodel.py index 63a7f6e24..2516230d0 100644 --- a/lib/taurus/core/taurusmodel.py +++ b/lib/taurus/core/taurusmodel.py @@ -225,7 +225,8 @@ def addListener(self, listener): if self._listeners is None or listener is None: return False - weak_listener = self._getCallableRef(listener, self._listenerDied) + weak_listener = self._getCallableRef( + listener, BoundMethodWeakref(self._listenerDied)) if weak_listener in self._listeners: return False self._listeners.append(weak_listener) From 99975c323e5427a4e203dd93956215b34c5dfa76 Mon Sep 17 00:00:00 2001 From: Matej Komel Date: Wed, 14 Feb 2018 13:38:48 +0100 Subject: [PATCH 249/288] Fix of TaurusConfigurationControllerHelper call to get the fragment name on the widget. --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index e0aeb2be1..097e23269 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -243,7 +243,7 @@ def deviceObj(self): @property def configParam(self): if self._configParam is None: - self._configParam = self.widget().getModelFragmentName() or '' + self._configParam = self.widget().modelFragmentName or '' return self._configParam def getDisplayValue(self, write=False): From 95a02257b21a92ae26599de543bd54b794570020 Mon Sep 17 00:00:00 2001 From: Matej Komel Date: Wed, 14 Feb 2018 14:03:31 +0100 Subject: [PATCH 250/288] TaurusLabel._updateForeground: converting obj to str for concatenation. --- lib/taurus/qt/qtgui/display/tauruslabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 17feca9b2..543f41741 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -91,7 +91,7 @@ def _updateForeground(self, label): pass else: value = self.getDisplayValue() - self._text = text = label.prefixText + value + label.suffixText + self._text = text = label.prefixText + str(value) + label.suffixText # Checks that the display fits in the widget and sets it to "..." if # it does not fit the widget From b456fb1abeb40ec430ee066f5dd3fc74158c5460 Mon Sep 17 00:00:00 2001 From: Matej Komel Date: Wed, 14 Feb 2018 14:26:33 +0100 Subject: [PATCH 251/288] Support for formatting in TaurusConfigurationControllerHelper. --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 097e23269..76638c271 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -257,7 +257,7 @@ def getDisplayValue(self, write=False): try: no_val = getattr(model, "no_" + param) # TODO: Tango-centric if val.lower() == no_val.lower(): - val = widget.getNoneValue() + return widget.getNoneValue() except: pass except AttributeError: @@ -278,13 +278,13 @@ def getDisplayValue(self, write=False): val = val.replace('', dev.getNormalName() or '---') else: - val = widget.getNoneValue() + return widget.getNoneValue() except: widget.debug("Invalid configuration parameter '%s'" % param) - val = widget.getNoneValue() + return widget.getNoneValue() if val is None: - val = widget.getNoneValue() - return val + return widget.getNoneValue() + return widget.displayValue(val) StyleSheetTemplate = """border-style: outset; border-width: 2px; border-color: {0}; {1}""" From 3c1cf0e8f5ab60dca239bfdb9921b197cfbab805 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 14 Feb 2018 16:57:11 +0100 Subject: [PATCH 252/288] Make bgRole='none' for the second label of the demo Change default value to avoid raising exceptions. --- lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py index 4daa3570b..6851f5840 100644 --- a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py +++ b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py @@ -124,7 +124,7 @@ def __init__(self, parent=None): p1.w_model.setText("sys/tg_test/1/double_scalar") p2 = TaurusLabelTestPanel() p2.w_model.setText("sys/tg_test/1/double_scalar#label") - p2.w_bg.setCurrentIndex(2) + p2.w_bg.setCurrentIndex(3) # bgRole='none' layout.addWidget(p1, 0, 0) layout.addWidget(p2, 0, 1) layout.addItem(Qt.QSpacerItem(10, 10), 1, 0, 1, 2) From f3325ce210eae231d47fbf4d79377ff05d0dc5a6 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 14 Feb 2018 17:13:27 +0100 Subject: [PATCH 253/288] Add unittest to check TaurusLabel.setModelIndex feature The TaurusLabel.setModelIndex functionality was not covered by unittest, and a regression occurred from Taurus3. Add unittest to check it. --- lib/taurus/qt/qtgui/display/test/test_tauruslabel.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py b/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py index 9d51f001a..21e8d60b4 100644 --- a/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/test/test_tauruslabel.py @@ -100,6 +100,10 @@ def test_relativemodelclass(self): @testOldFgroles(fgRole='quality', expected='ATTR_VALID') @testOldFgroles(fgRole='none', expected='') # ------------------------------------------------------------------------------ +@insertTest(helper_name='text', + model='tango:' + DEV_NAME + '/double_spectrum', + modelIndex=1, + expected='1.23 mm') @insertTest(helper_name='text', model='tango:' + DEV_NAME + '/double_scalar#state', expected='Ready') @@ -138,11 +142,14 @@ class TaurusLabelTest2(TangoSchemeTestLauncher, BaseWidgetTestCase, ''' _klass = TaurusLabel - def text(self, model=None, expected=None, fgRole=None, maxdepr=0): - '''Check that the label text''' + def text(self, model=None, expected=None, fgRole=None, maxdepr=0, + modelIndex=None): + """Check that the label text""" self._widget.setModel(model) if fgRole is not None: self._widget.setFgRole(fgRole) + if modelIndex is not None: + self._widget.setModelIndex(modelIndex) self.processEvents(repetitions=10, sleep=.1) got = str(self._widget.text()) msg = ('wrong text for "%s":\n expected: %s\n got: %s' % From 0a380950c9fb6ec91cdc292471ecdad09947c29a Mon Sep 17 00:00:00 2001 From: Matej Komel Date: Thu, 15 Feb 2018 10:21:04 +0100 Subject: [PATCH 254/288] Handling custom fgRole on tauruslabel. --- lib/taurus/qt/qtgui/base/tauruscontroller.py | 16 ++++++++-------- lib/taurus/qt/qtgui/display/tauruslabel.py | 2 +- lib/taurus/qt/qtgui/display/tauruslcd.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 76638c271..5063e1d27 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -175,25 +175,25 @@ def deviceObj(self): class TaurusScalarAttributeControllerHelper(TaurusAttributeControllerHelper): - def getDisplayValue(self, write=False): + def getDisplayValue(self, write=False, fragmentName=None): valueObj = self.valueObj() widget = self.widget() if valueObj is None or valueObj.rvalue is None: - return widget.getDisplayValue() + return widget.getDisplayValue(fragmentName=fragmentName) format = self.attrObj().data_format if format == DataFormat._0D: - return self._getDisplayValue(widget, valueObj, None, write) + return self._getDisplayValue(widget, valueObj, None, write, fragmentName) idx = widget.getModelIndexValue() - return self._getDisplayValue(widget, valueObj, idx, write) + return self._getDisplayValue(widget, valueObj, idx, write, fragmentName) - def _getDisplayValue(self, widget, valueObj, idx, write): + def _getDisplayValue(self, widget, valueObj, idx, write, fragmentName): try: if write: value = valueObj.wvalue else: - value = valueObj.rvalue + value = widget.getModelFragmentObj(fragmentName) if idx is not None and len(idx): for i in idx: value = value[i] @@ -246,14 +246,14 @@ def configParam(self): self._configParam = self.widget().modelFragmentName or '' return self._configParam - def getDisplayValue(self, write=False): + def getDisplayValue(self, write=False, fragmentName=None): widget = self.widget() model = self.configObj() if model is None: return widget.getNoneValue() param = self.configParam try: - val = widget.getModelFragmentObj() + val = widget.getModelFragmentObj(fragmentName) try: no_val = getattr(model, "no_" + param) # TODO: Tango-centric if val.lower() == no_val.lower(): diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 543f41741..c1703f71f 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -90,7 +90,7 @@ def _updateForeground(self, label): elif fgRole.lower() in ('', 'none'): pass else: - value = self.getDisplayValue() + value = self.getDisplayValue(fragmentName=fgRole) self._text = text = label.prefixText + str(value) + label.suffixText # Checks that the display fits in the widget and sets it to "..." if diff --git a/lib/taurus/qt/qtgui/display/tauruslcd.py b/lib/taurus/qt/qtgui/display/tauruslcd.py index ffc744211..c44966378 100644 --- a/lib/taurus/qt/qtgui/display/tauruslcd.py +++ b/lib/taurus/qt/qtgui/display/tauruslcd.py @@ -96,7 +96,7 @@ def __init__(self, lcd): TaurusScalarAttributeControllerHelper.__init__(self) TaurusLCDController.__init__(self, lcd) - def _getDisplayValue(self, widget, valueObj, idx, write): + def _getDisplayValue(self, widget, valueObj, idx, write, fragmentName): try: if write: value = valueObj.wvalue.magnitude From 5cf1efb7fdfce2b09c62880ebd9ca95c9e0c5e29 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 14 Feb 2018 17:48:02 +0100 Subject: [PATCH 255/288] Remove travis_wait directives travis_wait commands were introduced to avoid spurious travis errors but they may be not be needed anymore and may be a cause of false success in the tests. Remove them to confirm this. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 306dfe8e0..19ef7864b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,14 +22,14 @@ matrix: - env: DOCKER_IMG=cpascual/taurus-test:debian-buster before_install: - - travis_wait docker run -d --name=taurus-test -h taurus-test --volume=`pwd`:/taurus $DOCKER_IMG + - docker run -d --name=taurus-test -h taurus-test --volume=`pwd`:/taurus $DOCKER_IMG - sleep 10 script: - python -c "import fcntl; fcntl.fcntl(1, fcntl.F_SETFL, 0)" - set -e - docker exec -t taurus-test /bin/bash -c "cd /taurus ; python setup.py install" - - travis_wait docker exec -t taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" + - docker exec -t taurus-test /bin/bash -c "TAURUS_STARTER_WAIT=5 taurustestsuite -e 'taurus\.core\.util\.test\.test_timer'" - docker exec -t taurus-test /bin/bash -c "cd /taurus ; python setup.py build_sphinx" # deploy sphinx-built docs to taurus-doc repo - if [[ "$DOCKER_IMG" == "cpascual/taurus-test:debian-stretch" && "$TRAVIS_REPO_SLUG" == "taurus-org/taurus" ]]; then From 4c9dc12f0d482838513a47bafc28d07701660764 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 15 Feb 2018 11:17:32 +0100 Subject: [PATCH 256/288] Use old unbuntu image in Travis Try solution similar to Sardana's PR 669: https://github.com/sardana-org/sardana/pull/669/ --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 19ef7864b..b95076515 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +# Use old ubuntu image as a workarount to Tango DS launch problems +group: deprecated-2017Q4 + language: python python: From 907c22c88bdb2f9551f206b6bcdab0267a294435 Mon Sep 17 00:00:00 2001 From: Matej Komel Date: Thu, 15 Feb 2018 15:48:03 +0100 Subject: [PATCH 257/288] Reverting changes in other classes, and implementing modelindex handling in TaurusBase --- lib/taurus/qt/qtgui/base/taurusbase.py | 13 ++++++++++ lib/taurus/qt/qtgui/base/tauruscontroller.py | 26 ++++++++++---------- lib/taurus/qt/qtgui/display/tauruslabel.py | 4 +-- lib/taurus/qt/qtgui/display/tauruslcd.py | 2 +- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 1d2aafa45..82c44dff9 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -640,6 +640,14 @@ def getModelFragmentObj(self, fragmentName=None): fragmentName = self.modelFragmentName return self.modelObj.getFragmentObj(fragmentName) + def getModelIndexValue(self): + """ + Called inside getDisplayValue to use with spectrum attributes. By default not used, but some widget might want to support this feature. + + Override when needed. + """ + return None + def getFormatedToolTip(self, cache=True): """Returns a string with contents to be displayed in a tooltip. @@ -818,6 +826,11 @@ def getDisplayValue(self, cache=True, fragmentName=None): except: return self.getNoneValue() + idx = self.getModelIndexValue() + if v is not None and idx is not None and len(idx): + for i in idx: + v = v[i] + return self.displayValue(v) def setNoneValue(self, v): diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 5063e1d27..097e23269 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -175,25 +175,25 @@ def deviceObj(self): class TaurusScalarAttributeControllerHelper(TaurusAttributeControllerHelper): - def getDisplayValue(self, write=False, fragmentName=None): + def getDisplayValue(self, write=False): valueObj = self.valueObj() widget = self.widget() if valueObj is None or valueObj.rvalue is None: - return widget.getDisplayValue(fragmentName=fragmentName) + return widget.getDisplayValue() format = self.attrObj().data_format if format == DataFormat._0D: - return self._getDisplayValue(widget, valueObj, None, write, fragmentName) + return self._getDisplayValue(widget, valueObj, None, write) idx = widget.getModelIndexValue() - return self._getDisplayValue(widget, valueObj, idx, write, fragmentName) + return self._getDisplayValue(widget, valueObj, idx, write) - def _getDisplayValue(self, widget, valueObj, idx, write, fragmentName): + def _getDisplayValue(self, widget, valueObj, idx, write): try: if write: value = valueObj.wvalue else: - value = widget.getModelFragmentObj(fragmentName) + value = valueObj.rvalue if idx is not None and len(idx): for i in idx: value = value[i] @@ -246,18 +246,18 @@ def configParam(self): self._configParam = self.widget().modelFragmentName or '' return self._configParam - def getDisplayValue(self, write=False, fragmentName=None): + def getDisplayValue(self, write=False): widget = self.widget() model = self.configObj() if model is None: return widget.getNoneValue() param = self.configParam try: - val = widget.getModelFragmentObj(fragmentName) + val = widget.getModelFragmentObj() try: no_val = getattr(model, "no_" + param) # TODO: Tango-centric if val.lower() == no_val.lower(): - return widget.getNoneValue() + val = widget.getNoneValue() except: pass except AttributeError: @@ -278,13 +278,13 @@ def getDisplayValue(self, write=False, fragmentName=None): val = val.replace('', dev.getNormalName() or '---') else: - return widget.getNoneValue() + val = widget.getNoneValue() except: widget.debug("Invalid configuration parameter '%s'" % param) - return widget.getNoneValue() + val = widget.getNoneValue() if val is None: - return widget.getNoneValue() - return widget.displayValue(val) + val = widget.getNoneValue() + return val StyleSheetTemplate = """border-style: outset; border-width: 2px; border-color: {0}; {1}""" diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index c1703f71f..7bf7af39c 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -90,8 +90,8 @@ def _updateForeground(self, label): elif fgRole.lower() in ('', 'none'): pass else: - value = self.getDisplayValue(fragmentName=fgRole) - self._text = text = label.prefixText + str(value) + label.suffixText + value = label.getDisplayValue(fragmentName=fgRole) + self._text = text = label.prefixText + value + label.suffixText # Checks that the display fits in the widget and sets it to "..." if # it does not fit the widget diff --git a/lib/taurus/qt/qtgui/display/tauruslcd.py b/lib/taurus/qt/qtgui/display/tauruslcd.py index c44966378..ffc744211 100644 --- a/lib/taurus/qt/qtgui/display/tauruslcd.py +++ b/lib/taurus/qt/qtgui/display/tauruslcd.py @@ -96,7 +96,7 @@ def __init__(self, lcd): TaurusScalarAttributeControllerHelper.__init__(self) TaurusLCDController.__init__(self, lcd) - def _getDisplayValue(self, widget, valueObj, idx, write, fragmentName): + def _getDisplayValue(self, widget, valueObj, idx, write): try: if write: value = valueObj.wvalue.magnitude From de17a023eb26e29896f00654ab4894f7e0f52dea Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 16 Feb 2018 09:02:47 +0100 Subject: [PATCH 258/288] Add mechanism to discover taurus.qt.qtgui plugins Setuptools provides special support for plugins (entry_points). Plugins can register themselves for discovery. Add mechanism to discover and load all of the registered entry points for the taurus.qt.qtgui module by using "pkg_resources.iter_entry_points" method, and expose them as part of the module. --- lib/taurus/qt/qtgui/__init__.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/__init__.py b/lib/taurus/qt/qtgui/__init__.py index a2e0cc2ca..6bf48a88b 100644 --- a/lib/taurus/qt/qtgui/__init__.py +++ b/lib/taurus/qt/qtgui/__init__.py @@ -34,7 +34,9 @@ # register icon path files and icon theme on import of taurus.qt.qtgui import icon as __icon import os +import sys import glob +import pkg_resources from taurus import tauruscustomsettings as __S icon_dir = os.path.join(os.path.dirname(os.path.abspath(__icon.__file__))) @@ -45,4 +47,16 @@ path=getattr(__S, 'QT_THEME_DIR', ''), force=getattr(__S, 'QT_THEME_FORCE_ON_LINUX', False)) -del os, glob, __icon, icon_dir \ No newline at end of file +del os, glob, __icon, icon_dir + + +# Discover the taurus.qt.qtgui plugins +plugins = { + entry_point.name: entry_point.load() + for entry_point in pkg_resources.iter_entry_points('taurus.qt.qtgui') +} + +# Add plugins to the module +for mod_name, mod in plugins.items(): + setattr(sys.modules[__name__], mod_name, mod) + From 4e4b80312cd4cf17d89358e300c4f5b7dc77aceb Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Fri, 16 Feb 2018 09:22:35 +0100 Subject: [PATCH 259/288] Simplify if clause Changing `if foo is not None and len(foo)` for the shorter more robust equivalent `if foo`. Also a minor PEP8 modification --- lib/taurus/qt/qtgui/base/taurusbase.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 82c44dff9..f4b561240 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -642,7 +642,9 @@ def getModelFragmentObj(self, fragmentName=None): def getModelIndexValue(self): """ - Called inside getDisplayValue to use with spectrum attributes. By default not used, but some widget might want to support this feature. + Called inside getDisplayValue to use with spectrum attributes. + By default not used, but some widget might want to support this + feature. Override when needed. """ @@ -827,7 +829,7 @@ def getDisplayValue(self, cache=True, fragmentName=None): return self.getNoneValue() idx = self.getModelIndexValue() - if v is not None and idx is not None and len(idx): + if v is not None and idx: for i in idx: v = v[i] From 37af0f9939f234a2d2be400035e519c1490b3fd1 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Fri, 16 Feb 2018 12:07:03 +0100 Subject: [PATCH 260/288] Mark feature as experimental and make changes private Avoid adding more members to taurus.qt.qtgui (other than the discovered plugins). Mark this plugins feature as experimental --- lib/taurus/qt/qtgui/__init__.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/taurus/qt/qtgui/__init__.py b/lib/taurus/qt/qtgui/__init__.py index 6bf48a88b..67a5fcfba 100644 --- a/lib/taurus/qt/qtgui/__init__.py +++ b/lib/taurus/qt/qtgui/__init__.py @@ -47,16 +47,19 @@ path=getattr(__S, 'QT_THEME_DIR', ''), force=getattr(__S, 'QT_THEME_FORCE_ON_LINUX', False)) -del os, glob, __icon, icon_dir +# ------------------------------------------------------------------------ +# Note: this is an experimental feature introduced in v 4.3.0a +# It may be removed or changed in future releases - -# Discover the taurus.qt.qtgui plugins -plugins = { - entry_point.name: entry_point.load() - for entry_point in pkg_resources.iter_entry_points('taurus.qt.qtgui') +# Discover the taurus.qt.qtgui plugins +__plugins = { + __entry_point.name: __entry_point.load() + for __entry_point in pkg_resources.iter_entry_points('taurus.qt.qtgui') } - # Add plugins to the module -for mod_name, mod in plugins.items(): - setattr(sys.modules[__name__], mod_name, mod) +for __mod_name, __mod in __plugins.items(): + setattr(sys.modules[__name__], __mod_name, __mod) +# ------------------------------------------------------------------------ + +del os, glob, __icon, icon_dir, pkg_resources, sys From d2d0999ba9f61c498885bedbe5b148b4c8e0cadd Mon Sep 17 00:00:00 2001 From: reszelaz Date: Fri, 16 Feb 2018 15:07:29 +0100 Subject: [PATCH 261/288] Revert "Fix circular references" --- lib/taurus/core/tango/tangoattribute.py | 10 +++------- lib/taurus/core/taurusmodel.py | 3 +-- lib/taurus/core/tauruspollingtimer.py | 7 +++---- lib/taurus/core/util/event.py | 8 ++++---- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index cde4beaa3..7dde10491 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -48,7 +48,7 @@ SubscriptionState, TaurusAttrValue, DataFormat, DataType) from taurus.core.taurusoperation import WriteAttrOperation -from taurus.core.util.event import (EventListener, BoundMethodWeakref) +from taurus.core.util.event import EventListener from taurus.core.util.log import (debug, taurus4_deprecation, deprecation_decorator) @@ -293,8 +293,6 @@ def __init__(self, name, parent, **kwargs): if self.factory().is_tango_subscribe_enabled(): self._subscribeConfEvents() - def __del__(self): - self.cleanUp() def cleanUp(self): self.trace("[TangoAttribute] cleanUp") @@ -662,10 +660,9 @@ def _call_dev_hw_subscribe_event(self, stateless=True): attr_name = self.getSimpleName() - # connects to self.push_event callback self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, - BoundMethodWeakref(self.push_event), [], stateless) + self, [], stateless) # connects to self.push_event callback return self.__chg_evt_id @@ -712,11 +709,10 @@ def _subscribeConfEvents(self): attr_name = self.getSimpleName() try: - # connects to self.push_event callback self.__cfg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.ATTR_CONF_EVENT, - BoundMethodWeakref(self.push_event), [], True) + self, [], True) # connects to self.push_event callback except PyTango.DevFailed as e: self.debug("Error trying to subscribe to CONFIGURATION events.") self.traceback() diff --git a/lib/taurus/core/taurusmodel.py b/lib/taurus/core/taurusmodel.py index 2516230d0..63a7f6e24 100644 --- a/lib/taurus/core/taurusmodel.py +++ b/lib/taurus/core/taurusmodel.py @@ -225,8 +225,7 @@ def addListener(self, listener): if self._listeners is None or listener is None: return False - weak_listener = self._getCallableRef( - listener, BoundMethodWeakref(self._listenerDied)) + weak_listener = self._getCallableRef(listener, self._listenerDied) if weak_listener in self._listeners: return False self._listeners.append(weak_listener) diff --git a/lib/taurus/core/tauruspollingtimer.py b/lib/taurus/core/tauruspollingtimer.py index c34b2976c..3cf50c150 100644 --- a/lib/taurus/core/tauruspollingtimer.py +++ b/lib/taurus/core/tauruspollingtimer.py @@ -30,11 +30,10 @@ __docformat__ = "restructuredtext" import time -import weakref import threading from .util.log import Logger, DebugIt -from .util.containers import CaselessWeakValueDict +from .util.containers import CaselessDict from .util.timer import Timer @@ -98,9 +97,9 @@ def addAttribute(self, attribute, auto_start=True): attr_dict = self.dev_dict.get(dev) if attr_dict is None: if attribute.factory().caseSensitive: - self.dev_dict[dev] = attr_dict = weakref.WeakValueDictionary() + self.dev_dict[dev] = attr_dict = {} else: - self.dev_dict[dev] = attr_dict = CaselessWeakValueDict() + self.dev_dict[dev] = attr_dict = CaselessDict() if attr_name not in attr_dict: attr_dict[attr_name] = attribute self.attr_nb += 1 diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index c825d8920..5697e0895 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -48,8 +48,8 @@ class BoundMethodWeakref(object): def __init__(self, bound_method, del_cb=None): cb = (del_cb and self._deleted) - self.func_ref = weakref.ref(bound_method.__func__, cb) - self.obj_ref = weakref.ref(bound_method.__self__, cb) + self.func_ref = weakref.ref(bound_method.im_func, cb) + self.obj_ref = weakref.ref(bound_method.im_self, cb) if cb: self.del_cb = CallableRef(del_cb) self.already_deleted = 0 @@ -61,12 +61,12 @@ def _deleted(self, obj): del_cb(self) self.already_deleted = 1 - def __call__(self, *args, **kwargs): + def __call__(self): obj = self.obj_ref() if obj is not None: func = self.func_ref() if func is not None: - return func(obj, *args, **kwargs) + return func.__get__(obj) def __hash__(self): return id(self) From 53ddca7fcf15348d09982a80f0b90d2afe279e23 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Sat, 17 Feb 2018 20:55:19 +0100 Subject: [PATCH 262/288] Improve widgets plugins import Simplify plugins load and also insert plugin modules in sys.modules. --- lib/taurus/qt/qtgui/__init__.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/taurus/qt/qtgui/__init__.py b/lib/taurus/qt/qtgui/__init__.py index 67a5fcfba..72f9fb9fb 100644 --- a/lib/taurus/qt/qtgui/__init__.py +++ b/lib/taurus/qt/qtgui/__init__.py @@ -51,15 +51,13 @@ # Note: this is an experimental feature introduced in v 4.3.0a # It may be removed or changed in future releases -# Discover the taurus.qt.qtgui plugins -__plugins = { - __entry_point.name: __entry_point.load() - for __entry_point in pkg_resources.iter_entry_points('taurus.qt.qtgui') -} -# Add plugins to the module -for __mod_name, __mod in __plugins.items(): - setattr(sys.modules[__name__], __mod_name, __mod) +# Discover the taurus.qt.qtgui plugins +for __p in pkg_resources.iter_entry_points('taurus.qt.qtgui'): + __mod = __p.load() + setattr(sys.modules[__name__], __p.name, __mod) + sys.modules['%s.%s' % (__name__, __p.name)] = __mod + # ------------------------------------------------------------------------ -del os, glob, __icon, icon_dir, pkg_resources, sys +del os, glob, __icon, icon_dir, pkg_resources, sys, __mod From 947538fdd1d3699248f31e88d2af64c0cb3f7c40 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Sun, 18 Feb 2018 17:32:43 +0100 Subject: [PATCH 263/288] Protect plugins load with exception handling Make sure that not-behaving plugins do not break the import of taurus.qt.qtgui --- lib/taurus/qt/qtgui/__init__.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/taurus/qt/qtgui/__init__.py b/lib/taurus/qt/qtgui/__init__.py index 72f9fb9fb..b7db52d26 100644 --- a/lib/taurus/qt/qtgui/__init__.py +++ b/lib/taurus/qt/qtgui/__init__.py @@ -38,6 +38,7 @@ import glob import pkg_resources from taurus import tauruscustomsettings as __S +from taurus import debug as __debug icon_dir = os.path.join(os.path.dirname(os.path.abspath(__icon.__file__))) # TODO: get .path file glob pattern from tauruscustomsettings @@ -53,11 +54,18 @@ # Discover the taurus.qt.qtgui plugins for __p in pkg_resources.iter_entry_points('taurus.qt.qtgui'): - __mod = __p.load() - setattr(sys.modules[__name__], __p.name, __mod) - sys.modules['%s.%s' % (__name__, __p.name)] = __mod + try: + __modname = '%s.%s' % (__name__, __p.name) + __mod = __p.load() + # Add it to the current module + setattr(sys.modules[__name__], __p.name, __mod) + # Add it to sys.modules + sys.modules[__modname] = __mod + __debug('Plugin "%s" loaded as "%s"', __p.module_name, __modname) + except Exception as e: + __debug('Could not load plugin "%s". Reason: %s', __p.module_name, e) # ------------------------------------------------------------------------ -del os, glob, __icon, icon_dir, pkg_resources, sys, __mod +del os, glob, __icon, icon_dir, pkg_resources, sys, __mod, __modname, __debug From 9eb57f54721fbc1bd8fede943d3ec30305b8d55d Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Sun, 18 Feb 2018 17:40:48 +0100 Subject: [PATCH 264/288] Initialize tmp vars to avoid exception __mod and __modname may not be defined if there are no plugins. Initialize them to avoid exceptions in such case. --- lib/taurus/qt/qtgui/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/taurus/qt/qtgui/__init__.py b/lib/taurus/qt/qtgui/__init__.py index b7db52d26..c0499237f 100644 --- a/lib/taurus/qt/qtgui/__init__.py +++ b/lib/taurus/qt/qtgui/__init__.py @@ -53,6 +53,7 @@ # It may be removed or changed in future releases # Discover the taurus.qt.qtgui plugins +__mod = __modname = None for __p in pkg_resources.iter_entry_points('taurus.qt.qtgui'): try: __modname = '%s.%s' % (__name__, __p.name) From adc4773c247929219e44989c106182c6179740db Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 19 Feb 2018 15:05:45 +0100 Subject: [PATCH 265/288] (m) Fix formatting issues in release guide --- doc/how_to_release.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/how_to_release.md b/doc/how_to_release.md index cacc6aaa0..771c6b05f 100644 --- a/doc/how_to_release.md +++ b/doc/how_to_release.md @@ -8,16 +8,18 @@ of stuff that should be manually tested. 1. During all the development, use the Milestones to keep track of the intended release for each issue 2. Previous to the release deadline, re-check the open issues/PR and update the assignation issues/PR to the release milestone. Request feedback from the devel community. -3. Work to close all the PR/issues remaining open in the milestone. This can be either done in develop or in a release branch called `release-XXX` (where `XXX` is the milestone name, e.g. `Jul17`). If a release branch is used, `develop` is freed to continue with integrations that may not be suitable for this release. On the other hand, it adds a bit more work because the release-related PRs (which are done against the `release-XXX` branch), may need to be also merged to develop. Note: the `release-XXX` branch *can* live in the taurus-org repo or on a personal fork (in which case you should do step 4.4 and **now** to allow other integrators to push directly to it). +3. Work to close all the PR/issues remaining open in the milestone. This can be either done in develop or in a release branch called `release-XXX` (where `XXX` is the milestone name, e.g. `Jul17`). If a release branch is used, `develop` is freed to continue with integrations that may not be suitable for this release. On the other hand, it adds a bit more work because the release-related PRs (which are done against the `release-XXX` branch), may need to be also merged to develop. Note: the `release-XXX` branch *can* live in the taurus-org repo or on a personal fork (in which case you should do step 4.iv **now** to allow other integrators to push directly to it). 4. Create the release branch if it was not done already in the previous step and: -4.1. Review and update the CHANGELOG.md if necessary. See [this](http://keepachangelog.com) -4.2. Bump version using `bumpversion && bumpversion release` (use [semver](http://semver.org/) criteria to choose amongst `major`, `minor` or `patch` -4.3. Update man pages: - `cd /doc` - `./makeman` - `git add man` - `git commit -m "Update man pages"` -4.4. Create a PR to merge the `release-XXX` against the **`master`** branch of the taurus-org repo + 1. Review and update the CHANGELOG.md if necessary. See [this](http://keepachangelog.com) + 2. Bump version using `bumpversion && bumpversion release` (use [semver](http://semver.org/) criteria to choose amongst `major`, `minor` or `patch` + 3. Update man pages: + ``` + cd /doc + ./makeman + git add man + git commit -m "Update man pages" + ``` + 4. Create a PR to merge the `release-XXX` against the **`master`** branch of the taurus-org repo 5. Request reviews in the PR from at least one integrator from each participating institute. The master branch is protected, so the reviews need to be cleared (or dismissed with an explanation) before the release can be merged. 6. Perform manual tests (see checklist below). You may use the CI artifacts (e.g., from appveyor) and post the results in the comments of the PR. 7. Once all reviews a cleared, merge the PR and tag in master @@ -152,4 +154,4 @@ Hint: this list can be used as a template to be copy-pasted on a release PR ### taurusiconcatalog - [ ] Launch `taurusiconcatalog`. Several tabs with an array of icons [should be displayed](http://taurus-scada.org/en/latest/devel/icon_guide.html#taurus-icon-catalog) - [ ] Check that tooltips give info on each icon -- [ ] Click on some icons and check that they give a bigger view of the icon and more info. \ No newline at end of file +- [ ] Click on some icons and check that they give a bigger view of the icon and more info. From a895b428c67eebffe3093bd9359e77387ed17142 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 19 Feb 2018 16:53:40 +0100 Subject: [PATCH 266/288] Update how_to_release.md --- doc/how_to_release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/how_to_release.md b/doc/how_to_release.md index 771c6b05f..c4036f999 100644 --- a/doc/how_to_release.md +++ b/doc/how_to_release.md @@ -22,7 +22,7 @@ of stuff that should be manually tested. 4. Create a PR to merge the `release-XXX` against the **`master`** branch of the taurus-org repo 5. Request reviews in the PR from at least one integrator from each participating institute. The master branch is protected, so the reviews need to be cleared (or dismissed with an explanation) before the release can be merged. 6. Perform manual tests (see checklist below). You may use the CI artifacts (e.g., from appveyor) and post the results in the comments of the PR. -7. Once all reviews a cleared, merge the PR and tag in master +7. Once all reviews a cleared, update the date of the release in the CHANGELOG.md, merge the PR and tag in master 8. Merge also the `release-XXX` branch into develop, and bump the version of develop with `bumpversion patch` From ff4cecd25e59c144cd087b8b458f8c4064ed5297 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 19 Feb 2018 16:27:39 +0100 Subject: [PATCH 267/288] Bump version 4.3.0-alpha to 4.3.0 --- .bumpversion.cfg | 2 +- lib/taurus/core/release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5db361996..3f46ee995 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ commit = True message = Bump version {current_version} to {new_version} tag = False tag_name = {new_version} -current_version = 4.3.0-alpha +current_version = 4.3.0 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index 45709596a..31baabfff 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -48,7 +48,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.3.0-alpha' +version = '4.3.0' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: From 99c94433b9b581ea6bf791e0c4a1571daf8face5 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 19 Feb 2018 16:47:22 +0100 Subject: [PATCH 268/288] Update CHANGELOG.md (TODO: update date before release) The release date is still unknown. Fix it before merging to master. --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99967a35c..4f67367a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Note: changes in the [support-3.x] branch (which was split from the master branch after [3.7.1] and maintained in parallel to the develop branch) won't be reflected in this file. -## Unreleased +## [4.3.0] - 2018-02-2X ### Deprecated - taurus.core.tango.search - TaurusMainWindow's "Change Tango Host" action (#379) @@ -23,7 +23,8 @@ develop branch) won't be reflected in this file. - State and event support in TangoSchemeTest DS (#628, #655) - Model info in widget tooltips (#640) - (experimental) Delayed event subscription API (#605, #593) -- Support DevVoid in Tango to numpy type translation dicts (#666) +- (experimental) Entry point for taurus.qt.qtgui extensions (#684) +- Support DevVoid in Tango-to-numpy type translation dicts (#666) ### Changed - Treat unit="No unit" as unitless in Tango attributes (#662) @@ -42,7 +43,7 @@ develop branch) won't be reflected in this file. - Missing icons in buttons (#583, #598) - Exception in TaurusCommandForm (#608) - Launchers not showing output on MS Windows (#644) -- Various issues with input widgets (#623, #661, #650, #669, #651, #663) +- Various issues with input widgets (#623, #661, #663, #669, #674, #681) - Regressions in: - TaurusTrend (#618) - TaurusGrid (#609) From 0fb0769534eb8aa01a2404978e264b75ec12a980 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Mon, 19 Feb 2018 16:59:04 +0100 Subject: [PATCH 269/288] Update man pages --- doc/man/taurusconfigbrowser.1 | 7 +++++-- doc/man/tauruscurve.1 | 7 +++++-- doc/man/taurusdemo.1 | 5 ++++- doc/man/taurusdesigner.1 | 4 ++-- doc/man/taurusdevicepanel.1 | 7 +++++-- doc/man/taurusform.1 | 7 +++++-- doc/man/taurusgui.1 | 7 +++++-- doc/man/taurusiconcatalog.1 | 7 +++++-- doc/man/taurusimage.1 | 7 +++++-- doc/man/tauruspanel.1 | 7 +++++-- doc/man/taurusplot.1 | 10 ++++++++-- doc/man/taurustestsuite.1 | 4 ++-- doc/man/taurustrend.1 | 7 +++++-- doc/man/taurustrend1d.1 | 7 +++++-- doc/man/taurustrend2d.1 | 7 +++++-- 15 files changed, 71 insertions(+), 29 deletions(-) diff --git a/doc/man/taurusconfigbrowser.1 b/doc/man/taurusconfigbrowser.1 index 2eb0910e7..a409d4426 100644 --- a/doc/man/taurusconfigbrowser.1 +++ b/doc/man/taurusconfigbrowser.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSCONFIGEDITOR "1" "July 2017" "taurusconfigeditor 4.1.0" "User Commands" +.TH TAURUSCONFIGEDITOR "1" "February 2018" "taurusconfigeditor 4.3.0" "User Commands" .SH NAME -taurusconfigeditor \- manual page for taurusconfigeditor 4.1.0 +taurusconfigeditor \- manual page for taurusconfigeditor 4.3.0 .SH SYNOPSIS .B taurusconfigbrowser [\fI\,options\/\fR] [\fI\,INIFILENAME\/\fR] @@ -37,3 +37,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/tauruscurve.1 b/doc/man/tauruscurve.1 index f0ac137a3..3e1afc260 100644 --- a/doc/man/tauruscurve.1 +++ b/doc/man/tauruscurve.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUS "1" "July 2017" "Taurus Curve Dialog 4.1.0" "User Commands" +.TH TAURUS "1" "February 2018" "Taurus Curve Dialog 4.3.0" "User Commands" .SH NAME -Taurus \- manual page for Taurus Curve Dialog 4.1.0 +Taurus \- manual page for Taurus Curve Dialog 4.3.0 .SH SYNOPSIS .B tauruscurve [\fI\,options\/\fR] [\fI\, \/\fR[\fI\,\/\fR] ...] @@ -43,3 +43,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurusdemo.1 b/doc/man/taurusdemo.1 index 738adc42a..5d22067ad 100644 --- a/doc/man/taurusdemo.1 +++ b/doc/man/taurusdemo.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSDEMO "1" "July 2017" "taurusdemo 1.0" "User Commands" +.TH TAURUSDEMO "1" "February 2018" "taurusdemo 1.0" "User Commands" .SH NAME taurusdemo \- manual page for taurusdemo 1.0 .SH SYNOPSIS @@ -37,3 +37,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurusdesigner.1 b/doc/man/taurusdesigner.1 index dae6a8575..da49b11f8 100644 --- a/doc/man/taurusdesigner.1 +++ b/doc/man/taurusdesigner.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSDESIGNER "1" "July 2017" "taurusdesigner 4.1.0" "User Commands" +.TH TAURUSDESIGNER "1" "February 2018" "taurusdesigner 4.3.0" "User Commands" .SH NAME -taurusdesigner \- manual page for taurusdesigner 4.1.0 +taurusdesigner \- manual page for taurusdesigner 4.3.0 .SH SYNOPSIS .B taurusdesigner [\fI\,options\/\fR] \fI\,\/\fR diff --git a/doc/man/taurusdevicepanel.1 b/doc/man/taurusdevicepanel.1 index 9bece2b35..a4cab30f3 100644 --- a/doc/man/taurusdevicepanel.1 +++ b/doc/man/taurusdevicepanel.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSDEVICEPANEL "1" "July 2017" "TaurusDevicePanel 4.1.0" "User Commands" +.TH TAURUSDEVICEPANEL "1" "February 2018" "TaurusDevicePanel 4.3.0" "User Commands" .SH NAME -TaurusDevicePanel \- manual page for TaurusDevicePanel 4.1.0 +TaurusDevicePanel \- manual page for TaurusDevicePanel 4.3.0 .SH SYNOPSIS .B taurusdevicepanel [\fI\,options\/\fR] [\fI\,devname \/\fR[\fI\,attrs\/\fR]] @@ -40,3 +40,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurusform.1 b/doc/man/taurusform.1 index 971735dd0..cab9c121e 100644 --- a/doc/man/taurusform.1 +++ b/doc/man/taurusform.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSFORM "1" "July 2017" "taurusform 4.1.0" "User Commands" +.TH TAURUSFORM "1" "February 2018" "taurusform 4.3.0" "User Commands" .SH NAME -taurusform \- manual page for taurusform 4.1.0 +taurusform \- manual page for taurusform 4.3.0 .SH SYNOPSIS .B taurusform [\fI\,options\/\fR] [\fI\,model1 \/\fR[\fI\,model2 \/\fR...]] @@ -43,3 +43,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurusgui.1 b/doc/man/taurusgui.1 index da209abe1..fb61f43bd 100644 --- a/doc/man/taurusgui.1 +++ b/doc/man/taurusgui.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSGUI "1" "July 2017" "taurusgui 4.1.0" "User Commands" +.TH TAURUSGUI "1" "February 2018" "taurusgui 4.3.0" "User Commands" .SH NAME -taurusgui \- manual page for taurusgui 4.1.0 +taurusgui \- manual page for taurusgui 4.3.0 .SH SYNOPSIS .B taurusgui [\fI\,options\/\fR] \fI\,confname\/\fR @@ -49,3 +49,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurusiconcatalog.1 b/doc/man/taurusiconcatalog.1 index 275389f58..2a97ea087 100644 --- a/doc/man/taurusiconcatalog.1 +++ b/doc/man/taurusiconcatalog.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSICONCATALOG "1" "July 2017" "taurusiconcatalog 4.1.0" "User Commands" +.TH TAURUSICONCATALOG "1" "February 2018" "taurusiconcatalog 4.3.0" "User Commands" .SH NAME -taurusiconcatalog \- manual page for taurusiconcatalog 4.1.0 +taurusiconcatalog \- manual page for taurusiconcatalog 4.3.0 .SH SYNOPSIS .B taurusiconcatalog [\fI\,options\/\fR] @@ -35,3 +35,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurusimage.1 b/doc/man/taurusimage.1 index 9220d1757..2b611af5c 100644 --- a/doc/man/taurusimage.1 +++ b/doc/man/taurusimage.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUS "1" "July 2017" "Taurus Image Dialog 4.1.0" "User Commands" +.TH TAURUS "1" "February 2018" "Taurus Image Dialog 4.3.0" "User Commands" .SH NAME -Taurus \- manual page for Taurus Image Dialog 4.1.0 +Taurus \- manual page for Taurus Image Dialog 4.3.0 .SH SYNOPSIS .B taurusimage [\fI\,options\/\fR] \fI\,\/\fR @@ -46,3 +46,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/tauruspanel.1 b/doc/man/tauruspanel.1 index 6b1048c06..9705988d6 100644 --- a/doc/man/tauruspanel.1 +++ b/doc/man/tauruspanel.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSPANEL "1" "July 2017" "tauruspanel 4.1.0" "User Commands" +.TH TAURUSPANEL "1" "February 2018" "tauruspanel 4.3.0" "User Commands" .SH NAME -tauruspanel \- manual page for tauruspanel 4.1.0 +tauruspanel \- manual page for tauruspanel 4.3.0 .SH SYNOPSIS .B tauruspanel [\fI\,options\/\fR] [\fI\,devname\/\fR] @@ -37,3 +37,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurusplot.1 b/doc/man/taurusplot.1 index 18f9669ac..e43fd6e45 100644 --- a/doc/man/taurusplot.1 +++ b/doc/man/taurusplot.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSPLOT "1" "July 2017" "taurusplot 4.1.0" "User Commands" +.TH TAURUSPLOT "1" "February 2018" "taurusplot 4.3.0" "User Commands" .SH NAME -taurusplot \- manual page for taurusplot 4.1.0 +taurusplot \- manual page for taurusplot 4.3.0 .SH SYNOPSIS .B taurusplot [\fI\,options\/\fR] [\fI\, \/\fR[\fI\,\/\fR] ...] @@ -20,6 +20,9 @@ as a synonim of n) \fB\-\-config\fR=\fI\,CONFIG_FILE\/\fR, \fB\-\-config\-file\fR=\fI\,CONFIG_FILE\/\fR use the given config file for initialization .TP +\fB\-\-import\-ascii\fR=\fI\,IMPORT_ASCII\/\fR +import the given ascii file into the plot +.TP \fB\-\-export\fR=\fI\,EXPORT_FILE\/\fR, \fB\-\-export\-file\fR=\fI\,EXPORT_FILE\/\fR use the given file to as output instead of showing the plot @@ -52,3 +55,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurustestsuite.1 b/doc/man/taurustestsuite.1 index b2d248c3b..0cc7b36d9 100644 --- a/doc/man/taurustestsuite.1 +++ b/doc/man/taurustestsuite.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSTESTSUITE "1" "July 2017" "taurustestsuite 4.1.0" "User Commands" +.TH TAURUSTESTSUITE "1" "February 2018" "taurustestsuite 4.3.0" "User Commands" .SH NAME -taurustestsuite \- manual page for taurustestsuite 4.1.0 +taurustestsuite \- manual page for taurustestsuite 4.3.0 .SH DESCRIPTION usage: taurustestsuite [\-h] [\-\-skip\-gui\-tests] [\-e EXCLUDE_PATTERN] .IP diff --git a/doc/man/taurustrend.1 b/doc/man/taurustrend.1 index df56fbbce..b34a92787 100644 --- a/doc/man/taurustrend.1 +++ b/doc/man/taurustrend.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUSTREND "1" "July 2017" "taurustrend 4.1.0" "User Commands" +.TH TAURUSTREND "1" "February 2018" "taurustrend 4.3.0" "User Commands" .SH NAME -taurustrend \- manual page for taurustrend 4.1.0 +taurustrend \- manual page for taurustrend 4.3.0 .SH SYNOPSIS .B taurustrend [\fI\,options\/\fR] [\fI\, \/\fR[\fI\,\/\fR] ...] @@ -62,3 +62,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurustrend1d.1 b/doc/man/taurustrend1d.1 index e2cadd49b..2cf81f6b2 100644 --- a/doc/man/taurustrend1d.1 +++ b/doc/man/taurustrend1d.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUS "1" "July 2017" "Taurus Trend 4.1.0" "User Commands" +.TH TAURUS "1" "February 2018" "Taurus Trend 4.3.0" "User Commands" .SH NAME -Taurus \- manual page for Taurus Trend 4.1.0 +Taurus \- manual page for Taurus Trend 4.3.0 .SH SYNOPSIS .B taurustrend1d [\fI\,options\/\fR] \fI\,\/\fR @@ -53,3 +53,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter diff --git a/doc/man/taurustrend2d.1 b/doc/man/taurustrend2d.1 index 2a9b7c2ab..f1a2a1ec5 100644 --- a/doc/man/taurustrend2d.1 +++ b/doc/man/taurustrend2d.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH TAURUS "1" "July 2017" "Taurus Trend 2D 4.1.0" "User Commands" +.TH TAURUS "1" "February 2018" "Taurus Trend 2D 4.3.0" "User Commands" .SH NAME -Taurus \- manual page for Taurus Trend 2D 4.1.0 +Taurus \- manual page for Taurus Trend 2D 4.3.0 .SH SYNOPSIS .B taurustrend2d [\fI\,options\/\fR] \fI\,\/\fR @@ -53,3 +53,6 @@ e.g. tango://foo:1234) .TP \fB\-\-remote\-console\-port\fR=\fI\,PORT\/\fR enables remote debugging using the given port +.TP +\fB\-\-default\-formatter\fR=\fI\,FORMATTER\/\fR +Override the default formatter From 902360bb870a83052bc83ed7e6fec6e4582c4f3c Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 20 Feb 2018 08:36:57 +0100 Subject: [PATCH 270/288] Revert "Revert "Fix circular references"" This reverts commit d2d0999ba9f61c498885bedbe5b148b4c8e0cadd. --- lib/taurus/core/tango/tangoattribute.py | 10 +++++++--- lib/taurus/core/taurusmodel.py | 3 ++- lib/taurus/core/tauruspollingtimer.py | 7 ++++--- lib/taurus/core/util/event.py | 8 ++++---- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 7dde10491..cde4beaa3 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -48,7 +48,7 @@ SubscriptionState, TaurusAttrValue, DataFormat, DataType) from taurus.core.taurusoperation import WriteAttrOperation -from taurus.core.util.event import EventListener +from taurus.core.util.event import (EventListener, BoundMethodWeakref) from taurus.core.util.log import (debug, taurus4_deprecation, deprecation_decorator) @@ -293,6 +293,8 @@ def __init__(self, name, parent, **kwargs): if self.factory().is_tango_subscribe_enabled(): self._subscribeConfEvents() + def __del__(self): + self.cleanUp() def cleanUp(self): self.trace("[TangoAttribute] cleanUp") @@ -660,9 +662,10 @@ def _call_dev_hw_subscribe_event(self, stateless=True): attr_name = self.getSimpleName() + # connects to self.push_event callback self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, - self, [], stateless) # connects to self.push_event callback + BoundMethodWeakref(self.push_event), [], stateless) return self.__chg_evt_id @@ -709,10 +712,11 @@ def _subscribeConfEvents(self): attr_name = self.getSimpleName() try: + # connects to self.push_event callback self.__cfg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.ATTR_CONF_EVENT, - self, [], True) # connects to self.push_event callback + BoundMethodWeakref(self.push_event), [], True) except PyTango.DevFailed as e: self.debug("Error trying to subscribe to CONFIGURATION events.") self.traceback() diff --git a/lib/taurus/core/taurusmodel.py b/lib/taurus/core/taurusmodel.py index 63a7f6e24..2516230d0 100644 --- a/lib/taurus/core/taurusmodel.py +++ b/lib/taurus/core/taurusmodel.py @@ -225,7 +225,8 @@ def addListener(self, listener): if self._listeners is None or listener is None: return False - weak_listener = self._getCallableRef(listener, self._listenerDied) + weak_listener = self._getCallableRef( + listener, BoundMethodWeakref(self._listenerDied)) if weak_listener in self._listeners: return False self._listeners.append(weak_listener) diff --git a/lib/taurus/core/tauruspollingtimer.py b/lib/taurus/core/tauruspollingtimer.py index 3cf50c150..c34b2976c 100644 --- a/lib/taurus/core/tauruspollingtimer.py +++ b/lib/taurus/core/tauruspollingtimer.py @@ -30,10 +30,11 @@ __docformat__ = "restructuredtext" import time +import weakref import threading from .util.log import Logger, DebugIt -from .util.containers import CaselessDict +from .util.containers import CaselessWeakValueDict from .util.timer import Timer @@ -97,9 +98,9 @@ def addAttribute(self, attribute, auto_start=True): attr_dict = self.dev_dict.get(dev) if attr_dict is None: if attribute.factory().caseSensitive: - self.dev_dict[dev] = attr_dict = {} + self.dev_dict[dev] = attr_dict = weakref.WeakValueDictionary() else: - self.dev_dict[dev] = attr_dict = CaselessDict() + self.dev_dict[dev] = attr_dict = CaselessWeakValueDict() if attr_name not in attr_dict: attr_dict[attr_name] = attribute self.attr_nb += 1 diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index 5697e0895..c825d8920 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -48,8 +48,8 @@ class BoundMethodWeakref(object): def __init__(self, bound_method, del_cb=None): cb = (del_cb and self._deleted) - self.func_ref = weakref.ref(bound_method.im_func, cb) - self.obj_ref = weakref.ref(bound_method.im_self, cb) + self.func_ref = weakref.ref(bound_method.__func__, cb) + self.obj_ref = weakref.ref(bound_method.__self__, cb) if cb: self.del_cb = CallableRef(del_cb) self.already_deleted = 0 @@ -61,12 +61,12 @@ def _deleted(self, obj): del_cb(self) self.already_deleted = 1 - def __call__(self): + def __call__(self, *args, **kwargs): obj = self.obj_ref() if obj is not None: func = self.func_ref() if func is not None: - return func.__get__(obj) + return func(obj, *args, **kwargs) def __hash__(self): return id(self) From 6a5d4a63251290be542e9fd0636dd7f55428d839 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 19 Feb 2018 15:16:53 +0100 Subject: [PATCH 271/288] Revert Improve BoundMethodWeakref class commit Revert __calls__ method changes in commit cfd07129 --- lib/taurus/core/util/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index c825d8920..3a873e0ee 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -61,12 +61,12 @@ def _deleted(self, obj): del_cb(self) self.already_deleted = 1 - def __call__(self, *args, **kwargs): + def __call__(self): obj = self.obj_ref() if obj is not None: func = self.func_ref() if func is not None: - return func(obj, *args, **kwargs) + return func.__get__(obj) def __hash__(self): return id(self) From f0bf0c48521085249ce49ae407b18e907e22d32e Mon Sep 17 00:00:00 2001 From: cfalcon Date: Mon, 19 Feb 2018 14:52:33 +0100 Subject: [PATCH 272/288] Reimplementation of BoundMethodWeakref class Reimplementation of BoundMethodWeakref class to avoid to have a hard reference in the event callbacks. (commit cfd07129 changes) Reimplement "__call__" method to retrieve references and call callback with arguments. --- lib/taurus/core/tango/tangoattribute.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index cde4beaa3..33c8fcb74 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -65,6 +65,22 @@ data_type_from_tango) +# Reimplementation of BoundMethodWeakref class to avoid to have a hard +# reference in the event callbacks. +# Related to "Keeping references to event callbacks after unsubscribe_event" +# PyTango #185 issue. +class __BoundMethodWeakrefWithCall(BoundMethodWeakref): + + def __call__(self, *args, **kwargs): + """ Retrieve references and call callback with arguments + """ + obj = self.obj_ref() + if obj is not None: + func = self.func_ref() + if func is not None: + return func(obj, *args, **kwargs) + + class TangoAttrValue(TaurusAttrValue): """A TaurusAttrValue specialization to decode PyTango.DeviceAttribute objects @@ -665,7 +681,7 @@ def _call_dev_hw_subscribe_event(self, stateless=True): # connects to self.push_event callback self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, - BoundMethodWeakref(self.push_event), [], stateless) + __BoundMethodWeakrefWithCall(self.push_event), [], stateless) return self.__chg_evt_id @@ -716,7 +732,7 @@ def _subscribeConfEvents(self): self.__cfg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.ATTR_CONF_EVENT, - BoundMethodWeakref(self.push_event), [], True) + __BoundMethodWeakrefWithCall(self.push_event), [], True) except PyTango.DevFailed as e: self.debug("Error trying to subscribe to CONFIGURATION events.") self.traceback() From 7475a247efd31bfd488ed524a1db50ff79ab298a Mon Sep 17 00:00:00 2001 From: zreszela Date: Mon, 19 Feb 2018 18:57:32 +0100 Subject: [PATCH 273/288] Allow to remove log handlers from Logger Add method removeLogHandler to provide API for removing log handlers. --- lib/taurus/core/util/log.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/taurus/core/util/log.py b/lib/taurus/core/util/log.py index 189e00534..9f9bd3251 100644 --- a/lib/taurus/core/util/log.py +++ b/lib/taurus/core/util/log.py @@ -700,6 +700,14 @@ def addLogHandler(self, handler): self.log_obj.addHandler(handler) self.log_handlers.append(handler) + def removeLogHandler(self, handler): + """Removes the given handler from this object's logger + + :param handler: (logging.Handler) the handler to be removed + """ + self.log_obj.removeHandler(handler) + self.log_handlers.remove(handler) + def copyLogHandlers(self, other): """Copies the log handlers of other object to this object From 330627c64b9ca3fc9df2476eb05561fef7e59561 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Tue, 20 Feb 2018 11:43:55 +0100 Subject: [PATCH 274/288] Relocate and rename __BoundMethodWeakrefWithCall Relocate __BoundMethodWeakrefWithCall method to taurus.core.util.event and rename as _BoundMethodWeakrefWithCall. Use this method for calling weak callbacks and mark they uses as workaround for PyTango #185 issue. Fix #511 and #512 --- lib/taurus/core/tango/tangoattribute.py | 26 +++++++------------------ lib/taurus/core/taurusmodel.py | 15 +++++++++----- lib/taurus/core/util/event.py | 16 +++++++++++++++ 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index 33c8fcb74..f5d038ee4 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -48,7 +48,7 @@ SubscriptionState, TaurusAttrValue, DataFormat, DataType) from taurus.core.taurusoperation import WriteAttrOperation -from taurus.core.util.event import (EventListener, BoundMethodWeakref) +from taurus.core.util.event import (EventListener, _BoundMethodWeakrefWithCall) from taurus.core.util.log import (debug, taurus4_deprecation, deprecation_decorator) @@ -65,22 +65,6 @@ data_type_from_tango) -# Reimplementation of BoundMethodWeakref class to avoid to have a hard -# reference in the event callbacks. -# Related to "Keeping references to event callbacks after unsubscribe_event" -# PyTango #185 issue. -class __BoundMethodWeakrefWithCall(BoundMethodWeakref): - - def __call__(self, *args, **kwargs): - """ Retrieve references and call callback with arguments - """ - obj = self.obj_ref() - if obj is not None: - func = self.func_ref() - if func is not None: - return func(obj, *args, **kwargs) - - class TangoAttrValue(TaurusAttrValue): """A TaurusAttrValue specialization to decode PyTango.DeviceAttribute objects @@ -679,9 +663,11 @@ def _call_dev_hw_subscribe_event(self, stateless=True): attr_name = self.getSimpleName() # connects to self.push_event callback + # TODO: _BoundMethodWeakrefWithCall is used as workaround for + # PyTango #185 issue self.__chg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.CHANGE_EVENT, - __BoundMethodWeakrefWithCall(self.push_event), [], stateless) + _BoundMethodWeakrefWithCall(self.push_event), [], stateless) return self.__chg_evt_id @@ -729,10 +715,12 @@ def _subscribeConfEvents(self): attr_name = self.getSimpleName() try: # connects to self.push_event callback + # TODO: _BoundMethodWeakrefWithCall is used as workaround for + # PyTango #185 issue self.__cfg_evt_id = self.__dev_hw_obj.subscribe_event( attr_name, PyTango.EventType.ATTR_CONF_EVENT, - __BoundMethodWeakrefWithCall(self.push_event), [], True) + _BoundMethodWeakrefWithCall(self.push_event), [], True) except PyTango.DevFailed as e: self.debug("Error trying to subscribe to CONFIGURATION events.") self.traceback() diff --git a/lib/taurus/core/taurusmodel.py b/lib/taurus/core/taurusmodel.py index 2516230d0..5546de9ac 100644 --- a/lib/taurus/core/taurusmodel.py +++ b/lib/taurus/core/taurusmodel.py @@ -34,7 +34,9 @@ import threading from .util.log import Logger -from .util.event import CallableRef, BoundMethodWeakref +from .util.event import (CallableRef, + BoundMethodWeakref, + _BoundMethodWeakrefWithCall) from .taurusbasetypes import TaurusEventType, MatchLevel from .taurushelper import Factory @@ -210,8 +212,8 @@ def _listenerDied(self, weak_listener): return try: self._listeners.remove(weak_listener) - except Exception, e: - pass + except Exception as e: + self.debug("Problem removing listener: %r", e) def _getCallableRef(self, listener, cb=None): # return weakref.ref(listener, self._listenerDied) @@ -225,8 +227,11 @@ def addListener(self, listener): if self._listeners is None or listener is None: return False + # TODO: _BoundMethodWeakrefWithCall is used as workaround for + # PyTango #185 issue weak_listener = self._getCallableRef( - listener, BoundMethodWeakref(self._listenerDied)) + listener, _BoundMethodWeakrefWithCall(self._listenerDied)) + # listener, self._listenerDied) if weak_listener in self._listeners: return False self._listeners.append(weak_listener) @@ -238,7 +243,7 @@ def removeListener(self, listener): weak_listener = self._getCallableRef(listener) try: self._listeners.remove(weak_listener) - except Exception, e: + except Exception as e: return False return True diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index 3a873e0ee..8e85e39c6 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -100,6 +100,22 @@ def CallableRef(object, del_cb=None): return weakref.ref(object, del_cb) +# Reimplementation of BoundMethodWeakref class to avoid to have a hard +# reference in the event callbacks. +# Related to "Keeping references to event callbacks after unsubscribe_event" +# PyTango #185 issue. +class _BoundMethodWeakrefWithCall(BoundMethodWeakref): + + def __call__(self, *args, **kwargs): + """ Retrieve references and call callback with arguments + """ + obj = self.obj_ref() + if obj is not None: + func = self.func_ref() + if func is not None: + return func(obj, *args, **kwargs) + + class EventStack(object): "internal usage event stack" From 3a45c56ab8b23cca38987e0420ef25233a518963 Mon Sep 17 00:00:00 2001 From: reszelaz Date: Tue, 20 Feb 2018 12:23:36 +0100 Subject: [PATCH 275/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f67367a7..bd0fc2fb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ develop branch) won't be reflected in this file. - (experimental) Delayed event subscription API (#605, #593) - (experimental) Entry point for taurus.qt.qtgui extensions (#684) - Support DevVoid in Tango-to-numpy type translation dicts (#666) +- `removeLogHandler` method to `Logger` class (#691) ### Changed - Treat unit="No unit" as unitless in Tango attributes (#662) From 54a3586637701c8658aeaed914799891cb3adbe8 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 20 Feb 2018 12:45:50 +0100 Subject: [PATCH 276/288] Add listedModels kwarg to modelChooserDlg Add a listedModels kwarg to the modelChooserDlg static method of TaurusModelChooser. Also protect TaurusModelChooser code that was Tango-centric. --- lib/taurus/qt/qtgui/panel/taurusmodelchooser.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py index 9d2f3b21c..8a88149cd 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py +++ b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py @@ -165,8 +165,12 @@ def __init__(self, parent=None, selectables=None, host=None, designMode=None, si model. Otherwise (default) a list of models can be selected ''' TaurusWidget.__init__(self, parent) + if host is None: - host = taurus.Authority().getNormalName() + try: # TODO: Tango-centric! + host = taurus.Factory('tango').getAuthority().getFullName() + except Exception as e: + taurus.info('Cannot populate Tango Tree: %r', e) self._allowDuplicates = False @@ -296,7 +300,9 @@ def resetSingleModelMode(self): self.setSingleModelMode(self, False) @staticmethod - def modelChooserDlg(parent=None, selectables=None, host=None, asMimeData=False, singleModel=False, windowTitle='Model Chooser'): + def modelChooserDlg(parent=None, selectables=None, host=None, asMimeData=False, + singleModel=False, windowTitle='Model Chooser', + listedModels=None): '''Static method that launches a modal dialog containing a TaurusModelChooser :param parent: (QObject) parent for the dialog @@ -322,6 +328,8 @@ def modelChooserDlg(parent=None, selectables=None, host=None, asMimeData=False, layout = Qt.QVBoxLayout() w = TaurusModelChooser( parent=parent, selectables=selectables, host=host, singleModel=singleModel) + if listedModels is not None: + w.setListedModels(listedModels) layout.addWidget(w) dlg.setLayout(layout) w.updateModels.connect(dlg.accept) From 050fdb55af075ffc68e38c8e9929a13bf02e8799 Mon Sep 17 00:00:00 2001 From: ryo Date: Tue, 20 Feb 2018 14:48:44 +0100 Subject: [PATCH 277/288] Allow case sensitivity for epics object Set caseSensitive for epics object to True so that epics PVs containing upper case in their name would be displayed on TaurusPlot and TaurusTrend, which, at the latest version, disables it by _lowerIfInsensitive function on taurusplot.py --- lib/taurus/core/epics/epicsfactory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/core/epics/epicsfactory.py b/lib/taurus/core/epics/epicsfactory.py index 5b01b0f88..aeda48f34 100644 --- a/lib/taurus/core/epics/epicsfactory.py +++ b/lib/taurus/core/epics/epicsfactory.py @@ -57,7 +57,7 @@ class EpicsFactory(Singleton, TaurusFactory, Logger): schemes = ("ca", "epics",) DEFAULT_DEVICE = 'ca:' DEFAULT_AUTHORITY = 'ca://' - caseSensitive = False + caseSensitive = True elementTypesMap = {TaurusElementType.Authority: EpicsAuthority, TaurusElementType.Device: EpicsDevice, TaurusElementType.Attribute: EpicsAttribute From f21810d700fc4caaad042707ca3f9103e0911373 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 20 Feb 2018 15:11:49 +0100 Subject: [PATCH 278/288] Document listedModels argument --- lib/taurus/qt/qtgui/panel/taurusmodelchooser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py index 8a88149cd..26365975b 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py +++ b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py @@ -316,6 +316,8 @@ def modelChooserDlg(parent=None, selectables=None, host=None, asMimeData=False, :param singleModel: (bool) If True, the selection will be of just one model. Otherwise (default) a list of models can be selected :param windowTitle: (str) Title of the dialog (default="Model Chooser") + :param listedModels: (list) List of model names for initializing the + model list :return: (list,bool or QMimeData,bool) Returns a models,ok tuple. models can be either a list of models or a QMimeData object, depending on From 180d5b6e5f998ae12303b81ed53a37c2e1a6e6bf Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 20 Feb 2018 15:51:21 +0100 Subject: [PATCH 279/288] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd0fc2fb4..a5238f119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ develop branch) won't be reflected in this file. - (experimental) Entry point for taurus.qt.qtgui extensions (#684) - Support DevVoid in Tango-to-numpy type translation dicts (#666) - `removeLogHandler` method to `Logger` class (#691) +- modelChooserDlg static method now accepts listedModels arg (#693) ### Changed - Treat unit="No unit" as unitless in Tango attributes (#662) @@ -49,6 +50,7 @@ develop branch) won't be reflected in this file. - TaurusTrend (#618) - TaurusGrid (#609) - TaurusGUI edit with `taurusgui --new-gui` (#532) +- Epics scheme is now case sensitive (#694) - [Many other issues](https://github.com/taurus-org/taurus/issues?utf8=%E2%9C%93&q=milestone%3AJan18%20label%3Abug%20) ### Removed From fa33ac9aa07291190d1ce2452c8541b679f1c037 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Tue, 20 Feb 2018 16:11:04 +0100 Subject: [PATCH 280/288] (minor) Add TODO on import of private member --- lib/taurus/core/tango/tangoattribute.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py index f5d038ee4..deed0369d 100755 --- a/lib/taurus/core/tango/tangoattribute.py +++ b/lib/taurus/core/tango/tangoattribute.py @@ -48,7 +48,11 @@ SubscriptionState, TaurusAttrValue, DataFormat, DataType) from taurus.core.taurusoperation import WriteAttrOperation -from taurus.core.util.event import (EventListener, _BoundMethodWeakrefWithCall) +from taurus.core.util.event import EventListener +# ------------------------------------------------------------------------- +# TODO: remove this when PyTango's bug 185 is fixed +from taurus.core.util.event import _BoundMethodWeakrefWithCall +# ------------------------------------------------------------------------- from taurus.core.util.log import (debug, taurus4_deprecation, deprecation_decorator) From 387417fba662f9684e984ff5f2ae8f05e67575fb Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Wed, 21 Feb 2018 12:56:46 +0100 Subject: [PATCH 281/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5238f119..5f9003b73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ develop branch) won't be reflected in this file. - Exception in TaurusCommandForm (#608) - Launchers not showing output on MS Windows (#644) - Various issues with input widgets (#623, #661, #663, #669, #674, #681) +- TangoAttribute receiving events after being deleted (#692) - Regressions in: - TaurusTrend (#618) - TaurusGrid (#609) From 93f3d33fc5782e83912a91cf34ce441f3da9e021 Mon Sep 17 00:00:00 2001 From: cfalcon Date: Fri, 23 Feb 2018 10:51:43 +0100 Subject: [PATCH 282/288] Add __name__ attribute in _BoundMethodWeakrefWithCall class _BoundMethodWeakrefWithCall is a class so it does not have a __name__ attribute and the function decorator @wraps in PyTango thows exception. Add the __name__ attribute in the class. This __name__ attribute is the name of the BoundMethod. Fix #700 --- lib/taurus/core/util/event.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index 8e85e39c6..1c9cf182b 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -106,6 +106,12 @@ def CallableRef(object, del_cb=None): # PyTango #185 issue. class _BoundMethodWeakrefWithCall(BoundMethodWeakref): + def __init__(self, bound_method, del_cb=None): + """ Reimplementation of __init__ method""" + super(_BoundMethodWeakrefWithCall, self).__init__(bound_method, + del_cb=del_cb) + self.__name__ = self.func_ref().__name__ + def __call__(self, *args, **kwargs): """ Retrieve references and call callback with arguments """ From 3d0306eecef3d737de935c837482e616ceb0a580 Mon Sep 17 00:00:00 2001 From: mrosanes Date: Fri, 23 Feb 2018 10:59:00 +0100 Subject: [PATCH 283/288] Import PyTango for Tango centric functions Import PyTango for Tango centric functions. Add TODO, in order to remember that these functions are Tango centric as of today. --- lib/taurus/qt/qtgui/panel/taurusmessagepanel.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py index 338af7c20..65c5dac0f 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py @@ -553,6 +553,8 @@ def py_exc(): def tg_exc(): """Shows a tango exception in a TaurusMessagePanel""" + # TODO: This function is Tango centric + import PyTango try: PyTango.Except.throw_exception( 'TangoException', 'A simple tango exception', 'right here') @@ -563,6 +565,8 @@ def tg_exc(): def tg_serv_exc(): """Shows a tango exception from a server in a TaurusMessagePanel""" + # TODO: This function is Tango centric + import PyTango import taurus dev = taurus.Device("sys/tg_test/1") try: @@ -577,6 +581,8 @@ def tg_serv_exc(): def py_tg_serv_exc(): """Shows a tango exception from a python server in a TaurusMessagePanel""" + # TODO: This function is Tango centric + import PyTango try: PyTango.Except.throw_exception( 'TangoException', 'A simple tango exception', 'right here') From ef7a18581d557a5bf3dbc5cd48b8fb06a3dffc00 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Fri, 23 Feb 2018 12:03:21 +0100 Subject: [PATCH 284/288] Add missing PyTango import PR #704 missed one PyTango import failure. Correct it too. --- lib/taurus/qt/qtgui/panel/taurusmessagepanel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py index 65c5dac0f..8c022e8a5 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py @@ -486,7 +486,7 @@ def getError(self): return self._exc_info try: - import Pyango + import PyTango ErrorHandlers = {PyTango.DevFailed: TangoMessageErrorHandler} except: ErrorHandlers = {} From 9d84b3c5d3ce6962bb53cbe1cac3d8002327c642 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Fri, 23 Feb 2018 12:06:27 +0100 Subject: [PATCH 285/288] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f9003b73..e63bc6f9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ develop branch) won't be reflected in this file. - Exception in TaurusCommandForm (#608) - Launchers not showing output on MS Windows (#644) - Various issues with input widgets (#623, #661, #663, #669, #674, #681) +- Exceptions in TaurusMessagePanel (#704) - TangoAttribute receiving events after being deleted (#692) - Regressions in: - TaurusTrend (#618) From 72ecf52946279dcc3f3fc9678f86fc5872138a65 Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 28 Feb 2018 22:40:37 +0100 Subject: [PATCH 286/288] Fix #707 (stepping in line edits and spin boxes does not work) The step up/down feature of TaurusValueLineEdit (e.g. using the up/down keys or the arrows of a TaurusValueSpinBox) was broken when merging #669 which implemented fragments support in TaurusValueLineEdit for allowing the display of the wvalue without units Now we see that supporting fragments generically in TaurusValueLineEdit is problematic (e.g. it makes little sense to use "rvalue" as a fragment since "wvalue" will be modified when eventually applying). Therefore for now limit the fragment support in line edits to the "wvalue.magnitude" case. Also fix a hook error introduced in some previous merge. --- lib/taurus/qt/qtgui/input/tauruslineedit.py | 52 +++++++-------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 075da632d..6ce50d2ca 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -109,20 +109,20 @@ def handleEvent(self, evt_src, evt_type, evt_value): # handle the case in which the line edit is not yet initialized if self._last_value is None: try: - self.getModelObj().read(cache=True) - frag_name = self.modelFragmentName or 'wvalue' - value = self.getModelFragmentObj(fragmentName=frag_name) - self.debug('Overwriting wvalue=None with %s' % (value)) - self.setValue(value) - self.setEnabled(value is not None) + value = self.getModelObj().read(cache=True) + self._updateValidator(value) + self.setValue(value.wvalue) except Exception as e: - self.debug('Failed attempt to initialize value: %r', e) + self.info('Failed attempt to initialize value: %r', e) self.setEnabled(evt_type != TaurusEventType.Error) + if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic): self._updateValidator(evt_value) + TaurusBaseWritableWidget.handleEvent( self, evt_src, evt_type, evt_value) + if evt_type == TaurusEventType.Error: self.updateStyle() @@ -215,36 +215,18 @@ def _stepBy(self, v): value = self.getValue() self.setValue(value + Quantity(v, value.units)) - def getDisplayValue(self, cache=True, fragmentName=None): - """Returns a string representation of the model value associated with - this component. As this is a writable widget, if there is no fragment - specified, the default behaviour is to display the wvalue. - - :param cache: (bool) (ignored, just for bck-compat). - :param fragmentName: (str or None) the returned value will correspond - to the given fragmentName. If None passed, - self.modelFragmentName will be used, and if None is - set, the defaultFragmentName of the model will be used - instead. - - :return: (str) a string representation of the model value. - """ - if fragmentName is None and self.modelFragmentName is None: - return TaurusBaseWritableWidget.getDisplayValue( - self, cache=cache, fragmentName='wvalue') - else: - return TaurusBaseWritableWidget.getDisplayValue( - self, cache=cache, fragmentName=fragmentName) - def setValue(self, v): - model = self.getModelObj() - if model is None: - v_str = str(v) - else: - v_str = str(self.getDisplayValue(v)) - v_str = v_str.strip() + """Set the displayed text from a given value object""" + # Support displaying the value without units (enabled by fragment) + # Other fragments are ignored by setValue + if self.modelFragmentName == "wvalue.magnitude": + try: + units = self.validator().units + v = v.to(units).magnitude + except Exception as e: + self.debug('Cannot enforce fragment. Reason: %r', e) self._last_value = v - self.setText(v_str) + self.setText(str(self.displayValue(v)).strip()) def getValue(self): text = self.text() From fc9f07a7c2db806e18a0b3ce0a53bb3dcfab204c Mon Sep 17 00:00:00 2001 From: cpascual Date: Wed, 28 Feb 2018 23:33:48 +0100 Subject: [PATCH 287/288] Fix #708 (Infinite recursion in TaurusValueSpinBox) Initialize the validator in TaurusValueSpinBox to avoid infinite recursion when traces are added to the code (see #708) --- lib/taurus/qt/qtgui/input/taurusspinbox.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/taurus/qt/qtgui/input/taurusspinbox.py b/lib/taurus/qt/qtgui/input/taurusspinbox.py index 078f00637..6623b5557 100644 --- a/lib/taurus/qt/qtgui/input/taurusspinbox.py +++ b/lib/taurus/qt/qtgui/input/taurusspinbox.py @@ -32,6 +32,7 @@ from tauruslineedit import TaurusValueLineEdit from taurus.qt.qtgui.icon import getStandardIcon from taurus.external.pint import Quantity +from taurus.qt.qtgui.util import PintValidator __all__ = ["TaurusValueSpinBox", "TaurusValueSpinBoxEx"] @@ -51,6 +52,7 @@ def __init__(self, qt_parent=None, designMode=False): self._singleStep = 1.0 lineEdit = TaurusValueLineEdit(designMode=designMode) + lineEdit.setValidator(PintValidator(self)) self.setLineEdit(lineEdit) self.setAccelerated(True) From 1c51e0bf25c74d709c422079c6248d825cfdddc5 Mon Sep 17 00:00:00 2001 From: Carlos Pascual Date: Thu, 1 Mar 2018 09:59:33 +0100 Subject: [PATCH 288/288] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e63bc6f9c..ba471e613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Note: changes in the [support-3.x] branch (which was split from the master branch after [3.7.1] and maintained in parallel to the develop branch) won't be reflected in this file. -## [4.3.0] - 2018-02-2X +## [4.3.0] - 2018-03-01 ### Deprecated - taurus.core.tango.search - TaurusMainWindow's "Change Tango Host" action (#379) @@ -297,6 +297,8 @@ and several other places](https://sf.net/p/tauruslib/tickets/milestone/Jul15/) [TEP3]: http://www.taurus-scada.org/tep/?TEP3.md [TEP14]: http://www.taurus-scada.org/tep/?TEP14.md [Unreleased]: https://github.com/taurus-org/taurus/tree/develop +[4.3.0]: https://github.com/taurus-org/taurus/tree/4.3.0 +[4.1.1]: https://github.com/taurus-org/taurus/tree/4.1.1 [4.1.0]: https://github.com/taurus-org/taurus/tree/4.1.0 [4.0.3]: https://github.com/taurus-org/taurus/tree/4.0.3 [4.0.1]: https://github.com/taurus-org/taurus/tree/4.0.1