diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d7e9f411c..3ebf6dffb 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.4.0 +current_version = 4.5.0 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? serialize = {major}.{minor}.{patch}-{release} diff --git a/.travis.yml b/.travis.yml index b0b7a15d0..1dee1f727 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ env: - DOCKER_IMG=cpascual/taurus-test:debian-jessie - DOCKER_IMG=cpascual/taurus-test:debian-stretch - DOCKER_IMG=cpascual/taurus-test:debian-buster + - DOCKER_IMG=cpascual/taurus-test:debian-stretch-py3 + - DOCKER_IMG=cpascual/taurus-test:debian-stretch-py3qt5 matrix: allow_failures: diff --git a/CHANGELOG.md b/CHANGELOG.md index 282438404..9c2a2f2c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,52 @@ 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.5.0] - 2019-01-29 + +This is a special release for meeting the deadline of debian buster +freeze (debian 10). + +### Added +- Support of Python3 (beta stage, not yet production ready) (#703, #829, #835) +- Support of other Qt bindings: PyQt4, PyQt5, PySide2, PySide + (beta stage, not yet production ready) (TEP18) +- (experimental) Entry point for schemes in TaurusManager (#833) + +### Removed +- taurus.qt.qtgui.tree.taurusdevicetree submodule (obsolete, unused) +- Trend dockwidget in TaurusDevPanel +- `taurus.qt.qtgui.taurusgui.macrolistener` (now provided by + `sardana.taurus.qt.qtgui.macrolistener`) + +### Changed +- `taurus.qt.qtgui.plot` is now deprecated, but the same Qwt5-based + API is now available in `taurus.qt.qtgui.qwt5` +- `taurus.qt.qtcore.util.emmiter.QEmitter.doSomething` signal signature + changes from `collections.Iterable` to `list` +- Updated Pypy's Trove classifiers (we are now officially stable!) (#844) +- Default serialization mode for Tango reverted to `TangoSerial` (in 4.4.0 + the defaultfor Tango was changed to `Serial`) (#850) + +### Fixed +- bug when copying tango or evaluation attribute values (#831, #849) +- bug when adding listener to non-ready Tango device (#792) +- Various issues with Taurus Forms (#800, #805) +- problem when displaying TaurusWheelEdit in vertically-limited space (#788) +- bug when managing subscription event in Tango (#809) +- Other (#793, #819) + +### Deprecated +- `taurus.qt.qtgui.plot` +- `QtColorPalette.qvariant()` +- `TaurusBaseTreeItem.qdisplay()` +- `taurus.qt.qtdesigner.qtdesigner_prepare_taurus()` +- The following have been implicitly deprecated since 4.0 (when API1 + support was dropped) but only now we deprecate them explicitly + - `taurus.external.qt.QtCore.QString` + - `taurus.external.qt.QtCore.QVariant` + - `taurus.external.qt.QtCore.from_qvariant` + - `taurus.external.qt.QtCore.to_qvariant` + ## [4.4.0] - 2018-07-26 ### Deprecated @@ -356,6 +402,7 @@ and several other places](https://sf.net/p/tauruslib/tickets/milestone/Jul15/) [TEP14]: http://www.taurus-scada.org/tep/?TEP14.md [TEP15]: http://www.taurus-scada.org/tep/?TEP15.md [Unreleased]: https://github.com/taurus-org/taurus/tree/develop +[4.5.0]: https://github.com/taurus-org/taurus/tree/release-4.5.0 [4.4.0]: https://github.com/taurus-org/taurus/tree/4.4.0 [4.3.1]: https://github.com/taurus-org/taurus/tree/4.3.1 [4.3.0]: https://github.com/taurus-org/taurus/tree/4.3.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2dd3fd017..97288fcce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ In general, the contributions to taurus should consider following: - The licensing terms for the contributed code must be compatible with (and preferably the same as) the license chosen for the Taurus - project (at the time of writing this TEP, it is the [LGPL][], + project (at the time of writing this, it is the [LGPL][], version 3 *or later*). diff --git a/ci/win-test.bat b/ci/win-test.bat index be73200a2..1ba3d3195 100644 --- a/ci/win-test.bat +++ b/ci/win-test.bat @@ -14,6 +14,9 @@ pip install pint :: Install enum34 pip install enum34 +:: Install enum34 +pip install future + :: Install guiqwt :: TODO, install guiqwt from pypi :: pip install --upgrade guiqwt diff --git a/doc/auto_rst4api.py b/doc/auto_rst4api.py index ce31ddf26..17f7f75b5 100644 --- a/doc/auto_rst4api.py +++ b/doc/auto_rst4api.py @@ -26,7 +26,9 @@ ''' Creates a tree of dirs and restructured text stub files for documenting the API of a python module with sphinx''' - +from __future__ import print_function +from builtins import zip +from builtins import object import sys import os import imp @@ -93,10 +95,10 @@ def cleanAutogenerated(self, apipath): fullname = os.path.join(dirpath, f) try: if self._isautogeneratedfile(fullname): - print "Removing %s" % fullname + print("Removing %s" % fullname) os.remove(fullname) - except Exception, e: - print 'Error accessing %s:%s' % (fullname, repr(e)) + except Exception as e: + print('Error accessing %s:%s' % (fullname, repr(e))) def createClassIndex(self, info, ofname): ''' @@ -114,17 +116,17 @@ def createClassIndex(self, info, ofname): classes = ['.'.join((m, c)) for m, c in classes] # make a full classname if self.verbose: - print 'creating "%s" ...' % ofname, + print('creating "%s" ...' % ofname, end=' ') if not os.path.exists(ofname) or (self.overwrite_old and self._isautogeneratedfile(ofname)): text = self.classindextemplate.render(info=info, classes=classes) f = open(ofname, "w") f.write('\n'.join((self.AUTOGEN_SIGNATURE, self.AUTOGEN_MESSAGE, text))) f.close() if self.verbose: - print ' ok.' + print(' ok.') else: if self.verbose: - print ' skipping (file already exists)' + print(' skipping (file already exists)') def createStubs(self, info, docparentpath): '''creates rst stub files for modules and classes according to the @@ -139,26 +141,26 @@ def createStubs(self, info, docparentpath): # create the module doc dir if it didn't exist absdocpath = os.path.join(docparentpath, info['basemodulename']) if not os.path.exists(absdocpath): - os.makedirs(absdocpath, mode=0755) + os.makedirs(absdocpath, mode=0o755) # create module index stub in doc parent dir ofname = os.path.join(docparentpath, "%s.rst" % info['basemodulename']) if self.verbose: - print 'creating "%s" ...' % ofname, + print('creating "%s" ...' % ofname, end=' ') if not os.path.exists(ofname) or (self.overwrite_old and self._isautogeneratedfile(ofname)): text = self.moduletemplate.render(info=info) f = open(ofname, "w") f.write('\n'.join((self.AUTOGEN_SIGNATURE, self.AUTOGEN_MESSAGE, text))) f.close() if self.verbose: - print ' ok.' + print(' ok.') else: if self.verbose: - print ' skipping (file already exists)' + print(' skipping (file already exists)') # create class stubs for name in info['localclassnames']: ofname = os.path.join(absdocpath, "_%s.rst" % name) if self.verbose: - print 'creating "%s" ...' % ofname, + print('creating "%s" ...' % ofname, end=' ') if not os.path.exists(ofname) or (self.overwrite_old and self._isautogeneratedfile(ofname)): text = self.classtemplate.render(info=info, classname=name) f = open(ofname, "w") @@ -166,12 +168,12 @@ def createStubs(self, info, docparentpath): '\n'.join((self.AUTOGEN_SIGNATURE, self.AUTOGEN_MESSAGE, text))) f.close() if self.verbose: - print ' ok.' + print(' ok.') else: if self.verbose: - print ' skipping (file already exists)' + print(' skipping (file already exists)') # recurse for submodules - for sminfo in info['submodules'].itervalues(): + for sminfo in info['submodules'].values(): self.createStubs(sminfo, absdocpath) def documentModule(self, modulename, docparentpath, exclude_patterns=None): @@ -191,7 +193,7 @@ def documentModule(self, modulename, docparentpath, exclude_patterns=None): :return: (list) list of warning messages ''' if self.verbose: - print "\nDocumenting %s..." % modulename + print("\nDocumenting %s..." % modulename) if exclude_patterns is None: exclude_patterns = self.exclude_patterns moduleinfo, w = ModuleExplorer.explore(modulename, @@ -203,22 +205,22 @@ def documentModule(self, modulename, docparentpath, exclude_patterns=None): if len(w) == 0: return [] else: - return zip(*w)[1] + return list(zip(*w))[1] def main(): import sys if len(sys.argv) != 3: - print 'Usage:\n\t%s modulename docpreffix\n\n' % sys.argv[0] + print('Usage:\n\t%s modulename docpreffix\n\n' % sys.argv[0]) sys.exit(1) modulename, docparentpath = sys.argv[1:] creator = Auto_rst4API_Creator(verbose=True) r = creator.documentModule( modulename, docparentpath, exclude_patterns=['.*\.test']) - print '\n\n' + '*' * 50 - print "Auto Creation of API docs for %s Finished with %i warnings:" % (modulename, len(r)) - print '\n'.join(r) - print '*' * 50 + '\n' + print('\n\n' + '*' * 50) + print("Auto Creation of API docs for %s Finished with %i warnings:" % (modulename, len(r))) + print('\n'.join(r)) + print('*' * 50 + '\n') if __name__ == "__main__": main() diff --git a/doc/how_to_release.md b/doc/how_to_release.md index ac9eb9baa..04fb88bd2 100644 --- a/doc/how_to_release.md +++ b/doc/how_to_release.md @@ -53,13 +53,13 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f - [ ] For TaurusLabel, in order to test the background role=value, you can use the following attribute: `eval:["FAULT","ON","OFF","ALARM"][randint(4)]` - [ ] For TaurusLabel, use a model with fragment (e.g., `sys/tg_test/1/ampli#rvalue.magnitude`, `eval:Q('1mm')#rvalue.unit"`, `eval:10*arange(9)#rvalue[3:4]`) - [ ] For LCD: Test the foreground roles and the background role -- [ ] For Led: Test the colors, ON color, Off color. +- [ ] For Led: Test the colors, ON color, Off color. (hint: you can use `eval:False` as a model for testing) ### taurusplot (basically try all features described in the [user's guide](http://taurus-scada.org/en/latest/users/ui/index.html) - [ ] Execute: `taurusplot "eval:Q(rand(333),'mm')" sys/tg_test/1/wave` -- [ ] Check region Zoom in and out with region zoom and go back stacked zoom levels with +- [ ] Check region Zoom in and out with region zoom and go back stacked zoom levels with the mouse middle button - [ ] Check mouse wheel Zoom - [ ] Test panning (dragging with CTRL pressed) - [ ] Test inspector mode @@ -68,6 +68,8 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f - [ ] Test plot configuration dialog - [ ] Test changing curve titles - [ ] Test Save & restore config (change curve properties, zoom, etc & check that everything is restored) +- [ ] Open the "Input data selection" dialog and add/remove/reorder /edit models +- [ ] export one curve data to ASCII and then load it using "Input data selection" -> raw data -> open file - [ ] ... other features from [user's guide](http://taurus-scada.org/en/latest/users/ui/index.html) ### taurustrend @@ -75,7 +77,7 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f - [ ] Execute: `taurustrend "eval:Q(rand(),'mm')" sys/tg_test/1/ampli` - [ ] Execute: `taurustrend -xe "eval:Q(rand(),'mm')" sys/tg_test/1/ampli` -- [ ] Check region Zoom in and out with region zoom and go back stacked zoom levels with +- [ ] Check region Zoom in and out with region zoom and go back stacked zoom levels with the mouse middle button - [ ] Check mouse wheel Zoom - [ ] Test panning (dragging with CTRL pressed) - [ ] Test inspector mode @@ -83,8 +85,10 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f - [ ] Move curves between axes by clicking on legend (and test zoom on Y2) - [ ] Test plot configuration dialog - [ ] Test Forced reading mode +- [ ] Test autopanning mode +- [ ] Test autoscale x mode - [ ] Test Save & restore config (change curve properties, zoom, etc & check that everything is restored) -- [ ] ... other features from [user's guide](http://taurus-scada.org/en/latest/users/ui/index.html) +- [ ] ... other features from [user's guide](http://taurus-scada.org/users/ui/index.html) ### Test taurusimage @@ -99,16 +103,6 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f - [ ] Test auto-scroll and auto-scale tools - [ ] Test Save & restore config (change axes range, zoom, tool status colormap etc & check that everything is restored) -### Tauruscurve & taurustrend1d -(unused and to be deprecated, you may test but **do not worry too much if they fail**) - -- [ ] Execute: `tauruscurve --demo` -- [ ] Change size -- [ ] Move curve with mouse -- [ ] Resize curve with mouse -- [ ] Test some option of the menu with mouse. -- [ ] Execute: `taurustrend1d "eval:Q(rand(),'mm')"` and test it in the same way - ### taurusdesigner - [ ] Check that taurusdesigner is correctly opened and taurus widgets are present in the catalog - [ ] Create an empty widget and drag various taurus widgets to it (they should be correctly dropped) @@ -124,9 +118,10 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f - [ ] Navigate in the tree and select the TangoTest device (the attr an command panels should be populated) ### taurusform -(basically try all features described in the [user's guide](http://taurus-scada.org/en/latest/users/ui/index.html) +(basically try all features described in the [user's guide](http://taurus-scada.org/users/ui/index.html) - [ ] Launch `taurusform sys/tg_test/1/short_scalar` +- [ ] go to label context menu, change the configuration and set range to (-1000, 1000), alarm to (-500, 500) and unit to `mm`. Close the form and relaunch. The new units should be used. Change the the write value and check that the orange color is used when in warning values, and that the write widget does not allow to write values out of range. - [ ] Test to drag and drop of this attribute onto the same form many times (4 times) (If it crashes, you are seeing bug #96) - [ ] Open "Modify Contents" and add sys/tg_test/1 and all of its attributes. They should all show ok @@ -134,16 +129,16 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f value (from the context menu of a value label) - [ ] Test compact mode for all values (from the context menu of the whole form) - [ ] Test changing labels -- [ ] Test changing the formatter for a single value (from the context menu of a value label) (use, e.g. `>>{}<<`) +- [ ] Test changing the formatter for a single value (from the context menu of a value label) (use, e.g. `>>{}<<`). Do this in compact and non compact modes. - [ ] Test changing the formatter for all values (from the context menu of the whole form) - [ ] Test re-order of values with "Modify contents" - [ ] Test the different "show" buttons (tables, images, spectra) - [ ] Change the write widget of double_scalar by a TaurusWheelEdit - [ ] Change other read and write widgets -- [ ] ... other features from [user's guide](http://taurus-scada.org/en/latest/users/ui/index.html) +- [ ] ... other features from [user's guide](http://taurus-scada.org/users/ui/index.html) ### taurusgui -(basically try all features described in the [user's guide](http://taurus-scada.org/en/latest/users/ui/index.html) +(basically try all features described in the [user's guide](http://taurus-scada.org/users/ui/index.html) - [ ] Launch `taurusgui example01` - [ ] Test (un)lock view @@ -157,7 +152,7 @@ Hint: this list can be used as a template to be copy-pasted on an issue linked f - [ ] Create a new TaurusGui (call it `foogui`) with `taurusgui --new-gui` (follow the wizard) - [ ] Install `foogui` with pip (using a virtualenv may be a good idea) - [ ] launch `foogui` using the script that has been installed -- [ ] ... other features from [user's guide](http://taurus-scada.org/en/latest/users/ui/index.html) +- [ ] ... other features from [user's guide](http://taurus-scada.org/users/ui/index.html) ### taurusconfigbrowser diff --git a/doc/source/conf.py b/doc/source/conf.py index ae962ee26..63d63c0e5 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -23,6 +23,7 @@ # along with Taurus. If not, see . ## ############################################################################## +from __future__ import print_function import sys import os @@ -64,10 +65,16 @@ def _build_doc_api(): 'taurus\.qt\.qtgui\.resource', 'taurus\.qt\.qtgui\.taurusgui\.conf', ] + if sys.version_info.major > 2: + excl += [ + 'taurus\.qt\.qtgui\.qwt5', # qwt5 not available in PY3 + '.*\.(object|zip|str)', # avoid warnings from builtins + ] + rstCreator = API_Creator(exclude_patterns=excl, templatespath=_doc_dir, overwrite_old=True, - verbose=True) + verbose=False) # clean previously existing rst files rstCreator.cleanAutogenerated(_api_dir) # generate api @@ -295,5 +302,4 @@ def _build_doc_api(): 'sardana': ('https://sardana-controls.org', None), 'pint': ('http://pint.readthedocs.io/en/stable/', None), 'PyTango': ('http://pytango.readthedocs.io/en/stable/', None), - 'PyQt4': ('http://pyqt.sourceforge.net/Docs/PyQt4/', None), } diff --git a/doc/source/devel/coding_guide.rst b/doc/source/devel/coding_guide.rst index 48fd1667d..a6b477d38 100644 --- a/doc/source/devel/coding_guide.rst +++ b/doc/source/devel/coding_guide.rst @@ -50,9 +50,8 @@ Coding conventions - use ``lower_case`` for method names, except in the context of taurus.qt where the prevailing convention is ``mixedCase`` due to influence from PyQt -- Code must be python 2.7 compatible, and, if possible, new contributions - should also consider being compatible with python3.5 (to prepare for - python3 support) +- Code must be simultaneously compatible with python 2.7 and >=3.5. If required, + the future_ module can be used for helping in writing python 2+3 compatible code. - Every python module file should contain license information (see template below). The preferred license is the LGPL_. If you need/want to use a different one, it should be compatible with the LGPL v3+. @@ -131,9 +130,8 @@ PyQt4, PyQt5 and PySide versions. from taurus.external.qt import QtWebKit from taurus.external.qt import Qwt5 -2. Since Taurus v>=4.0, Qt-based code in Taurus may assume - that `PyQt API v2`_ is used. PyQt API 1 code, which was supported by Taurus 3, - is no longer guaranteed to work. +2. Since Taurus v>=4.0, Qt-based code in Taurus assumes + that `PyQt API v2`_ is used. PyQt API 1 code is not accepted in taurus. - Use standard python strings (e.g., use :class:`str` for Qt strings instead of :class:`QString`). Code like:: @@ -147,8 +145,7 @@ PyQt4, PyQt5 and PySide versions. my_string2 = my_string.strip() - - Do not use :class:`QVariant`. QVariant objects don't exist in - PySide or in the new PyQt4 API 2. Code like:: + - Do not use :class:`QVariant`. Code like:: def setData(self, index, qvalue, role=Qt.Qt.EditRole): value = qvalue.toString() # this assumes qvalue to be a :class:`QVariant` @@ -175,9 +172,9 @@ PyQt4, PyQt5 and PySide versions. else: return None - For compatibility reasons, :func:`~taurus.external.qt.Qt` defines `QVariant` and - `from_qvariant` which is internally used used to write code that supports both - API v1 and v2 for QVariant. But new code in Taurus v>=4 may assume v2 only. + For backwards-compatibility reasons, `taurus.external.qt.QtCore` defines `QVariant`, + `from_qvariant()` and `to_qvariant()`, but they are deprecated and should not be used + anymore. 3. Use `new-style signals`_. Old-style code like the following:: @@ -198,7 +195,10 @@ PyQt4, PyQt5 and PySide versions. self.mySignal.connect(self.bar) self.mySignal.emit(123) -4. Use of :class:`taurus.qt.qtgui.application.TaurusApplication` instead of +4. The `taurus.external.qt.compat` module defines some convenience utilities + that help in writing Qt-binding agnostic code + +5. Use of :class:`taurus.qt.qtgui.application.TaurusApplication` instead of :class:`QApplication` is recommended (it takes care of various initialization and exit tasks that are convenient). @@ -207,4 +207,5 @@ PyQt4, PyQt5 and PySide versions. .. _PEP8: http://www.python.org/peps/pep-0008.html .. _LGPL: http://www.gnu.org/licenses/lgpl.html .. _`PyQt API v2`: http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html -.. _`new-style signals`: http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html \ No newline at end of file +.. _`new-style signals`: http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html +.. _future: https://python-future.org/ \ No newline at end of file diff --git a/doc/source/devel/examples/TaurusTest.py b/doc/source/devel/examples/TaurusTest.py index 9e5b523be..381ffccdb 100644 --- a/doc/source/devel/examples/TaurusTest.py +++ b/doc/source/devel/examples/TaurusTest.py @@ -1,3 +1,4 @@ +from __future__ import print_function import PyTango import sys import math @@ -10,24 +11,24 @@ def __init__(self, cl, name): TaurusTest.init_device(self) def delete_device(self): - print "[Device delete_device method] for device", self.get_name() + print("[Device delete_device method] for device", self.get_name()) def init_device(self): - print "In ", self.get_name(), "::init_device()" + print("In ", self.get_name(), "::init_device()") self.set_state(PyTango.DevState.ON) self.get_device_properties(self.get_device_class()) self._position = 50.0 self._velocity = 20.0 self._acceleration = 4.0 self._simulation_mode = False - self._abscissas = [x / 50.0 for x in xrange(1024)] + self._abscissas = [x / 50.0 for x in range(1024)] self._curve = [math.sin(x) for x in self._abscissas] def always_executed_hook(self): - print "In ", self.get_name(), "::always_excuted_hook()" + print("In ", self.get_name(), "::always_excuted_hook()") def read_attr_hardware(self, data): - print "In ", self.get_name(), "::read_attr_hardware()" + print("In ", self.get_name(), "::read_attr_hardware()") def read_Position(self, attr): attr.set_value(self._position) @@ -68,7 +69,7 @@ def write_Curve(self, attr): self._curve = attr.get_write_value() def create_device_cb(self, device_name): - print "About to create device", device_name + print("About to create device", device_name) def CreateTaurusTestDevice(self, device_name): klass = self.get_device_class() @@ -173,7 +174,7 @@ class TaurusTestClass(PyTango.DeviceClass): def __init__(self, name): PyTango.DeviceClass.__init__(self, name) self.set_type(name) - print "In TaurusTestClass constructor" + print("In TaurusTestClass constructor") if __name__ == '__main__': @@ -185,7 +186,7 @@ def __init__(self, name): U.server_init() U.server_run() - except PyTango.DevFailed, e: - print '-------> Received a DevFailed exception:', e - except Exception, e: - print '-------> An unforeseen exception occured....', e + except PyTango.DevFailed as e: + print('-------> Received a DevFailed exception:', e) + except Exception as e: + print('-------> An unforeseen exception occured....', e) diff --git a/doc/source/devel/examples/parentmodel_issue_demo.py b/doc/source/devel/examples/parentmodel_issue_demo.py index ced093365..364daaac1 100644 --- a/doc/source/devel/examples/parentmodel_issue_demo.py +++ b/doc/source/devel/examples/parentmodel_issue_demo.py @@ -8,7 +8,7 @@ call recheckTaurusParent for all designer created widgets that use TaurusParentModel. You can do it right after calling the setupUi method. ''' - +from __future__ import print_function from taurus.external.qt import Qt from taurus.qt.qtgui.container import TaurusWidget from taurus.qt.qtgui.display import TaurusLabel @@ -60,8 +60,8 @@ # c.recheckTaurusParent() p.setModel('sys/tg_test/1/state') -print 'p model:', p.getModelName() -print 'c model:', c.getModelName() +print('p model:', p.getModelName()) +print('c model:', c.getModelName()) p.show() diff --git a/doc/source/devel/examples/pyqwt_issue_test.py b/doc/source/devel/examples/pyqwt_issue_test.py index cc2e7906b..56c66fad2 100644 --- a/doc/source/devel/examples/pyqwt_issue_test.py +++ b/doc/source/devel/examples/pyqwt_issue_test.py @@ -39,14 +39,14 @@ class MyPlot(Qwt5.QwtPlot): def __init__(self, parent=None): Qwt5.QwtPlot.__init__(self, parent) self.setAxisScaleDraw(Qwt5.QwtPlot.xBottom, MyScaleDrawSafe()) - print "Replotting with MyScaleDrawSafe:..." + print("Replotting with MyScaleDrawSafe:...") self.replot() - print "ok" + print("ok") self.setAxisScaleDraw(Qwt5.QwtPlot.xBottom, MyScaleDrawDanger()) - print "Replotting with MyScaleDrawDanger (if it crashes now you are affected by the bug) :..." + print("Replotting with MyScaleDrawDanger (if it crashes now you are affected by the bug) :...") self.replot() - print "SAFE!!!" - print "if this is printed, the sip/PyQwt bug does not affect you" + print("SAFE!!!") + print("if this is printed, the sip/PyQwt bug does not affect you") app = Qt.QApplication([]) p = MyPlot() diff --git a/doc/source/sphinxext/taurusextension.py b/doc/source/sphinxext/taurusextension.py index 760dcba5d..2a19beef9 100644 --- a/doc/source/sphinxext/taurusextension.py +++ b/doc/source/sphinxext/taurusextension.py @@ -25,6 +25,9 @@ """helper methods for taurus sphinx documentation""" +from __future__ import print_function + + __expr = ('or',) @@ -65,9 +68,9 @@ def process_param(line): new_lines.append('%s:type %s: %s' % (prefix, param_name, klass)) new_lines.append('%s:param %s: %s' % (prefix, param_name, desc)) - except Exception, e: - print "Taurus sphinx extension: Not able to process param: '%s'" % line - print " Reason:", str(e) + except Exception as e: + print("Taurus sphinx extension: Not able to process param: '%s'" % line) + print(" Reason:", str(e)) new_lines.append(line) return new_lines @@ -85,9 +88,9 @@ def process_return(line): desc = desc[pos + 1:] new_lines.append('%s:rtype: %s' % (prefix, klass)) new_lines.append('%s:return: %s' % (prefix, desc)) - except Exception, e: - print "TaurusExtension: Not able to process 'return': '%s'" % line - print " Reason:", str(e) + except Exception as e: + print("TaurusExtension: Not able to process 'return': '%s'" % line) + print(" Reason:", str(e)) new_lines.append(line) return new_lines @@ -105,9 +108,9 @@ def process_raise(line): klass = "(" + process_type(elem_type, obj_type='exc') + ")" desc = desc[pos + 1:] new_lines.append('%s:raise: %s %s' % (prefix, klass, desc)) - except Exception, e: - print "TaurusExtension: Not able to process 'raise': '%s'" % line - print " Reason:", str(e) + except Exception as e: + print("TaurusExtension: Not able to process 'raise': '%s'" % line) + print(" Reason:", str(e)) new_lines.append(line) return new_lines diff --git a/doc/source/tep/TEP18.md b/doc/source/tep/TEP18.md new file mode 100644 index 000000000..c7f1783ab --- /dev/null +++ b/doc/source/tep/TEP18.md @@ -0,0 +1,503 @@ + Title: Implement support for Qt5 in taurus + TEP: 18 + State: ACCEPTED + Date: 2019-01-28 + Drivers: Carlos Pascual-Izarra cpascual@cells.es + URL: http://www.taurus-scada.org/tep/?TEP18.md + License: http://www.jclark.com/xml/copying.txt + Abstract: + Implement support for Qt5 in taurus. Also define the + migration strategy for applications using taurus. + + +## Intro + +PyQt4 has been deprecated and unsupported for quite some time. + +Support for Qt4 is going to be dropped in debian buster (freeze +coming in Jan2019) and packages depending on Qt4 will be dropped. +A release critical bug on taurus is already pending (see [taurus_bug]) + +### Current situation + +The current Taurus stable release (4.4) provides a shim module +(`taurus.external.qt`) which is used throughout the taurus and which is +recommended for apps using taurus. + +The current implementation of `taurus.external.qt` was adapted +from an early version in `spyder` and heavily modified by +us, but it is definitely buggy for bindings other than PyQt4. +It is also "dirty" due to legacy code related to sip API 1 +(which is no longer supported in Taurus 4). + +The taurus files (and the apps using taurus 4) are currently written in PyQt4 style, +(e.g. without differentiating QtGui and QtWidgets). + +### Goals + +These are **desired** goals in rough order of importance. They are **not** +all compatible with each other: + +1. Allow taurus to run with PyQt5 binding +2. Do not force apps using taurus to migrate to Qt5 (i.e. still support + the PyQt4 binding and respect the app choice of bindings) +3. Keep backwards-compat (do not impose code changes on apps that use`taurus.external.qt`) +4. Avoid patching the loaded binding (or at most make only "inoquous" patches) +5. Use existing solution. Avoid reinventing / maintaining our own solution. +6. Modernize the code style in taurus (use Qt5 code style throughout taurus) +7. Minimize the amount of changes needed in the taurus code +8. Avoid introducing heavy dependencies in the `taurus.qt` submodule + + +## Proposed implementation + +This section describes and justifies the key decisions behind the proposed implementation. + +### Existing shims used as references + +We studied Qt shims implemented by other projects: + +- spyder: [qtpy] : Emulates PyQt5 PySide2 binding for the other bindings. It may patch the + existing binding in incompatible ways (side-effects). It provides some wrappers. + It is popular and has an active and responsive community. It is intended to be used by other projects. +- Qt.py: [Qt.py] : Emulates PySide2 binding for the other bindings. It avoids patching by + design in favour of wrappers. It is intended to be used by other projects. +- silx: [silx.qt] : Provides a custom consolidated module that is uniform across bindings + (e.g. the `silx.qt` module provides all classes from QtCore, QtGui, QtWidgets, ....). + It also provides some wrappers and does limited inocuous patching. It is more of a + custom solution, not designed to be used by other projects. +- pyqtgraph [pyqtgraph.Qt] : Emulates PyQt4 binding for the other bindings. It patches existing bindings + adding members (e.g. if using the PyQt5 module, it monkey-patches all QtWidgets into QtGui). + It is more of a custom solution, not designed to be used by other projects. + +### Necessity of writing our own shim (but not from scratch!) + +After studying all the mentioned implementations, we conclude that all of them fulfill +Goals 1 and 2 but none of them, if used directly *as-is* fulfills Goal 3: qtpy +and Qt.py impose using PySide2 style (e.g. they do not expose pyqtSignal). In the case +of silx.qt, the layout of the consolidated `silx.qt` does not provide, e.g. `QtGui` or `QtCore`, +which is expected by apps that use `taurus.external.qt.QtGui`. In the case of `pyqtgraph.Qt`, +since it does not separate the submodules into subpackages, something like +`from pyqtgraph.Qt.QtCore import QObject` fails. + +Therefore we are forced to do some custom shimming if we want to keep backwards compatibility +for apps using `taurus.external.qt`. While this necessarily implies renouncing to fulfill +Goal 5, we still considered importing one of the existing shims in order to "outsource" +some of the maintenance effort. + +Unfortunately, importing `silx.gui.qt` or `pyqtgraph.Qt` would go +against Goal 8 (even if both silx and pyqtgraph are dependencies for some submodules +of `taurus.qt.qtgui`, we would like to avoid making them mandatory at the `taurus.qt` +level since they are relatively heavy). + +On the other hand, `qtpy` and `Qt.py` are probably light enough to consider using them but +the fact that `Qt.py` is not yet packaged for debian and that the version of `qtpy` packaged +in debian9 introduces disruptive patching of the PyQt4 binding ([qtpy_issue119]) weighted +against it. + +In conclusion, for the proposed implementation we opted for writing our own shim based heavily +on the qtpy implementation, but with enough changes that Goal 5 cannot be considered +as being fulfilled. + +### Patching instead of wrapping + +Regarding the way of smoothing binding incompatibilities, we should avoid patching existing +bindings (Goal 4). In this regard, the approaches of Qt.py and silx.gui.qt should be our +reference. The preferred solution is to use adapters (`taurus.external.qt.Qt*`) +and provide compatibility wrappers whenever this cannot be done. In the worst case, we could +accept patching an existing binding, but only if the result never introduces +a side-efect in an application that uses valid code (similar to the compromise taken in +[qtpy_issue121]). + +### Bindings support + +Our priority is on adding PyQt5 (v>=5.6) binding support while maintaining the PyQt4 binding +(v>=4.6) support. +The proposed implementation also attempts to provide PySide and PySide2 support, but the +development and testing is done on PyQt4 and PyQt5 and therefore this first proposed +implementation is likely not expected to be fully functional under PySide/Pyside2. +Nevertheless, this situation is subject to change if there is enough interest in the +community to dedicate more efforts to test and support taurus with PySide and/or PySide2. + +The proposed implementation allows the user to select the QT binding by declaring +the `QT_API` environment variable with one of the currently accepted values: + +- `pyqt` (or `pyqt4`) for PyQt4 +- `pyqt5` for PyQt5 +- `pyside2` for PySide2 +- `pyside` for PySide + +If the `QT_API` environment variable is not declared, Taurus will fall back to the value set in +`taurus.tauruscustomsettings.DEFAULT_QT_API`. + +If the selected binding is not available in the system, Taurus will try the next +ones from the list above. + +### Support for multiple Qt styles within taurus.external.qt + +Independently of which plugins are supported as backends, we had to decide which programming +style(s) are be supported. For example, qtpy supports all 4 plugins but only one programming +style (that of PySide2). + +In our case, the Goal 3 immediately dictates that we must support at least the PyQt4 style. +Also, by guaranteeing backwards compatibility for the apps using `taurus.external.qt`, we +are also allowing `taurus.qt` itself to keep using the old style (fulfilling Goal 7). + +On the other hand, Goal 6 demands that we also support PyQt5 or PySide2 style but +for simplicity, we focused the proposed implementation in providing good support for the currently +supported style (PyQt4) regardless of the binding. In other words, the proposed +implementation does not fulfill Goal 6 (but it still is flexible enough to allow the support of +a more modern style in the future) + +## How to import Qt modules when writing taurus code. + +Until v 4.4, we have recommended taurus users to always import QtCore, QtGui, etc +from `taurus.external.qt`. But with the improved support of multiple bindings +provided by this TEP, this recommendation can be revised as follows: + +- *For code that is going to be part of Taurus* (and consequently potentially + used as library by other other people), Qt, QtGui, QtCore, etc. should still be + imported from `taurus.external.qt`. The same applies to plugins to taurus + that intend to be used as a library (otherwise, the plugins should be capable + of failing gracefully in case of incompatible bindings). + +- *For an end-user application based on taurus* it is probably better to import + directly from a specific binding (PyQt5 is the best supported) and let taurus to + adapt to that choice. In this way, one can write idiomatic code that better + matches the chosen binding. Using the `taurus.external.qt` shim + is also possible if one wants to make the code binding-agnostic, but in that + case one must keep in mind that the resulting code will be less idiomatic + and that the shim's API may be eventually altered to better fit with taurus + own requirements (and that those changes may not be aligned with the + application needs). + +Notes: +- See the APPENDIX I for tips for writing code that is Qt binding agnostic +- See the APPENDIX II for code snippets that exemplify different ways of using + the `taurus.external.qt` shim from the proposed implementation. + + +## Links to more details and discussions + +Discussions for this TEP (and the proposed implementation) are conducted in its +associated Pull Request: + +https://github.com/taurus-org/taurus/pull/814 + +Related issues are: + +https://github.com/taurus-org/taurus/issues/203 +https://github.com/taurus-org/taurus/issues/148 + + +## APPENDIX I: Tips for writing code that is Qt binding agnostic: + +Apart from using taurus.external.qt for importing the Qt submodules, the +following tips were found useful when porting applications to taurus 4.5 + +- all signal usage must be ["new-style signals"](http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html) + - and keep in mind that signals with default arguments are not + supported in exposed as various signals in PyQt>=5.3 + +- Use only APIv2 (e.g. QString should *not* be used,...). One way to ensure that + your code uses APIv2 (in case that you use PyQt4) is to + [explicitly set it](http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html) + **before importing PyQt4**: + ```python + import sip + sip.setapi('QString', 2) + sip.setapi('QVariant', 2) + sip.setapi('QDate', 2) + sip.setapi('QDateTime', 2) + sip.setapi('QTextStream', 2) + sip.setapi('QTime', 2) + sip.setapi('QUrl', 2) + ``` + +- [QVariant should not be used at all](http://pyqt.sourceforge.net/Docs/PyQt5/pyqt_qvariant.html): + replace `QVariant(foo)`, `from_qvariant(foo)` and `to_qvariant(foo)` + by `foo`. Also replace any occurrence of `QVariant()` (i.e. null `QVariant`) + by `None`. + +- The code should not use Qt deprecated features. See the following for a + list of things to keep in mind: + - https://pyqt.readthedocs.io/en/latest/incompatibilities.html + - https://pyqt.readthedocs.io/en/latest/pyqt4_differences.html + +- Multiple inheritance. See + http://pyqt.sf.net/Docs/PyQt5/pyqt4_differences.html#cooperative-multi-inheritance + - Note that in Taurus we use explicit calls to __init__ methods which + in some cases it can lead to double-calls to initialization code in pyqt5. + This needs to be addressed. + - Also note that we had to convert some positional args into keyword args in + our mixin classes (TaurusBaseComponent,...) to make them work with Qt5 + +- The `taurus.qt.qtgui.plot` module depends on the Qwt5 module to provide its + full API, but Qwt5 is only available in the PyQt4 binding and therefore the + `taurus.qt.qtgui.plot` module has been deprecated. For the other bindings, + this module attempts to provide a very limited backwards compatibility API + if the `taurus_pyqtgraph` module is installed. If an application is intended + to support PyQt4 only, then it can avoid the deprecation warnings by replacing + `taurus.qt.qtgui.plot` by `taurus.qt.qtgui.qwt5`. Otherwise, using + `taurus.qt.qtgui.tpg` (provided by the `taurus_pyqtgraph` plugin) is recommended. + +- `QLayout.margin` and `.setMargin` were [deprecated in Qt 4.8](http://doc.qt.io/archives/qt-4.8/qlayout-obsolete.html) + Use `.getContentsMargins()` and `setContentsMargins()` instead. + +- be careful with qInstallMsgHandler (Qt4) vs qInstallMessageHandler (Qt5). + The following code can be used as a reference: + + ```python + if hasattr(QtCore, "qInstallMessageHandler"): + # Qt5 + def taurusMessageHandler(msg_type, log_ctx, msg): + f = QT_LEVEL_MATCHER.get(msg_type) + return f("Qt%s %s.%s[%s]: %a", log_ctx.category, log_ctx.file, + log_ctx.function, log_ctx.line, msg) + + QtCore.qInstallMessageHandler(taurusMessageHandler) + elif hasattr(QtCore, "qInstallMsgHandler"): + # Qt4 + def taurusMsgHandler(msg_type, msg): + f = QT_LEVEL_MATCHER.get(msg_type) + return f("Qt: " + msg) + + QtCore.qInstallMsgHandler(taurusMsgHandler) + ``` + +- Be aware of the [renamed methods in QHeaderView](http://doc.qt.io/qt-5/sourcebreaks.html#changes-to-qheaderview) (from Qt4 to Qt5) + and the fact that in Qt5, setSectionResizeMode may **crash the GUI** if + called on an empty header. The following code snippet takes care of both + issues: + ```python + headerView = ... # <-- QHeaderView + if headerView.length() > 0: + try: + headerView.setSectionResizeMode(headerView.Stretch) + except AttributeError: # PyQt4 + headerView.setResizeMode(headerView.Stretch) + ``` + +- Note that the `getOpenFileName`, `getOpenFileNames` and `getSaveFileName` + static methods from `QFileDialog` only return the name (or names) in PyQt4 + but they return a (name/s, filter) tuple in the other bindings. + In order to facilitate writing binding-agnostic code, we provide the + `getOpenFileName`, `getOpenFileNames` and `getSaveFileName` functions in + the `taurus.external.qt.compat` module which return the tuple regardless + of the binding. + +- Beware of [a bug in KDE](https://bugs.kde.org/show_bug.cgi?id=345023) which + modifies the `text` property of buttons and actions by auto-inserting and + "&" character in unpredictable positions when running under KDE and using + Qt5. In practice the best is to avoid relying on the return value of the + `text` property of buttons or actions in the program logic. + + + +## APPENDIX II: Some examples of code + +### Code that already works in v4.4: + +The following snippets work on taurus 4.4. They should also work after refactoring. + +Most common use of `taurus.external.qt` (with implicit selection of PyQt4 binding): + +```python +# emulate an application that has previously imported PyQt4 (with API 2) +import sys +import sip +API_NAMES = ["QDate", "QDateTime", "QString", "QTextStream", "QTime", "QUrl", + "QVariant"] +for name in API_NAMES: + sip.setapi(name, 2) + +import PyQt4.QtGui + +from taurus.external.qt import Qt, QtGui, QtCore +from taurus.qt.qtgui.display import TaurusLabel + +a = Qt.QApplication(sys.argv) +o = QtCore.QObject() +w = QtGui.QLabel() +l = TaurusLabel() +``` + +Most common use of `taurus.external.qt` (with explicit selection of PyQt4 binding) : + +```python +import sys +from taurus import tauruscustomsettings +tauruscustomsettings.DEFAULT_QT_API = 'pyqt' + +from taurus.external.qt import Qt, QtGui, QtCore +from taurus.qt.qtgui.display import TaurusLabel + +a = Qt.QApplication(sys.argv) +o = QtCore.QObject() +w = QtGui.QLabel() +l = TaurusLabel() +``` + +Exotic (but allowed) use of `taurus.external.qt` (with implicit selection of PyQt4 binding): + +```python +# emulate an application that has previously imported PyQt4 (with API 2) +import sys +import sip +API_NAMES = ["QDate", "QDateTime", "QString", "QTextStream", "QTime", "QUrl", + "QVariant"] +for name in API_NAMES: + sip.setapi(name, 2) + +import PyQt4.QtGui + +import taurus.external.qt.Qt +import taurus.external.qt.QtCore +import taurus.external.qt.QtGui +from taurus.qt.qtgui.display import TaurusLabel + +a = taurus.external.qt.Qt.QApplication(sys.argv) +o = taurus.external.qt.QtCore.QObject() +w = taurus.external.qt.QtGui.QLabel() +l = TaurusLabel() +``` + +### code that works with the proposed implementation of this TEP: + +Most common use of `taurus.external.qt` (with implicit selection of PyQt5 binding) : + +```python +import sys +import PyQt5.QtWidgets # force using PyQt5 + +from taurus.external.qt import Qt, QtGui, QtCore +from taurus.qt.qtgui.display import TaurusLabel + +a = Qt.QApplication(sys.argv) +o = QtCore.QObject() +w = QtGui.QLabel() +l = TaurusLabel() + +assert(QtGui.QLabel is PyQt5.QtWidgets.QLabel) +``` + +Most common use of `taurus.external.qt` (with explicit selection of PyQt5 binding) : + +```python +import sys +from taurus import tauruscustomsettings +tauruscustomsettings.DEFAULT_QT_API = 'pyqt5' + +from taurus.external.qt import Qt, QtGui, QtCore +from taurus.qt.qtgui.display import TaurusLabel + +a = Qt.QApplication(sys.argv) +o = QtCore.QObject() +w = QtGui.QLabel() +l = TaurusLabel() + +import PyQt5.QtWidgets +assert(QtGui.QLabel is PyQt5.QtWidgets.QLabel) +``` + + +Exotic (but allowed) use of `taurus.external.qt`: + +```python +import sys +import PyQt5.QtWidgets # force using PyQt5 + +import taurus.external.qt.Qt +import taurus.external.qt.QtCore +import taurus.external.qt.QtGui +from taurus.qt.qtgui.display import TaurusLabel + +a = taurus.external.qt.Qt.QApplication(sys.argv) +o = taurus.external.qt.QtCore.QObject() +w = taurus.external.qt.QtGui.QLabel() +l = TaurusLabel() +``` + +Using `taurus.external.qt` with PyQt5 style and Pyqt5 binding: + +```python +import sys +from taurus import tauruscustomsettings +tauruscustomsettings.DEFAULT_QT_API = 'pyqt5' + +from taurus.external.qt import Qt, QtGui, QtCore, QtWidgets +from taurus.qt.qtgui.display import TaurusLabel + +g = QtGui.QGuiApplication(sys.argv) +a = Qt.QApplication(sys.argv) +o = QtCore.QObject() +w = QtWidgets.QLabel() +l = TaurusLabel() +``` + +## Cases not fully covered in the proposed implementation. + +The possibility of using `taurus.external.qt` with Qt5 style but a PyQt4 +binding would be desirable because it would smooth the transition of +taurus and taurus-based applications towards the newer style (Goal 6) but +out of the scope of the current implementation. +The following snippet shows an example of some APIs that work in this +scenario while some other APIs are not yet supported. + +```python +import sys +from taurus import tauruscustomsettings +tauruscustomsettings.DEFAULT_QT_API = 'pyqt' + +from taurus.external.qt import Qt, QtGui, QtCore, QtWidgets +from taurus.qt.qtgui.display import TaurusLabel + +g = QtGui.QGuiApplication(sys.argv) # <-- this is Qt5 style and not supported +a = Qt.QApplication(sys.argv) +o = QtCore.QObject() +w = QtWidgets.QLabel() # <-- this is Qt5 style but is supported +l = TaurusLabel() +``` + +## License + +Copyright (c) 2018 Carlos Pascual-Izarra + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Changes + +- 2018-10-04 [cpascual][]. Initial version +- 2018-11-27 [cpascual][]. Moving to CANDIDATE. Prototype implementation underway +- 2019-01-16 [cpascual][]. Update text to reflect the finished proposed implementation. +- 2019-01-28 [cpascual][]. Moving to ACCEPTED (merged https://github.com/taurus-org/taurus/pull/814) + + + +[taurus_bug]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=875202 +[qtpy_issue119]: https://github.com/spyder-ide/qtpy/issues/119 +[qtpy_issue121]: https://github.com/spyder-ide/qtpy/issues/121 + +[qtpy]: https://github.com/spyder-ide/qtpy +[silx.qt]: https://github.com/silx-kit/silx/blob/master/silx/gui/qt +[pyqtgraph.Qt]: https://github.com/pyqtgraph/pyqtgraph/blob/develop/pyqtgraph/Qt.py +[Qt.py]: https://github.com/mottosso/Qt.py + +[cpascual]: https://github.com/cpascual diff --git a/doc/source/tep/index.md b/doc/source/tep/index.md index 56cccb6c7..41480f384 100644 --- a/doc/source/tep/index.md +++ b/doc/source/tep/index.md @@ -24,6 +24,7 @@ Proposals list [TEP15][] | ACCEPTED | fragment-based slicing support in URIs [TEP16][] | ACCEPTED | Moving Taurus to Github [TEP17][] | DRAFT | Implement plots with pyqtgraph + [TEP18][] | ACCEPTED | Implement support for Qt5 in taurus [TEP0]: http://www.taurus-scada.org/tep/?TEP0.md [TEP3]: http://www.taurus-scada.org/tep/?TEP3.md @@ -36,6 +37,7 @@ Proposals list [SEP12]: http:/www.sardana-controls.org/sep/?SEP12.md [TEP13]: http://www.taurus-scada.org/tep/?TEP13.md [TEP14]: http://www.taurus-scada.org/tep/?TEP14.md -[TEP15]: https://github.com/cpascual/taurus/blob/tep15/doc/source/tep/TEP15.md +[TEP15]: http://www.taurus-scada.org/tep/?TEP15.md [TEP16]: http://www.taurus-scada.org/tep/?TEP16.md [TEP17]: https://github.com/cpascual/taurus/blob/tep17/doc/source/tep/TEP17.md +[TEP18]: http://www.taurus-scada.org/tep/?TEP18.md diff --git a/doc/source/users/getting_started.rst b/doc/source/users/getting_started.rst index 8724bb39a..5570282a5 100644 --- a/doc/source/users/getting_started.rst +++ b/doc/source/users/getting_started.rst @@ -91,18 +91,23 @@ Then, to work in develop mode, just do:: Dependencies ------------ -Strictly speaking, Taurus only depends on numpy, but that will leave -out most of the features normally expected of Taurus (which are -considered "extras"). For example: +Strictly speaking, Taurus only depends on numpy_, pint_ and future_ +but that will leave out most of the features normally +expected of Taurus (which are considered "extras"). For example: - Interacting with a Tango controls system requires PyTango_. - Interacting with an Epics controls system requires pyepics_. -- Using the taurus Qt widgets, requires PyQt_ 4.x (4.8 <= v < 5). - (PyQt5 support coming soon). +- Using the taurus Qt_ widgets, requires either PyQt_ (v4 or v5) + or PySide_ (v1 or v2). Note that most development and testing of + is done with PyQt4 and PyQt5, so many features may not be + regularly tested with PySide and PySide2. -- The :mod:`taurus.qt.qtgui.plot` module requires PyQwt_. +- The :mod:`taurus.qt.qtgui.plot` module requires PyQwt_, which is + only available when using PyQt4 and python2. As an alternative + that supports both python2 and python3 and all the Qt bindings, + refer to the taurus_pyqtgraph_ plugin. - The image widgets require the guiqwt_ library. @@ -151,13 +156,17 @@ installation method: .. _numpy: http://numpy.org/ +.. _pint: http://pint.readthedocs.org/ +.. _future: https://python-future.org/ .. _PLY: http://www.dabeaz.com/ply/ .. _Python(x,y): http://python-xy.github.io/ .. _Tango: http://www.tango-controls.org/ .. _PyTango: http://pytango.readthedocs.io .. _Qt: http://qt.nokia.com/products/ .. _PyQt: http://www.riverbankcomputing.co.uk/software/pyqt/ +.. _PySide: https://wiki.qt.io/Qt_for_Python .. _PyQwt: http://pyqwt.sourceforge.net/ +.. _taurus_pyqtgraph: https://github.com/taurus-org/taurus_pyqtgraph .. _guiqwt: https://pypi.python.org/pypi/guiqtw .. _IPython: http://ipython.org .. _PyMca5: http://pymca.sourceforge.net/ diff --git a/lib/taurus/__init__.py b/lib/taurus/__init__.py index 3ed39289e..9a8e92b89 100644 --- a/lib/taurus/__init__.py +++ b/lib/taurus/__init__.py @@ -32,7 +32,9 @@ class Release: pass -Release.__dict__.update(__R.__dict__) +for attr, value in __R.__dict__.items(): + setattr(Release, attr, value) + Release.__doc__ = __R.__doc__ from .core.taurushelper import * diff --git a/lib/taurus/console/list.py b/lib/taurus/console/list.py index 035f135a2..9ebdf7bc2 100644 --- a/lib/taurus/console/list.py +++ b/lib/taurus/console/list.py @@ -25,14 +25,19 @@ """ """ -__all__ = ["List"] - -__docformat__ = "restructuredtext" +from __future__ import absolute_import +from future.utils import string_types +from builtins import map +from builtins import range import textwrap -import operator +import collections +from future.utils import string_types +from .enums import Alignment -from enums import Alignment + +__all__ = ["List"] +__docformat__ = "restructuredtext" class List(list): @@ -60,7 +65,7 @@ def __init__(self, header, header_separator=HeaderSeparator, self.append(header) def setHeaderSeparator(self, header_separator): - if isinstance(header_separator, (str, unicode)): + if isinstance(header_separator, string_types): header_separator = self.col_nb * [header_separator] self.HeaderSeparator = header_separator @@ -70,7 +75,7 @@ def getHeaderSeparator(self): header_separator = property(getHeaderSeparator, setHeaderSeparator) def setRowSeparator(self, row_separator): - if isinstance(row_separator, (str, unicode)): + if isinstance(row_separator, string_types): row_separator = self.col_nb * [row_separator] self.RowSeparator = row_separator @@ -82,7 +87,7 @@ def getRowSeparator(self): def setMaxColumnWidth(self, max_col_width): if max_col_width is None: max_col_width = -1 - if not operator.isSequenceType(max_col_width): + if not isinstance(max_col_width, collections.Sequence): max_col_width = self.col_nb * [max_col_width] self.MaxColumnWidth = max_col_width @@ -92,7 +97,7 @@ def getMaxColumnWidth(self): max_column_width = property(getMaxColumnWidth, setMaxColumnWidth) def setTextAlignment(self, text_alignment): - if not operator.isSequenceType(text_alignment): + if not isinstance(text_alignment, collections.Sequence): text_alignment = self.col_nb * [text_alignment] self.TextAlignment = text_alignment @@ -102,7 +107,7 @@ def getTextAlignment(self): text_alignment = property(getTextAlignment, setTextAlignment) def _transform_row(self, row): - return map(str, row[:self.col_nb]) + return list(map(str, row[:self.col_nb])) def __setitem__(self, i, row): return list.__setitem__(self, i, self._transform_row(row)) @@ -135,7 +140,7 @@ def genOutput(self): def _get_separator_row(self, separator): columns = [] for i, width in enumerate(self.cur_col_width): - if isinstance(separator[i], str): + if isinstance(separator[i], string_types): column = " " + (width - 1) * separator[i] else: column = " " + separator[i][:width - 1] diff --git a/lib/taurus/console/table.py b/lib/taurus/console/table.py index dfdf7943a..389ac5380 100644 --- a/lib/taurus/console/table.py +++ b/lib/taurus/console/table.py @@ -24,13 +24,19 @@ ############################################################################# """ """ +from __future__ import division +from builtins import map +from builtins import range +from builtins import object +from past.utils import old_div +from functools import reduce __all__ = ["Table"] __docformat__ = "restructuredtext" -class Table: +class Table(object): DefTermWidth = 80 @@ -57,14 +63,14 @@ def __init__(self, elem_list, elem_fmt=None, term_width=None, self.col_head_sep = col_head_sep self.border = border - max_len_fn = lambda x: reduce(max, map(len, x)) + max_len_fn = lambda x: reduce(max, list(map(len, x))) self.row_head_str = row_head_str self.row_head_fmt = row_head_fmt if row_head_str is not None and len(row_head_str) != self.nr_row: msg = 'RowHeadStr nr (%d) and RowNr (%d) mistmatch' % \ (len(row_head_str), self.nr_row) - raise ValueError, msg + raise ValueError(msg) if row_head_width is None: if row_head_str is not None: row_head_width = max_len_fn(row_head_str) @@ -75,12 +81,12 @@ def __init__(self, elem_list, elem_fmt=None, term_width=None, self.col_head_str = col_head_str self.col_head_fmt = col_head_fmt if col_head_str is not None and len(col_head_str) != self.nr_col: - msg = 'ColHeadStr nr (%d) and ColNr (%d) mistmatch' % \ - len(col_head_str), self.nr_col - raise ValueError, msg + msg = 'ColHeadStr nr (%d) and ColNr (%d) mistmatch' % ( + len(col_head_str), self.nr_col) + raise ValueError(msg) if col_head_width is None: if col_head_str is not None: - col_head_width = reduce(max, map(max_len_fn, col_head_str)) + col_head_width = reduce(max, list(map(max_len_fn, col_head_str))) else: col_head_width = 10 self.col_head_width = col_head_width @@ -106,7 +112,7 @@ def genOutput(self, term_width=None): width = term_width - chw # At least one disp column! if rhw > 0: width -= rhw + lcs - disp_cols = width / (chw + lcs) + 1 + disp_cols = old_div(width, (chw + lcs)) + 1 tot_width = chw + (disp_cols - 1) * (chw + lcs) tot_rows = chl + self.nr_row if rhw > 0: @@ -123,36 +129,36 @@ def genOutput(self, term_width=None): else: row_head = [''] * tot_rows - for i in xrange(0, self.nr_col, disp_cols): + for i in range(0, self.nr_col, disp_cols): if i > 0: - nr_sep = tot_width / len(self.row_sep) + nr_sep = old_div(tot_width, len(self.row_sep)) output.append(self.row_sep * nr_sep) row_end = min(i + disp_cols, self.nr_col) line = list(row_head) - for j in xrange(i, row_end): + for j in range(i, row_end): elem = self.elem_list[j] if chl: col_head = self.col_head_str[j] if j > i: - for k in xrange(tot_rows): + for k in range(tot_rows): line[k] += self.col_sep fmt = self.col_head_fmt - for k in xrange(chl): + for k in range(chl): line[k] += fmt % (chw, col_head[k]) - for k in xrange(self.nr_row): + for k in range(self.nr_row): fmt = self.elem_fmt[k] line[chl + k] += fmt % (chw, elem[k]) - max_width = reduce(max, map(len, line)) + max_width = reduce(max, list(map(len, line))) if self.border is not None: - nr_border = max_width / len(self.border) + nr_border = old_div(max_width, len(self.border)) output.append(self.border * nr_border) for l in line[:chl]: output.append(l) if self.col_head_sep is not None: - nr_sep = max_width / len(self.col_head_sep) + nr_sep = old_div(max_width, len(self.col_head_sep)) output.append(self.col_head_sep * nr_sep) for l in line[chl:]: output.append(l) diff --git a/lib/taurus/core/__init__.py b/lib/taurus/core/__init__.py index 0ef1314bb..82b5e5e76 100644 --- a/lib/taurus/core/__init__.py +++ b/lib/taurus/core/__init__.py @@ -24,15 +24,17 @@ ############################################################################# """The core module""" +from __future__ import absolute_import + +import taurus.tauruscustomsettings __docformat__ = "restructuredtext" -import taurus.tauruscustomsettings LIGHTWEIGHT_IMPORTS = getattr( taurus.tauruscustomsettings, 'LIGHTWEIGHT_IMPORTS', False) if LIGHTWEIGHT_IMPORTS: - from init_lightweight import * + from .init_lightweight import * else: - from init_bkcomp import * + from .init_bkcomp import * diff --git a/lib/taurus/core/epics/__init__.py b/lib/taurus/core/epics/__init__.py index 086177492..b39cf4a27 100644 --- a/lib/taurus/core/epics/__init__.py +++ b/lib/taurus/core/epics/__init__.py @@ -66,5 +66,5 @@ are just convenience dummy objects in the epics scheme at this point. Epics records may eventually be mapped as Devices. """ - -from epicsfactory import * +from __future__ import absolute_import +from .epicsfactory import * diff --git a/lib/taurus/core/epics/epicsattribute.py b/lib/taurus/core/epics/epicsattribute.py index 75f1eec41..bba58f843 100644 --- a/lib/taurus/core/epics/epicsattribute.py +++ b/lib/taurus/core/epics/epicsattribute.py @@ -22,24 +22,28 @@ ## ############################################################################# -''' +""" Epics module. See __init__.py for more detailed documentation -''' -__all__ = ['EpicsAttribute'] +""" +from __future__ import print_function +from __future__ import absolute_import import numpy from taurus.core.units import Quantity - from taurus.core.taurusbasetypes import (TaurusEventType, TaurusAttrValue, TaurusTimeVal, AttrQuality, DataType, DataFormat) from taurus.core.taurusattribute import TaurusAttribute - import epics from epics.ca import ChannelAccessException from epics import dbr + +__all__ = ['EpicsAttribute'] + + + # map for epics DBR types to Taurus Types. Dbr2TaurusType = {dbr.STRING: DataType.String, dbr.INT: DataType.Integer, @@ -81,7 +85,7 @@ class EpicsAttribute(TaurusAttribute): """ # TODO: support non-numerical PVs - def __init__(self, name, parent, storeCallback=None): + def __init__(self, name='', parent=None, storeCallback=None): self.call__init__(TaurusAttribute, name, parent, storeCallback=storeCallback) @@ -233,12 +237,12 @@ def _unsubscribeEvents(self): # ------------------------------------------------------------------------------ def factory(self): - from epicsfactory import EpicsFactory + from .epicsfactory import EpicsFactory return EpicsFactory() @classmethod def getNameValidator(cls): - from epicsvalidator import EpicsAttributeNameValidator + from .epicsvalidator import EpicsAttributeNameValidator return EpicsAttributeNameValidator() @@ -251,6 +255,6 @@ def getNameValidator(cls): b.write(4.) s.read() - print "!$!", s.read(cache=False) - print "a,b,s", a.read().rvalue, b.read().rvalue, s.read().rvalue - print "DF=", a.getDataFormat(), DataFormat.whatis(a.getDataFormat()) + print("!$!", s.read(cache=False)) + print("a,b,s", a.read().rvalue, b.read().rvalue, s.read().rvalue) + print("DF=", a.getDataFormat(), DataFormat.whatis(a.getDataFormat())) diff --git a/lib/taurus/core/epics/epicsfactory.py b/lib/taurus/core/epics/epicsfactory.py index aeda48f34..93cc43abe 100644 --- a/lib/taurus/core/epics/epicsfactory.py +++ b/lib/taurus/core/epics/epicsfactory.py @@ -22,14 +22,11 @@ ## ############################################################################# -''' +""" Epics module. See __init__.py for more detailed documentation -''' -__all__ = ['EpicsFactory'] - - +""" +from __future__ import absolute_import import weakref - try: import epics except ImportError: @@ -37,16 +34,16 @@ debug('cannot import epics module. ' + 'Taurus will not support the "epics" scheme') raise - from taurus.core.taurusexception import TaurusException from taurus.core.util.singleton import Singleton from taurus.core.util.log import Logger from taurus.core.taurusbasetypes import TaurusElementType from taurus.core.taurusfactory import TaurusFactory +from .epicsattribute import EpicsAttribute +from .epicsdevice import EpicsDevice +from .epicsauthority import EpicsAuthority -from epicsattribute import EpicsAttribute -from epicsdevice import EpicsDevice -from epicsauthority import EpicsAuthority +__all__ = ['EpicsFactory'] class EpicsFactory(Singleton, TaurusFactory, Logger): @@ -145,17 +142,17 @@ def getAttribute(self, attr_name): def getAuthorityNameValidator(self): """Return EpicsAuthorityNameValidator""" - import epicsvalidator + from . import epicsvalidator return epicsvalidator.EpicsAuthorityNameValidator() def getDeviceNameValidator(self): """Return EpicsDeviceNameValidator""" - import epicsvalidator + from . import epicsvalidator return epicsvalidator.EpicsDeviceNameValidator() def getAttributeNameValidator(self): """Return EpicsAttributeNameValidator""" - import epicsvalidator + from . import epicsvalidator return epicsvalidator.EpicsAttributeNameValidator() if __name__ == "__main__": diff --git a/lib/taurus/core/epics/test/test_epicsattribute.py b/lib/taurus/core/epics/test/test_epicsattribute.py index 54189cc8c..3a32c91f3 100755 --- a/lib/taurus/core/epics/test/test_epicsattribute.py +++ b/lib/taurus/core/epics/test/test_epicsattribute.py @@ -56,7 +56,7 @@ error=None, ) ) -@unittest.skipIf(sys.modules.has_key('epics') is False, +@unittest.skipIf(('epics' in sys.modules) is False, "epics module is not available") class AttributeTestCase(unittest.TestCase): """TestCase for the taurus.Attribute helper""" @@ -101,7 +101,7 @@ def write_read_attr(self, attrname=None, setvalue=None, expected=None, self.assertTrue(isinstance(read_value, TaurusAttrValue), msg) # Test attribute - for k, exp in expected.iteritems(): + for k, exp in expected.items(): try: got = getattr(a, k) except AttributeError: @@ -113,7 +113,7 @@ def write_read_attr(self, attrname=None, setvalue=None, expected=None, self.__assertValidValue(exp, got, msg) # Test attribute value - for k, exp in expected_attrv.iteritems(): + for k, exp in expected_attrv.items(): try: got = getattr(read_value, k) except AttributeError: diff --git a/lib/taurus/core/epics/test/test_epicsvalidator.py b/lib/taurus/core/epics/test/test_epicsvalidator.py index 3a8e8a103..889da1487 100644 --- a/lib/taurus/core/epics/test/test_epicsvalidator.py +++ b/lib/taurus/core/epics/test/test_epicsvalidator.py @@ -29,7 +29,6 @@ __docformat__ = 'restructuredtext' import sys - import unittest from taurus.core.test import (valid, invalid, names, AbstractNameValidatorTestCase) @@ -47,7 +46,7 @@ @invalid(name='ca:/') @invalid(name='ca:///') @invalid(name='ca://a') -@unittest.skipIf(sys.modules.has_key('epics') is False, +@unittest.skipIf(('epics' in sys.modules) is False, "epics module is not available") class EpicsAuthValidatorTestCase(AbstractNameValidatorTestCase, unittest.TestCase): @@ -66,7 +65,7 @@ class EpicsAuthValidatorTestCase(AbstractNameValidatorTestCase, @invalid(name='ca:foo') # device requires absolute path @invalid(name='ca:/foo') # devname must be empty (for now) @invalid(name='ca:@foo') -@unittest.skipIf(sys.modules.has_key('epics') is False, +@unittest.skipIf(('epics' in sys.modules) is False, "epics module is not available") class EpicsDevValidatorTestCase(AbstractNameValidatorTestCase, unittest.TestCase): @@ -129,7 +128,7 @@ class EpicsDevValidatorTestCase(AbstractNameValidatorTestCase, @valid(name='ca:1#units', groups={'fragment': 'units'}) @valid(name='ca:a') @names(name='ca:XXX:sum', out=('ca:XXX:sum', 'XXX:sum', 'XXX:sum')) -@unittest.skipIf(sys.modules.has_key('epics') is False, +@unittest.skipIf(('epics' in sys.modules) is False, "epics module is not available") class EpicsAttrValidatorTestCase(AbstractNameValidatorTestCase, unittest.TestCase): diff --git a/lib/taurus/core/evaluation/__init__.py b/lib/taurus/core/evaluation/__init__.py index a03060bff..bd489b9d5 100644 --- a/lib/taurus/core/evaluation/__init__.py +++ b/lib/taurus/core/evaluation/__init__.py @@ -173,8 +173,9 @@ class MyClass(object): This syntax is now deprecated and should not be used. Taurus will issue warnings if detected. """ +from __future__ import absolute_import -from evalfactory import EvaluationFactory -from evalattribute import EvaluationAttribute -from evalauthority import EvaluationAuthority -from evaldevice import EvaluationDevice +from .evalfactory import EvaluationFactory +from .evalattribute import EvaluationAttribute +from .evalauthority import EvaluationAuthority +from .evaldevice import EvaluationDevice diff --git a/lib/taurus/core/evaluation/evalattribute.py b/lib/taurus/core/evaluation/evalattribute.py index 7b9b85696..f7277f2eb 100644 --- a/lib/taurus/core/evaluation/evalattribute.py +++ b/lib/taurus/core/evaluation/evalattribute.py @@ -60,6 +60,10 @@ def __init__(self, attr=None, config=None): self.config = self._attrRef def __getattr__(self, name): + # Do not try to delegate special methods + if name.startswith('__') and name.endswith('__'): + raise AttributeError("'%s' object has no attribute %s" + % (self.__class__.__name__, name)) try: ret = getattr(self._attrRef, name) except AttributeError: @@ -167,7 +171,7 @@ class EvaluationAttribute(TaurusAttribute): _factory = None _scheme = 'eval' - def __init__(self, name, parent, **kwargs): + def __init__(self, name='', parent=None, **kwargs): self.call__init__(TaurusAttribute, name, parent, **kwargs) self._value = EvaluationAttrValue(attr=self) @@ -200,7 +204,7 @@ def _initWritable(self, trstring): for n in names[1:-1]: obj = getattr(obj, n) obj = getattr(obj.__class__, names[-1]) - except Exception, e: + except Exception as e: # self.info("%r", e) return ###################################################################### @@ -260,7 +264,7 @@ def preProcessTransformation(self, trstring): trstring = v.replaceUnquotedRef(trstring, '{%s}' % r, symbol) # validate the expression (look for missing symbols) - safesymbols = evaluator.getSafe().keys() + safesymbols = list(evaluator.getSafe().keys()) # remove literal text strings from the validation trimmedstring = re.sub(QUOTED_TEXT_RE, '', trstring) for s in set(re.findall(PY_VAR_RE, trimmedstring)): @@ -359,7 +363,7 @@ def applyTransformation(self): self._value.rvalue = rvalue self._value.time = TaurusTimeVal.now() self._value.quality = AttrQuality.ATTR_VALID - except Exception, e: + except Exception as e: self._value.quality = AttrQuality.ATTR_INVALID msg = " the function '%s' could not be evaluated. Reason: %s" \ % (self._transformation, repr(e)) diff --git a/lib/taurus/core/evaluation/evaldevice.py b/lib/taurus/core/evaluation/evaldevice.py index 74eac3fae..5efe2c042 100644 --- a/lib/taurus/core/evaluation/evaldevice.py +++ b/lib/taurus/core/evaluation/evaldevice.py @@ -47,7 +47,7 @@ class EvaluationDevice(TaurusDevice, SafeEvaluator): _factory = None _scheme = 'eval' - def __init__(self, name, **kw): + def __init__(self, name='', **kw): """Object initialization.""" self.call__init__(TaurusDevice, name, **kw) safedict = {} diff --git a/lib/taurus/core/evaluation/evalfactory.py b/lib/taurus/core/evaluation/evalfactory.py index 95b5cd05a..f7bd6c5cf 100755 --- a/lib/taurus/core/evaluation/evalfactory.py +++ b/lib/taurus/core/evaluation/evalfactory.py @@ -25,20 +25,20 @@ ''' evaluation module. See __init__.py for more detailed documentation ''' -__all__ = ['EvaluationFactory'] - +from __future__ import absolute_import import weakref - from taurus.core.taurusbasetypes import TaurusElementType -from evalattribute import EvaluationAttribute -from evalauthority import EvaluationAuthority -from evaldevice import EvaluationDevice +from .evalattribute import EvaluationAttribute +from .evalauthority import EvaluationAuthority +from .evaldevice import EvaluationDevice from taurus.core.taurusexception import TaurusException, DoubleRegistration from taurus.core.util.log import Logger from taurus.core.util.singleton import Singleton from taurus.core.taurusfactory import TaurusFactory +__all__ = ['EvaluationFactory'] + class EvaluationFactory(Singleton, TaurusFactory, Logger): """ @@ -195,7 +195,7 @@ def getAttribute(self, attr_name, **kwargs): if a is None: # if the full name is not there, create one dev = self.getDevice(validator.getDeviceName(attr_name)) kwargs['storeCallback'] = self._storeAttr - if not kwargs.has_key('pollingPeriod'): + if 'pollingPeriod' not in kwargs: kwargs['pollingPeriod'] = self.getDefaultPollingPeriod() a = EvaluationAttribute(fullname, parent=dev, **kwargs) return a @@ -242,15 +242,15 @@ def _storeConfig(self, fullname, config): def getAuthorityNameValidator(self): """Return EvaluationAuthorityNameValidator""" - import evalvalidator + from . import evalvalidator return evalvalidator.EvaluationAuthorityNameValidator() def getDeviceNameValidator(self): """Return EvaluationDeviceNameValidator""" - import evalvalidator + from . import evalvalidator return evalvalidator.EvaluationDeviceNameValidator() def getAttributeNameValidator(self): """Return EvaluationAttributeNameValidator""" - import evalvalidator + from . import evalvalidator return evalvalidator.EvaluationAttributeNameValidator() diff --git a/lib/taurus/core/evaluation/evalvalidator.py b/lib/taurus/core/evaluation/evalvalidator.py index 3876f84fc..be91ca10f 100644 --- a/lib/taurus/core/evaluation/evalvalidator.py +++ b/lib/taurus/core/evaluation/evalvalidator.py @@ -22,12 +22,12 @@ ## ############################################################################# -__all__ = ['EvaluationDeviceNameValidator', - 'EvaluationAttributeNameValidator'] +from __future__ import absolute_import +from future.utils import string_types +from builtins import zip import re import hashlib - import taurus from taurus import isValidName, debug from taurus.core import TaurusElementType @@ -36,6 +36,9 @@ TaurusDeviceNameValidator, TaurusAuthorityNameValidator) +__all__ = ['EvaluationDeviceNameValidator', + 'EvaluationAttributeNameValidator'] + # Pattern for python variables PY_VAR = r'(?", a.writable, a.read().rvalue + print(" -->", a.writable, a.read().rvalue) models = [ # instance models diff --git a/lib/taurus/core/evaluation/test/test_evalattribute.py b/lib/taurus/core/evaluation/test/test_evalattribute.py index 6ae428a86..6e9a7388e 100755 --- a/lib/taurus/core/evaluation/test/test_evalattribute.py +++ b/lib/taurus/core/evaluation/test/test_evalattribute.py @@ -278,7 +278,7 @@ def read_attr(self, attr_fullname, expected={}, expected_attrv={}, self.assertTrue(isinstance(read_value, EvaluationAttrValue), msg) # Test attribute - for k, exp in expected.iteritems(): + for k, exp in expected.items(): try: got = getattr(a, k) except AttributeError: @@ -290,7 +290,7 @@ def read_attr(self, attr_fullname, expected={}, expected_attrv={}, self.__assertValidValue(exp, got, msg) # Test attribute value - for k, exp in expected_attrv.iteritems(): + for k, exp in expected_attrv.items(): try: got = getattr(read_value, k) except AttributeError: @@ -330,7 +330,7 @@ def write_read_attr(self, attr_fullname=None, setvalue=None, expected=None, self.assertTrue(isinstance(read_value, EvaluationAttrValue), msg) # Test attribute - for k, exp in expected.iteritems(): + for k, exp in expected.items(): try: got = getattr(a, k) except AttributeError: @@ -342,7 +342,7 @@ def write_read_attr(self, attr_fullname=None, setvalue=None, expected=None, self.__assertValidValue(exp, got, msg) # Test attribute value - for k, exp in expected_attrv.iteritems(): + for k, exp in expected_attrv.items(): try: got = getattr(read_value, k) except AttributeError: diff --git a/lib/taurus/core/init_bkcomp.py b/lib/taurus/core/init_bkcomp.py index 72777df8e..26f640529 100644 --- a/lib/taurus/core/init_bkcomp.py +++ b/lib/taurus/core/init_bkcomp.py @@ -24,10 +24,9 @@ ############################################################################# """The core module""" +from __future__ import absolute_import -__docformat__ = "restructuredtext" - -import release as Release +from . import release as Release # from .enums import * #note: all the enums from enums.py were moved to # taurusbasetypes.py from .taurusbasetypes import * @@ -49,3 +48,5 @@ from .tauv1 import * except: pass + +__docformat__ = "restructuredtext" diff --git a/lib/taurus/core/release.py b/lib/taurus/core/release.py index f4a960686..00dda8f65 100644 --- a/lib/taurus/core/release.py +++ b/lib/taurus/core/release.py @@ -23,8 +23,6 @@ ## ############################################################################# -__docformat__ = "restructuredtext" - """ Release data for the taurus project. It contains the following members: @@ -40,6 +38,7 @@ """ # -*- coding: utf-8 -*- +__docformat__ = "restructuredtext" # Name of the package for release purposes. This is the name which labels @@ -48,7 +47,7 @@ # we use semantic versioning (http://semver.org/) and we update it using the # bumpversion script (https://github.com/peritus/bumpversion) -version = '4.4.0' +version = '4.5.0' # generate version_info and revision (**deprecated** since version 4.0.2-dev). if '-' in version: diff --git a/lib/taurus/core/resource/__init__.py b/lib/taurus/core/resource/__init__.py index 7dcd15eb6..552312db0 100644 --- a/lib/taurus/core/resource/__init__.py +++ b/lib/taurus/core/resource/__init__.py @@ -99,5 +99,6 @@ is not even defined). """ +from __future__ import absolute_import -from resfactory import * +from .resfactory import * diff --git a/lib/taurus/core/resource/resfactory.py b/lib/taurus/core/resource/resfactory.py index 48d714f89..564164d48 100644 --- a/lib/taurus/core/resource/resfactory.py +++ b/lib/taurus/core/resource/resfactory.py @@ -26,11 +26,12 @@ """ resfactory.py: """ +from __future__ import absolute_import +from future.utils import string_types import os import imp -import operator -import types +import collections from taurus.core.taurushelper import Manager from taurus.core.util.singleton import Singleton @@ -84,13 +85,13 @@ def reloadResource(self, obj=None, priority=1, name=None): """ if priority < 1: raise ValueError('priority must be >=1') - if operator.isMappingType(obj): + if isinstance(obj, collections.Mapping): name = name or 'DICT%02d' % priority - elif type(obj) in types.StringTypes or obj is None: + elif type(obj) in (str,) or obj is None: name, mod = self.__reloadResource(obj) obj = {} for k, v in mod.__dict__.items(): - if not k.startswith('_') and isinstance(v, basestring): + if not k.startswith('_') and isinstance(v, string_types): obj[k] = v else: raise TypeError @@ -105,7 +106,7 @@ def reloadResource(self, obj=None, priority=1, name=None): if pl is None: self._resource_priority[priority] = pl = [] pl.append(name) - self._resource_priority_keys = self._resource_priority.keys() + self._resource_priority_keys = list(self._resource_priority.keys()) self._resource_priority_keys.sort() return obj @@ -136,7 +137,7 @@ def __reloadResource(self, name=None): m = imp.load_module(module_name, file_, pathname, desc) if file_: file_.close() - except Exception, e: + except Exception as e: if file_: file_.close() raise e @@ -246,15 +247,15 @@ def getAttribute(self, name): def getAuthorityNameValidator(self): """Return ResourceAuthorityNameValidator""" - import resvalidator + from . import resvalidator return resvalidator.ResourceAuthorityNameValidator() def getDeviceNameValidator(self): """Return ResourceDeviceNameValidator""" - import resvalidator + from . import resvalidator return resvalidator.ResourceDeviceNameValidator() def getAttributeNameValidator(self): """Return ResourceAttributeNameValidator""" - import resvalidator + from . import resvalidator return resvalidator.ResourceAttributeNameValidator() diff --git a/lib/taurus/core/resource/resvalidator.py b/lib/taurus/core/resource/resvalidator.py index 3eeed3da0..6e3e99a67 100644 --- a/lib/taurus/core/resource/resvalidator.py +++ b/lib/taurus/core/resource/resvalidator.py @@ -22,15 +22,15 @@ ## ############################################################################# -__all__ = ['ResDeviceNameValidator', - 'ResAttributeNameValidator'] - +from builtins import object from taurus.core import TaurusElementType from taurus.core.taurusvalidator import (TaurusAttributeNameValidator, TaurusDeviceNameValidator, TaurusAuthorityNameValidator) from taurus.core.taurushelper import getSchemeFromName, Factory +__all__ = ['ResDeviceNameValidator', + 'ResAttributeNameValidator'] # Pattern for python variables PY_VAR = r'(? or sequence") @@ -536,7 +543,7 @@ def addDevice(self, dev_info): def getDomainDevices(self, domain): """Returns all devices under the given domain. Returns empty list if the domain doesn't exist or doesn't contain any devices""" - return self._devices.get(domain, {}).values() + return list(self._devices.get(domain, {}).values()) def getFamilyDevices(self, domain, family): """Returns all devices under the given domain/family. Returns empty list if @@ -544,7 +551,7 @@ def getFamilyDevices(self, domain, family): families = self.get(domain) if families is None: return - return families.get(family, {}).values() + return list(families.get(family, {}).values()) class TangoServerTree(dict): @@ -556,14 +563,14 @@ def __init__(self, other=None): def _update(self, other): try: - if operator.isMappingType(other): - other = other.values() + if isinstance(other, collections.Mapping): + other = list(other.values()) for serv in other: try: self.addServer(serv) - except Exception, e: - print e - except Exception, e: + except Exception as e: + print(e) + except Exception as e: raise Exception( "Must give dict or sequence") @@ -577,7 +584,7 @@ def addServer(self, serv_info): def getServerNameInstances(self, serverName): """Returns all servers under the given serverName. Returns empty list if the server name doesn't exist or doesn't contain any instances""" - return self.get(serverName, {}).values() + return list(self.get(serverName, {}).values()) def get_home(): @@ -648,7 +655,7 @@ def get_env_var(env_var_name): # illegal line! continue - key, val = map(str.strip, tup) + key, val = list(map(str.strip, tup)) if key == env_var_name: return val @@ -662,11 +669,11 @@ class TangoAuthority(TaurusAuthority): _description = 'A Tango Authority' def __init__(self, host=None, port=None, parent=None): - pars = () if host is None or port is None: try: - host, port = TangoAuthority.get_default_tango_host().rsplit(':', 1) - except Exception, e: + _hp = TangoAuthority.get_default_tango_host() + host, port = _hp.rsplit(':', 1) + except Exception: from taurus import warning warning("Error getting default Tango host") @@ -700,7 +707,7 @@ def __get_class_for_device(self, dev_name): serv_name = self.command_inout("DbGetDeviceInfo", dev_name)[1][3] devs = self.get_device_class_list(serv_name) dev_name_lower = dev_name.lower() - for i in xrange(len(devs) / 2): + for i in range(len(devs) // 2): idx = i * 2 if devs[idx].lower() == dev_name_lower: return devs[idx + 1] diff --git a/lib/taurus/core/tango/tangodevice.py b/lib/taurus/core/tango/tangodevice.py index 7f5899516..562faa662 100755 --- a/lib/taurus/core/tango/tangodevice.py +++ b/lib/taurus/core/tango/tangodevice.py @@ -25,9 +25,7 @@ """This module defines the TangoDevice object""" -__all__ = ["TangoDevice"] - -__docformat__ = "restructuredtext" +from builtins import object import time from PyTango import (DeviceProxy, DevFailed, LockerInfo, DevState) @@ -38,14 +36,20 @@ from taurus.core.util.log import taurus4_deprecation +__all__ = ["TangoDevice"] + +__docformat__ = "restructuredtext" + + class _TangoInfo(object): + pass - def __init__(self): - self.dev_class = self.dev_type = 'TangoDevice' - self.doc_url = 'http://www.esrf.fr/computing/cs/tango/tango_doc/ds_doc/' - self.server_host = 'Unknown' - self.server_id = 'Unknown' - self.server_version = 1 +def __init__(self): + self.dev_class = self.dev_type = 'TangoDevice' + self.doc_url = 'http://www.esrf.fr/computing/cs/tango/tango_doc/ds_doc/' + self.server_host = 'Unknown' + self.server_id = 'Unknown' + self.server_version = 1 class TangoDevice(TaurusDevice): @@ -58,7 +62,7 @@ class TangoDevice(TaurusDevice): _scheme = 'tango' _description = "A Tango Device" - def __init__(self, name, **kw): + def __init__(self, name='', **kw): """Object initialization.""" self.call__init__(TaurusDevice, name, **kw) self._deviceObj = self._createHWObject() @@ -168,9 +172,11 @@ def getValueObj(self, cache=True): rvalue of the returned TangoAttributeValue is now a member of TaurusDevState instead of TaurusSWDevState """ + if not cache: + self.warning('Ignoring argument `cache=False`to getValueObj()') from taurus.core.tango.tangoattribute import TangoAttrValue ret = TangoAttrValue() - ret.rvalue = self.state(cache) + ret.rvalue = self.state return ret def getDisplayDescrObj(self, cache=True): @@ -201,8 +207,8 @@ def getDisplayValue(self, cache=True): def _createHWObject(self): try: return DeviceProxy(self.getFullName()) - except DevFailed, e: - self.warning('Could not create HW object: %s' % (e[0].desc)) + except DevFailed as e: + self.warning('Could not create HW object: %s' % (e.args[0].desc)) self.traceback() @taurus4_deprecation(alt="getDeviceProxy()") @@ -281,7 +287,12 @@ def addListener(self, listener): if weWereListening: # We were listening already, so we must fake an event to the new # subscribed listener with the current value - evt_value = self.__decode(self.stateObj.read()) + try: + evt_value = self.__decode(self.stateObj.read()) + except: + # the value may not be available (e.g. if device is not ready) + self.debug('Cannot read state') + return ret listeners = hasattr(listener, '__iter__') and listener or [ listener] self.fireEvent(TaurusEventType.Change, evt_value, listeners) @@ -335,7 +346,7 @@ def __pollResult(self, attrs, ts, result, error=False): def __pollAsynch(self, attrs): ts = time.time() try: - req_id = self.read_attributes_asynch(attrs.keys()) + req_id = self.read_attributes_asynch(list(attrs.keys())) except DevFailed as e: return False, e, ts return True, req_id, ts @@ -363,7 +374,7 @@ def poll(self, attrs, asynch=False, req_id=None): error = False ts = time.time() try: - result = self.read_attributes(attrs.keys()) + result = self.read_attributes(list(attrs.keys())) except DevFailed as e: error = True result = e diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py index bfb88cb10..755e915ab 100644 --- a/lib/taurus/core/tango/tangofactory.py +++ b/lib/taurus/core/tango/tangofactory.py @@ -25,19 +25,20 @@ """This module provides the `TangoFactory` object""" -__all__ = ["TangoFactory"] - -__docformat__ = "restructuredtext" +from __future__ import absolute_import +from future.utils import string_types try: - import PyTango + pass except ImportError: + # note that if PyTango is not installed the factory will not be available from taurus.core.util.log import debug msg = 'cannot import PyTango module. ' + \ 'Taurus will not support the "tango" scheme' debug(msg) raise +import PyTango from taurus import tauruscustomsettings from taurus.core.taurusbasetypes import (TaurusElementType, @@ -58,6 +59,10 @@ _Device = TangoDevice +__all__ = ["TangoFactory"] +__docformat__ = "restructuredtext" + + class TangoFactory(Singleton, TaurusFactory, Logger): """A :class:`TaurusFactory` singleton class to provide Tango-specific Taurus Element objects (TangoAuthority, TangoDevice, TangoAttribute) @@ -109,7 +114,7 @@ def init(self, *args, **kwargs): self.scheme = 'tango' self._serialization_mode = TaurusSerializationMode.get( getattr(tauruscustomsettings, 'TANGO_SERIALIZATION_MODE', - 'Serial')) + 'TangoSerial')) def reInit(self): """Reinitialize the singleton""" @@ -228,7 +233,7 @@ def unregisterAttributeClass(self, attr_name): :param attr_name: (str) attribute name """ - if self.tango_attr_klasses.has_key(attr_name): + if attr_name in self.tango_attr_klasses: del self.tango_attr_klasses[attr_name] def registerDeviceClass(self, dev_klass_name, dev_klass): @@ -247,7 +252,7 @@ def unregisterDeviceClass(self, dev_klass_name): :param dev_klass_name: (str) tango device class name """ - if self.tango_dev_klasses.has_key(dev_klass_name): + if dev_klass_name in self.tango_dev_klasses: del self.tango_dev_klasses[dev_klass_name] def getDatabase(self, name=None): @@ -407,7 +412,7 @@ def getAttribute(self, attr_name, create_if_needed=True, **kwargs): attr_klass = self._getAttributeClass( attr_name=attr_name) kwargs['storeCallback'] = self._storeAttribute - if not kwargs.has_key('pollingPeriod'): + if 'pollingPeriod' not in kwargs: kwargs[ 'pollingPeriod'] = self.getDefaultPollingPeriod() attr = attr_klass(full_attr_name, dev, **kwargs) @@ -447,7 +452,7 @@ def getConfiguration(self, param): :return: (taurus.core.tango.TangoAttribute) configuration object """ - if isinstance(param, str): + if isinstance(param, string_types): return self.getAttribute(param) return param @@ -523,10 +528,10 @@ def removeExistingDevice(self, dev_or_dev_name): raise KeyError("Device %s not found" % dev_or_dev_name) dev.cleanUp() full_name = dev.getFullName() - if self.tango_devs.has_key(full_name): + if full_name in self.tango_devs: del self.tango_devs[full_name] simp_name = dev.getSimpleName() - if self.tango_alias_devs.has_key(simp_name): + if simp_name in self.tango_alias_devs: del self.tango_alias_devs[simp_name] def removeExistingAttribute(self, attr_or_attr_name): @@ -542,7 +547,7 @@ def removeExistingAttribute(self, attr_or_attr_name): raise KeyError("Attribute %s not found" % attr_or_attr_name) attr.cleanUp() full_name = attr.getFullName() - if self.tango_attrs.has_key(full_name): + if full_name in self.tango_attrs: del self.tango_attrs[full_name] def isPollingEnabled(self): @@ -557,14 +562,14 @@ def disablePolling(self): if not self.isPollingEnabled(): return self._polling_enabled = False - for period, timer in self.polling_timers.iteritems(): + for period, timer in self.polling_timers.items(): timer.stop() def enablePolling(self): """Enable the application tango polling""" if self.isPollingEnabled(): return - for period, timer in self.polling_timers.iteritems(): + for period, timer in self.polling_timers.items(): timer.start() self._polling_enabled = True @@ -576,17 +581,17 @@ def getDatabaseNameValidator(self): def getAuthorityNameValidator(self): """Return TangoAuthorityNameValidator""" - import tangovalidator + from . import tangovalidator return tangovalidator.TangoAuthorityNameValidator() def getDeviceNameValidator(self): """Return TangoDeviceNameValidator""" - import tangovalidator + from . import tangovalidator return tangovalidator.TangoDeviceNameValidator() def getAttributeNameValidator(self): """Return TangoAttributeNameValidator""" - import tangovalidator + from . import tangovalidator return tangovalidator.TangoAttributeNameValidator() def setOperationMode(self, mode): diff --git a/lib/taurus/core/tango/test/__init__.py b/lib/taurus/core/tango/test/__init__.py index 7ba300bdc..769f3e503 100644 --- a/lib/taurus/core/tango/test/__init__.py +++ b/lib/taurus/core/tango/test/__init__.py @@ -23,4 +23,5 @@ ## ############################################################################# -from tgtestds import TangoSchemeTestLauncher +from __future__ import absolute_import +from .tgtestds import TangoSchemeTestLauncher diff --git a/lib/taurus/core/tango/test/res/TangoSchemeTest b/lib/taurus/core/tango/test/res/TangoSchemeTest index 8deda674a..467fce31b 100755 --- a/lib/taurus/core/tango/test/res/TangoSchemeTest +++ b/lib/taurus/core/tango/test/res/TangoSchemeTest @@ -22,16 +22,42 @@ # along with Taurus. If not, see . ## ############################################################################# -import numpy + +from __future__ import print_function +from future.utils import with_metaclass + +from distutils.version import LooseVersion from time import (time, sleep) +import numpy +import PyTango from PyTango import DeviceProxy, AttrWriteType, DevState, AttrQuality -from PyTango.server import Device, DeviceMeta, attribute, run, command +from PyTango.server import Device, DeviceMeta, LatestDeviceImpl, attribute, run, command from threading import Thread -class TangoSchemeTest(Device): +""" +We should check PyTango version +If it lower than 9.2.0 then DeviceMeta is a function and we can not use directly. +""" + +if LooseVersion(PyTango.__version__). +## +############################################################################# + +"""Tests for taurus.core.tango.tangofactory""" + + +import taurus + +from taurus.external.unittest import TestCase +from taurus.core.tango.test.tgtestds import TangoSchemeTestLauncher + + +class TestFactoryGarbageCollection(TangoSchemeTestLauncher, TestCase): + + # in order to not interfere with the following tests this device should + # not be used in another tests + DEV_NAME = 'TangoSchemeTest/unittest/temp-tfgc-1' + + def setUp(self): + self.factory = taurus.Factory() + + # TODO: Uncomment this test when tango factory recycles authority + # def test_authority(self): + # old_len = len(self.factory.tango_db) + # + # def create(): + # taurus.Authority() + # + # create() + # msg = "factory is polluted with authority " + # self.assertEqual(len(self.factory.tango_db), old_len, msg) + + def test_device(self): + old_len = len(self.factory.tango_devs) + + def create(): + taurus.Device(self.DEV_NAME) + + create() + msg = "factory is polluted with device" + self.assertEqual(len(self.factory.tango_devs), old_len, msg) + + def test_attribute(self): + old_len = len(self.factory.tango_attrs) + + def create(): + taurus.Attribute(self.DEV_NAME + "/state") + + create() + msg = "factory is polluted with attribute" + self.assertEqual(len(self.factory.tango_attrs), old_len, msg) + + def tearDown(self): + self.factory = None diff --git a/lib/taurus/core/tango/test/tgtestds.py b/lib/taurus/core/tango/test/tgtestds.py index 42fa70335..3f9dab823 100644 --- a/lib/taurus/core/tango/test/tgtestds.py +++ b/lib/taurus/core/tango/test/tgtestds.py @@ -25,15 +25,15 @@ """Module containing base classes for using the TangoSchemeTest DS in tests""" -__all__ = ['TangoSchemeTestLauncher'] - -__docformat__ = 'restructuredtext' - +from builtins import object import PyTango - from taurus.core.tango.starter import ProcessStarter from taurus.test import getResourcePath +__all__ = ['TangoSchemeTestLauncher'] + +__docformat__ = 'restructuredtext' + class TangoSchemeTestLauncher(object): """A base class for TestCase classes wishing to start a TangoSchemeTest. diff --git a/lib/taurus/core/tango/util/__init__.py b/lib/taurus/core/tango/util/__init__.py index 206e4640d..2c9f7a68f 100644 --- a/lib/taurus/core/tango/util/__init__.py +++ b/lib/taurus/core/tango/util/__init__.py @@ -24,7 +24,7 @@ ############################################################################# """The sardana package. It contains specific part of sardana""" +from __future__ import absolute_import +from .formatter import tangoFormatter __docformat__ = 'restructuredtext' - -from formatter import tangoFormatter \ No newline at end of file diff --git a/lib/taurus/core/taurusattribute.py b/lib/taurus/core/taurusattribute.py index 73e485568..9f61d5e10 100644 --- a/lib/taurus/core/taurusattribute.py +++ b/lib/taurus/core/taurusattribute.py @@ -43,7 +43,7 @@ class TaurusAttribute(TaurusModel): _description = "A Taurus Attribute" defaultFragmentName = "rvalue" # fragment to be used if none is specified - def __init__(self, name, parent, **kwargs): + def __init__(self, name='', parent=None, **kwargs): self.call__init__(TaurusModel, name, parent) # User enabled/disabled polling diff --git a/lib/taurus/core/taurusauthority.py b/lib/taurus/core/taurusauthority.py index b0b88786d..c5ffdb7c8 100644 --- a/lib/taurus/core/taurusauthority.py +++ b/lib/taurus/core/taurusauthority.py @@ -25,20 +25,21 @@ """This module contains the base class for a taurus database""" -__all__ = ["TaurusAuthority"] - -__docformat__ = "restructuredtext" - +from __future__ import absolute_import from .taurusbasetypes import TaurusElementType from .taurusmodel import TaurusModel +__all__ = ["TaurusAuthority"] + +__docformat__ = "restructuredtext" + class TaurusAuthority(TaurusModel): _description = "A Taurus Authority" - def __init__(self, complete_name, parent=None): + def __init__(self, complete_name='', parent=None): self.call__init__(TaurusModel, complete_name, parent) #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- @@ -87,7 +88,7 @@ def getChildObj(self, child_name): def getDevice(self, devname): """Returns the device object given its name""" - import taurusdevice + from . import taurusdevice return self.factory().getObject(taurusdevice.TaurusDevice, devname) @property diff --git a/lib/taurus/core/taurusbasetypes.py b/lib/taurus/core/taurusbasetypes.py index d23ec530c..0bce150c2 100644 --- a/lib/taurus/core/taurusbasetypes.py +++ b/lib/taurus/core/taurusbasetypes.py @@ -22,9 +22,18 @@ ## ############################################################################# -''' +""" a misc collection of basic types -''' +""" + +import datetime + +from .util.enumeration import Enumeration +from .util.log import taurus4_deprecation +from enum import IntEnum +from future.utils import PY2 +from builtins import object + __all__ = ["TaurusSWDevState", "TaurusSWDevHealth", "OperationMode", "TaurusSerializationMode", "SubscriptionState", "TaurusEventType", @@ -35,12 +44,6 @@ __docformat__ = "restructuredtext" -import datetime - -from .util.enumeration import Enumeration -from .util.log import taurus4_deprecation -from enum import IntEnum - class TaurusDevState(IntEnum): """Enumeration of possible states of :class:`taurus.core.TaurusDevice` @@ -150,14 +153,15 @@ class TaurusDevState(IntEnum): __PYTHON_TYPE_TO_TAURUS_DATATYPE = { str: DataType.String, int: DataType.Integer, - long: DataType.Integer, + # long : DataType.Integer, # (only in py2) see below... float: DataType.Float, bool: DataType.Boolean, - # bytes : DataType.Bytes, # see below... + # bytes : DataType.Bytes, # (only in py>=3) see below... } -if str is not bytes: # Python >=3 +if PY2: # Python 2 + __PYTHON_TYPE_TO_TAURUS_DATATYPE[long] = DataType.Integer +else: # Python >=3 __PYTHON_TYPE_TO_TAURUS_DATATYPE[bytes] = DataType.Bytes -# Note: in Python2, DataType.from_python_type(bytes) --> DataType.String DataType.from_python_type = __PYTHON_TYPE_TO_TAURUS_DATATYPE.get SubscriptionState = Enumeration( diff --git a/lib/taurus/core/taurusconfiguration.py b/lib/taurus/core/taurusconfiguration.py index 164be13c3..f95b3856d 100644 --- a/lib/taurus/core/taurusconfiguration.py +++ b/lib/taurus/core/taurusconfiguration.py @@ -27,13 +27,15 @@ """[DEPRECATED since taurus v4] This module contains the base class for a taurus attribute configuration""" +from builtins import object +from .taurusmodel import TaurusModel +from .util.log import taurus4_deprecation + + __all__ = ["TaurusConfigurationProxy", "TaurusConfiguration"] __docformat__ = "restructuredtext" -from .taurusmodel import TaurusModel -from .util.log import taurus4_deprecation - class TaurusConfigurationProxy(object): """ diff --git a/lib/taurus/core/taurusdevice.py b/lib/taurus/core/taurusdevice.py index af4e909e7..ae605e1f6 100755 --- a/lib/taurus/core/taurusdevice.py +++ b/lib/taurus/core/taurusdevice.py @@ -41,7 +41,7 @@ class TaurusDevice(TaurusModel): _description = "A Taurus Device" - def __init__(self, name, **kw): + def __init__(self, name='', **kw): """Object initialization.""" parent = kw.pop('parent', None) storeCallback = kw.pop('storeCallback', None) diff --git a/lib/taurus/core/taurusfactory.py b/lib/taurus/core/taurusfactory.py index db71cce0b..01a481483 100644 --- a/lib/taurus/core/taurusfactory.py +++ b/lib/taurus/core/taurusfactory.py @@ -55,21 +55,23 @@ ) """ - -__all__ = ["TaurusFactory"] - -__docformat__ = "restructuredtext" +from __future__ import absolute_import +from builtins import object import atexit from weakref import WeakValueDictionary -from taurusbasetypes import TaurusElementType -from taurusauthority import TaurusAuthority -from taurusdevice import TaurusDevice -from taurusattribute import TaurusAttribute -from taurusconfiguration import TaurusConfiguration, TaurusConfigurationProxy -from taurusexception import TaurusException +from .taurusbasetypes import TaurusElementType +from .taurusauthority import TaurusAuthority +from .taurusdevice import TaurusDevice +from .taurusattribute import TaurusAttribute +from .taurusconfiguration import TaurusConfiguration, TaurusConfigurationProxy +from .taurusexception import TaurusException from taurus.core.tauruspollingtimer import TaurusPollingTimer +__all__ = ["TaurusFactory"] + +__docformat__ = "restructuredtext" + class TaurusFactory(object): """The base class for valid Factories in Taurus.""" @@ -93,7 +95,7 @@ def __init__(self): self._devs = WeakValueDictionary() self._auths = WeakValueDictionary() - import taurusmanager + from . import taurusmanager manager = taurusmanager.TaurusManager() self._serialization_mode = manager.getSerializationMode() @@ -323,14 +325,14 @@ def disablePolling(self): if not self.isPollingEnabled(): return self._polling_enabled = False - for period, timer in self.polling_timers.iteritems(): + for period, timer in self.polling_timers.items(): timer.stop() def enablePolling(self): """Enable the application tango polling""" if self.isPollingEnabled(): return - for period, timer in self.polling_timers.iteritems(): + for period, timer in self.polling_timers.items(): timer.start() self._polling_enabled = True @@ -353,7 +355,7 @@ def removeAttributeFromPolling(self, attribute): :param attribute: (str) attribute name. """ p = None - for period, timer in self.polling_timers.iteritems(): + for period, timer in self.polling_timers.items(): if timer.containsAttribute(attribute): timer.removeAttribute(attribute) if timer.getAttributeCount() == 0: diff --git a/lib/taurus/core/taurushelper.py b/lib/taurus/core/taurushelper.py index 78170fe92..a6de167bd 100644 --- a/lib/taurus/core/taurushelper.py +++ b/lib/taurus/core/taurushelper.py @@ -25,6 +25,14 @@ """a list of helper methods""" +from __future__ import print_function + +from builtins import str +from future.utils import string_types +import re +from taurus import tauruscustomsettings +from .util.log import taurus4_deprecation + __all__ = ['check_dependencies', 'log_dependencies', 'getSchemeFromName', 'getValidTypesForName', 'isValidName', 'makeSchemeExplicit', 'Manager', 'Factory', 'Device', 'Attribute', 'Configuration', @@ -38,12 +46,6 @@ __docformat__ = "restructuredtext" -import sys -import re -from taurus import tauruscustomsettings -from .util.log import taurus4_deprecation - - # regexp for finding the scheme __SCHEME_RE = re.compile(r'([^:/?#]+):.*') @@ -74,36 +76,36 @@ def check_dependencies(): } import pkg_resources d = pkg_resources.get_distribution('taurus') - print "Dependencies for %s:" % d + print("Dependencies for %s:" % d) # minimum requirements (without extras) for r in d.requires(): try: pkg_resources.require(str(r)) - print '\t[*]', + print('\t[*]', end=' ') except Exception: - print '\t[ ]', - print '%s' % r + print('\t[ ]', end=' ') + print('%s' % r) # requirements for the extras - print '\nExtras:' + print('\nExtras:') for extra in sorted(d.extras): - print "Dependencies for taurus[%s]:" % extra + print("Dependencies for taurus[%s]:" % extra) # requirements from PyPI for r in d.requires(extras=[extra]): try: r = str(r).split(';')[0] # remove marker if present (see #612) pkg_resources.require(r) - print '\t[*]', + print('\t[*]', end=' ') except Exception: - print '\t[ ]', - print '%s' % r + print('\t[ ]', end=' ') + print('%s' % r) # requirements outside PyPI for r, check in non_pypi.get(extra, ()): try: check() - print '\t[*]', + print('\t[*]', end=' ') except Exception: - print '\t[ ]', - print '%s (not in PyPI)' % r + print('\t[ ]', end=' ') + print('%s (not in PyPI)' % r) def log_dependencies(): @@ -296,7 +298,7 @@ def Attribute(dev_or_attr_name, attr_name=None): if attr_name is None: return Factory(scheme=getSchemeFromName(dev_or_attr_name)).getAttribute(dev_or_attr_name) else: - if type(dev_or_attr_name) in types.StringTypes: + if isinstance(dev_or_attr_name, string_types): dev = Device(dev_or_attr_name) else: dev = dev_or_attr_name diff --git a/lib/taurus/core/tauruslistener.py b/lib/taurus/core/tauruslistener.py index dddd36b07..6a33127cd 100644 --- a/lib/taurus/core/tauruslistener.py +++ b/lib/taurus/core/tauruslistener.py @@ -25,17 +25,20 @@ """This module contains the taurus base listeners classes""" +from __future__ import print_function +from builtins import object +from .util.log import Logger + + __all__ = ["TaurusListener", "TaurusExceptionListener"] __docformat__ = "restructuredtext" -from .util.log import Logger - class TaurusListener(Logger): """ TaurusListener Interface""" - def __init__(self, name, parent=None): + def __init__(self, name='', parent=None): self.call__init__(Logger, name, parent) def eventReceived(self, src, type, evt_value): @@ -70,4 +73,4 @@ def exceptionReceived(self, exception): self._printException(exception) def _printException(self, exception): - print self.__class__.__name__, "received", exception.__class__.__name__, str(exception) + print(self.__class__.__name__, "received", exception.__class__.__name__, str(exception)) diff --git a/lib/taurus/core/taurusmanager.py b/lib/taurus/core/taurusmanager.py index 371f4f2cb..5ef57107e 100755 --- a/lib/taurus/core/taurusmanager.py +++ b/lib/taurus/core/taurusmanager.py @@ -24,18 +24,17 @@ ############################################################################# """This module contains the taurus base manager class""" +from __future__ import print_function -__all__ = ["TaurusManager"] - -__docformat__ = "restructuredtext" +from builtins import range import os import atexit +import pkg_resources from .util.singleton import Singleton from .util.log import Logger, taurus4_deprecation from .util.threadpool import ThreadPool - from .taurusbasetypes import (OperationMode, ManagerState, TaurusSerializationMode) from .taurusauthority import TaurusAuthority @@ -44,9 +43,15 @@ from .taurusexception import TaurusException from .taurusfactory import TaurusFactory from .taurushelper import getSchemeFromName +import taurus from taurus import tauruscustomsettings +__all__ = ["TaurusManager"] + +__docformat__ = "restructuredtext" + + class TaurusManager(Singleton, Logger): """A :class:`taurus.core.util.singleton.Singleton` class designed to provide Taurus management. @@ -323,7 +328,7 @@ def _build_plugins(self): for plugin_class in plugin_classes: schemes = list(plugin_class.schemes) for scheme in schemes: - if plugins.has_key(scheme): + if scheme in plugins: k = plugins[scheme] self.warning( "Conflicting plugins: %s and %s both implement " @@ -367,10 +372,22 @@ def _get_plugin_classes(self): full_module_names.extend( getattr(tauruscustomsettings, 'EXTRA_SCHEME_MODULES', [])) + full_module_names.extend( + getattr(taurus.core, 'PLUGIN_SCHEME_MODULES', [])) + + # --------------------------------------------------------------------- + # Note: this is an experimental feature introduced in v 4.5.0a + # It may be removed or changed in future releases + + # Discover the taurus.core.schemes plugins + schemes_ep = pkg_resources.iter_entry_points('taurus.core.schemes') + full_module_names.extend([p.name for p in schemes_ep]) + # --------------------------------------------------------------------- + for full_module_name in full_module_names: try: m = __import__(full_module_name, fromlist=['*'], level=0) - except Exception, imp1: + except Exception as imp1: # just in case we are in python 2.4 try: m = __import__(full_module_name, @@ -402,7 +419,7 @@ def _get_plugin_classes(self): def _find_scheme(self, factory_class): class_name = factory_class.__name__ - for i in xrange(1, len(class_name)): + for i in range(1, len(class_name)): if class_name[i].isupper(): return class_name[:i].lower() @@ -432,4 +449,4 @@ def __repr__(self): if __name__ == '__main__': manager = TaurusManager() - print manager.getPlugins() + print(manager.getPlugins()) diff --git a/lib/taurus/core/taurusmodel.py b/lib/taurus/core/taurusmodel.py index c36ae03b5..01662257f 100644 --- a/lib/taurus/core/taurusmodel.py +++ b/lib/taurus/core/taurusmodel.py @@ -25,13 +25,10 @@ """This module contains the base TaurusModel class""" -__all__ = ["TaurusModel"] - -__docformat__ = "restructuredtext" +from builtins import object import weakref -import operator -import threading +import collections from .util.log import Logger from .util.event import (CallableRef, @@ -40,6 +37,10 @@ from .taurusbasetypes import TaurusEventType, MatchLevel from .taurushelper import Factory +__all__ = ["TaurusModel"] + +__docformat__ = "restructuredtext" + class TaurusModel(Logger): @@ -47,7 +48,7 @@ class TaurusModel(Logger): RegularEvent = (TaurusEventType.Change, TaurusEventType.Config, TaurusEventType.Periodic) - def __init__(self, full_name, parent, serializationMode=None): + def __init__(self, full_name='', parent=None, serializationMode=None): v = self.getNameValidator() self._full_name, self._norm_name, self._simp_name = v.getNames( full_name, self.factory()) @@ -214,7 +215,7 @@ def _listenerDied(self, weak_listener): def _getCallableRef(self, listener, cb=None): # return weakref.ref(listener, self._listenerDied) meth = getattr(listener, 'eventReceived', None) - if meth is not None and operator.isCallable(meth): + if meth is not None and hasattr(meth, '__call__'): return weakref.ref(listener, cb) else: return CallableRef(listener, cb) @@ -244,7 +245,7 @@ def removeListener(self, listener): return True def forceListening(self): - class __DummyListener: + class __DummyListener(object): def eventReceived(self, *args): pass @@ -276,7 +277,7 @@ def fireEvent(self, event_type, event_value, listeners=None): if listeners is None: return - if not operator.isSequenceType(listeners): + if not isinstance(listeners, collections.Sequence): listeners = listeners, for listener in listeners: @@ -287,9 +288,9 @@ def fireEvent(self, event_type, event_value, listeners=None): if l is None: continue meth = getattr(l, 'eventReceived', None) - if meth is not None and operator.isCallable(meth): + if meth is not None and hasattr(meth, '__call__'): l.eventReceived(self, event_type, event_value) - elif operator.isCallable(l): + elif hasattr(l, '__call__'): l(self, event_type, event_value) def isWritable(self): diff --git a/lib/taurus/core/tauruspollingtimer.py b/lib/taurus/core/tauruspollingtimer.py index 13be63abb..31f787d06 100644 --- a/lib/taurus/core/tauruspollingtimer.py +++ b/lib/taurus/core/tauruspollingtimer.py @@ -25,18 +25,17 @@ """This module contains the polling class""" -__all__ = ["TaurusPollingTimer"] - -__docformat__ = "restructuredtext" - -import time import weakref import threading -from .util.log import Logger, DebugIt +from .util.log import Logger from .util.containers import CaselessWeakValueDict from .util.timer import Timer +__all__ = ["TaurusPollingTimer"] + +__docformat__ = "restructuredtext" + class TaurusPollingTimer(Logger): """ Polling timer manages a list of attributes that have to be polled in @@ -75,7 +74,7 @@ def containsAttribute(self, attribute): self.lock.acquire() try: attr_dict = self.dev_dict.get(dev) - return attr_dict and attr_dict.has_key(attr_name) + return attr_dict and attr_name in attr_dict finally: self.lock.release() diff --git a/lib/taurus/core/taurusvalidator.py b/lib/taurus/core/taurusvalidator.py index 8985a07a6..ef82b3870 100644 --- a/lib/taurus/core/taurusvalidator.py +++ b/lib/taurus/core/taurusvalidator.py @@ -24,7 +24,12 @@ ############################################################################# """This module contains the base taurus name validator classes""" +from __future__ import print_function +import re +from taurus import tauruscustomsettings +from taurus.core.util.singleton import Singleton +from taurus.core.taurushelper import makeSchemeExplicit __all__ = ["TaurusAuthorityNameValidator", "TaurusDeviceNameValidator", "TaurusAttributeNameValidator"] @@ -32,11 +37,6 @@ __docformat__ = "restructuredtext" -import re -from taurus import tauruscustomsettings -from taurus.core.util.singleton import Singleton -from taurus.core.taurushelper import makeSchemeExplicit - class _TaurusBaseValidator(Singleton): '''This is a private base class for taurus base validators. Do not derive @@ -313,5 +313,5 @@ class FooAttributeNameValidator(TaurusAttributeNameValidator): v = FooAttributeNameValidator() name = 'foo://bar#label' - print v.isValid(name) - print v.getUriGroups(name) + print(v.isValid(name)) + print(v.getUriGroups(name)) diff --git a/lib/taurus/core/test/basevalidator.py b/lib/taurus/core/test/basevalidator.py index 06f2af10f..215f92338 100644 --- a/lib/taurus/core/test/basevalidator.py +++ b/lib/taurus/core/test/basevalidator.py @@ -27,14 +27,14 @@ #__all__ = [] -__docformat__ = 'restructuredtext' - - +from builtins import object from functools import partial from taurus.test import insertTest - from taurus.core.taurusvalidator import TaurusAttributeNameValidator +__docformat__ = 'restructuredtext' + + valid = partial(insertTest, helper_name='isValid') invalid = partial(insertTest, helper_name='isInvalid') names = partial(insertTest, helper_name='getNames') @@ -49,7 +49,7 @@ def isValid(self, name=None, groups=None, strict=True): self.assertTrue(self.validator().isValid(name, strict=strict), msg) if groups is not None: returned = self.validator().getUriGroups(name, strict=strict) - for k, v in groups.iteritems(): + for k, v in groups.items(): msg = ('"%s" not in %s.getUriGroups("%s"). Returned %s' % (k, self.validator.__name__, name, returned)) self.assertIn(k, returned, msg=msg) diff --git a/lib/taurus/core/test/modelequality.py b/lib/taurus/core/test/modelequality.py index 6f57e3f96..9afd5ce78 100644 --- a/lib/taurus/core/test/modelequality.py +++ b/lib/taurus/core/test/modelequality.py @@ -22,8 +22,8 @@ ## ############################################################################# +from builtins import object import functools - from taurus import Device, Attribute # , Authority from taurus.test import insertTest diff --git a/lib/taurus/core/test/test_taurushelper.py b/lib/taurus/core/test/test_taurushelper.py index 49a096dfd..252471bac 100644 --- a/lib/taurus/core/test/test_taurushelper.py +++ b/lib/taurus/core/test/test_taurushelper.py @@ -212,7 +212,7 @@ def isValid(self, name=None, expected=True, elementType=None, elementType = [elementType] manager = taurus.Manager() scheme = manager.getScheme(name) - supportedSchemes = manager.getPlugins().keys() + supportedSchemes = list(manager.getPlugins().keys()) if scheme not in supportedSchemes: self.skipTest('"%s" scheme not supported' % scheme) returned = taurus.isValidName(name, etypes=elementType, strict=strict) @@ -240,7 +240,7 @@ def get_object(self, name=None, klass=None): klass = TaurusAuthority manager = taurus.Manager() scheme = manager.getScheme(name) - supportedSchemes = manager.getPlugins().keys() + supportedSchemes = list(manager.getPlugins().keys()) if scheme not in supportedSchemes: self.skipTest('"%s" scheme not supported' % scheme) a = taurus.Authority(name) @@ -255,7 +255,7 @@ def get_object(self, name=None, klass=None): @insertTest(helper_name='get_object', name='eval:@Foo') @insertTest(helper_name='get_object', name='eval://dev=Foo') @insertTest(helper_name='get_object', name='eval:@datetime.*') -@insertTest(helper_name='get_object', name='eval:@d=datetime.date(2017,03,29)') +@insertTest(helper_name='get_object', name='eval:@d=datetime.date(2017,3,29)') class DeviceTestCase(unittest.TestCase): '''TestCase for the taurus.Device helper''' @@ -265,7 +265,7 @@ def get_object(self, name=None, klass=None): klass = TaurusDevice manager = taurus.Manager() scheme = manager.getScheme(name) - supportedSchemes = manager.getPlugins().keys() + supportedSchemes = list(manager.getPlugins().keys()) if scheme not in supportedSchemes: self.skipTest('"%s" scheme not supported' % scheme) @@ -404,7 +404,7 @@ def get_object(self, name=None, klass=None): label='Q("1km").to("mm").magnitude', type=DataType.Float)) @insertTest(helper_name='read_attr', - name='eval:@d=datetime.date(1931,04,14)/d.isoformat()', + name='eval:@d=datetime.date(1931,4,14)/d.isoformat()', expected=dict(rvalue='1931-04-14', value='1931-04-14', wvalue=None, w_value=None, label='d.isoformat()', @@ -419,7 +419,7 @@ def get_object(self, name=None, klass=None): klass = TaurusAttribute manager = taurus.Manager() scheme = manager.getScheme(name) - supportedSchemes = manager.getPlugins().keys() + supportedSchemes = list(manager.getPlugins().keys()) if scheme not in supportedSchemes: self.skipTest('"%s" scheme not supported' % scheme) a = taurus.Attribute(name) @@ -441,7 +441,7 @@ def read_attr(self, name=None, expected=None, skip=False, msg = ('read() for "%s" did not return a TaurusAttrValue (got a %s)' % (name, readvalue.__class__.__name__)) self.assertTrue(isinstance(readvalue, TaurusAttrValue), msg) - for k, exp in expected.iteritems(): + for k, exp in expected.items(): try: got = getattr(readvalue, k) except AttributeError: diff --git a/lib/taurus/core/util/__init__.py b/lib/taurus/core/util/__init__.py index 207517da5..336853193 100644 --- a/lib/taurus/core/util/__init__.py +++ b/lib/taurus/core/util/__init__.py @@ -33,15 +33,15 @@ #. if python >= 2.6 use standard json from python distribution #. otherwise use private implementation distributed with taurus """ +from __future__ import absolute_import +import taurus.tauruscustomsettings __docformat__ = "restructuredtext" -import taurus.tauruscustomsettings - LIGHTWEIGHT_IMPORTS = getattr( taurus.tauruscustomsettings, 'LIGHTWEIGHT_IMPORTS', False) if LIGHTWEIGHT_IMPORTS: - from init_lightweight import * + from .init_lightweight import * else: - from init_bkcomp import * + from .init_bkcomp import * diff --git a/lib/taurus/core/util/argparse/taurusargparse.py b/lib/taurus/core/util/argparse/taurusargparse.py index 6bb77e4e8..b21e717a3 100644 --- a/lib/taurus/core/util/argparse/taurusargparse.py +++ b/lib/taurus/core/util/argparse/taurusargparse.py @@ -200,8 +200,8 @@ def init_taurus_args(parser=None, args=None, values=None): rfoo.utils.rconsole.spawn_server(port=options.remote_console_port) taurus.info("rconsole started. You can connect to it by typing: rconsole -p %d", options.remote_console_port) - except Exception, e: - taurus.warning("Cannot spawn debugger. Reason: %s", str(e)) + except Exception as e: + taurus.warning("Cannot spawn debugger. Reason: %s", e) # initialize default formatter if options.default_formatter is not None: diff --git a/lib/taurus/core/util/codecs.py b/lib/taurus/core/util/codecs.py index d39701fda..0b91df54a 100644 --- a/lib/taurus/core/util/codecs.py +++ b/lib/taurus/core/util/codecs.py @@ -62,21 +62,31 @@ >>> codec = CodecFactory().getCodec(v.format) >>> f, d = codec.decode((v.format, v.value)) """ - -__all__ = ["Codec", "NullCodec", "ZIPCodec", "BZ2Codec", "JSONCodec", - "FunctionCodec", "PlotCodec", "CodecPipeline", "CodecFactory"] - -__docformat__ = "restructuredtext" +from __future__ import absolute_import +from builtins import str import copy # need by VideoImageCodec import struct +import sys import numpy -from singleton import Singleton -from log import Logger -from containers import CaselessDict +from future.utils import (PY2, string_types) + +from .singleton import Singleton +from .log import Logger +from .containers import CaselessDict + +__all__ = ["Codec", "NullCodec", "ZIPCodec", "BZ2Codec", "JSONCodec", + "FunctionCodec", "PlotCodec", "CodecPipeline", "CodecFactory"] + +__docformat__ = "restructuredtext" + +if PY2: + buffer_types = buffer, memoryview, +else: + buffer_types = memoryview, class Codec(Logger): @@ -278,8 +288,8 @@ def decode(self, data, *args, **kwargs): return data format = data[0].partition('_')[2] - if isinstance(data[1], buffer): - data = data[0], str(data[1]) + if isinstance(data[1], buffer_types): + data = data[0], bytes(data[1]) return format, pickle.loads(data[1]) @@ -321,7 +331,7 @@ def encode(self, data, *args, **kwargs): format += '_%s' % data[0] # make it compact by default kwargs['separators'] = kwargs.get('separators', (',', ':')) - return format, json.dumps(data[1], *args, **kwargs) + return format, json.dumps(data[1], *args, **kwargs).encode('utf-8') def decode(self, data, *args, **kwargs): """decodes the given data from a json string. @@ -338,16 +348,18 @@ def decode(self, data, *args, **kwargs): ensure_ascii = kwargs.pop('ensure_ascii', False) - if isinstance(data[1], buffer): + if isinstance(data[1], buffer_types): data = data[0], str(data[1]) - + elif isinstance(data[1], bytes): + data = data[0], data[1].decode('utf-8') + data = json.loads(data[1]) if ensure_ascii: data = self._transform_ascii(data) return format, data def _transform_ascii(self, data): - if isinstance(data, unicode): + if isinstance(data, string_types): return data.encode('utf-8') elif isinstance(data, dict): return self._transform_dict(data) @@ -363,7 +375,7 @@ def _transform_list(self, lst): def _transform_dict(self, dct): newdict = {} - for k, v in dct.iteritems(): + for k, v in dct.items(): newdict[self._transform_ascii(k)] = self._transform_ascii(v) return newdict @@ -425,7 +437,7 @@ def decode(self, data, *args, **kwargs): return format, data def _transform_ascii(self, data): - if isinstance(data, unicode): + if isinstance(data, string_types): return data.encode('utf-8') elif isinstance(data, dict): return self._transform_dict(data) @@ -441,7 +453,7 @@ def _transform_list(self, lst): def _transform_dict(self, dct): newdict = {} - for k, v in dct.iteritems(): + for k, v in dct.items(): newdict[self._transform_ascii(k)] = self._transform_ascii(v) return newdict @@ -659,7 +671,7 @@ def __unpackHeader(self, header): def __packHeader(self, imgMode, frameNumber, width, height): magic = 0x5644454f version = 1 - endian = ord(struct.pack('=H', 1)[-1]) + endian = 0 if sys.byteorder == 'little' else 1 hsize = struct.calcsize(self.VIDEO_HEADER_FORMAT) return struct.pack(self.VIDEO_HEADER_FORMAT, magic, @@ -879,7 +891,7 @@ def registerCodec(self, format, klass): self._codec_klasses[format] = klass # del old codec if exists - if self._codecs.has_key(format): + if format in self._codecs: del self._codecs[format] def unregisterCodec(self, format): @@ -889,10 +901,10 @@ def unregisterCodec(self, format): :param format: (str) the codec id :raises: KeyError""" - if self._codec_klasses.has_key(format): + if format in self._codec_klasses: del self._codec_klasses[format] - if self._codecs.has_key(format): + if format in self._codecs: del self._codecs[format] def getCodec(self, format): diff --git a/lib/taurus/core/util/colors.py b/lib/taurus/core/util/colors.py index 9f9780e2d..b0731aa42 100644 --- a/lib/taurus/core/util/colors.py +++ b/lib/taurus/core/util/colors.py @@ -24,11 +24,12 @@ ############################################################################# """This module contains color codes for state and quality""" +from __future__ import print_function +from builtins import object __all__ = ["DEVICE_STATE_DATA", "ATTRIBUTE_QUALITY_DATA", "ColorPalette", "DEVICE_STATE_PALETTE", "ATTRIBUTE_QUALITY_PALETTE"] -import types DEVICE_STATE_DATA = { # map for TaurusDevState states (used for agnostic TaurusDevice.state) @@ -94,7 +95,7 @@ def __init__(self, dat, int_decoder_dict=None): self._int_decoder_dict = int_decoder_dict def _decoder(self, elem): - if type(elem) == types.IntType or type(elem) == types.LongType: + if type(elem) == int: elem = self._int_decoder_dict.get(elem) return str(elem) @@ -122,7 +123,7 @@ def number(self, stoq, fg=False): return r[0] * 256 * 256 + r[1] * 256 + r[2] def __iter__(self): - return self._rgb_data.keys().__iter__() + return list(self._rgb_data.keys()).__iter__() def name(self, stoq, fg=False): """Returns the name of the color.""" @@ -133,7 +134,7 @@ def name(self, stoq, fg=False): return self._rgb_data[name][0] def has(self, name): - return self._rgb_data.has_key(name) + return name in self._rgb_data def size(self): return len(self._rgb_data) @@ -215,7 +216,7 @@ def print_color_palette(pal): bg_color = pal.name(stoq) rgb = "(%3.3d, %3.3d, %3.3d)" % pal.rgb(stoq) hx = pal.hex(stoq) - print "%7s %5s on %13s %15s #%s" % (stoq, fg_color, bg_color, rgb, hx) + print("%7s %5s on %13s %15s #%s" % (stoq, fg_color, bg_color, rgb, hx)) if __name__ == "__main__": @@ -223,8 +224,8 @@ def print_color_palette(pal): print_color_palette(ATTRIBUTE_QUALITY_PALETTE) from taurus.core import TaurusDevState import PyTango - print - print DEVICE_STATE_PALETTE.rgb(TaurusDevState.Ready) - print DEVICE_STATE_PALETTE.rgb('TaurusDevState.Ready') - print DEVICE_STATE_PALETTE.rgb(PyTango.DevState.ON) - print DEVICE_STATE_PALETTE.rgb(0) + print() + print(DEVICE_STATE_PALETTE.rgb(TaurusDevState.Ready)) + print(DEVICE_STATE_PALETTE.rgb('TaurusDevState.Ready')) + print(DEVICE_STATE_PALETTE.rgb(PyTango.DevState.ON)) + print(DEVICE_STATE_PALETTE.rgb(0)) diff --git a/lib/taurus/core/util/console.py b/lib/taurus/core/util/console.py index dcece967c..051f721d3 100644 --- a/lib/taurus/core/util/console.py +++ b/lib/taurus/core/util/console.py @@ -25,6 +25,8 @@ """This module contains ANSI color codes""" +from builtins import object + __all__ = ["make_color_table", "NoColors", "TermColors", "HTMLColors"] __docformat__ = "restructuredtext" @@ -63,13 +65,13 @@ def make_color_table(in_class, use_name=False, fake=False): setattr(in_class, name, in_class._base % value) -class NoColors: +class NoColors(object): NoColor = '' # for color schemes in color-less terminals. Normal = '' # Reset normal coloring _base = '' # Template for all other colors -class TermColors: +class TermColors(object): """Color escape sequences. This class defines the escape sequences for all the standard (ANSI?) @@ -86,7 +88,7 @@ class TermColors: _base = '\033[%sm' # Template for all other colors -class HTMLColors: +class HTMLColors(object): NoColor = '' Normal = '' diff --git a/lib/taurus/core/util/constant.py b/lib/taurus/core/util/constant.py index 2221d72a5..c793322ea 100644 --- a/lib/taurus/core/util/constant.py +++ b/lib/taurus/core/util/constant.py @@ -46,10 +46,12 @@ consttype.__del__() # Remove all attributes """ +from builtins import object + __docformat__ = "restructuredtext" -class _consttype: +class _consttype(object): class _ConstTypeError(TypeError): pass @@ -60,8 +62,8 @@ def __repr__(self): def __setattr__(self, name, value): v = self.__dict__.get(name, value) if type(v) is not type(value): - raise self._ConstTypeError, "Can't rebind %s to %s" % ( - type(v), type(value)) + raise self._ConstTypeError("Can't rebind %s to %s" % ( + type(v), type(value))) self.__dict__[name] = value def __del__(self): diff --git a/lib/taurus/core/util/containers.py b/lib/taurus/core/util/containers.py index eba3a4655..bbc8d762b 100644 --- a/lib/taurus/core/util/containers.py +++ b/lib/taurus/core/util/containers.py @@ -27,6 +27,18 @@ This module contains a set of useful containers that are not part of the standard python distribution. """ +from __future__ import print_function + +from builtins import range +from builtins import object + +from future.utils import string_types + +import copy +import collections +import time +import weakref + __all__ = ["CaselessList", "CaselessDict", "CaselessWeakValueDict", "LoopList", "CircBuf", "LIFO", "TimedQueue", "self_locked", "ThreadDict", @@ -35,11 +47,6 @@ __docformat__ = "restructuredtext" -import copy -import time -import weakref -import operator - class CaselessList(list): """A case insensitive lists that has some caseless methods. Only allows @@ -58,21 +65,21 @@ class CaselessList(list): def __init__(self, inlist=[]): list.__init__(self) for entry in inlist: - if not isinstance(entry, basestring): + if not isinstance(entry, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\" which is \"%s\"' % (entry, type(entry))) self.append(entry) def __lowerstreq(self, a, b): - a = type(a) == str and a or str(a) - b = type(b) == str and b or str(b) + a = str(a) + b = str(b) return (a.lower() == b.lower()) def findentry(self, item): """A caseless way of checking if an item is in the list or not. It returns None or the entry.""" - if not isinstance(item, basestring): + if not isinstance(item, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(item)) for entry in self: @@ -109,7 +116,7 @@ def lowercopy(self): def append(self, item): """Adds an item to the list and checks it's a string.""" - if not isinstance(item, basestring): + if not isinstance(item, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(item)) list.append(self, item) @@ -121,7 +128,7 @@ def extend(self, item): raise TypeError('You can only extend lists with lists. ' 'You supplied \"%s\"' % type(item)) for entry in item: - if not isinstance(entry, basestring): + if not isinstance(entry, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(entry)) list.append(self, entry) @@ -129,7 +136,7 @@ def extend(self, item): def count(self, item): """Counts references to 'item' in a caseless manner. If item is not a string it will always return 0.""" - if not isinstance(item, basestring): + if not isinstance(item, string_types): return 0 count = 0 for entry in self: @@ -148,7 +155,7 @@ def index(self, item, minindex=0, maxindex=None): maxindex = len(self) minindex = max(0, minindex) - 1 maxindex = min(len(self), maxindex) - if not isinstance(item, basestring): + if not isinstance(item, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(item)) index = minindex @@ -161,7 +168,7 @@ def index(self, item, minindex=0, maxindex=None): def insert(self, i, x): """s.insert(i, x) same as s[i:i] = [x] Raises TypeError if x isn't a string.""" - if not isinstance(x, basestring): + if not isinstance(x, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(x)) list.insert(self, i, x) @@ -175,7 +182,7 @@ def __setitem__(self, index, value): the same length as the slice object requires. """ if isinstance(index, int): - if not isinstance(value, basestring): + if not isinstance(value, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(value)) list.__setitem__(self, index, value) @@ -184,7 +191,7 @@ def __setitem__(self, index, value): raise TypeError( 'Value given to set slice is not a sequence object.') for entry in value: - if not isinstance(entry, basestring): + if not isinstance(entry, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(entry)) list.__setitem__(self, index, value) @@ -194,7 +201,7 @@ def __setitem__(self, index, value): def __setslice__(self, i, j, sequence): """Called to implement assignment to self[i:j].""" for entry in sequence: - if not isinstance(entry, basestring): + if not isinstance(entry, string_types): raise TypeError('Members of this object must be strings. ' 'You supplied \"%s\"' % type(entry)) list.__setslice__(self, i, j, sequence) @@ -264,8 +271,8 @@ def __contains__(self, key): return dict.__contains__(self, key.lower()) def has_key(self, key): - """overwritten from :meth:`dict.has_key`""" - return dict.has_key(self, key.lower()) + """overwritten from :meth:`dict.has_key` (needed for python2)""" + return key.lower() in self def get(self, key, def_val=None): """overwritten from :meth:`dict.get`""" @@ -297,7 +304,7 @@ def __delitem__(self, k): class CaselessWeakValueDict(weakref.WeakValueDictionary): def __init__(self, other=None): - weakref.WeakValueDictionary.__init__(self) + weakref.WeakValueDictionary.__init__(self, other) if other: # Doesn't do keyword args if isinstance(other, dict): @@ -317,8 +324,10 @@ def __contains__(self, key): return weakref.WeakValueDictionary.__contains__(self, key.lower()) def has_key(self, key): - """overwritten from :meth:`weakref.WeakValueDictionary.has_key`""" - return weakref.WeakValueDictionary.has_key(self, key.lower()) + """overwritten from :meth:`weakref.WeakValueDictionary` + (needed for python2) + """ + return key in self def get(self, key, def_val=None): """overwritten from :meth:`weakref.WeakValueDictionary.get`""" @@ -330,8 +339,9 @@ def setdefault(self, key, def_val=None): def update(self, other): """overwritten from :meth:`weakref.WeakValueDictionary.update`""" - for k, v in other.items(): - weakref.WeakValueDictionary.__setitem__(self, k.lower(), v) + if other: + for k, v in other.items(): + weakref.WeakValueDictionary.__setitem__(self, k.lower(), v) def fromkeys(self, iterable, value=None): d = CaselessWeakValueDict() @@ -409,7 +419,7 @@ def __exit__(self, *exc_info): def dump(self, fileobj): if self.format == 'csv': - csv.writer(fileobj).writerows(self.items()) + csv.writer(fileobj).writerows(list(self.items())) elif self.format == 'json': json.dump(self, fileobj, separators=(',', ':')) elif self.format == 'pickle': @@ -483,7 +493,7 @@ def getCurrentIndex(self): '''returns the current index''' return self._index - def next(self): + def __next__(self): '''advances one item in the list and returns it''' self._index += 1 return self.current() @@ -619,7 +629,7 @@ def __init__(self, arg=None): """ Initializes the list with a sequence or an initial value. """ if arg is None: list.__init__(self) - elif operator.isSequenceType(arg): + elif isinstance(arg, collections.Sequence): list.__init__(self, arg) else: list.__init__(self) @@ -682,12 +692,12 @@ def lock_fun(self, *args, **kwargs): self.lock.acquire() try: if self.trace: - print "locked: %s" % self.lock + print("locked: %s" % self.lock) result = func(self, *args, **kwargs) finally: self.lock.release() if self.trace: - print "released: %s" % self.lock + print("released: %s" % self.lock) return result return lock_fun @@ -721,7 +731,7 @@ def __init__(self, other=None, read_method=None, write_method=None, timewait=0.1 self.parent = type(self).mro()[1] def tracer(self, text): - print text + print(text) def start(self): import threading @@ -798,7 +808,7 @@ def set_timewait(self, value): @self_locked def append(self, key, value=None): - if not dict.has_key(self, key): + if key not in self: self.parent.__setitem__(self, key, value) if key not in self._threadkeys: self._threadkeys.append(key) @@ -864,16 +874,16 @@ def __repr__(self): #__repr__ = self_locked(dict.__repr__) #get = self_locked(dict.get) - has_key = self_locked(dict.has_key) + #has_key = self_locked(dict.has_key) update = self_locked(dict.update) copy = self_locked(dict.copy) keys = self_locked(dict.keys) values = self_locked(dict.values) items = self_locked(dict.items) - iterkeys = self_locked(dict.iterkeys) - itervalues = self_locked(dict.itervalues) - iteritems = self_locked(dict.iteritems) + #iterkeys = self_locked(dict.iterkeys) + #itervalues = self_locked(dict.itervalues) + #iteritems = self_locked(dict.iteritems) class SortedDict(dict): @@ -893,7 +903,7 @@ def sort(self, key): or a callable providing a sorting key algorithm. """ import operator - if operator.isCallable(key): + if hasattr(key, '__call__'): self._keys = sorted(self._keys, key=key) else: for k in self._keys: @@ -985,7 +995,7 @@ def __reduce__(self): args = tuple() else: args = self.default_factory, - return type(self), args, None, None, self.items() + return type(self), args, None, None, list(self.items()) def copy(self): return self.__copy__() @@ -996,7 +1006,7 @@ def __copy__(self): def __deepcopy__(self, memo): import copy return type(self)(self.default_factory, - copy.deepcopy(self.items())) + copy.deepcopy(list(self.items()))) def __repr__(self): return 'defaultdict(%s, %s)' % (self.default_factory, @@ -1047,12 +1057,12 @@ def getDictAsTree(dct): """This method will print a recursive dict in a tree-like shape:: - >>> print getDictAsTree({'A':{'B':[1,2],'C':[3]}})""" + >>> print(getDictAsTree({'A':{'B':[1,2],'C':[3]}}))""" def add_to_level(l, d): lines = [] if isinstance(d, dict): for k, v in d.items(): - print 'with key "%s"' % k + print('with key "%s"' % k) lines.append([''] * l + [str(k)]) lines += add_to_level(l + 1, v) elif type(d) in [list, set]: # End of recursion @@ -1065,7 +1075,7 @@ def add_to_level(l, d): lines.append([''] * l + [str(d)]) return lines ls = ['\t'.join(line) for line in add_to_level(0, dct)] - print 'lines are : \n', ls + print('lines are : \n', ls) return '\n'.join(ls) @@ -1124,8 +1134,8 @@ def __repr__(self): def __str__(self): return self.__buffer[:self.__end].__str__() - def __nonzero__(self): - return self.__buffer[:self.__end].__nonzero__() + def __bool__(self): + return self.__buffer[:self.__end].__bool__() def __setitem__(self, i, x): self.__buffer[:self.__end].__setitem__(i, x) @@ -1313,5 +1323,5 @@ def remainingSize(self): def chunks(l, n): '''Generator which yields successive n-sized chunks from l''' - for i in xrange(0, len(l), n): + for i in range(0, len(l), n): yield l[i:i + n] diff --git a/lib/taurus/core/util/decorator/memoize.py b/lib/taurus/core/util/decorator/memoize.py index d4a4129be..7c65cf785 100644 --- a/lib/taurus/core/util/decorator/memoize.py +++ b/lib/taurus/core/util/decorator/memoize.py @@ -23,6 +23,7 @@ ## ############################################################################# +from builtins import object import functools diff --git a/lib/taurus/core/util/decorator/typecheck.py b/lib/taurus/core/util/decorator/typecheck.py index 66947025a..88ec269e3 100644 --- a/lib/taurus/core/util/decorator/typecheck.py +++ b/lib/taurus/core/util/decorator/typecheck.py @@ -63,13 +63,13 @@ """ +from __future__ import print_function +import sys + __all__ = ["accepts", "returns"] __docformat__ = "restructuredtext" -import sys - - def accepts(*types, **kw): """ Function decorator. Checks that inputs given to decorated function are of the expected type. @@ -97,17 +97,17 @@ def newf(*args): if argtypes != types: msg = info(f.__name__, types, argtypes, 0) if debug == 1: - print >> sys.stderr, 'TypeWarning: ', msg + print('TypeWarning: ', msg, file=sys.stderr) elif debug == 2: - raise TypeError, msg + raise TypeError(msg) return f(*args) newf.__name__ = f.__name__ return newf return decorator - except KeyError, key: - raise KeyError, key + "is not a valid keyword argument" - except TypeError, msg: - raise TypeError, msg + except KeyError as key: + raise KeyError(key + "is not a valid keyword argument") + except TypeError as msg: + raise TypeError(msg) def returns(ret_type, **kw): @@ -138,17 +138,17 @@ def newf(*args): if res_type != ret_type: msg = info(f.__name__, (ret_type,), (res_type,), 1) if debug == 1: - print >> sys.stderr, 'TypeWarning: ', msg + print('TypeWarning: ', msg, file=sys.stderr) elif debug == 2: - raise TypeError, msg + raise TypeError(msg) return result newf.__name__ = f.__name__ return newf return decorator - except KeyError, key: - raise KeyError, key + "is not a valid keyword argument" - except TypeError, msg: - raise TypeError, msg + except KeyError as key: + raise KeyError(key + "is not a valid keyword argument") + except TypeError as msg: + raise TypeError(msg) def info(fname, expected, actual, flag): diff --git a/lib/taurus/core/util/enumeration.py b/lib/taurus/core/util/enumeration.py index 7a216565d..82f275a7e 100644 --- a/lib/taurus/core/util/enumeration.py +++ b/lib/taurus/core/util/enumeration.py @@ -33,8 +33,13 @@ values (specified and unspecified) are unique. Enum values then are attributes of an Enumeration class (Volkswagen.BEETLE, Volkswagen.PASSAT, etc.).""" -__all__ = ["EnumException", "Enumeration"] +from builtins import int +from builtins import str +from builtins import object +from future.utils import string_types + +__all__ = ["EnumException", "Enumeration"] __docformat__ = "restructuredtext" @@ -95,9 +100,9 @@ def __init__(self, name, enumList, flaggable=False, no_doc=False): raise EnumException( "flagable enum does not accept tuple items") x, i = x - if not isinstance(x, (str, unicode)): + if not isinstance(x, string_types): raise EnumException("enum name is not a string: " + str(x)) - if not isinstance(i, (int, long)): + if not isinstance(i, int): raise EnumException( "enum value is not an integer: " + str(i)) if x in uniqueNames: @@ -111,7 +116,7 @@ def __init__(self, name, enumList, flaggable=False, no_doc=False): reverseLookup[i] = x for x in enumList: if not isinstance(x, tuple): - if not isinstance(x, (str, unicode)): + if not isinstance(x, string_types): raise EnumException("enum name is not a string: " + str(x)) if x in uniqueNames: raise EnumException("enum name is not unique: " + str(x)) @@ -142,20 +147,26 @@ def _generateUniqueId(self): self._uniqueId += 1 return n + def __contains__(self, i): + if isinstance(i, int): + return i in self.reverseLookup + elif isinstance(i, string_types): + return i in self.lookup + def __getitem__(self, i): - if isinstance(i, (int, long)): + if isinstance(i, int): return self.whatis(i) - elif isinstance(i, (str, unicode)): + elif isinstance(i, string_types): return self.lookup[i] def __getattr__(self, attr): - if not self.has_key(attr): + if attr not in self: raise AttributeError return self.lookup[attr] def __doc_enum(self): rl = self.reverseLookup - keys = rl.keys() + keys = list(rl) keys.sort() values = "\n".join([" - {0} ({1})".format(rl[k], k) for k in keys]) self.__doc__ = self._name + " enumeration. " + \ @@ -163,14 +174,14 @@ def __doc_enum(self): def __str__(self): rl = self.reverseLookup - keys = rl.keys() + keys = list(rl) keys.sort() values = ", ".join([rl[k] for k in keys]) return self._name + "(" + values + ")" def __repr__(self): rl = self.reverseLookup - keys = rl.keys() + keys = list(rl) keys.sort() values = [rl[k] for k in keys] return "Enumeration('" + self._name + "', " + str(values) + ")" @@ -187,7 +198,7 @@ def keys(self): """Returns an iterable containning the valid enumeration keys :return: an interable containning the valid enumeration keys :rtype: iter""" - return self.lookup.keys() + return list(self.lookup.keys()) def whatis(self, value): """Returns a string representation of the value in the enumeration. diff --git a/lib/taurus/core/util/event.py b/lib/taurus/core/util/event.py index 1c9cf182b..d2e67449a 100644 --- a/lib/taurus/core/util/event.py +++ b/lib/taurus/core/util/event.py @@ -26,6 +26,17 @@ """ event.py: """ +from __future__ import print_function +from __future__ import absolute_import +from builtins import range +from builtins import object +import sys +import weakref +import threading +import time +import collections +import taurus.core + __all__ = ["BoundMethodWeakref", "CallableRef", "EventGenerator", "ConfigEventGenerator", "ListEventGenerator", "EventListener", @@ -33,14 +44,6 @@ __docformat__ = "restructuredtext" -import sys -import weakref -import threading -import time -import operator - -import taurus.core - class BoundMethodWeakref(object): """This class represents a weak reference to a method of an object since @@ -73,11 +76,21 @@ def __hash__(self): def __cmp__(self, other): if other.__class__ == self.__class__: + from past.builtins import cmp ret = cmp((self.func_ref, self.obj_ref), (other.func_ref, other.obj_ref)) return ret return 1 + def __eq__(self, other): + if hasattr(other, 'func_ref') and hasattr(other, 'obj_ref'): + return ((self.func_ref, self.obj_ref) + == (other.func_ref, other.obj_ref)) + return False + + def __ne__(self, other): + return not self == other + def __repr__(self): obj, func = self.obj_ref(), self.func_ref() return 'BoundMethodWeakRef of %s.%s' % (obj, func) @@ -94,9 +107,14 @@ def CallableRef(object, del_cb=None): :return: a weak reference for the given callable :rtype: BoundMethodWeakref or weakref.ref""" - if hasattr(object, 'im_self'): - if object.im_self is not None: - return BoundMethodWeakref(object, del_cb) + im_self = None + if hasattr(object, '__self__'): + im_self = object.__self__ + elif hasattr(object, 'im_self'): + im_self = object.im_self + + if im_self is not None: + return BoundMethodWeakref(object, del_cb) return weakref.ref(object, del_cb) @@ -157,7 +175,7 @@ def getAllRead(self): return read -from object import Object +from .object import Object class EventGenerator(Object): @@ -208,7 +226,7 @@ def subscribeEvent(self, cb, data=None, with_first_event=True): :type data: boolean """ if not self.events_active: - raise RuntimeError, ('%s does not have ' + raise RuntimeError('%s does not have ' 'events/polling active' % self.event_name) cb_ref = CallableRef(cb, self.unsubscribeDeletedEvent) @@ -216,7 +234,7 @@ def subscribeEvent(self, cb, data=None, with_first_event=True): try: self.lock() if (cb_ref, data) in self.cb_list: - raise RuntimeError, ('Callback %s(%s) already reg. on %s' % + raise RuntimeError('Callback %s(%s) already reg. on %s' % (cb, data, self.event_name)) self.cb_list.append((cb_ref, data)) if with_first_event: @@ -229,7 +247,7 @@ def unsubscribeDeletedEvent(self, cb_ref): try: self.lock() aux_list = list(self.cb_list) - for i in xrange(len(aux_list) - 1, -1, -1): + for i in range(len(aux_list) - 1, -1, -1): pair = self.cb_list[i] if pair[0] is cb_ref: del self.cb_list[i] @@ -320,7 +338,7 @@ def waitEvent(self, val=None, equal=True, any=False, timeout=None, :return: the value of the event that unblocked the wait :rtype: object""" if not self.events_active: - raise RuntimeError, ('%s does not have ' + raise RuntimeError('%s does not have ' 'events/polling active' % self.event_name) try: self.lock() @@ -443,7 +461,7 @@ class MyEvtListener(EventListener): if t and t >= after: return else: - for v, t in s.items(): + for v, t in list(s.items()): if v == val: continue if t >= after: @@ -545,8 +563,8 @@ def unlock(self): name = th.name else: name = "" - print "WARNING: Thread %s trying to unlock condition previously " \ - "locked by thread %s" % (curr_th.name, name) + print("WARNING: Thread %s trying to unlock condition previously " \ + "locked by thread %s" % (curr_th.name, name)) def clearEventSet(self): "Clears the internal event buffer" @@ -658,9 +676,9 @@ def waitEvent(self, val, after=0, equal=True, timeout=None, retries=-1, return self._cond.wait(timeout) retries -= 1 - except Exception, e: + except Exception as e: sys.stderr.write( - "AttributeEventWait: Caught exception while waitting: %s\n" % str(e)) + "AttributeEventWait: Caught exception while waiting: %s\n" % str(e)) raise e finally: self.unlock() @@ -676,7 +694,7 @@ def __init__(self, *attrs): self.connect(attrs) def connect(self, attrs): - if not operator.isSequenceType(attrs): + if not isinstance(attrs, collections.Sequence): attrs = (attrs,) self.disconnect() self._attrs = attrs @@ -699,8 +717,8 @@ def unlock(self): lock = getattr(self._cond, "_Condition__lock") th = getattr(lock, "_RLock__owner") curr_th = threading.current_thread() - print "WARNING: Thread %s trying to unlock condition previously " \ - "locked by thread %s" % (curr_th.name, th.name) + print(("WARNING: Thread %s trying to unlock condition previously " + + "locked by thread %s") % (curr_th.name, th.name)) def eventReceived(self, s, t, v): if t not in (taurus.core.taurusbasetypes.TaurusEventType.Change, taurus.core.taurusbasetypes.TaurusEventType.Periodic): @@ -722,7 +740,7 @@ def events(self, timeout=1): while True: self._cond.wait(timeout) yield self._data - except Exception, e: - print "INFO: Caught exception while waiting", str(e) + except Exception as e: + print("INFO: Caught exception while waiting", str(e)) finally: self.unlock() diff --git a/lib/taurus/core/util/eventfilters.py b/lib/taurus/core/util/eventfilters.py index 165679255..f6067407c 100644 --- a/lib/taurus/core/util/eventfilters.py +++ b/lib/taurus/core/util/eventfilters.py @@ -26,6 +26,8 @@ """event filters library to be used with :meth:`taurus.qt.qtgui.base.TaurusBaseComponent.setFilters`""" +from builtins import object + def IGNORE_ALL(s, t, v): '''Will discard all events''' diff --git a/lib/taurus/core/util/excepthook.py b/lib/taurus/core/util/excepthook.py index 7ba8967d6..e632775e5 100644 --- a/lib/taurus/core/util/excepthook.py +++ b/lib/taurus/core/util/excepthook.py @@ -25,12 +25,13 @@ """This module contains a base class for exception hooks""" +import sys +from builtins import object + __all__ = ["BaseExceptHook"] __docformat__ = "restructuredtext" -import sys - class BaseExceptHook(object): """A callable class that acts as an excepthook that handles an exception. diff --git a/lib/taurus/core/util/fandango_search.py b/lib/taurus/core/util/fandango_search.py index 3eb2c530f..9ef480406 100644 --- a/lib/taurus/core/util/fandango_search.py +++ b/lib/taurus/core/util/fandango_search.py @@ -31,6 +31,7 @@ """ # TODO: tango-centric +from builtins import str import re import taurus @@ -68,30 +69,35 @@ def extend_regexp(s): def isString(s): + # TODO: UGLY AND FRAGILE!!! (Refactor) May not even work with py3 typ = s.__class__.__name__.lower() return not hasattr(s, '__iter__') and 'str' in typ and 'list' not in typ def isCallable(obj): + # TODO: UGLY AND FRAGILE!!! (Refactor) May not even work with py3 return hasattr(obj, '__call__') def isMap(obj): + # TODO: UGLY AND FRAGILE!!! (Refactor) May not even work with py3 return hasattr(obj, 'has_key') or hasattr(obj, 'items') def isDictionary(obj): + # TODO: UGLY AND FRAGILE!!! (Refactor) return isMap(obj) def isSequence(obj): + # TODO: UGLY AND FRAGILE!!! (Refactor) May not even work with py3 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)): + if isString(modelNames): modelNames = str(modelNames).replace(',', ' ') modelNames = modelNames.split() if isSequence(modelNames): # isinstance(modelNames,(list.Qt.QStringList)): @@ -116,28 +122,30 @@ def get_matching_devices(expressions, limit=0, exported=False): #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)) + result.extend([d for d in all_devs if any(matchCl(extend_regexp(e), d) + for e in expressions)]) return result def get_device_for_alias(alias): + # TODO: Use validators instead db = taurus.Authority() try: return db.get_device_alias(alias) - except Exception, e: + except Exception as e: if 'no device found' in str(e).lower(): return None return None # raise e def get_alias_for_device(dev): + # TODO: Use validators instead db = taurus.Authority() try: # .get_database_device().DbGetDeviceAlias(dev) result = db.get_alias(dev) return result - except Exception, e: + except Exception as e: if 'no alias found' in str(e).lower(): return None return None # raise e diff --git a/lib/taurus/core/util/init_bkcomp.py b/lib/taurus/core/util/init_bkcomp.py index ccd1a9522..981ae1564 100644 --- a/lib/taurus/core/util/init_bkcomp.py +++ b/lib/taurus/core/util/init_bkcomp.py @@ -33,8 +33,8 @@ #. if python >= 2.6 use standard json from python distribution #. otherwise use private implementation distributed with taurus """ +from __future__ import absolute_import -__docformat__ = "restructuredtext" # taurus cannot work properly without the following modules so # they are promptly imported here has a facility (also for backward @@ -61,13 +61,14 @@ from .threadpool import * from .user import * -import eventfilters +from . import eventfilters try: from lxml import etree except: etree = None +__docformat__ = "restructuredtext" def dictFromSequence(seq): """Translates a sequence into a dictionary by converting each to elements of @@ -77,7 +78,17 @@ def dictFromSequence(seq): :return: (dict) dictionary built from the given sequence""" def _pairwise(iterable): """Utility method used by dictFromSequence""" - itnext = iter(iterable).next + itnext = iter(iterable).__next__ while True: yield itnext(), itnext() return dict(_pairwise(seq)) + + + + + + + + + + diff --git a/lib/taurus/core/util/init_lightweight.py b/lib/taurus/core/util/init_lightweight.py index 6ce866347..22e20c6d7 100644 --- a/lib/taurus/core/util/init_lightweight.py +++ b/lib/taurus/core/util/init_lightweight.py @@ -77,7 +77,7 @@ def dictFromSequence(seq): :return: (dict) dictionary built from the given sequence""" def _pairwise(iterable): """Utility method used by dictFromSequence""" - itnext = iter(iterable).next + itnext = iter(iterable).__next__ while True: yield itnext(), itnext() return dict(_pairwise(seq)) diff --git a/lib/taurus/core/util/lock.py b/lib/taurus/core/util/lock.py index 66b97f9e7..fb40ee8a9 100644 --- a/lib/taurus/core/util/lock.py +++ b/lib/taurus/core/util/lock.py @@ -26,13 +26,14 @@ """This module defines a *slow* lock class that provides additional debugging information""" +from builtins import object +import threading +import logging + __all__ = ["TaurusLock"] __docformat__ = 'restructuredtext' -import threading -import logging - _VERBOSE = False diff --git a/lib/taurus/core/util/log.py b/lib/taurus/core/util/log.py index 9f9bd3251..2a78dfdf2 100644 --- a/lib/taurus/core/util/log.py +++ b/lib/taurus/core/util/log.py @@ -25,16 +25,13 @@ """This module contains a set of useful logging elements based on python's :mod:`logging` system.""" +from __future__ import print_function +from __future__ import absolute_import -__all__ = ["LogIt", "TraceIt", "DebugIt", "InfoIt", "WarnIt", "ErrorIt", - "CriticalIt", "MemoryLogHandler", "LogExceptHook", "Logger", - "LogFilter", - "_log", "trace", "debug", "info", "warning", "error", "fatal", - "critical", "deprecated", "deprecation_decorator", - "taurus4_deprecation"] - -__docformat__ = "restructuredtext" +from builtins import str +from builtins import object +import io import os import sys import logging.handlers @@ -45,15 +42,25 @@ import threading import functools -from object import Object -from wrap import wraps -from excepthook import BaseExceptHook +from .object import Object +from .wrap import wraps +from .excepthook import BaseExceptHook # ------------------------------------------------------------------------------ # TODO: substitute this ugly hack (below) by a more general mechanism from collections import defaultdict +__all__ = ["LogIt", "TraceIt", "DebugIt", "InfoIt", "WarnIt", "ErrorIt", + "CriticalIt", "MemoryLogHandler", "LogExceptHook", "Logger", + "LogFilter", + "_log", "trace", "debug", "info", "warning", "error", "fatal", + "critical", "deprecated", "deprecation_decorator", + "taurus4_deprecation"] + +__docformat__ = "restructuredtext" + + class _DeprecationCounter(defaultdict): def __init__(self): @@ -61,14 +68,13 @@ def __init__(self): def getTotal(self): c = 0 - for v in self.itervalues(): + for v in self.values(): c += v return c def pretty(self): from operator import itemgetter - sorted_items = sorted( - self.iteritems(), key=itemgetter(1), reverse=True) + sorted_items = sorted(self.items(), key=itemgetter(1), reverse=True) ret = '\n'.join(['\t%d * "%s"' % (v, k) for k, v in sorted_items]) return "< Deprecation Counts (%d):\n%s >" % (self.getTotal(), ret) @@ -168,7 +174,7 @@ def wrapper(*args, **kwargs): return f(*args, **kwargs) has_log = hasattr(f_self, "log") - fname = f.func_name + fname = f.__name__ log_obj = f_self if not has_log: log_obj = logging.getLogger() @@ -188,7 +194,7 @@ def wrapper(*args, **kwargs): out_msg = "<-" try: ret = f(*args, **kwargs) - except Exception, e: + except Exception as e: exc_info = sys.exc_info() out_msg += " (with %s) %s" % (e.__class__.__name__, fname) log_obj.log(self._level, out_msg, exc_info=exc_info) @@ -325,27 +331,27 @@ def __init__(self, showargs=False, showret=False): def __call__(self, f): @wraps(f) def wrapper(*args, **kwargs): - fname = f.func_name + fname = f.__name__ in_msg = "-> %s" % fname if self._showargs: if len(args) > 1: in_msg += str(args[1:]) if len(kwargs): in_msg += str(kwargs) - print - print in_msg + print() + print(in_msg) out_msg = "<-" try: ret = f(*args, **kwargs) - except Exception, e: + except Exception as e: out_msg += " (with %s) %s" % (e.__class__.__name__, fname) - print out_msg + print(out_msg) raise out_msg += " %s" % fname if not ret is None and self._showret: out_msg += " = %s" % str(ret) - print out_msg - print + print(out_msg) + print() return ret return wrapper @@ -408,30 +414,6 @@ def report(self, *exc_info): self._log.log(self._level, "Unhandled exception:\n%s", text) -class _Logger(logging.Logger): - - def findCaller(self): - """ - Find the stack frame of the caller so that we can note the source - file name, line number and function name. - """ - f = currentframe() - # On some versions of IronPython, currentframe() returns None if - # IronPython isn't run with -X:Frames. - if f is not None: - f = f.f_back - rv = "(unknown file)", 0, "(unknown function)" - while hasattr(f, "f_code"): - co = f.f_code - filename = os.path.normcase(co.co_filename) - if filename in (_srcfile, logging._srcfile): - f = f.f_back - continue - rv = (co.co_filename, f.f_lineno, co.co_name) - break - return rv - - class Logger(Object): """The taurus logger class. All taurus pertinent classes should inherit directly or indirectly from this class if they need taurus logging @@ -645,7 +627,7 @@ def getRootLog(cls): def _getLogger(name=None): orig_logger_class = logging.getLoggerClass() try: - logging.setLoggerClass(_Logger) + logging.setLoggerClass(logging.Logger) ret = logging.getLogger(name) return ret finally: @@ -678,7 +660,7 @@ def getChildren(self): :return: (sequence _MAX_DEPRECATIONS_LOGGED: return - if _callerinfo is None: _callerinfo = self.log_obj.findCaller() - filename, lineno, _ = _callerinfo + filename, lineno = _callerinfo[:2] depr_msg = warnings.formatwarning( msg, DeprecationWarning, filename, lineno) self.log_obj.warning(depr_msg, **kw) @@ -1078,4 +1059,4 @@ def foo(x): - zab """ - print foo.__doc__ + print(foo.__doc__) diff --git a/lib/taurus/core/util/object.py b/lib/taurus/core/util/object.py index 29c623d30..eb20003e5 100644 --- a/lib/taurus/core/util/object.py +++ b/lib/taurus/core/util/object.py @@ -25,6 +25,8 @@ """This module contains the base Object class for taurus.""" +from builtins import object + __all__ = ["Object"] __docformat__ = "restructuredtext" diff --git a/lib/taurus/core/util/parse_args.py b/lib/taurus/core/util/parse_args.py index b97c95507..3da75c671 100644 --- a/lib/taurus/core/util/parse_args.py +++ b/lib/taurus/core/util/parse_args.py @@ -23,6 +23,7 @@ ## ############################################################################# +from __future__ import print_function from ast import literal_eval @@ -60,7 +61,7 @@ def parse_args(s, strip_pars=False): if __name__ == "__main__": - print parse_args('1, 2, b=3, c=4') - print parse_args(' (1, 2, b=3, c=4 )', strip_pars=True) + print(parse_args('1, 2, b=3, c=4')) + print(parse_args(' (1, 2, b=3, c=4 )', strip_pars=True)) - print parse_args('1, 2, b=3, c=4, 5') # <--this should raise a SyntaxError + print(parse_args('1, 2, b=3, c=4, 5')) # <--this should raise a SyntaxError diff --git a/lib/taurus/core/util/prop.py b/lib/taurus/core/util/prop.py index cb6d41c16..3f1d17076 100644 --- a/lib/taurus/core/util/prop.py +++ b/lib/taurus/core/util/prop.py @@ -24,6 +24,9 @@ ############################################################################# """This module contains a decorator to simplify the use of property.""" +from __future__ import print_function +from __future__ import absolute_import + __all__ = ["propertx"] @@ -47,7 +50,7 @@ def propertx(fct): if __name__ == '__main__': - from log import Logger + from .log import Logger class example(object, Logger): @@ -60,15 +63,15 @@ def __init__(self): def bar(): # BAR doc def get(self): - print "\tgetting", self._a + print("\tgetting", self._a) return self._a def set(self, val): - print "\tsetting", val + print("\tsetting", val) self._a = val return get, set foo = example() - print foo.bar + print(foo.bar) # foo.bar='egg' # print foo.bar diff --git a/lib/taurus/core/util/property_parser.py b/lib/taurus/core/util/property_parser.py index e8fae071e..784055c2f 100644 --- a/lib/taurus/core/util/property_parser.py +++ b/lib/taurus/core/util/property_parser.py @@ -25,11 +25,12 @@ """This is an experimental property parser""" -import os +from __future__ import print_function +from builtins import str +import os import ply.lex as lex import ply.yacc as yacc - from .containers import CaselessDict from .log import Logger @@ -47,7 +48,7 @@ 'COMMA', 'LLST', 'RLST', #'LBRACKET', 'RBRACKET', - ] + reserved.values() + ] + list(reserved.values()) t_EQUALS = r'\=' t_LLST = r'\[' @@ -68,7 +69,7 @@ def t_NUMBER(t): else: t.value = float(t.value) except: - print "[%d]: Number %s is not valid!" % (t.lineno, t.value) + print("[%d]: Number %s is not valid!" % (t.lineno, t.value)) t.value = 0 return t @@ -117,12 +118,12 @@ def t_newline(t): def t_error(t): - print "[%d]: Illegal character '%s'" % (t.lexer.lineno, t.value[0]) + print("[%d]: Illegal character '%s'" % (t.lexer.lineno, t.value[0])) t.lexer.skip(1) def p_error(p): - print "[%d]: Syntax error in input [%s]" % (p.lineno, (str(p))) + print("[%d]: Syntax error in input [%s]" % (p.lineno, (str(p)))) #------------------------------------------------------------------------- # Yacc Starting symbol @@ -228,7 +229,7 @@ def parse(self, filename, logger=None, debug=0, optimize=1): res = self.parse_file( f, logger=logger, debug=debug, optimize=optimize) f.close() - except IOError, e: + except IOError as e: if f: f.close() raise @@ -241,4 +242,4 @@ def getLastFilename(self): import sys pp = PropertyParser() res = pp.parse(sys.argv[1]) - print res + print(res) diff --git a/lib/taurus/core/util/propertyfile.py b/lib/taurus/core/util/propertyfile.py index 15a3ab669..a608b884a 100644 --- a/lib/taurus/core/util/propertyfile.py +++ b/lib/taurus/core/util/propertyfile.py @@ -34,15 +34,16 @@ Modified - Tiago Coutinho """ -__all__ = ["Properties"] - -__docformat__ = "restructuredtext" +from builtins import next +from builtins import object import sys -import os import re import time +__all__ = ["Properties"] + +__docformat__ = "restructuredtext" class Properties(object): """ A Python replacement for java.util.Properties """ @@ -170,7 +171,7 @@ def __parse(self, lines): # same property while line[-1] == '\\': # Read next line - nextline = i.next() + nextline = next(i) nextline = nextline.strip() lineno += 1 # This line will become part of the value @@ -216,7 +217,7 @@ def processPair(self, key, value): self._props[key] = value.strip() # Check if an entry exists in pristine keys - if self._keymap.has_key(key): + if key in self._keymap: oldkey = self._keymap.get(key) self._origprops[oldkey] = oldvalue.strip() else: @@ -247,15 +248,15 @@ def load(self, stream): # For the time being only accept file input streams if type(stream) is not file: - raise TypeError, 'Argument should be a file object!' + raise TypeError('Argument should be a file object!') # Check for the opened mode if stream.mode != 'r': - raise ValueError, 'Stream should be opened in read-only mode!' + raise ValueError('Stream should be opened in read-only mode!') try: lines = stream.readlines() self.__parse(lines) - except IOError, e: + except IOError as e: raise def getProperty(self, key): @@ -269,20 +270,20 @@ def setProperty(self, key, value): if type(key) is str and type(value) is str: self.processPair(key, value) else: - raise TypeError, 'both key and value should be strings!' + raise TypeError('both key and value should be strings!') def propertyNames(self): """ Return an iterator over all the keys of the property dictionary, i.e the names of the properties """ - return self._props.keys() + return list(self._props.keys()) def list(self, out=sys.stdout): """ Prints a listing of the properties to the stream 'out' which defaults to the standard output """ out.write('-- listing properties --\n') - for key, value in self._props.items(): + for key, value in list(self._props.items()): out.write(''.join((key, '=', value, '\n'))) def store(self, out, header=""): @@ -290,7 +291,7 @@ def store(self, out, header=""): with the optional 'header' """ if out.mode[0] != 'w': - raise ValueError, 'Steam should be opened in write mode!' + raise ValueError('Steam should be opened in write mode!') try: out.write(''.join(('#', header, '\n'))) @@ -298,11 +299,11 @@ def store(self, out, header=""): tstamp = time.strftime('%a %b %d %H:%M:%S %Z %Y', time.localtime()) out.write(''.join(('#', tstamp, '\n'))) # Write properties from the pristine dictionary - for prop, val in self._origprops.items(): + for prop, val in list(self._origprops.items()): out.write(''.join((prop, '=', self.escape(val), '\n'))) out.close() - except IOError, e: + except IOError as e: raise def getPropertyDict(self): diff --git a/lib/taurus/core/util/remotelogmonitor.py b/lib/taurus/core/util/remotelogmonitor.py index b16434135..9b7988bee 100644 --- a/lib/taurus/core/util/remotelogmonitor.py +++ b/lib/taurus/core/util/remotelogmonitor.py @@ -28,7 +28,8 @@ from __future__ import print_function from __future__ import with_statement -__all__ = ["LogRecordStreamHandler", "LogRecordSocketReceiver", "log"] +from future import standard_library +standard_library.install_aliases() import time import socket @@ -38,10 +39,10 @@ import struct import weakref -try: - import socketserver -except: - import SocketServer as socketserver +import socketserver + + +_all__ = ["LogRecordStreamHandler", "LogRecordSocketReceiver", "log"] class LogRecordStreamHandler(socketserver.StreamRequestHandler): diff --git a/lib/taurus/core/util/report/report.py b/lib/taurus/core/util/report/report.py index a3444b4e1..b426a47e6 100644 --- a/lib/taurus/core/util/report/report.py +++ b/lib/taurus/core/util/report/report.py @@ -25,6 +25,8 @@ """This module provides a panel to display taurus messages""" +from builtins import object + __all__ = ["TaurusMessageReportHandler"] __docformat__ = 'restructuredtext' diff --git a/lib/taurus/core/util/safeeval.py b/lib/taurus/core/util/safeeval.py index 3c11ac62c..43565643c 100644 --- a/lib/taurus/core/util/safeeval.py +++ b/lib/taurus/core/util/safeeval.py @@ -26,6 +26,10 @@ """ safeeval.py: Safe eval replacement with whitelist support """ +from __future__ import print_function + +from builtins import range +from builtins import object __all__ = ["SafeEvaluator"] @@ -116,14 +120,14 @@ def getSafe(self): if __name__ == '__main__': - x = range(6) + x = list(range(6)) sev = SafeEvaluator() - print "trying to evaluate a variable that has not been registered" + print("trying to evaluate a variable that has not been registered") try: # This will fail because 'x' is not registered in sev - print sev.safeEval('x+2') + print(sev.safeEval('x+2')) except: - print "failed!!" + print("failed!!") sev.addSafe({'x': x}) # After registering x, we can use it... f0 = 'x' @@ -135,16 +139,17 @@ def getSafe(self): f5 = 'open("/etc/passwd")' for f in [f0, f1, f2, f3, f4, f5]: - print 'Evaluating "%s":' % f + print('Evaluating "%s":' % f) try: - print sev.eval(f) + print(sev.eval(f)) except: - print 'ERROR: %s cannot be evaluated' % f + print('ERROR: %s cannot be evaluated' % f) + import numpy vector = numpy.arange(6) # Another way of registering a variable is using the init method... sev2 = SafeEvaluator({'x': x, 'y': vector}, defaultSafe=False) - print 'x*y=', sev2.eval('x*y') + print('x*y=', sev2.eval('x*y')) y = 0 # note that the registered variable is local to the evaluator!! # here, y still has the previously registered value instead of 0 - print 'x*y=', sev2.eval('x*y') + print('x*y=', sev2.eval('x*y')) diff --git a/lib/taurus/core/util/singleton.py b/lib/taurus/core/util/singleton.py index 89f8faa28..67aeee093 100644 --- a/lib/taurus/core/util/singleton.py +++ b/lib/taurus/core/util/singleton.py @@ -26,6 +26,8 @@ """This module contains a class which can be used as a super class for all classes that need to implement the Singleton design pattern.""" +from builtins import object + __all__ = ["Singleton"] __docformat__ = "restructuredtext" diff --git a/lib/taurus/core/util/tablepprint.py b/lib/taurus/core/util/tablepprint.py index d65b96f14..cab4c13e5 100644 --- a/lib/taurus/core/util/tablepprint.py +++ b/lib/taurus/core/util/tablepprint.py @@ -24,14 +24,19 @@ ############################################################################# """Adapted from http://code.activestate.com/recipes/267662/""" - -__docformat__ = "restructuredtext" - -import cStringIO +from __future__ import print_function +from future import standard_library +standard_library.install_aliases() +from builtins import zip +from builtins import str +from builtins import range +from functools import reduce import operator import re import math +__docformat__ = "restructuredtext" + def indent(rows, hasHeader=False, headerChar='-', delim=' | ', justify='left', separateRows=False, prefix='', postfix='', wrapfunc=lambda x: x): @@ -56,13 +61,13 @@ def indent(rows, hasHeader=False, headerChar='-', delim=' | ', justify='left', # closure for breaking logical rows to physical, using wrapfunc def rowWrapper(row): newRows = [wrapfunc(item).split('\n') for item in row] - return [[substr or '' for substr in item] for item in map(None, *newRows)] + return [[substr or '' for substr in item] for item in list(*newRows)] # break each logical row into one or more physical ones logicalRows = [rowWrapper(row) for row in rows] # columns of physical rows - columns = map(None, *reduce(operator.add, logicalRows)) + columns = list(*reduce(operator.add, logicalRows)) # get the maximum of each column by the string length of its items maxWidths = [max([len(str(item)) for item in column]) @@ -118,14 +123,12 @@ def wrap_onspace_strict(text, width): wordRegex = re.compile(r'\S{' + str(width) + r',}') return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(), width), text), width) -import math - def wrap_always(text, width): """A simple word-wrap function that wraps text on exactly width characters. It doesn't split the text in words.""" return '\n'.join([text[width * i:width * (i + 1)] - for i in xrange(int(math.ceil(1. * len(text) / width)))]) + for i in range(int(math.ceil(1. * len(text) / width)))]) if __name__ == '__main__': labels = ('First Name', 'Last Name', 'Age', 'Position') @@ -135,19 +138,19 @@ def wrap_always(text, width): Aristidis,Papageorgopoulos,28,Senior Reseacher''' rows = [row.strip().split(',') for row in data.splitlines()] - print 'Without wrapping function\n' + print('Without wrapping function\n') for l in indent([labels] + rows, hasHeader=True): - print l + print(l) # test indent with different wrapping functions width = 10 for wrapper in (wrap_always, wrap_onspace, wrap_onspace_strict): - print 'Wrapping function: %s(x,width=%d)\n' % (wrapper.__name__, width) + print('Wrapping function: %s(x,width=%d)\n' % (wrapper.__name__, width)) o = indent([labels] + rows, headerChar='=', hasHeader=True, separateRows=False, prefix='|', postfix='|', delim=' ', wrapfunc=lambda x: wrapper(x, width)) for l in o: - print l + print(l) # output: # diff --git a/lib/taurus/core/util/tb.py b/lib/taurus/core/util/tb.py index 94ff863bc..aada3a004 100644 --- a/lib/taurus/core/util/tb.py +++ b/lib/taurus/core/util/tb.py @@ -26,8 +26,8 @@ """This module contains a set of useful traceback elements based on python's :mod:`traceback` system.""" +from builtins import str import sys -import inspect import traceback import threading @@ -50,7 +50,7 @@ def format_frame_stacks(frames=None, limit=None): frame_stacks = extract_frame_stacks(frames=frames, limit=limit) ret = [] - for ident, (frame, frame_stack) in frame_stacks.items(): + for ident, (frame, frame_stack) in list(frame_stacks.items()): curr_th, th = _get_thread(), _get_thread(ident) if th is None: th_name = "" @@ -73,7 +73,7 @@ def extract_frame_stacks(frames=None, limit=None): if frames is None: frames = _get_frames() ret = {} - for ident, frame in frames.items(): + for ident, frame in list(frames.items()): frame_stack = traceback.extract_stack(frame, limit=limit) ret[ident] = frame, frame_stack return ret diff --git a/lib/taurus/core/util/test/test_codecs.py b/lib/taurus/core/util/test/test_codecs.py index 20702ef2f..0543ca876 100644 --- a/lib/taurus/core/util/test/test_codecs.py +++ b/lib/taurus/core/util/test/test_codecs.py @@ -37,17 +37,17 @@ @insertTest(helper_name='encDec', cname='json', data=[1, 2, 3]) -@insertTest(helper_name='encDec', cname='zip', data='foobar') +@insertTest(helper_name='encDec', cname='zip', data=b'foobar') @insertTest(helper_name='encDec', cname='zip_json', data=[1, 2, 3]) @insertTest(helper_name='encDec', cname='videoimage', data=numpy.ones((2, 2), dtype='uint8')) @insertTest(helper_name='encDec', cname='zip_null_zip_videoimage', data=numpy.ones((2, 2), dtype='uint8')) @insertTest(helper_name='dec', cname='videoimage', - data='VDEO\x00\x01\x00\x07\x00\x00\x00\x00\x00\x00\x00' + - '\x01\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00 ' + - '\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01' + - '\x01\x01\x01\x01\x01\x01\x01\x01', + data=b'VDEO\x00\x01\x00\x07\x00\x00\x00\x00\x00\x00\x00' + + b'\x01\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00 ' + + b'\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01' + + b'\x01\x01\x01\x01\x01\x01\x01\x01', expected=numpy.ones((2, 2, 3), dtype='uint8')) class CodecTest(unittest.TestCase): '''TestCase for checking codecs''' diff --git a/lib/taurus/core/util/threadpool.py b/lib/taurus/core/util/threadpool.py index c0208977f..b4e9f806d 100644 --- a/lib/taurus/core/util/threadpool.py +++ b/lib/taurus/core/util/threadpool.py @@ -24,18 +24,25 @@ ############################################################################# """adapted from http://code.activestate.com/recipes/576576/""" +from __future__ import print_function +from __future__ import absolute_import -__all__ = ["ThreadPool", "Worker"] +from future import standard_library +standard_library.install_aliases() -__docformat__ = "restructuredtext" +from builtins import range from threading import Thread, currentThread -from Queue import Queue +from queue import Queue from time import sleep, time from traceback import extract_stack, format_list -from prop import propertx -from log import Logger, DebugIt, TraceIt +from .prop import propertx +from .log import Logger + +__all__ = ["ThreadPool", "Worker"] + +__docformat__ = "restructuredtext" class ThreadPool(Logger): @@ -152,44 +159,44 @@ def isBusy(self): def easyJob(*arg, **kw): n = arg[0] - print '\tSleep\t\t', n + print('\tSleep\t\t', n) sleep(n) return 'Slept\t%d' % n def longJob(*arg, **kw): - print "\tStart\t\t\t", arg, kw + print("\tStart\t\t\t", arg, kw) n = arg[0] * 3 sleep(n) return "Job done in %d" % n def badJob(*a, **k): - print '\n !!! OOOPS !!!\n' + print('\n !!! OOOPS !!!\n') a = 1 / 0 def show(*arg, **kw): - print 'callback : %s' % arg[0] + print('callback : %s' % arg[0]) def test_1(**kwargs): workers = kwargs.pop('workers', 5) jobqueue = kwargs.pop('jobqueue', 10) pool = ThreadPool(name='ThreadPool', Psize=workers, Qsize=jobqueue) - print "\n\t\t... let's add some jobs ...\n" + print("\n\t\t... let's add some jobs ...\n") for j in range(5): if j == 1: pool.add(badJob) for i in range(5, 0, -1): pool.add(longJob, show, i) pool.add(easyJob, show, i) - print ''' + print(''' \t\t... and now, we're waiting for the %i workers to get the %i jobs done ... - ''' % (pool.size, pool.qsize) + ''' % (pool.size, pool.qsize)) sleep(15) - print "\n\t\t... ok, that may take a while, let's get some reinforcement ...\n" + print("\n\t\t... ok, that may take a while, let's get some reinforcement ...\n") sleep(5) pool.size = 50 - print '\n\t\t... Joining ...\n' + print('\n\t\t... Joining ...\n') pool.join() - print '\n\t\t... Ok ...\n' + print('\n\t\t... Ok ...\n') def test_2(**kwargs): workers = kwargs.pop('workers', 5) @@ -198,21 +205,21 @@ def test_2(**kwargs): sleep_t = kwargs.pop('sleep_t', 1) #from taurus.core.util.threadpool import ThreadPool pool = ThreadPool(name='ThreadPool', Psize=workers, Qsize=jobqueue) - print "\n\t\t... Check the number of busy workers ...\n" - print "Num of busy workers = %s" % (pool.getNumOfBusyWorkers()) - print "\n\t\t... let's add some jobs ...\n" + print("\n\t\t... Check the number of busy workers ...\n") + print("Num of busy workers = %s" % (pool.getNumOfBusyWorkers())) + print("\n\t\t... let's add some jobs ...\n") for i in range(numjobs): pool.add(easyJob, None, sleep_t) - print '\n\t\t... Monitoring the busy workers ...\n' + print('\n\t\t... Monitoring the busy workers ...\n') t0 = time() while pool.getNumOfBusyWorkers() > 0: - print "busy workers = %s" % (pool.getNumOfBusyWorkers()) + print("busy workers = %s" % (pool.getNumOfBusyWorkers())) sleep(0.5) t1 = time() - print "Run %s jobs of 1 second took %.3f" % (numjobs, t1 - t0) - print '\n\t\t... Joining ...\n' + print("Run %s jobs of 1 second took %.3f" % (numjobs, t1 - t0)) + print('\n\t\t... Joining ...\n') pool.join() - print '\n\t\t... Ok ...\n' + print('\n\t\t... Ok ...\n') def main(argv): kwargs = {} diff --git a/lib/taurus/external/qt/QtCore.py b/lib/taurus/external/qt/QtCore.py index ea96611cb..1a902f7ea 100644 --- a/lib/taurus/external/qt/QtCore.py +++ b/lib/taurus/external/qt/QtCore.py @@ -1,199 +1,133 @@ # -*- coding: utf-8 -*- - -############################################################################## -## -## 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 exposes QtCore module""" - -from taurus.external.qt import API_NAME - -__backend = API_NAME - - -def __to_qvariant_1(pyobj=None): - """Properly converts a python object into a proper QVariant according to - the PySide or PyQt API version in use - - :param pyobj: object to be converted - :return: A proper QVariant""" - from PyQt4.QtCore import QVariant - if pyobj is None: - return QVariant() # PyQt 4.4 doesn't accept QVariant(None) - return QVariant(pyobj) - - -def __from_qvariant_1(qobj=None, convfunc=None): - """Properly converts a QVariant/QVariant equivalent to a python object - according to the PySide or PyQt API version in use - - :param qobj: object to be converted - :param convfunc: - conversion function. Not used if QVariant is not available. - If QVariant is available: [default: None, meaning use - qobj.toPyObject()]. Can be a function like str, int, bool, float or - a string containing the conversion method (ex.: 'toByteArray') will - call qobj.toByteArray() - :return: A proper python object""" - if convfunc is None: - return qobj.toPyObject() - elif callable(convfunc): - if convfunc in (unicode, str): - return convfunc(qobj.toString()) - elif convfunc is bool: - return qobj.toBool() - elif convfunc is int: - return qobj.toInt()[0] - elif convfunc is float: - return qobj.toDouble()[0] - elif isinstance(convfunc, (str, unicode)): - return getattr(qobj, convfunc)() - - -def __QVariant_2(pyobj=None): - return pyobj - - -def __to_qvariant_2(pyobj=None): - """Properly converts a python object into a proper QVariant according to - the PySide or PyQt4 API version in use - - :param pyobj: object to be converted - :return: A proper QVariant""" - return pyobj - - -def __from_qvariant_2(qobj=None, convfunc=None): - """Properly converts a QVariant/QVariant equivalent to a python object - according to the PySide or PyQt4 API version in use - - :param qobj: object to be converted - :param convfunc: - conversion function. Not used if QVariant is not available. - If QVariant is available: [default: None, meaning use - qobj.toPyObject()]. Can be a function like str, int, bool, float or - a string containing the conversion method (ex.: 'toByteArray') will - call qobj.toByteArray() - :return: A proper python object""" +# +# Copyright © 2018- CELLS / ALBA Synchrotron, Bellaterra, Spain +# Copyright © 2014-2015 Colin Duquesnoy +# Copyright © 2009-2018 The Spyder Development Team +# +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) + +""" +Provides QtCore classes and functions. +""" +from builtins import str as __str +from taurus.core.util.log import deprecation_decorator as __deprecation + +from . import PYQT5, PYSIDE2, PYQT4, PYSIDE, PythonQtError + + +# -------------------------------------------------------------------------- +# QString, from_qvariant and to_qvariant are kept for now to +# facilitate transition of existing code but using them +# should be avoided (they only make sense with API 1, which is not supported) +@__deprecation(rel='4.0.1', alt='str') +class QString(__str): + pass + + +@__deprecation(rel='4.0.1', alt='python objects directly') +def from_qvariant(qobj=None, convfunc=None): return qobj -__QString_2 = str - -__QStringList_2 = list - - -if __backend == 'PyQt4': - from PyQt4 import QtCore as __QtCore - - # Alias PyQt-specific functions for PySide compatibility. - if hasattr(__QtCore, "pyqtSignal"): - Signal = __QtCore.pyqtSignal - if hasattr(__QtCore, "pyqtSlot"): - Slot = __QtCore.pyqtSlot - else: #implement dummy pyqtSlot decorator for PyQt<4.6 - class DummyPyqtSlot(object): - def __init__(self, *a, **kw): - pass - def __call__(self, f): - return f - Slot = pyqtSlot = DummyPyqtSlot - if hasattr(__QtCore, "pyqtProperty"): - Property = __QtCore.pyqtProperty +@__deprecation(rel='4.0.1', alt='python objects directly') +def to_qvariant(pyobj=None): + return pyobj +# -------------------------------------------------------------------------- - try: - __api_version__ = __QtCore.QT_VERSION_STR - except AttributeError: +if PYQT5: + from PyQt5.QtCore import * + from PyQt5.QtCore import pyqtSignal as Signal + from PyQt5.QtCore import pyqtSlot as Slot + from PyQt5.QtCore import pyqtProperty as Property + from PyQt5.QtCore import QT_VERSION_STR as __version__ + + # For issue #153 of qtpy + from PyQt5.QtCore import QDateTime + QDateTime.toPython = QDateTime.toPyDateTime + +elif PYSIDE2: + from PySide2.QtCore import * + from PySide2.QtCore import Signal as pyqtSignal + from PySide2.QtCore import Slot as pyqtSlot + from PySide2.QtCore import Property as pyqtProperty + try: # may be limited to PySide-5.11a1 only + from PySide2.QtGui import QStringListModel + except: pass - try: - import sip - PYQT_QVARIANT_API_1 = sip.getapi('QVariant') < 2 - PYQT_QSTRING_API_1 = sip.getapi('QString') < 2 - except (ImportError, AttributeError): - PYQT_QVARIANT_API_1 = True - PYQT_QSTRING_API_1 = True - - if PYQT_QVARIANT_API_1: - to_qvariant = __to_qvariant_1 - from_qvariant = __from_qvariant_1 - else: - __QtCore.QVariant = QVariant = __QVariant_2 - to_qvariant = __to_qvariant_2 - from_qvariant = __from_qvariant_2 - - if not PYQT_QSTRING_API_1: - __QtCore.QString = QString = __QString_2 - __QtCore.QStringList = QStringList = __QStringList_2 - +elif PYQT4: from PyQt4.QtCore import * - -elif __backend == 'PyQt5': - from PyQt5 import QtCore as __QtCore - - # Alias PyQt-specific functions for PySide compatibility. - if hasattr(__QtCore, "pyqtSignal"): - Signal = __QtCore.pyqtSignal - if hasattr(__QtCore, "pyqtSlot"): - Slot = __QtCore.pyqtSlot - if hasattr(__QtCore, "pyqtProperty"): - Property = __QtCore.pyqtProperty - - try: - __api_version__ = __QtCore.QT_VERSION_STR - except AttributeError: - pass - - __QtCore.QVariant = QVariant = __QVariant_2 - to_qvariant = __to_qvariant_2 - from_qvariant = __from_qvariant_2 - __QtCore.QString = QString = __QString_2 - __QtCore.QStringList = QStringList = __QStringList_2 - - from PyQt5.QtCore import * - -elif __backend == 'PySide': - from PySide import QtCore as __QtCore + from PyQt4.QtCore import QCoreApplication + from PyQt4.QtCore import Qt + from PyQt4.QtCore import pyqtSignal as Signal + from PyQt4.QtCore import pyqtSlot as Slot + from PyQt4.QtCore import pyqtProperty as Property + from PyQt4.QtGui import (QItemSelection, QItemSelectionModel, + QItemSelectionRange, QSortFilterProxyModel, + QStringListModel) + from PyQt4.QtCore import QT_VERSION_STR as __version__ + + # QDesktopServices has has been split into (QDesktopServices and + # QStandardPaths) in Qt5 + # This creates a dummy class that emulates QStandardPaths + from PyQt4.QtGui import QDesktopServices as _QDesktopServices + + class QStandardPaths(): + StandardLocation = _QDesktopServices.StandardLocation + displayName = _QDesktopServices.displayName + DesktopLocation = _QDesktopServices.DesktopLocation + DocumentsLocation = _QDesktopServices.DocumentsLocation + FontsLocation = _QDesktopServices.FontsLocation + ApplicationsLocation = _QDesktopServices.ApplicationsLocation + MusicLocation = _QDesktopServices.MusicLocation + MoviesLocation = _QDesktopServices.MoviesLocation + PicturesLocation = _QDesktopServices.PicturesLocation + TempLocation = _QDesktopServices.TempLocation + HomeLocation = _QDesktopServices.HomeLocation + DataLocation = _QDesktopServices.DataLocation + CacheLocation = _QDesktopServices.CacheLocation + writableLocation = _QDesktopServices.storageLocation + + + # Deprecated. QVariant is kept for now to facilitate transition of existing + # code but using QVariant should be avoided (with API 2 it is superfluous) + @__deprecation(rel='4.0.1', alt='python objects directly') + def QVariant(pyobj=None): + return pyobj + + +elif PYSIDE: from PySide.QtCore import * - __api_version__ = __QtCore.QT_VERSION_STR - - __QtCore.QVariant = QVariant = __QVariant_2 - to_qvariant = __to_qvariant_2 - from_qvariant = __from_qvariant_2 - __QtCore.QString = QString = __QString_2 - __QtCore.QStringList = QStringList = __QStringList_2 - - #a dummy pyqtsignature decorator - # CAUTION this totally nulifies the pupose of decorating with pyqtSignature - # todo: do a proper implementation of pyqtsignature - def pyqtSignature(f): - return f - - # Alias PySide functions for PyQt compatibility. - if hasattr(__QtCore, "Signal"): - pyqtSignal = Signal - if hasattr(__QtCore, "Slot"): - pyqtSlot = Slot - if hasattr(__QtCore, "Property"): - pyqtProperty = Property - -del API_NAME + from PySide.QtCore import Signal as pyqtSignal + from PySide.QtCore import Slot as pyqtSlot + from PySide.QtCore import Property as pyqtProperty + from PySide.QtGui import (QItemSelection, QItemSelectionModel, + QItemSelectionRange, QSortFilterProxyModel, + QStringListModel) + + # QDesktopServices has has been split into (QDesktopServices and + # QStandardPaths) in Qt5 + # This creates a dummy class that emulates QStandardPaths + from PySide.QtGui import QDesktopServices as _QDesktopServices + + class QStandardPaths(): + StandardLocation = _QDesktopServices.StandardLocation + displayName = _QDesktopServices.displayName + DesktopLocation = _QDesktopServices.DesktopLocation + DocumentsLocation = _QDesktopServices.DocumentsLocation + FontsLocation = _QDesktopServices.FontsLocation + ApplicationsLocation = _QDesktopServices.ApplicationsLocation + MusicLocation = _QDesktopServices.MusicLocation + MoviesLocation = _QDesktopServices.MoviesLocation + PicturesLocation = _QDesktopServices.PicturesLocation + TempLocation = _QDesktopServices.TempLocation + HomeLocation = _QDesktopServices.HomeLocation + DataLocation = _QDesktopServices.DataLocation + CacheLocation = _QDesktopServices.CacheLocation + writableLocation = _QDesktopServices.storageLocation + + import PySide.QtCore + __version__ = PySide.QtCore.__version__ +else: + raise PythonQtError('No Qt bindings could be found') \ No newline at end of file diff --git a/lib/taurus/external/qt/QtDesigner.py b/lib/taurus/external/qt/QtDesigner.py index e38e3e030..6e3e60e43 100644 --- a/lib/taurus/external/qt/QtDesigner.py +++ b/lib/taurus/external/qt/QtDesigner.py @@ -1,32 +1,22 @@ # -*- coding: utf-8 -*- +# +# Copyright © 2014-2015 Colin Duquesnoy +# +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) -############################################################################## -## -## 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 . -## -############################################################################## +""" +Provides QtDesigner classes and functions. +""" -"""This module exposes QtDesigner module""" +from . import PYQT5, PYQT4, PythonQtError -from taurus.external.qt import _updateQtSubModule -_updateQtSubModule(globals(), "QtDesigner") +if PYQT5: + from PyQt5.QtDesigner import * +elif PYQT4: + from PyQt4.QtDesigner import * +else: + raise PythonQtError('No Qt bindings could be found') -del _updateQtSubModule +del PYQT4, PYQT5, PythonQtError diff --git a/lib/taurus/external/qt/QtGui.py b/lib/taurus/external/qt/QtGui.py index ddb7e3a18..06ef8762b 100644 --- a/lib/taurus/external/qt/QtGui.py +++ b/lib/taurus/external/qt/QtGui.py @@ -1,41 +1,37 @@ # -*- coding: utf-8 -*- +# +# Copyright © 2018- CELLS / ALBA Synchrotron, Bellaterra, Spain +# Copyright © 2014-2015 Colin Duquesnoy +# Copyright © 2009-2018 The Spyder Development Team +# +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) -############################################################################## -## -## 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 . -## -############################################################################## +""" +Provides QtGui classes and functions. +.. warning:: Contrary to qtpy.QtGui, this module exposes the namespace + available in ``PyQt4.QtGui``. + See: http://pyqt.sourceforge.net/Docs/PyQt5/pyqt4_differences.html#qtgui-module +""" +import warnings -"""This module exposes QtGui module""" +from . import PYQT5, PYQT4, PYSIDE, PYSIDE2, PythonQtError -from taurus.external.qt import API_NAME, _updateQtSubModule -__backend = API_NAME +if PYQT5: + from PyQt5.QtGui import * + # import * from QtWidgets and QtPrintSupport for PyQt4 style compat + from PyQt5.QtWidgets import * + from PyQt5.QtPrintSupport import * +elif PYSIDE2: + from PySide2.QtGui import * + # import * from QtWidgets and QtPrintSupport for PyQt4 style compat + from PySide2.QtWidgets import * + from PySide2.QtPrintSupport import * +elif PYQT4: + from PyQt4.QtGui import * -_updateQtSubModule(globals(), "QtGui") - -if __backend == 'PyQt5': - _updateQtSubModule(globals(), "QtWidgets") +elif PYSIDE: + from PySide.QtGui import * else: - # early import of qtpy.QtWidgets as a workaround for - # https://github.com/taurus-org/taurus/issues/401 - import qtpy.QtWidgets as __qtpy_QtWidgets - -del _updateQtSubModule, API_NAME + raise PythonQtError('No Qt bindings could be found') diff --git a/lib/taurus/external/qt/QtHelp.py b/lib/taurus/external/qt/QtHelp.py index ce467bf4b..233c9e024 100755 --- a/lib/taurus/external/qt/QtHelp.py +++ b/lib/taurus/external/qt/QtHelp.py @@ -1,32 +1,24 @@ # -*- coding: utf-8 -*- +# +# Copyright © 2009- The Spyder Development Team +# +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) -############################################################################## -## -## 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 . -## -############################################################################## +"""QtHelp Wrapper.""" -"""This module exposes QtHelp module""" +from . import PYQT5 +from . import PYQT4 +from . import PYSIDE +from . import PYSIDE2 -from taurus.external.qt import _updateQtSubModule +if PYQT5: + from PyQt5.QtHelp import * +elif PYSIDE2: + from PySide2.QtHelp import * +elif PYQT4: + from PyQt4.QtHelp import * +elif PYSIDE: + from PySide.QtHelp import * -_updateQtSubModule(globals(), "QtHelp") - -del _updateQtSubModule +del PYQT4, PYQT5, PYSIDE, PYSIDE2 diff --git a/lib/taurus/external/qt/QtNetwork.py b/lib/taurus/external/qt/QtNetwork.py index f807a320d..49faded79 100644 --- a/lib/taurus/external/qt/QtNetwork.py +++ b/lib/taurus/external/qt/QtNetwork.py @@ -1,32 +1,25 @@ # -*- coding: utf-8 -*- +# +# Copyright © 2014-2015 Colin Duquesnoy +# Copyright © 2009- The Spyder Development Team +# +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) -############################################################################## -## -## 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 . -## -############################################################################## +""" +Provides QtNetwork classes and functions. +""" -"""This module exposes QtNetwork module""" +from . import PYQT5, PYSIDE2, PYQT4, PYSIDE, PythonQtError -from taurus.external.qt import _updateQtSubModule -_updateQtSubModule(globals(), "QtNetwork") - -del _updateQtSubModule +if PYQT5: + from PyQt5.QtNetwork import * +elif PYSIDE2: + from PySide2.QtNetwork import * +elif PYQT4: + from PyQt4.QtNetwork import * +elif PYSIDE: + from PySide.QtNetwork import * +else: + raise PythonQtError('No Qt bindings could be found') diff --git a/lib/taurus/external/qt/QtSvg.py b/lib/taurus/external/qt/QtSvg.py index a806a57bb..edc075eac 100644 --- a/lib/taurus/external/qt/QtSvg.py +++ b/lib/taurus/external/qt/QtSvg.py @@ -1,32 +1,24 @@ # -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright © 2009- The Spyder Development Team +# +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) +# ----------------------------------------------------------------------------- +"""Provides QtSvg classes and functions.""" -############################################################################## -## -## 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 . -## -############################################################################## +# Local imports +from . import PYQT4, PYSIDE2, PYQT5, PYSIDE, PythonQtError -"""This module exposes QtSvg module""" +if PYQT5: + from PyQt5.QtSvg import * +elif PYSIDE2: + from PySide2.QtSvg import * +elif PYQT4: + from PyQt4.QtSvg import * +elif PYSIDE: + from PySide.QtSvg import * +else: + raise PythonQtError('No Qt bindings could be found') -from taurus.external.qt import _updateQtSubModule - -_updateQtSubModule(globals(), "QtSvg") - -del _updateQtSubModule +del PYQT4, PYQT5, PYSIDE, PYSIDE2 diff --git a/lib/taurus/external/qt/QtUiTools.py b/lib/taurus/external/qt/QtUiTools.py index 4360ca354..e3e1362ec 100644 --- a/lib/taurus/external/qt/QtUiTools.py +++ b/lib/taurus/external/qt/QtUiTools.py @@ -23,10 +23,21 @@ ## ############################################################################## -"""This module exposes QtUiTools module""" +"""This module exposes the QtUiTools module (deprecated in taurus). +It only makes sense when using PySide(2) (which were not supported before +taurus 4.5) +""" -from taurus.external.qt import _updateQtSubModule -_updateQtSubModule(globals(), "QtUiTools") +from . import PYSIDE, PYSIDE2, API_NAME +from taurus.core.util import log as __log -del _updateQtSubModule +__log.deprecated(dep="taurus.external.qt.QtUiTools", rel="4.5", + alt='PySide(2).QtUiTools or PyQt(4,5).loadUi') + +if PYSIDE2: + from PySide2.QtUiTools import * +elif PYSIDE: + from PySide.QtUiTools import * +else: + raise ImportError('QtUiTools not supported for {}'.format(API_NAME)) \ No newline at end of file diff --git a/lib/taurus/external/qt/QtWebKit.py b/lib/taurus/external/qt/QtWebKit.py index 2135721a2..37bdc8d4a 100644 --- a/lib/taurus/external/qt/QtWebKit.py +++ b/lib/taurus/external/qt/QtWebKit.py @@ -25,8 +25,20 @@ """This module exposes QtWebKit module""" -from taurus.external.qt import _updateQtSubModule +from . import PYQT5, PYQT4, PYSIDE, PYSIDE2, PythonQtError -_updateQtSubModule(globals(), "QtWebKit") -del _updateQtSubModule +if PYQT5: + from PyQt5.WebKit import * + # import * from QtWebkitWidgets for PyQt4 style compat + from PyQt5.WebKitWidgets import * +elif PYSIDE2: + from PySide2.WebKit import * + # import * from QtWebkitWidgets for PyQt4 style compat + from PySide2.WebKitWidgets import * +elif PYQT4: + from PyQt4.WebKit import * +elif PYSIDE: + from PySide.WebKit import * +else: + raise PythonQtError('No Qt bindings could be found') diff --git a/lib/taurus/external/qt/QtWidgets.py b/lib/taurus/external/qt/QtWidgets.py new file mode 100644 index 000000000..6ec94ada2 --- /dev/null +++ b/lib/taurus/external/qt/QtWidgets.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# +# Copyright © 2014-2015 Colin Duquesnoy +# Copyright © 2009- The Spyder Developmet Team +# +# Licensed under the terms of the MIT License +# (see LICENSE.txt for details) + +""" +Provides widget classes and functions. +.. warning:: Only PyQt4/PySide QtGui classes compatible with PyQt5.QtWidgets + are exposed here. Therefore, you need to treat/use this package as if it + were the ``PyQt5.QtWidgets`` module. +""" + +from . import PYQT5, PYSIDE2, PYQT4, PYSIDE, PythonQtError +from taurus.core.util import log as __log + +if PYQT5: + from PyQt5.QtWidgets import * +elif PYSIDE2: + from PySide2.QtWidgets import * +elif PYQT4: + __log.warning('Using QtWidgets with PyQt4 is not supported and may fail ' + + 'in many cases. Use at your own risk ' + + '(or use a Qt5 binding)') + from PyQt4.QtGui import * + QStyleOptionViewItem = QStyleOptionViewItemV4 + del QStyleOptionViewItemV4 + + # These objects belong to QtGui + try: + # Older versions of PyQt4 do not provide these + del (QGlyphRun, + QMatrix2x2, QMatrix2x3, QMatrix2x4, QMatrix3x2, QMatrix3x3, + QMatrix3x4, QMatrix4x2, QMatrix4x3, QMatrix4x4, + QQuaternion, QRadialGradient, QRawFont, QRegExpValidator, + QStaticText, QTouchEvent, QVector2D, QVector3D, QVector4D, + qFuzzyCompare) + except NameError: + pass + del (QAbstractTextDocumentLayout, QActionEvent, QBitmap, QBrush, QClipboard, + QCloseEvent, QColor, QConicalGradient, QContextMenuEvent, QCursor, + QDesktopServices, QDoubleValidator, QDrag, QDragEnterEvent, + QDragLeaveEvent, QDragMoveEvent, QDropEvent, QFileOpenEvent, + QFocusEvent, QFont, QFontDatabase, QFontInfo, QFontMetrics, + QFontMetricsF, QGradient, QHelpEvent, QHideEvent, + QHoverEvent, QIcon, QIconDragEvent, QIconEngine, QImage, + QImageIOHandler, QImageReader, QImageWriter, QInputEvent, + QInputMethodEvent, QKeyEvent, QKeySequence, QLinearGradient, + QMouseEvent, QMoveEvent, QMovie, QPaintDevice, QPaintEngine, + QPaintEngineState, QPaintEvent, QPainter, QPainterPath, + QPainterPathStroker, QPalette, QPen, QPicture, QPictureIO, QPixmap, + QPixmapCache, QPolygon, QPolygonF, + QRegion, QResizeEvent, QSessionManager, QShortcutEvent, QShowEvent, + QStandardItem, QStandardItemModel, QStatusTipEvent, + QSyntaxHighlighter, QTabletEvent, QTextBlock, QTextBlockFormat, + QTextBlockGroup, QTextBlockUserData, QTextCharFormat, QTextCursor, + QTextDocument, QTextDocumentFragment, QTextDocumentWriter, + QTextFormat, QTextFragment, QTextFrame, QTextFrameFormat, + QTextImageFormat, QTextInlineObject, QTextItem, QTextLayout, + QTextLength, QTextLine, QTextList, QTextListFormat, QTextObject, + QTextObjectInterface, QTextOption, QTextTable, QTextTableCell, + QTextTableCellFormat, QTextTableFormat, QTransform, + QValidator, QWhatsThisClickedEvent, + QWheelEvent, QWindowStateChangeEvent, qAlpha, qBlue, + qGray, qGreen, qIsGray, qRed, qRgb, qRgba, QIntValidator, + QStringListModel) + + # These objects belong to QtPrintSupport + del (QAbstractPrintDialog, QPageSetupDialog, QPrintDialog, QPrintEngine, + QPrintPreviewDialog, QPrintPreviewWidget, QPrinter, QPrinterInfo) + + # These objects belong to QtCore + del (QItemSelection, QItemSelectionModel, QItemSelectionRange, + QSortFilterProxyModel) + + # Patch QComboBox to allow Python objects to be passed to userData + # patch_qcombobox(QComboBox) # We don't do it to avoid changing behaviour + + # QHeaderView: renamed methods + QHeaderView.sectionsClickable = QHeaderView.isClickable + QHeaderView.sectionsMovable = QHeaderView.isMovable + QHeaderView.sectionResizeMode = QHeaderView.resizeMode + QHeaderView.setSectionsClickable = QHeaderView.setClickable + QHeaderView.setSectionsMovable = QHeaderView.setMovable + QHeaderView.setSectionResizeMode = QHeaderView.setResizeMode + +elif PYSIDE: + __log.warning('Using QtWidgets with PySide is not supported and may fail ' + + 'in many cases. Use at your own risk ' + + '(or use a Qt5 binding)') + from PySide.QtGui import * + QStyleOptionViewItem = QStyleOptionViewItemV4 + del QStyleOptionViewItemV4 + + # These objects belong to QtGui + del (QAbstractTextDocumentLayout, QActionEvent, QBitmap, QBrush, QClipboard, + QCloseEvent, QColor, QConicalGradient, QContextMenuEvent, QCursor, + QDesktopServices, QDoubleValidator, QDrag, QDragEnterEvent, + QDragLeaveEvent, QDragMoveEvent, QDropEvent, QFileOpenEvent, + QFocusEvent, QFont, QFontDatabase, QFontInfo, QFontMetrics, + QFontMetricsF, QGradient, QHelpEvent, QHideEvent, + QHoverEvent, QIcon, QIconDragEvent, QIconEngine, QImage, + QImageIOHandler, QImageReader, QImageWriter, QInputEvent, + QInputMethodEvent, QKeyEvent, QKeySequence, QLinearGradient, + QMatrix2x2, QMatrix2x3, QMatrix2x4, QMatrix3x2, QMatrix3x3, + QMatrix3x4, QMatrix4x2, QMatrix4x3, QMatrix4x4, QMouseEvent, + QMoveEvent, QMovie, QPaintDevice, QPaintEngine, QPaintEngineState, + QPaintEvent, QPainter, QPainterPath, QPainterPathStroker, QPalette, + QPen, QPicture, QPictureIO, QPixmap, QPixmapCache, QPolygon, + QPolygonF, QQuaternion, QRadialGradient, QRegExpValidator, + QRegion, QResizeEvent, QSessionManager, QShortcutEvent, QShowEvent, + QStandardItem, QStandardItemModel, QStatusTipEvent, + QSyntaxHighlighter, QTabletEvent, QTextBlock, QTextBlockFormat, + QTextBlockGroup, QTextBlockUserData, QTextCharFormat, QTextCursor, + QTextDocument, QTextDocumentFragment, + QTextFormat, QTextFragment, QTextFrame, QTextFrameFormat, + QTextImageFormat, QTextInlineObject, QTextItem, QTextLayout, + QTextLength, QTextLine, QTextList, QTextListFormat, QTextObject, + QTextObjectInterface, QTextOption, QTextTable, QTextTableCell, + QTextTableCellFormat, QTextTableFormat, QTouchEvent, QTransform, + QValidator, QVector2D, QVector3D, QVector4D, QWhatsThisClickedEvent, + QWheelEvent, QWindowStateChangeEvent, qAlpha, qBlue, qGray, qGreen, + qIsGray, qRed, qRgb, qRgba, QIntValidator, QStringListModel) + + # These objects belong to QtPrintSupport + del (QAbstractPrintDialog, QPageSetupDialog, QPrintDialog, QPrintEngine, + QPrintPreviewDialog, QPrintPreviewWidget, QPrinter, QPrinterInfo) + + # These objects belong to QtCore + del (QItemSelection, QItemSelectionModel, QItemSelectionRange, + QSortFilterProxyModel) + + # Patch QComboBox to allow Python objects to be passed to userData + # patch_qcombobox(QComboBox) # We don't do it to avoid changing behaviour + + # QHeaderView: renamed methods + QHeaderView.sectionsClickable = QHeaderView.isClickable + QHeaderView.sectionsMovable = QHeaderView.isMovable + QHeaderView.sectionResizeMode = QHeaderView.resizeMode + QHeaderView.setSectionsClickable = QHeaderView.setClickable + QHeaderView.setSectionsMovable = QHeaderView.setMovable + QHeaderView.setSectionResizeMode = QHeaderView.setResizeMode + +else: + raise PythonQtError('No Qt bindings could be found') diff --git a/lib/taurus/external/qt/Qwt5.py b/lib/taurus/external/qt/Qwt5.py index aba6c6901..0345fa8b5 100644 --- a/lib/taurus/external/qt/Qwt5.py +++ b/lib/taurus/external/qt/Qwt5.py @@ -25,8 +25,13 @@ """This module exposes Qwt5 module""" -from taurus.external.qt import _updateQtSubModule -_updateQtSubModule(globals(), "Qwt5") +from . import PYQT4, API_NAME +from taurus.core.util import log as __log -del _updateQtSubModule + +if PYQT4: + __log.deprecated(dep="taurus.external.qt.Qwt5", rel="4.5") + from PyQt4.Qwt5 import * +else: + raise ImportError('Qwt5 bindings not supported for {}'.format(API_NAME)) \ No newline at end of file diff --git a/lib/taurus/external/qt/__init__.py b/lib/taurus/external/qt/__init__.py index 5404ad295..ee38d171b 100644 --- a/lib/taurus/external/qt/__init__.py +++ b/lib/taurus/external/qt/__init__.py @@ -26,40 +26,20 @@ """This module exposes PyQt4/PyQt5/PySide module""" __all__ = ["initialize", "API_NAME", - "getQtName", "getQt", "_updateQtSubModule", "requires"] + "getQtName", "getQt", "requires"] import os +import sys +import platform +from distutils.version import LooseVersion from taurus.core.util import log as __log from taurus import tauruscustomsettings as __config -# -------------------------------------------------------------------------- -# Deprecated (in Jul17) pending to be removed later on - -def getQtName(name=None, strict=True): - __log.deprecated (dep='taurus.external.qt.getQtName', - alt='taurus.external.qt.API_NAME', rel='4.0.4') - return API_NAME - - -def initialize(name=None, strict=True, logging=True, - resources=True, remove_inputhook=True): - __log.deprecated (dep='taurus.external.qt.initialize', rel='4.0.4') - return getQt() - -def requires(origin=None, exc_class=ImportError, **kwargs): - __log.deprecated (dep='taurus.external.qt.requires', rel='4.0.4') - return True - - -# Handle rename of DEFAULT_QT_AUTO_API --> DEFAULT_QT_API -if hasattr(__config, 'DEFAULT_QT_AUTO_API'): - __log.deprecated(dep='DEFAULT_QT_AUTO_API', alt='DEFAULT_QT_API', - rel='4.0.4') - if not hasattr(__config, 'DEFAULT_QT_API'): - __config.DEFAULT_QT_API = __config.DEFAULT_QT_AUTO_API +class PythonQtError(RuntimeError): + """Error raise if no bindings could be selected.""" + pass -# -------------------------------------------------------------------------- #: Qt API environment variable name QT_API = 'QT_API' @@ -73,126 +53,171 @@ def requires(origin=None, exc_class=ImportError, **kwargs): #: names of the expected PySide api PYSIDE_API = ['pyside'] -os.environ.setdefault(QT_API, getattr(__config, 'DEFAULT_QT_API', 'pyqt')) -API = os.environ[QT_API].lower() -assert API in (PYQT5_API + PYQT4_API + PYSIDE_API) +#: names of the expected PySide2 api +PYSIDE2_API = ['pyside2'] + -is_old_pyqt = is_pyqt46 = False PYQT5 = True -PYQT4 = PYSIDE = False +PYQT4 = PYSIDE = PYSIDE2 = False + +if 'PyQt5' in sys.modules: + API = 'pyqt5' +elif 'PySide2' in sys.modules: + API = 'pyside2' +elif 'PyQt4' in sys.modules: + API = 'pyqt4' +elif 'PySide' in sys.modules: + API = 'pyside' +else: + # if no binding is already loaded, use (in this order): + # - QT_API environment variable + # - tauruscustomsettings.DEFAULT_QT_API + # - 'pyqt5' + API = os.environ.get(QT_API, getattr(__config, 'DEFAULT_QT_API', 'pyqt')) + API = API.lower() + +assert API in (PYQT5_API + PYQT4_API + PYSIDE_API + PYSIDE2_API) +if API in PYQT4_API: + try: + import sip -class PythonQtError(Exception): - """Error raise if no bindings could be selected""" - pass + sip.setapi('QString', 2) + sip.setapi('QVariant', 2) + sip.setapi('QDate', 2) + sip.setapi('QDateTime', 2) + sip.setapi('QTextStream', 2) + sip.setapi('QTime', 2) + sip.setapi('QUrl', 2) + from PyQt4.QtCore import PYQT_VERSION_STR as PYQT_VERSION # analysis:ignore + from PyQt4.QtCore import QT_VERSION_STR as QT_VERSION # analysis:ignore + PYSIDE_VERSION = None + PYQT5 = False + PYQT4 = True + API = os.environ['QT_API'] = 'pyqt' # in case the original was "pyqt4" + except ImportError: + __log.debug('Cannot import PyQt4. Trying with PyQt5') + API = os.environ['QT_API'] = 'pyqt5' if API in PYQT5_API: try: - from PyQt5.Qt import PYQT_VERSION_STR as PYQT_VERSION # analysis:ignore - from PyQt5.Qt import QT_VERSION_STR as QT_VERSION # analysis:ignore + from PyQt5.QtCore import PYQT_VERSION_STR as PYQT_VERSION + from PyQt5.QtCore import QT_VERSION_STR as QT_VERSION + PYSIDE_VERSION = None + + if sys.platform == 'darwin': + macos_version = LooseVersion(platform.mac_ver()[0]) + if macos_version < LooseVersion('10.10'): + if LooseVersion(QT_VERSION) >= LooseVersion('5.9'): + raise PythonQtError("Qt 5.9 or higher only works in " + + "macOS 10.10 or higher. Your " + + "program will fail in this " + + "system.") + elif macos_version < LooseVersion('10.11'): + if LooseVersion(QT_VERSION) >= LooseVersion('5.11'): + raise PythonQtError("Qt 5.11 or higher only works in " + + "macOS 10.11 or higher. Your " + + "program will fail in this " + + "system.") + + del macos_version except ImportError: - API = os.environ['QT_API'] = 'pyqt' + __log.debug('Cannot import PyQt5. Trying with PySide2') + API = os.environ['QT_API'] = 'pyside2' -if API in PYQT4_API: +if API in PYSIDE2_API: try: - import sip - try: - sip.setapi('QString', 2) - sip.setapi('QVariant', 2) - sip.setapi('QDate', 2) - sip.setapi('QDateTime', 2) - sip.setapi('QTextStream', 2) - sip.setapi('QTime', 2) - sip.setapi('QUrl', 2) - except AttributeError: - # PyQt < v4.6 - pass - from PyQt4.Qt import PYQT_VERSION_STR as PYQT_VERSION # analysis:ignore - from PyQt4.Qt import QT_VERSION_STR as QT_VERSION # analysis:ignore - PYSIDE_VERSION = None + from PySide2 import __version__ as PYSIDE_VERSION # analysis:ignore + from PySide2.QtCore import __version__ as QT_VERSION # analysis:ignore + + PYQT_VERSION = None PYQT5 = False - PYQT4 = True - API = os.environ['QT_API'] = 'pyqt' # in case the original was "pyqt4" + PYSIDE2 = True + + if sys.platform == 'darwin': + macos_version = LooseVersion(platform.mac_ver()[0]) + if macos_version < LooseVersion('10.11'): + if LooseVersion(QT_VERSION) >= LooseVersion('5.11'): + raise PythonQtError("Qt 5.11 or higher only works in " + + "macOS 10.11 or higher. Your " + + "program will fail in this " + + "system.") + + del macos_version except ImportError: + __log.debug('Cannot import PyQt5. Trying with PySide') API = os.environ['QT_API'] = 'pyside' - else: - is_old_pyqt = PYQT_VERSION.startswith(('4.4', '4.5', '4.6', '4.7')) - is_pyqt46 = PYQT_VERSION.startswith('4.6') if API in PYSIDE_API: try: from PySide import __version__ as PYSIDE_VERSION # analysis:ignore from PySide.QtCore import __version__ as QT_VERSION # analysis:ignore + PYQT_VERSION = None PYQT5 = False PYSIDE = True except ImportError: + __log.debug('Cannot import PySide') raise PythonQtError('No Qt bindings could be found') API_NAME = {'pyqt5': 'PyQt5', 'pyqt': 'PyQt4', 'pyqt4': 'PyQt4', - 'pyside': 'PySide'}[API] + 'pyside': 'PySide', 'pyside2': 'PySide2'}[API] + +# Update the environment so that other libraries that also use the same +# convention (such as guidata or spyder) do a consistent choice +os.environ['QT_API'] = API def __initializeQtLogging(): - # from . import QtCore - QtCore = __importQt ('QtCore') + from importlib import import_module + QtCore = import_module(API_NAME + '.QtCore') QT_LEVEL_MATCHER = { - QtCore.QtDebugMsg: __log.debug, - QtCore.QtWarningMsg: __log.warning, - QtCore.QtCriticalMsg: __log.critical, - QtCore.QtFatalMsg: __log.fatal, - QtCore.QtSystemMsg: __log.critical, + QtCore.QtDebugMsg: __log.debug, + QtCore.QtWarningMsg: __log.warning, + QtCore.QtCriticalMsg: __log.critical, + QtCore.QtFatalMsg: __log.fatal, + QtCore.QtSystemMsg: __log.critical, } if hasattr(QtCore, "qInstallMessageHandler"): + # Qt5 def taurusMessageHandler(msg_type, log_ctx, msg): f = QT_LEVEL_MATCHER.get(msg_type) - return f("Qt%s %s.%s[%s]: %a", log_ctx.category, log_ctx.file, + return f("Qt%s %s.%s[%s]: %s", log_ctx.category, log_ctx.file, log_ctx.function, log_ctx.line, msg) + QtCore.qInstallMessageHandler(taurusMessageHandler) elif hasattr(QtCore, "qInstallMsgHandler"): + # Qt4 def taurusMsgHandler(msg_type, msg): f = QT_LEVEL_MATCHER.get(msg_type) return f("Qt: " + msg) - QtCore.qInstallMsgHandler(taurusMsgHandler) + QtCore.qInstallMsgHandler(taurusMsgHandler) def __removePyQtInputHook(): - try: - from . import QtCore + from importlib import import_module + QtCore = import_module(API_NAME + '.QtCore') + if hasattr(QtCore, "pyqtRemoveInputHook"): QtCore.pyqtRemoveInputHook() - except AttributeError: - pass - - -def _updateQtSubModule(glob_dict, qt_sub_module_name): - glob_dict.update(__importQt(qt_sub_module_name).__dict__) -def __import(name): - import sys - __import__(name) - return sys.modules[name] +def __addExceptHook(): + """ + Since PyQt 5.5 , unhandled python exceptions cause the application to + abort: -def __importQt(name): - return __import(API_NAME + "." + name) + http://pyqt.sf.net/Docs/PyQt5/incompatibilities.html#unhandled-python-exceptions - -def getQt(name=None, strict=True): - __log.deprecated (dep='taurus.external.qt.getQt', rel='4.0.4') - if PYQT5: - import PyQt5 as _qt - elif PYQT4: - import PyQt4 as _qt - elif PYSIDE: - import PySide as _qt - else: - raise ImportError("No suitable Qt found") - return _qt + By calling __addExceptHook, we restore the old behaviour (just print the + exception trace). + """ + import traceback + sys.excepthook = traceback.print_exception if getattr(__config, 'QT_AUTO_INIT_LOG', True): @@ -201,5 +226,48 @@ def getQt(name=None, strict=True): if getattr(__config, 'QT_AUTO_REMOVE_INPUTHOOK', True): __removePyQtInputHook() +if PYQT5 and getattr(__config, 'QT_AVOID_ABORT_ON_EXCEPTION', True): + # TODO: check if we also want to do this for PySide(2) + __addExceptHook() -__log.info('Using "%s" for Qt', API_NAME) +__log.info('Using %s (v%s , with Qt %s)', + API_NAME, + PYQT_VERSION or PYSIDE_VERSION, + QT_VERSION) + + +# -------------------------------------------------------------------------- +# Deprecated (in Jul17) pending to be removed later on + + +def getQt(name=None, strict=True): + __log.deprecated(dep='taurus.external.qt.getQt', rel='4.0.4') + from importlib import import_module + return import_module(API_NAME) + + +def getQtName(name=None, strict=True): + __log.deprecated(dep='taurus.external.qt.getQtName', + alt='taurus.external.qt.API_NAME', rel='4.0.4') + return API_NAME + + +def initialize(name=None, strict=True, logging=True, + resources=True, remove_inputhook=True): + __log.deprecated(dep='taurus.external.qt.initialize', rel='4.0.4') + return getQt() + + +def requires(origin=None, exc_class=ImportError, **kwargs): + __log.deprecated(dep='taurus.external.qt.requires', rel='4.0.4') + return True + + +# Handle rename of DEFAULT_QT_AUTO_API --> DEFAULT_QT_API +if hasattr(__config, 'DEFAULT_QT_AUTO_API'): + __log.deprecated(dep='DEFAULT_QT_AUTO_API', alt='DEFAULT_QT_API', + rel='4.0.4') + if not hasattr(__config, 'DEFAULT_QT_API'): + __config.DEFAULT_QT_API = __config.DEFAULT_QT_AUTO_API + +# -------------------------------------------------------------------------- diff --git a/lib/taurus/external/qt/compat.py b/lib/taurus/external/qt/compat.py new file mode 100755 index 000000000..735c207f6 --- /dev/null +++ b/lib/taurus/external/qt/compat.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +############################################################################## +## +## 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 utilities to smooth differences between different +Qt APIs +""" + +# provide substitutes for QFileDialog's getSaveFileName, getOpenFileName +# and getOpenFileNames that return the selected filter regardless of Qt API +from taurus.external.qt.Qt import QFileDialog + +getSaveFileName = getattr(QFileDialog, 'getSaveFileNameAndFilter', + QFileDialog.getSaveFileName) + +getOpenFileName = getattr(QFileDialog, 'getOpenFileNameAndFilter', + QFileDialog.getOpenFileName) + +getOpenFileNames = getattr(QFileDialog, 'getOpenFileNamesAndFilter', + QFileDialog.getOpenFileNames) + +del QFileDialog \ No newline at end of file diff --git a/lib/taurus/external/qt/uic.py b/lib/taurus/external/qt/uic.py index 981ee8e5e..6e97f4e8a 100644 --- a/lib/taurus/external/qt/uic.py +++ b/lib/taurus/external/qt/uic.py @@ -1,136 +1,224 @@ -# -*- coding: utf-8 -*- - -############################################################################## -## -## 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 exposes PyQt4/PSide uic module""" - -from taurus.core.util import log -from taurus.external.qt import API_NAME - -__backend = API_NAME - -if __backend == 'PyQt4': +# In PySide, loadUi does not exist, so we define it using QUiLoader, and +# then make sure we expose that function. This is adapted from qt-helpers +# which was released under a 3-clause BSD license: +# qt-helpers - a common front-end to various Qt modules +# +# Copyright (c) 2015, Chris Beaumont and Thomas Robitaille +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# * Neither the name of the Glue project nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Which itself was based on the solution at +# +# https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8 +# +# which was released under the MIT license: +# +# Copyright (c) 2011 Sebastian Wiesner +# Modifications by Charl Botha +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from . import PYSIDE, PYSIDE2, PYQT4, PYQT5 + +if PYQT5: + + from PyQt5.uic import * + +elif PYQT4: + from PyQt4.uic import * - from PyQt4.uic import uiparser - from PyQt4.uic import properties - # prevent ui parser from logging debug messages - class __IgnoreCalls: - def __call__(self, *args, **kwargs): - pass +else: - uiparser.DEBUG = __IgnoreCalls() - properties.DEBUG = __IgnoreCalls() + __all__ = ['loadUi'] - def loadUI(uiFilename, parent=None): - newWidget = loadUi(uiFilename) - newWidget.setParent(parent) - return newWidget + if PYSIDE: + from PySide.QtCore import QMetaObject + from PySide.QtUiTools import QUiLoader + elif PYSIDE2: + from PySide2.QtCore import QMetaObject + from PySide2.QtUiTools import QUiLoader -elif __backend == 'PyQt5': - from PyQt5.uic import * - from PyQt5.uic import uiparser - from PyQt5.uic import properties - - # prevent ui parser from logging debug messages - class __IgnoreCalls: - def __call__(self, *args, **kwargs): - pass - - uiparser.DEBUG = __IgnoreCalls() - properties.DEBUG = __IgnoreCalls() - - def loadUI(uiFilename, parent=None): - newWidget = loadUi(uiFilename) - newWidget.setParent(parent) - return newWidget - -elif __backend == 'PySide': - from . import QtCore as __QtCore - from . import QtUiTools as __QtUiTools - - __uiLoader = None - class UiLoader(__QtUiTools.QUiLoader): - def __init__(self): - super(UiLoader, self).__init__() - self._rootWidget = None - - def createWidget(self, className, parent=None, name=''): - widget = super(UiLoader, self).createWidget( - className, parent, name) - - if name: - if self._rootWidget is None: - self._rootWidget = widget - elif not hasattr(self._rootWidget, name): - setattr(self._rootWidget, name, widget) - else: - log.error("Qt: Name collision! Ignoring second " - "occurrance of %r.", name) + class UiLoader(QUiLoader): + """ + Subclass of :class:`~PySide.QtUiTools.QUiLoader` to create the user + interface in a base instance. + + Unlike :class:`~PySide.QtUiTools.QUiLoader` itself this class does not + create a new instance of the top-level widget, but creates the user + interface in an existing instance of the top-level class if needed. + + This mimics the behaviour of :func:`PyQt4.uic.loadUi`. + """ + + def __init__(self, baseinstance, customWidgets=None): + """ + Create a loader for the given ``baseinstance``. + + The user interface is created in ``baseinstance``, which must be an + instance of the top-level class in the user interface to load, or a + subclass thereof. + + ``customWidgets`` is a dictionary mapping from class name to class + object for custom widgets. Usually, this should be done by calling + registerCustomWidget on the QUiLoader, but with PySide 1.1.2 on + Ubuntu 12.04 x86_64 this causes a segfault. + + ``parent`` is the parent object of this loader. + """ + + QUiLoader.__init__(self, baseinstance) + + self.baseinstance = baseinstance + + if customWidgets is None: + self.customWidgets = {} + else: + self.customWidgets = customWidgets + + def createWidget(self, class_name, parent=None, name=''): + """ + Function that is called for each widget defined in ui file, + overridden here to populate baseinstance instead. + """ + + if parent is None and self.baseinstance: + # supposed to create the top-level widget, return the base + # instance instead + return self.baseinstance + + else: + + # For some reason, Line is not in the list of available + # widgets, but works fine, so we have to special case it here. + if class_name in self.availableWidgets() or class_name == 'Line': + # create a new widget for child widgets + widget = QUiLoader.createWidget(self, class_name, parent, name) - if parent is not None: - setattr(parent, name, widget) else: - # Sadly, we can't reparent it to self, since QUiLoader - # isn't a QWidget. - log.error("Qt: No parent specified! This will probably " - "crash due to C++ object deletion.") + # If not in the list of availableWidgets, must be a custom + # widget. This will raise KeyError if the user has not + # supplied the relevant class_name in the dictionary or if + # customWidgets is empty. + try: + widget = self.customWidgets[class_name](parent) + except KeyError: + raise Exception('No custom widget ' + class_name + ' ' + 'found in customWidgets') + + if self.baseinstance: + # set an attribute for the new child widget on the base + # instance, just like PyQt4.uic.loadUi does. + setattr(self.baseinstance, name, widget) + + return widget + + def _get_custom_widgets(ui_file): + """ + This function is used to parse a ui file and look for the + section, then automatically load all the custom widget classes. + """ + + import sys + import importlib + from xml.etree.ElementTree import ElementTree + + # Parse the UI file + etree = ElementTree() + ui = etree.parse(ui_file) + + # Get the customwidgets section + custom_widgets = ui.find('customwidgets') + + if custom_widgets is None: + return {} + + custom_widget_classes = {} + + for custom_widget in custom_widgets.getchildren(): + + cw_class = custom_widget.find('class').text + cw_header = custom_widget.find('header').text - return widget + module = importlib.import_module(cw_header) - def load(self, fileOrName, parentWidget=None): - if self._rootWidget is not None: - raise Exception("UiLoader is already started loading UI!") + custom_widget_classes[cw_class] = getattr(module, cw_class) - widget = super(UiLoader, self).load(fileOrName, parentWidget) + return custom_widget_classes - if widget != self._rootWidget: - log.error("Qt: Returned widget isn't the root widget... ") + def loadUi(uifile, baseinstance=None, workingDirectory=None): + """ + Dynamically load a user interface from the given ``uifile``. - self._rootWidget = None - return widget + ``uifile`` is a string containing a file name of the UI file to load. - def loadUI(uiFilename, parent=None): - global __uiLoader - if __uiLoader is None: - __uiLoader = UiLoader() + If ``baseinstance`` is ``None``, the a new instance of the top-level + widget will be created. Otherwise, the user interface is created within + the given ``baseinstance``. In this case ``baseinstance`` must be an + instance of the top-level widget class in the UI file to load, or a + subclass thereof. In other words, if you've created a ``QMainWindow`` + interface in the designer, ``baseinstance`` must be a ``QMainWindow`` + or a subclass thereof, too. You cannot load a ``QMainWindow`` UI file + with a plain :class:`~PySide.QtGui.QWidget` as ``baseinstance``. - uiFile = __QtCore.QFile(uiFilename, parent) - if not uiFile.open(__QtCore.QIODevice.ReadOnly): - log.error("Qt: Couldn't open file %r!", uiFilename) - return None + :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on + the created user interface, so you can implemented your slots according + to its conventions in your widget class. - try: - return __uiLoader.load(uiFile, parent) + Return ``baseinstance``, if ``baseinstance`` is not ``None``. Otherwise + return the newly created instance of the user interface. + """ - except: - log.exception("Exception loading UI from %r!", uiFilename) + # We parse the UI file and import any required custom widgets + customWidgets = _get_custom_widgets(uifile) - finally: - uiFile.close() - uiFile.deleteLater() + loader = UiLoader(baseinstance, customWidgets) - return None + if workingDirectory is not None: + loader.setWorkingDirectory(workingDirectory) -del API_NAME + widget = loader.load(uifile) + QMetaObject.connectSlotsByName(widget) + return widget diff --git a/lib/taurus/external/test/test_qt.py b/lib/taurus/external/test/test_qt.py index 979e05aa1..20024a10f 100644 --- a/lib/taurus/external/test/test_qt.py +++ b/lib/taurus/external/test/test_qt.py @@ -26,11 +26,8 @@ import sys import taurus -from taurus import tauruscustomsettings import unittest -_QtAPIs = ["PyQt4", "PySide", "PyQt5"] - def _import(name): __import__(name) @@ -39,18 +36,11 @@ def _import(name): class QtTestCase(unittest.TestCase): - QtAPI = None + _api_name = None def setUp(self): taurus.setLogLevel(taurus.Critical) - for qt in _QtAPIs: - if qt in sys.modules: - self.QtAPI = qt - break - else: - self.QtAPI = "PyQt4" - self.opt_mods = ("QtDesigner", "QtNetwork", "Qt", "QtSvg", "QtUiTools", "QtWebKit", "Qwt5", "uic") @@ -58,57 +48,61 @@ def setUp(self): self._orig_mods = set(sys.modules.keys()) # this import initializes Qt in case it is not loaded - from taurus.external.qt import Qt + from taurus.external.qt import Qt, API_NAME + + self._api_name = API_NAME self.__qt = Qt def test_qt_base_import(self): mods = set(sys.modules.keys()) - other_apis = set(_QtAPIs) - other_apis.remove(self.QtAPI) + other_apis = set(('PyQt5', 'PySide2', 'PyQt4', 'PySide')) + other_apis.remove(self._api_name) - from taurus.external.qt import API_NAME - - self.assertEquals(API_NAME, self.QtAPI) + # the selected API and the QtCore should be loaded + self.assertTrue(self._api_name in mods, self._api_name + " not loaded") + self.assertTrue(self._api_name + ".QtCore" in mods, + "QtCore not loaded") + # the other APIs should *not* be loaded for other_api in other_apis: - self.assertFalse(other_api in mods, other_api + " loaded in " + self.QtAPI + " test") - - self.assertTrue(self.QtAPI in mods, self.QtAPI + " not loaded") - self.assertTrue(self.QtAPI + ".QtCore" in mods, "QtCore not loaded") + self.assertFalse( + other_api in mods, + other_api + " loaded in " + self._api_name + " test") + # the other Qt submodules should *not* be loaded for opt_mod in self.opt_mods: - mod = "{0}.{1}".format(self.QtAPI, opt_mod) - self.assertFalse(mod in mods-self._orig_mods, mod + " is loaded") + mod = "{0}.{1}".format(self._api_name, opt_mod) + self.assertFalse(mod in mods - self._orig_mods, mod + " is loaded") def __test_qt_module(self, qt_mod_name): + """Checks that the given shim is complete""" taurus_qt_mod_name = "taurus.external.qt.{0}".format(qt_mod_name) - orig_qt_mod_name = "{0}.{1}".format(self.QtAPI, qt_mod_name) + orig_qt_mod_name = "{0}.{1}".format(self._api_name, qt_mod_name) TaurusQtMod = _import(taurus_qt_mod_name) OrigQtMod = _import(orig_qt_mod_name) - taurus_qt_mod_members = [ m for m in dir(TaurusQtMod) if not m.startswith("_") ] - orig_qt_mod_members = [ m for m in dir(OrigQtMod) if not m.startswith("_") ] + taurus_qt_mod_members = [m for m in dir(TaurusQtMod) + if not m.startswith("_")] + orig_qt_mod_members = [m for m in dir(OrigQtMod) + if not m.startswith("_")] for orig_member_name in orig_qt_mod_members: - self.assertTrue(orig_member_name in taurus_qt_mod_members, - "Taurus {0} does not contain {1}".format(qt_mod_name, orig_member_name)) + self.assertTrue( + orig_member_name in taurus_qt_mod_members, + "Taurus {0} does not contain {1}".format(qt_mod_name, + orig_member_name) + ) def test_qt_core(self): + """Check the QtCore shim""" return self.__test_qt_module("QtCore") def test_qt_gui(self): + """Check the QtGui shim""" return self.__test_qt_module("QtGui") - def test_icons(self): - '''check that theme icons work''' - from taurus.external.qt import Qt - icon = Qt.QIcon.fromTheme("folder-open") - msg = ('Theme icons not available ' + - '(if PyQt<4.6, make sure to build resources first!)') - self.assertFalse(icon.isNull(), msg) - -def main(): +def main(): unittest.main(verbosity=2) diff --git a/lib/taurus/qt/qtcore/communication/communication.py b/lib/taurus/qt/qtcore/communication/communication.py index 17072422a..e77715222 100644 --- a/lib/taurus/qt/qtcore/communication/communication.py +++ b/lib/taurus/qt/qtcore/communication/communication.py @@ -27,6 +27,8 @@ comunications.py: """ +from __future__ import print_function + from taurus.external.qt import QtCore import weakref @@ -327,16 +329,16 @@ def activeDataUIDs(self): :returns: (list) UIDs of currently shared data. ''' - return self.__models.keys() + return list(self.__models.keys()) def debugReader(self, data): ''' A slot which you can connect as a reader for debugging. It will print info to the stdout ''' - print "SharedDataManager: \n\tSender=: %s\n\tData=%s" % (self.sender(), repr(data)) + print("SharedDataManager: \n\tSender=: %s\n\tData=%s" % (self.sender(), repr(data))) def info(self): s = "" - for uid, m in sorted(self.__models.iteritems()): + for uid, m in sorted(self.__models.items()): s += m.info() + '\n' return s diff --git a/lib/taurus/qt/qtcore/configuration/configuration.py b/lib/taurus/qt/qtcore/configuration/configuration.py index 7e8979d0f..cee021bca 100644 --- a/lib/taurus/qt/qtcore/configuration/configuration.py +++ b/lib/taurus/qt/qtcore/configuration/configuration.py @@ -25,13 +25,20 @@ """This module provides the set of base classes designed to provide configuration features to the classes that inherit from them""" +from __future__ import print_function + +from future import standard_library +standard_library.install_aliases() +from builtins import str +from builtins import object +from future.utils import string_types __all__ = ["configurableProperty", "BaseConfigurableClass"] __docformat__ = 'restructuredtext' -class configurableProperty: +class configurableProperty(object): '''A dummy class used to handle properties with the configuration API .. warning:: this class is intended for internal use by the configuration @@ -47,7 +54,7 @@ def __init__(self, name, fget, fset, obj=None): def createConfig(self, allowUnpickable=False): '''returns value returned by the fget function of this property. the allowUnpickable parameter is ignored''' - if isinstance(self.fget, basestring): # fget is not a method but a method name... + if isinstance(self.fget, string_types): # fget is not a method but a method name... result = getattr(self._obj, self.fget)() else: result = self.fget() @@ -55,7 +62,7 @@ def createConfig(self, allowUnpickable=False): def applyConfig(self, value, depth=-1): '''calls the fset function for this property with the given value. The depth parameter is ignored''' - if isinstance(self.fget, basestring): # fget is not a method but a method name... + if isinstance(self.fget, string_types): # fget is not a method but a method name... getattr(self._obj, self.fset)(value) else: self.fset(value) @@ -65,7 +72,7 @@ def objectName(self): return self.name -class BaseConfigurableClass: +class BaseConfigurableClass(object): ''' A base class defining the API for configurable objects. @@ -149,7 +156,7 @@ def isTaurusConfig(x): for k in x['__orderedConfigNames__']: if k not in x['__itemConfigurations__']: - print 'missing configuration for "%s" in %s' % (k, repr(x)) + print('missing configuration for "%s" in %s' % (k, repr(x))) return True def createConfig(self, allowUnpickable=False): @@ -190,7 +197,7 @@ def createConfig(self, allowUnpickable=False): # store the configurations for all registered configurable items as # well itemcfgs = {} - for k, v in self.__configurableItems.iteritems(): + for k, v in self.__configurableItems.items(): itemcfgs[k] = v.createConfig(allowUnpickable=allowUnpickable) configdict["__itemConfigurations__"] = itemcfgs configdict["__orderedConfigNames__"] = self.__configurableItemNames @@ -250,7 +257,7 @@ def resetConfigurableItems(self): self.__configurableItems = {} def registerConfigurableItem(self, item, name=None): - print "Deprecation WARNING: %s.registerConfigurableItem() has been deprecated. Use registerConfigDelegate() instead" % repr(self) + print("Deprecation WARNING: %s.registerConfigurableItem() has been deprecated. Use registerConfigDelegate() instead" % repr(self)) self._registerConfigurableItem(item, name=name) def registerConfigDelegate(self, delegate, name=None): @@ -291,7 +298,7 @@ def registerConfigProperty(self, fget, fset, name): .. seealso:: :meth:`unregisterConfigurableItem`, :meth:`registerConfigDelegate`, :meth:`createConfig` ''' - if isinstance(fget, str) or isinstance(fset, str): + if isinstance(fget, string_types) or isinstance(fset, string_types): import weakref obj = weakref.proxy(self) else: @@ -347,7 +354,7 @@ def unregisterConfigurableItem(self, item, raiseOnError=True): .. seealso:: :meth:`registerConfigProperty`, :meth:`registerConfigDelegate` ''' - if isinstance(item, basestring): + if isinstance(item, string_types): name = str(item) else: name = str(item.objectName()) @@ -402,7 +409,7 @@ def createQConfig(self): .. seealso:: :meth:`restoreQConfig` ''' from taurus.external.qt import Qt - import cPickle as pickle + import pickle configdict = self.createConfig(allowUnpickable=False) return Qt.QByteArray(pickle.dumps(configdict)) @@ -416,7 +423,7 @@ def applyQConfig(self, qstate): ''' if qstate.isNull(): return - import cPickle as pickle + import pickle configdict = pickle.loads(qstate.data()) self.applyConfig(configdict) @@ -427,11 +434,14 @@ def saveConfigFile(self, ofile=None): :return: (str) file name used """ - import cPickle as pickle + import pickle if ofile is None: - from taurus.external.qt import Qt - ofile = unicode(Qt.QFileDialog.getSaveFileName( - self, 'Save Configuration', '%s.pck' % self.__class__.__name__, 'Configuration File (*.pck)')) + from taurus.external.qt import compat + ofile, _ = compat.getSaveFileName( + self, 'Save Configuration', + '%s.pck' % self.__class__.__name__, + 'Configuration File (*.pck)' + ) if not ofile: return if not isinstance(ofile, file): @@ -448,15 +458,16 @@ def loadConfigFile(self, ifile=None): :return: (str) file name used """ - import cPickle as pickle + import pickle if ifile is None: - from taurus.external.qt import Qt - ifile = unicode(Qt.QFileDialog.getOpenFileName( - self, 'Load Configuration', '', 'Configuration File (*.pck)')) + from taurus.external.qt import compat + ifile, _ = compat.getOpenFileName( + self, 'Load Configuration', '', 'Configuration File (*.pck)') if not ifile: return if not isinstance(ifile, file): ifile = open(ifile, 'r') + configdict = pickle.load(ifile) self.applyConfig(configdict) return ifile.name diff --git a/lib/taurus/qt/qtcore/model/__init__.py b/lib/taurus/qt/qtcore/model/__init__.py index d65eba15a..f8326e5c5 100644 --- a/lib/taurus/qt/qtcore/model/__init__.py +++ b/lib/taurus/qt/qtcore/model/__init__.py @@ -53,8 +53,9 @@ view.setModel(model) """ +from __future__ import absolute_import +from .taurusmodel import * +from .taurusdatabasemodel import * -__docformat__ = 'restructuredtext' -from taurusmodel import * -from taurusdatabasemodel import * +__docformat__ = 'restructuredtext' diff --git a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py index 4347da3d3..1a2c1063f 100644 --- a/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py +++ b/lib/taurus/qt/qtcore/model/taurusdatabasemodel.py @@ -26,6 +26,14 @@ """This module provides widgets that display the database in a tree format""" # TODO: tango-centric +from builtins import str, bytes + +from taurus.external.qt import Qt +from taurus.core.taurusbasetypes import TaurusElementType, TaurusDevState +import taurus.qt.qtcore.mimetypes +from .taurusmodel import TaurusBaseTreeItem, TaurusBaseModel, TaurusBaseProxyModel + + __all__ = ["TaurusTreeDevicePartItem", "TaurusTreeDeviceDomainItem", "TaurusTreeDeviceFamilyItem", "TaurusTreeDeviceMemberItem", "TaurusTreeSimpleDeviceItem", "TaurusTreeDeviceItem", "TaurusTreeAttributeItem", "TaurusTreeServerNameItem", @@ -39,11 +47,6 @@ __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt -from taurus.core.taurusbasetypes import TaurusElementType, TaurusDevState -import taurus.qt.qtcore.mimetypes - -from .taurusmodel import TaurusBaseTreeItem, TaurusBaseModel, TaurusBaseProxyModel ElemType = TaurusElementType @@ -81,11 +84,16 @@ def getDevStateToolTip(*args, **kwargs): class TaurusTreeDbBaseItem(TaurusBaseTreeItem): try: # TODO: tango-centric - from taurus.core.tango.tangodatabase import TangoInfo - DisplayFunc = TangoInfo.name + import taurus.core.tango + + @staticmethod + def DisplayFunc(obj): + from taurus.core.tango.tangodatabase import TangoInfo + return TangoInfo.name(obj) except: pass + class TaurusTreeDevicePartItem(TaurusTreeDbBaseItem): """A node designed to represent a 'part' (or totality) of a device name""" @@ -416,13 +424,15 @@ def mimeData(self, indexes): mime_data_item = tree_item.mimeData(index) if mime_data_item is None: continue - data.append(mime_data_item) + data.append(bytes(mime_data_item, encoding='utf8')) ret.setData( - taurus.qt.qtcore.mimetypes.TAURUS_MODEL_LIST_MIME_TYPE, "\r\n".join(data)) - ret.setText(", ".join(data)) + taurus.qt.qtcore.mimetypes.TAURUS_MODEL_LIST_MIME_TYPE, + b"\r\n".join(data) + ) + ret.setText(", ".join(map(str, data))) if len(data) == 1: ret.setData( - taurus.qt.qtcore.mimetypes.TAURUS_MODEL_MIME_TYPE, str(data[0])) + taurus.qt.qtcore.mimetypes.TAURUS_MODEL_MIME_TYPE, data[0]) return ret def pyData(self, index, role): @@ -553,15 +563,15 @@ def setupModelData(self, data): data = data.deviceTree() rootItem = self._rootItem - for domain in data.keys(): + for domain in data: families = data[domain] domainItem = TaurusTreeDeviceDomainItem( self, domain.upper(), rootItem) - for family in families.keys(): + for family in families: members = families[family] familyItem = TaurusTreeDeviceFamilyItem( self, family.upper(), domainItem) - for member in members.keys(): + for member in members: dev = members[member] memberItem = TaurusTreeDeviceItem( self, dev, parent=familyItem) @@ -726,13 +736,11 @@ def filterAcceptsRow(self, sourceRow, sourceParent): def deviceMatches(self, device, regexp): name = device.name() - # if Qt.QString(name).contains(regexp): if regexp.indexIn(name) != -1: return True name = device.alias() if name is None: return False - # return Qt.QString(name).contains(regexp) return regexp.indexIn(name) != -1 @@ -750,14 +758,13 @@ def filterAcceptsRow(self, sourceRow, sourceParent): serverName = treeItem.display() serverInstances = sourceModel.getServerNameInstances(serverName) for serverInstance in serverInstances: - # if Qt.QString(serverInstance.name()).contains(regexp): if regexp.indexIn(serverInstance.name()) != -1: return True return False if isinstance(treeItem, TaurusTreeServerItem): # return treeItem.qdisplay().contains(regexp) - return regexp.indexIn(treeItem.qdisplay()) != -1 + return regexp.indexIn(treeItem.display()) != -1 return True @@ -776,4 +783,4 @@ def filterAcceptsRow(self, sourceRow, sourceParent): regexp = self.filterRegExp() # return treeItem.qdisplay().contains(regexp) - return regexp.indexIn(treeItem.qdisplay()) != -1 + return regexp.indexIn(treeItem.display()) != -1 diff --git a/lib/taurus/qt/qtcore/model/taurusmodel.py b/lib/taurus/qt/qtcore/model/taurusmodel.py index 5bd663daf..7b60f3322 100644 --- a/lib/taurus/qt/qtcore/model/taurusmodel.py +++ b/lib/taurus/qt/qtcore/model/taurusmodel.py @@ -25,16 +25,18 @@ """This module provides base taurus tree item and a base tree model""" -__all__ = ["TaurusBaseTreeItem", "TaurusBaseModel", "TaurusBaseProxyModel"] - -__docformat__ = 'restructuredtext' +from builtins import object from taurus.external.qt import Qt from taurus.core.taurusbasetypes import TaurusElementType -from taurus.core.util.log import Logger +from taurus.core.util.log import Logger, deprecation_decorator QtQt = Qt.Qt +__all__ = ["TaurusBaseTreeItem", "TaurusBaseModel", "TaurusBaseProxyModel"] + +__docformat__ = 'restructuredtext' + class TaurusBaseTreeItem(object): """A generic node""" @@ -139,16 +141,9 @@ def display(self): self._display = self.DisplayFunc(self._itemData) return self._display + @deprecation_decorator(alt='display', rel='4.5') def qdisplay(self): - """Returns the display QString for this node - - :return: (Qt.QString) the node's display string""" - if not hasattr(self, "_qdisplay"): - d = self.display() - if d is None: - return None - self._qdisplay = Qt.QString(d) - return self._qdisplay + return str(self.display()) def mimeData(self, index): return self.data(index) @@ -178,12 +173,6 @@ class TaurusBaseModel(Qt.QAbstractItemModel, Logger): def __init__(self, parent=None, data=None): Qt.QAbstractItemModel.__init__(self, parent) Logger.__init__(self) - # if qt < 4.6, beginResetModel and endResetModel don't exist. In this - # case we set beginResetModel to be an empty function and endResetModel - # to be reset. - if not hasattr(Qt.QAbstractItemModel, "beginResetModel"): - self.beginResetModel = lambda: None - self.endResetModel = self.reset self._data_src = None self._rootItem = None self._filters = [] @@ -276,24 +265,16 @@ def pyData(self, index, role=QtQt.DisplayRole): elif role == QtQt.FontRole: ret = self.DftFont elif role == QtQt.UserRole: - ret = Qt.QVariant(item) + ret = item return ret def data(self, index, role=QtQt.DisplayRole): ret = self.pyData(index, role) - if ret is None: - ret = Qt.QVariant() - else: - ret = Qt.QVariant(ret) return ret def _setData(self, index, qvalue, role=QtQt.EditRole): item = index.internalPointer() - pyobj = Qt.from_qvariant(qvalue) - if pyobj is NotImplemented: - self.warning( - "Failed attempt to convert a QValue. Maybe it is due to Qt<4.6") - item.setData(index, pyobj) + item.setData(index, qvalue) return True def flags(self, index): @@ -325,7 +306,7 @@ def headerData(self, section, orientation, role=QtQt.DisplayRole): elif role == QtQt.DecorationRole: ret = self.columnIcon(section) - return Qt.QVariant(ret) + return ret def index(self, row, column, parent=Qt.QModelIndex()): if not self.hasIndex(row, column, parent): diff --git a/lib/taurus/qt/qtcore/util/__init__.py b/lib/taurus/qt/qtcore/util/__init__.py index e029ac989..64ccc147d 100644 --- a/lib/taurus/qt/qtcore/util/__init__.py +++ b/lib/taurus/qt/qtcore/util/__init__.py @@ -24,7 +24,8 @@ ############################################################################# """This package provides a set of utilities (e.g. logging) to taurus qtcore""" +from __future__ import absolute_import -__docformat__ = 'restructuredtext' +from .tauruslog import * -from tauruslog import * +__docformat__ = 'restructuredtext' diff --git a/lib/taurus/qt/qtcore/util/emitter.py b/lib/taurus/qt/qtcore/util/emitter.py index df6d4c235..2105a53a9 100644 --- a/lib/taurus/qt/qtcore/util/emitter.py +++ b/lib/taurus/qt/qtcore/util/emitter.py @@ -28,17 +28,18 @@ TaurusDevTree widgets """ -from Queue import Queue, Empty +from future import standard_library +standard_library.install_aliases() +from builtins import str +from builtins import object +from queue import Queue, Empty import traceback -from functools import partial -from collections import Iterable + +from future.utils import string_types import taurus from taurus.external.qt import Qt from taurus.core.util.log import Logger -from taurus.core.util.singleton import Singleton - -from taurus.core.taurusbasetypes import SubscriptionState ############################################################################### @@ -46,7 +47,7 @@ def isString(seq): - if isinstance(seq, basestring): + if isinstance(seq, string_types): return True # It matches most python str-like classes if any(s in str(type(seq)).lower() for s in ('vector', 'array', 'list',)): return False @@ -80,7 +81,7 @@ def setModel(self, value): class QEmitter(Qt.QObject): """Emitter class providing two signals.""" - doSomething = Qt.pyqtSignal(Iterable) + doSomething = Qt.pyqtSignal(list) somethingDone = Qt.pyqtSignal() newQueue = Qt.pyqtSignal() @@ -144,7 +145,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): @@ -279,7 +280,7 @@ def _doSomething(self, params): method(*args) except: self.log.error('At TaurusEmitterThread._doSomething(%s): \n%s' - % (map(str, args), traceback.format_exc())) + % (list(map(str, args)), traceback.format_exc())) self.emitter.somethingDone.emit() self._done += 1 return @@ -376,8 +377,7 @@ def getUnsubscribedAttributes(self): """Check all pending subscriptions in the current factory """ attrs = [] - items = self._factory.getExistingAttributes().items() - for name, attr in items: + for name, attr in self._factory.getExistingAttributes().items(): if attr is None: continue elif attr.hasListeners() and not attr.isUsingEvents(): @@ -416,7 +416,7 @@ def cleanUp(self): Logger.cleanUp(self) -class SingletonWorker(): +class SingletonWorker(object): """ SingletonWorker is used to manage TaurusEmitterThread as Singleton objects diff --git a/lib/taurus/qt/qtcore/util/properties.py b/lib/taurus/qt/qtcore/util/properties.py index db292eed9..b7b225ed9 100644 --- a/lib/taurus/qt/qtcore/util/properties.py +++ b/lib/taurus/qt/qtcore/util/properties.py @@ -55,7 +55,8 @@ def resetFilters(self): """ -from functools import partial +from builtins import str +from builtins import map from taurus.external.qt import Qt from taurus.core.util.fandango_search import isSequence, isDictionary @@ -141,8 +142,9 @@ def set_property_methods(obj, name, type_="QString", default=None, getter=None, setattr(obj, 'get%s' % mname, getter) setattr(obj, 'reset%s' % mname, reset) if qt: - setattr(klass, lname, Qt.pyqtProperty( - "QString", getter, setter, reset)) + setattr(klass, lname, + Qt.pyqtProperty("QString", getter, setter, reset) + ) if config: obj.registerConfigProperty(getter, setter, name) reset() diff --git a/lib/taurus/qt/qtcore/util/signal.py b/lib/taurus/qt/qtcore/util/signal.py index e82d2e1f1..af3e2bfe8 100644 --- a/lib/taurus/qt/qtcore/util/signal.py +++ b/lib/taurus/qt/qtcore/util/signal.py @@ -1,11 +1,14 @@ """Provide a Signal class for non-QObject objects""" +from __future__ import print_function -__all__ = ['baseSignal'] +from builtins import object -from PyQt4 import Qt +from taurus.external.qt import Qt from threading import Lock from weakref import WeakKeyDictionary +__all__ = ['baseSignal'] + def baseSignal(name, *args): """Signal class for non-Qobject objects. diff --git a/lib/taurus/qt/qtcore/util/tauruslog.py b/lib/taurus/qt/qtcore/util/tauruslog.py index 393f2fab3..296ba461a 100644 --- a/lib/taurus/qt/qtcore/util/tauruslog.py +++ b/lib/taurus/qt/qtcore/util/tauruslog.py @@ -50,14 +50,30 @@ def getQtLogger(): return qtLogger -def qtTaurusMsgHandler(type, msg): +def qtTaurusMessageHandler(msg_type, log_ctx, msg): + # Qt5 version global qtLogger if qtLogger is not None: - caller = QT_LEVEL_MATCHER.get(type) + caller = QT_LEVEL_MATCHER.get(msg_type) + return caller("Qt%s %s.%s[%s]: %a", log_ctx.category, log_ctx.file, + log_ctx.function, log_ctx.line, msg) + +def qtTaurusMsgHandler(msg_type, msg): + # Qt4 version + global qtLogger + if qtLogger is not None: + caller = QT_LEVEL_MATCHER.get(msg_type) caller(qtLogger, msg) def initTaurusQtLogger(): global qtLogger if not qtLogger: - Qt.qInstallMsgHandler(qtTaurusMsgHandler) + if hasattr(Qt, "qInstallMessageHandler"): + # Qt5 + Qt.qInstallMessageHandler(qtTaurusMessageHandler) + + elif hasattr(Qt, "qInstallMsgHandler"): + # Qt4 + Qt.qInstallMsgHandler(qtTaurusMsgHandler) + diff --git a/lib/taurus/qt/qtdesigner/containerplugin.py b/lib/taurus/qt/qtdesigner/containerplugin.py index 077a1eca7..70009e0a0 100644 --- a/lib/taurus/qt/qtdesigner/containerplugin.py +++ b/lib/taurus/qt/qtdesigner/containerplugin.py @@ -37,13 +37,11 @@ editing the widget model (same has 'Edit model...' task menu item """ -from taurus.core.util.log import Logger -from taurus.external.qt import Qt -from taurus.external.qt import QtDesigner +from __future__ import absolute_import +from taurus.external.qt import QtDesigner from taurus.qt.qtgui.container.qcontainer import QGroupWidget -#import sip Q_TYPEID = {'QPyDesignerContainerExtension': 'com.trolltech.Qt.Designer.Container', 'QPyDesignerPropertySheetExtension': 'com.trolltech.Qt.Designer.PropertySheet', @@ -105,7 +103,8 @@ def createExtension(self, obj, iid, parent): def create_plugin(): - from taurusplugin.taurusplugin import TaurusWidgetPlugin + #from .taurusplugin.taurusplugin import TaurusWidgetPlugin # - after futurize stage1 + from taurusplugin.taurusplugin import TaurusWidgetPlugin # + after futurize stage1 class QGroupWidgetPlugin(TaurusWidgetPlugin): diff --git a/lib/taurus/qt/qtdesigner/taurusdesigner.py b/lib/taurus/qt/qtdesigner/taurusdesigner.py index b559144de..3ceee9903 100644 --- a/lib/taurus/qt/qtdesigner/taurusdesigner.py +++ b/lib/taurus/qt/qtdesigner/taurusdesigner.py @@ -23,13 +23,14 @@ ## ############################################################################# +from builtins import str import sys import os.path import optparse import taurus from taurus.external.qt import Qt - +from taurus.core.util.log import deprecation_decorator def env_index(env, env_name): env_name = str(env_name) @@ -94,8 +95,8 @@ def get_taurus_designer_path(): return [taurus_qt_designer_path] +@deprecation_decorator(alt='get_taurus_designer_env', rel='4.5') def qtdesigner_prepare_taurus(env=None, taurus_extra_path=None): - # Tell Qt Designer where it can find the directory containing the plugins if env is None: env = Qt.QProcess.systemEnvironment() @@ -115,13 +116,35 @@ def qtdesigner_prepare_taurus(env=None, taurus_extra_path=None): return env +def get_taurus_designer_env(taurus_extra_path=None): + + env = Qt.QProcessEnvironment.systemEnvironment() + + # Set PYQTDESIGNERPATH to look inside taurus for designer plugins + (taurus_designer_path,) = get_taurus_designer_path() + env.insert("PYQTDESIGNERPATH", taurus_designer_path) + + # Set TAURUSQTDESIGNERPATH + if taurus_extra_path is not None: + env.insert("TAURUSQTDESIGNERPATH", taurus_extra_path) + env.insert("PYTHONPATH", taurus_extra_path) + + return env + + def qtdesigner_start(args, env=None): + # Start Designer. designer_bin = get_qtdesigner_bin() designer = Qt.QProcess() designer.setProcessChannelMode(Qt.QProcess.ForwardedChannels) - designer.setEnvironment(env) + if isinstance(env, Qt.QProcessEnvironment): + designer.setProcessEnvironment(env) + else: # obsolete call, only for bck-compat + taurus.deprecated(dep='passing env which is not a QProcessEnvironment', + alt='QProcessEnvironment', rel='4.5') + designer.setEnvironment(env) designer.start(designer_bin, args) designer.waitForFinished(-1) @@ -129,6 +152,9 @@ def qtdesigner_start(args, env=None): def main(env=None): + if env is not None: + taurus.info('ignoring obsolete env parameter to qtdesigner_start') + version = "taurusdesigner %s" % (taurus.Release.version) usage = "Usage: %prog [options] " description = "The Qt designer application customized for taurus" @@ -146,8 +172,8 @@ def main(env=None): if len(options.tauruspath) > 0: taurus_extra_path = options.tauruspath - env = qtdesigner_prepare_taurus( - env=env, taurus_extra_path=taurus_extra_path) + if env is None: + env = get_taurus_designer_env(taurus_extra_path=taurus_extra_path) sys.exit(qtdesigner_start(args, env=env)) diff --git a/lib/taurus/qt/qtdesigner/taurusplugin/taurusplugin.py b/lib/taurus/qt/qtdesigner/taurusplugin/taurusplugin.py index ba4f950e2..0034f8d72 100644 --- a/lib/taurus/qt/qtdesigner/taurusplugin/taurusplugin.py +++ b/lib/taurus/qt/qtdesigner/taurusplugin/taurusplugin.py @@ -37,6 +37,7 @@ editing the widget model (same has 'Edit model...' task menu item """ +from __future__ import print_function import inspect from taurus.external.qt import Qt @@ -46,8 +47,8 @@ def Q_TYPEID(class_name): - """ Helper function to generate an IID for Qt. Returns a QString.""" - return Qt.QString("com.trolltech.Qt.Designer.%s" % class_name) + """ Helper function to generate an IID for Qt.""" + return "com.trolltech.Qt.Designer.%s" % class_name designer_logger = Logger("PyQtDesigner") @@ -103,11 +104,11 @@ def createWidget(self, parent): designMode=True, parent=parent) w = klass(*args, **kwargs) - except Exception, e: + except Exception as e: name = self._getWidgetClassName() - print 100 * "=" - print "taurus designer plugin error creating %s: %s" % (name, str(e)) - print 100 * "-" + print(100 * "=") + print("taurus designer plugin error creating %s: %s" % (name, str(e))) + print(100 * "-") import traceback traceback.print_exc() w = None diff --git a/lib/taurus/qt/qtdesigner/tauruspluginplugin.py b/lib/taurus/qt/qtdesigner/tauruspluginplugin.py index f7d1cf447..2abb98f93 100644 --- a/lib/taurus/qt/qtdesigner/tauruspluginplugin.py +++ b/lib/taurus/qt/qtdesigner/tauruspluginplugin.py @@ -26,12 +26,14 @@ """ tauruspluginplugin.py: """ +from __future__ import absolute_import from taurus.external.qt import QtDesigner def build_qtdesigner_widget_plugin(klass): - import taurusplugin + #from . import taurusplugin # - after futurize stage 1 + import taurusplugin.taurusplugin as taurusplugin # + after futurize stage 1 class Plugin(taurusplugin.TaurusWidgetPlugin): WidgetClass = klass @@ -74,7 +76,7 @@ def main(): #_log.debug("E2: Canceled %s (widget doesn't have getQtDesignerPluginInfo())" % name) e2_nb += 1 cont = True - except Exception, e: + except Exception as e: #_log.debug("E3: Canceled %s (%s)" % (name, str(e))) e3_nb += 1 cont = True @@ -82,7 +84,7 @@ def main(): if cont: continue for k in ('module', ): - if not qt_info.has_key(k): + if k not in qt_info: #_log.debug("E4: Canceled %s (getQtDesignerPluginInfo doesn't have key %s)" % (name, k)) e4_nb += 1 cont = True diff --git a/lib/taurus/qt/qtgui/__init__.py b/lib/taurus/qt/qtgui/__init__.py index c0499237f..85e7ce558 100644 --- a/lib/taurus/qt/qtgui/__init__.py +++ b/lib/taurus/qt/qtgui/__init__.py @@ -27,12 +27,10 @@ taurus models. The widgets are generic in the sence that they do not assume any behavior associated with a specific HW device. They intend to represent only abstract model data.""" - -__docformat__ = 'restructuredtext' - +from __future__ import absolute_import # register icon path files and icon theme on import of taurus.qt.qtgui -import icon as __icon +from . import icon as __icon import os import sys import glob @@ -40,6 +38,9 @@ from taurus import tauruscustomsettings as __S from taurus import debug as __debug + +__docformat__ = 'restructuredtext' + icon_dir = os.path.join(os.path.dirname(os.path.abspath(__icon.__file__))) # TODO: get .path file glob pattern from tauruscustomsettings __icon.registerPathFiles(glob.glob(os.path.join(icon_dir,'*.path'))) diff --git a/lib/taurus/qt/qtgui/application/taurusapplication.py b/lib/taurus/qt/qtgui/application/taurusapplication.py index dcd0c78b5..07a6c127d 100644 --- a/lib/taurus/qt/qtgui/application/taurusapplication.py +++ b/lib/taurus/qt/qtgui/application/taurusapplication.py @@ -26,11 +26,7 @@ """This module provides the base :class:`taurus.qt.qtgui.application.TaurusApplication` class.""" -from __future__ import with_statement - -__all__ = ["TaurusApplication"] - -__docformat__ = 'restructuredtext' +from builtins import str import os import sys @@ -44,6 +40,11 @@ import taurus.core.util.argparse +__all__ = ["TaurusApplication"] + +__docformat__ = 'restructuredtext' + + class STD(Logger): FlushWaterMark = 1000 @@ -66,6 +67,11 @@ def __init__(self, name='', parent=None, format=None, std=None, self.buffer = '' self.log_obj.propagate = False self.std = std + + #Trying to mimic stderr + @property + def errors(self): + return self.std.errors def addLogHandler(self, handler): """When called, set to use a private handler and DON'T send messages diff --git a/lib/taurus/qt/qtgui/base/taurusbase.py b/lib/taurus/qt/qtgui/base/taurusbase.py index 66dda2663..931b6d609 100644 --- a/lib/taurus/qt/qtgui/base/taurusbase.py +++ b/lib/taurus/qt/qtgui/base/taurusbase.py @@ -27,14 +27,11 @@ """This module provides the set of base classes from which the Qt taurus widgets should inherit to be considered valid taurus widgets.""" -__all__ = ["TaurusBaseComponent", "TaurusBaseWidget", - "TaurusBaseWritableWidget", "defaultFormatter"] - -__docformat__ = 'restructuredtext' - import sys import threading from types import MethodType +from future.builtins import str +from future.utils import string_types from taurus.external.qt import Qt from enum import Enum @@ -45,19 +42,27 @@ from taurus.core.taurusbasetypes import TaurusElementType, TaurusEventType from taurus.core.taurusattribute import TaurusAttribute from taurus.core.taurusdevice import TaurusDevice -from taurus.core.taurusconfiguration import (TaurusConfiguration, - TaurusConfigurationProxy) +from taurus.core.taurusconfiguration import TaurusConfigurationProxy from taurus.core.tauruslistener import TaurusListener, TaurusExceptionListener from taurus.core.taurusoperation import WriteAttrOperation from taurus.core.util.eventfilters import filterEvent from taurus.core.util.log import deprecation_decorator from taurus.qt.qtcore.util.signal import baseSignal from taurus.qt.qtcore.configuration import BaseConfigurableClass -from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_MODEL_MIME_TYPE +from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE +from taurus.qt.qtcore.mimetypes import TAURUS_DEV_MIME_TYPE +from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_MIME_TYPE from taurus.qt.qtgui.util import ActionFactory from taurus.core.units import Quantity + +__all__ = ["TaurusBaseComponent", "TaurusBaseWidget", + "TaurusBaseWritableWidget", "defaultFormatter"] + +__docformat__ = 'restructuredtext' + + DefaultNoneValue = "-----" @@ -105,7 +110,7 @@ class TaurusBaseComponent(TaurusListener, BaseConfigurableClass): taurusEvent = baseSignal('taurusEvent', object, object, object) - def __init__(self, name, parent=None, designMode=False): + def __init__(self, name='', parent=None, designMode=False): """Initialization of TaurusBaseComponent""" self.modelObj = None self.modelName = '' @@ -739,7 +744,7 @@ def baseFormatter(dtype=None, basecomponent=None, **kwargs): if self._format is None: try: self._updateFormat(type(v)) - except Exception, e: + except Exception as e: self.warning(('Cannot update format. Reverting to default.' + ' Reason: %r'), e) self.setFormat(defaultFormatter) @@ -762,7 +767,7 @@ def _updateFormat(self, dtype, **kwargs): :param kwargs: keyword arguments that will be passed to :attribute:`FORMAT` if it is a callable """ - if not isinstance(self.FORMAT, basestring): + if not isinstance(self.FORMAT, string_types): # unbound method to callable if isinstance(self.FORMAT, MethodType): self.FORMAT = self.FORMAT.__func__ @@ -781,7 +786,7 @@ def setFormat(self, format): "full.module.callable" format) """ # Check if the format is a callable string representation - if isinstance(format, basestring): + if isinstance(format, string_types): try: moduleName, formatterName = format.rsplit('.', 1) __import__(moduleName) @@ -798,7 +803,7 @@ def getFormat(self): :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): + if isinstance(self.FORMAT, string_types): formatter = self.FORMAT else: formatter = '{0}.{1}'.format(self.FORMAT.__module__, @@ -1301,7 +1306,7 @@ class TaurusBaseWidget(TaurusBaseComponent): _dragEnabled = False - def __init__(self, name, parent=None, designMode=False): + def __init__(self, name='', parent=None, designMode=False): self._disconnect_on_hide = False self._supportedMimeTypes = None self._autoTooltip = True @@ -1722,7 +1727,7 @@ def handleMimeData(self, mimeData, method): formats = mimeData.formats() for mtype in supported: if mtype in formats: - d = str(mimeData.data(mtype)) + d = bytes(mimeData.data(mtype)) if d is None: return None try: @@ -1743,7 +1748,7 @@ def getModelMimeData(self): :return: (QMimeData) ''' mimeData = Qt.QMimeData() - modelname = self.getModelName() + modelname = str(self.getModelName()).encode(encoding='utf8') mimeData.setData(TAURUS_MODEL_MIME_TYPE, modelname) try: modelclass = self.getModelClass() @@ -1905,7 +1910,7 @@ class TaurusBaseWritableWidget(TaurusBaseWidget): applied = baseSignal('applied') - def __init__(self, name, taurus_parent=None, designMode=False): + def __init__(self, name='', taurus_parent=None, designMode=False): self.call__init__(TaurusBaseWidget, name, parent=taurus_parent, designMode=designMode) @@ -2147,31 +2152,6 @@ def updateStyle(self): v_str, model_v_str) self.setToolTip(toolTip) - def _updateValidator(self, evt_value): # TODO: Remove this method - # re-set the validator ranges if applicable - if evt_value is None: - return - v = self.validator() - if isinstance(v, Qt.QIntValidator): - bottom = evt_value.min_value - top = evt_value.max_value - bottom = int( - bottom) if bottom != TaurusConfiguration.no_min_value else -sys.maxint - top = int( - top) if top != TaurusConfiguration.no_max_value else sys.maxint - v.setRange(bottom, top) - self.debug("Validator range set to %i-%i" % (bottom, top)) - elif isinstance(v, Qt.QDoubleValidator): - bottom = evt_value.min_value - top = evt_value.max_value - bottom = float( - bottom) if bottom != TaurusConfiguration.no_min_value else -float("inf") - top = float( - top) if top != TaurusConfiguration.no_max_value else float("inf") - v.setBottom(bottom) - v.setTop(top) - self.debug("Validator range set to %f-%f" % (bottom, top)) - @classmethod def getQtDesignerPluginInfo(cls): '''reimplemented from :class:`TaurusBaseWidget`''' diff --git a/lib/taurus/qt/qtgui/base/tauruscontroller.py b/lib/taurus/qt/qtgui/base/tauruscontroller.py index 28103fe6d..da84afdf7 100644 --- a/lib/taurus/qt/qtgui/base/tauruscontroller.py +++ b/lib/taurus/qt/qtgui/base/tauruscontroller.py @@ -26,12 +26,8 @@ """This module provides the set of base class taurus controllers.""" -__all__ = ["TaurusBaseController", "TaurusAttributeControllerHelper", - "TaurusScalarAttributeControllerHelper", - "TaurusConfigurationControllerHelper", - "updateLabelBackground"] - -__docformat__ = 'restructuredtext' +from builtins import str +from builtins import object import weakref @@ -43,6 +39,14 @@ from taurus.qt.qtgui.util import QT_DEVICE_STATE_PALETTE +__all__ = ["TaurusBaseController", "TaurusAttributeControllerHelper", + "TaurusScalarAttributeControllerHelper", + "TaurusConfigurationControllerHelper", + "updateLabelBackground"] + +__docformat__ = 'restructuredtext' + + class TaurusBaseController(object): """Base class for all taurus controllers""" @@ -204,7 +208,7 @@ def _getDisplayValue(self, widget, valueObj, idx, write): for i in idx: value = value[i] return widget.displayValue(value) - except Exception, e: + except Exception as e: return widget.getNoneValue() def displayValue(self, value): diff --git a/lib/taurus/qt/qtgui/button/taurusbutton.py b/lib/taurus/qt/qtgui/button/taurusbutton.py index 81b962ceb..9649065e4 100644 --- a/lib/taurus/qt/qtgui/button/taurusbutton.py +++ b/lib/taurus/qt/qtgui/button/taurusbutton.py @@ -25,10 +25,11 @@ ############################################################################# """This module provides a taurus QPushButton based widgets""" +from __future__ import print_function -__all__ = ["TaurusLauncherButton", "TaurusCommandButton", "TaurusLockButton"] - -__docformat__ = 'restructuredtext' +from builtins import map +from builtins import str +from future.utils import string_types from taurus.external.qt import Qt from taurus.core.taurusbasetypes import LockStatus, TaurusLockInfo @@ -38,6 +39,11 @@ from taurus.qt.qtgui.dialog import ProtectTaurusMessageBox +__all__ = ["TaurusLauncherButton", "TaurusCommandButton", "TaurusLockButton"] + +__docformat__ = 'restructuredtext' + + class _ButtonDialog(Qt.QDialog): _widget = None deleteWidgetOnClose = False @@ -174,7 +180,7 @@ def createWidget(self): if self._dialog.previousWidgetConfig is not None: try: widget.applyConfig(self._dialog.previousWidgetConfig) - except Exception, e: + except Exception as e: self.warning( 'Cannot apply previous configuration to widget. Reason: %s', repr(e)) @@ -342,7 +348,7 @@ def _onClicked(self): modelobj.set_timeout_millis(int(self._timeout * 1000)) result = modelobj.command_inout(self._command, self._castParameters( self._parameters, self._command, modelobj)) - except Exception, e: + except Exception as e: self.error('Unexpected error when executing command %s of %s: %s' % ( self._command, modelobj.getNormalName(), str(e))) raise @@ -376,7 +382,7 @@ def _castParameters(self, parameters=None, command=None, dev=None): try: param_type = dev.command_query(command).in_type - except Exception, e: + except Exception as e: self.warning( 'Cannot get parameters info for command %s:%s' % (command, str(e))) return parameters @@ -400,7 +406,7 @@ def _castParameters(self, parameters=None, command=None, dev=None): else: return parameters else: - return map(cast_type, parameters) + return list(map(cast_type, parameters)) def setCommand(self, commandName): '''sets the command to be executed when the button is clicked @@ -439,7 +445,7 @@ def setParameters(self, parameters): quotes will be removed and the quoted text will not be splitted. ''' - if isinstance(parameters, (basestring, Qt.QString)): + if isinstance(parameters, string_types): parameters = str(parameters).strip() if parameters[0] in ('"', "'") and parameters[0] == parameters[-1]: parameters = [parameters[1:-1]] @@ -632,7 +638,7 @@ def lockButtonMain(): if len(args) == 0: w = demo() else: - models = map(str.lower, args) + models = list(map(str.lower, args)) w = Qt.QWidget() layout = Qt.QGridLayout() @@ -661,7 +667,7 @@ def commandButtonMain(): 'Booo scary command!!\n Maybe you should think twice!') def f(*a): - print a + print(a) form.commandExecuted.connect(f) form.show() sys.exit(app.exec_()) diff --git a/lib/taurus/qt/qtgui/compact/abstractswitcher.py b/lib/taurus/qt/qtgui/compact/abstractswitcher.py index b8fa8de33..610eea45e 100644 --- a/lib/taurus/qt/qtgui/compact/abstractswitcher.py +++ b/lib/taurus/qt/qtgui/compact/abstractswitcher.py @@ -23,16 +23,19 @@ ## ############################################################################# -"""This module provides base classes from which the compact widgets should inherit +""" +This module provides base classes from which the compact widgets should inherit """ __all__ = ["TaurusReadWriteSwitcher"] __docformat__ = 'restructuredtext' +from future.utils import string_types + from taurus.external.qt import Qt from taurus.qt.qtgui.container import TaurusWidget -from taurus.qt.qtgui.base import TaurusBaseWritableWidget +# from taurus.qt.qtgui.base import TaurusBaseWritableWidget class TaurusReadWriteSwitcher(TaurusWidget): @@ -135,8 +138,8 @@ def __init__(self, parent=None, designMode=False, self.exitEditAction.setShortcutContext( Qt.Qt.WidgetWithChildrenShortcut) self.addAction(self.exitEditAction) - self.enterEditAction.triggered[()].connect(self._onEnterEditActionTriggered) - self.exitEditAction.triggered[()].connect(self._onExitEditActionTriggered) + self.enterEditAction.triggered.connect(self._onEnterEditActionTriggered) + self.exitEditAction.triggered.connect(self._onExitEditActionTriggered) # add read and write widgets if self.readWClass is not None: @@ -157,7 +160,7 @@ def _classifyTriggers(self, triggers): shortcuts.append(Qt.QKeySequence(e)) elif isinstance(e, Qt.QEvent.Type): eventTypes.append(e) - elif isinstance(e, (basestring, Qt.QString)): + elif isinstance(e, string_types): signals.append(e) else: raise TypeError('Unsupported trigger type: %s' % repr(type(e))) @@ -193,7 +196,7 @@ def setReadWidget(self, widget): for sig in self.enterEditSignals: try: getattr(self.readWidget, sig).connect(self.enterEdit) - except Exception, e: + except Exception as e: self.debug('Cannot connect signal. Reason: %s', e) # update size policy self._updateSizePolicy() @@ -220,7 +223,7 @@ def setWriteWidget(self, widget): for sig in self.exitEditSignals: try: getattr(self.writeWidget, sig).connect(self.exitEdit) - except Exception, e: + except Exception as e: if isinstance(e, AttributeError) and hasattr(Qt, "SIGNAL"): # Support old-style signal self.connect(self.writeWidget, Qt.SIGNAL(sig), diff --git a/lib/taurus/qt/qtgui/compact/basicswitcher.py b/lib/taurus/qt/qtgui/compact/basicswitcher.py index 7cd4d2e1c..b21f371fd 100644 --- a/lib/taurus/qt/qtgui/compact/basicswitcher.py +++ b/lib/taurus/qt/qtgui/compact/basicswitcher.py @@ -26,14 +26,15 @@ """This module provides some basic usable widgets based on TaurusReadWriteSwitcher """ -__all__ = ["TaurusLabelEditRW", "TaurusBoolRW"] - -__docformat__ = 'restructuredtext' +from __future__ import absolute_import -from taurus.external.qt import Qt from taurus.qt.qtgui.display import TaurusLabel, TaurusLed from taurus.qt.qtgui.input import TaurusValueLineEdit, TaurusValueCheckBox -from abstractswitcher import TaurusReadWriteSwitcher +from .abstractswitcher import TaurusReadWriteSwitcher + +__all__ = ["TaurusLabelEditRW", "TaurusBoolRW"] + +__docformat__ = 'restructuredtext' class TaurusLabelEditRW(TaurusReadWriteSwitcher): diff --git a/lib/taurus/qt/qtgui/console/__init__.py b/lib/taurus/qt/qtgui/console/__init__.py index f0dfefcf9..a2aa414b4 100644 --- a/lib/taurus/qt/qtgui/console/__init__.py +++ b/lib/taurus/qt/qtgui/console/__init__.py @@ -35,7 +35,7 @@ try: from silx.gui.console import IPythonWidget as TaurusConsole -except Exception, e: +except Exception as e: from taurus.qt.qtgui.display import TaurusFallBackWidget class TaurusConsole(TaurusFallBackWidget): diff --git a/lib/taurus/qt/qtgui/container/qcontainer.py b/lib/taurus/qt/qtgui/container/qcontainer.py index 817299474..5bb98328f 100644 --- a/lib/taurus/qt/qtgui/container/qcontainer.py +++ b/lib/taurus/qt/qtgui/container/qcontainer.py @@ -25,16 +25,18 @@ """This module provides basic pure Qt container widgets""" -__all__ = ["QGroupWidget"] - -__docformat__ = 'restructuredtext' +from builtins import str -import sys import json from taurus.external.qt import Qt from taurus.qt.qtgui.icon import getStandardIcon +__all__ = ["QGroupWidget"] + +__docformat__ = 'restructuredtext' + + _TitleBarStyleExpanded = """.QFrame {{ border-width: 0px; border-style: solid; @@ -137,14 +139,14 @@ def __init__(self, parent=None, designMode=False): def __init(self): panelLayout = Qt.QVBoxLayout() panelLayout.setSpacing(0) - panelLayout.setMargin(0) + panelLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(panelLayout) self._titleBar = titleBar = Qt.QFrame() panelLayout.addWidget(titleBar, 0) l = Qt.QHBoxLayout() - l.setMargin(2) + l.setContentsMargins(2, 2, 2, 2) l.setSpacing(2) self._titleBar.setLayout(l) diff --git a/lib/taurus/qt/qtgui/container/taurusbasecontainer.py b/lib/taurus/qt/qtgui/container/taurusbasecontainer.py index 8432374be..2ef19d98e 100644 --- a/lib/taurus/qt/qtgui/container/taurusbasecontainer.py +++ b/lib/taurus/qt/qtgui/container/taurusbasecontainer.py @@ -52,7 +52,7 @@ class TaurusBaseContainer(TaurusBaseWidget): stateWidget.model = 'sys/database/2/state' """ - def __init__(self, name, parent=None, designMode=False): + def __init__(self, name='', parent=None, designMode=False): name = name or self.__class__.__name__ self.call__init__(TaurusBaseWidget, name, diff --git a/lib/taurus/qt/qtgui/container/taurusframe.py b/lib/taurus/qt/qtgui/container/taurusframe.py index 7b094873d..1e20ca0ea 100644 --- a/lib/taurus/qt/qtgui/container/taurusframe.py +++ b/lib/taurus/qt/qtgui/container/taurusframe.py @@ -25,14 +25,16 @@ """This module provides basic taurus container widgets""" +from __future__ import absolute_import + +from taurus.external.qt import Qt +from .taurusbasecontainer import TaurusBaseContainer + + __all__ = ["TaurusFrame"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt -from taurus.qt.qtgui.base import TaurusBaseComponent -from taurusbasecontainer import TaurusBaseContainer - class TaurusFrame(Qt.QFrame, TaurusBaseContainer): """This is a Qt.QFrame that additionally accepts a model property. diff --git a/lib/taurus/qt/qtgui/container/taurusgroupbox.py b/lib/taurus/qt/qtgui/container/taurusgroupbox.py index 12edda868..8b87fff69 100644 --- a/lib/taurus/qt/qtgui/container/taurusgroupbox.py +++ b/lib/taurus/qt/qtgui/container/taurusgroupbox.py @@ -25,13 +25,15 @@ """This module provides basic taurus group box widget""" +from __future__ import absolute_import + +from taurus.external.qt import Qt +from .taurusbasecontainer import TaurusBaseContainer + __all__ = ["TaurusGroupBox"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt -from taurusbasecontainer import TaurusBaseContainer - class TaurusGroupBox(Qt.QGroupBox, TaurusBaseContainer): """This is a Qt.QGroupBox that additionally accepts a model property. diff --git a/lib/taurus/qt/qtgui/container/taurusgroupwidget.py b/lib/taurus/qt/qtgui/container/taurusgroupwidget.py index d84924c8d..e0bf83575 100644 --- a/lib/taurus/qt/qtgui/container/taurusgroupwidget.py +++ b/lib/taurus/qt/qtgui/container/taurusgroupwidget.py @@ -25,14 +25,17 @@ """This module provides a taurus group widget""" +from __future__ import absolute_import + +from taurus.external.qt import Qt +from .qcontainer import QGroupWidget +from .taurusbasecontainer import TaurusBaseContainer + + __all__ = ["TaurusGroupWidget"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt -from qcontainer import QGroupWidget -from taurusbasecontainer import TaurusBaseContainer - class TaurusGroupWidget(QGroupWidget, TaurusBaseContainer): """This is a :class:`taurus.qt.qtgui.container.QGroupWidget` that additionally diff --git a/lib/taurus/qt/qtgui/container/taurusmainwindow.py b/lib/taurus/qt/qtgui/container/taurusmainwindow.py index 69cc8cc5d..8464dcd81 100644 --- a/lib/taurus/qt/qtgui/container/taurusmainwindow.py +++ b/lib/taurus/qt/qtgui/container/taurusmainwindow.py @@ -27,23 +27,32 @@ mainwindow.py: a main window implementation with many added features by default """ -__all__ = ["TaurusMainWindow"] +from __future__ import absolute_import -__docformat__ = 'restructuredtext' +from builtins import str import os import sys +from functools import partial + +from future.utils import string_types + from taurus import tauruscustomsettings from taurus.core.util import deprecation_decorator -from taurus.external.qt import Qt -from taurusbasecontainer import TaurusBaseContainer +from taurus.external.qt import Qt, compat +from .taurusbasecontainer import TaurusBaseContainer from taurus.qt.qtcore.configuration import BaseConfigurableClass from taurus.qt.qtgui.util import ExternalAppAction from taurus.qt.qtgui.dialog import protectTaurusMessageBox +__all__ = ["TaurusMainWindow"] + +__docformat__ = 'restructuredtext' + + class CommandArgsLineEdit(Qt.QLineEdit): ''' An specialized QLineEdit that can transform its text from/to command argument lists''' @@ -53,7 +62,7 @@ def __init__(self, extapp, *args): self.textEdited.connect(self.setCmdText) def setCmdText(self, cmdargs): - if not isinstance(cmdargs, (basestring, Qt.QString)): + if not isinstance(cmdargs, string_types): cmdargs = " ".join(cmdargs) self.setText(cmdargs) self._extapp.setCmdArgs(self.getCmdArgs(), False) @@ -90,7 +99,7 @@ def addExternalAppConfig(self, extapp): self.externalAppsPage.setWidgetResizable(True) self._tabwidget.addTab(self.externalAppsPage, "External Application Paths") - label = "Command line for %s" % unicode(extapp.text()) + label = "Command line for %s" % str(extapp.text()) editWidget = CommandArgsLineEdit(extapp, " ".join(extapp.cmdArgs())) #editWidget = Qt.QLineEdit(" ".join(extapp.cmdArgs())) self.externalAppsPage.widget().layout().addRow(label, editWidget) @@ -110,8 +119,7 @@ def deleteExternalAppConfig(self, extapp): if widget is not None: text = str(widget.text()) # command1 if isinstance(widget, Qt.QLabel): - dialog_text = "Command line for %s" % unicode( - extapp.text()) + dialog_text = "Command line for %s" % str(extapp.text()) if text == dialog_text: layout.removeWidget(widget) widget.close() @@ -176,7 +184,7 @@ class TaurusMainWindow(Qt.QMainWindow, TaurusBaseContainer): ''' modelChanged = Qt.pyqtSignal('const QString &') - perspectiveChanged = Qt.pyqtSignal(str) + perspectiveChanged = Qt.pyqtSignal('QString') # customization options: # blinking semi-period in ms. Set to None for not showing the Heart beat @@ -349,13 +357,23 @@ def updatePerspectivesMenu(self): return None self.perspectivesMenu.clear() for pname in self.getPerspectivesList(): - self.perspectivesMenu.addAction( + a = self.perspectivesMenu.addAction( pname, self.__onPerspectiveSelected) + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + a.perspective_name = pname # <-- ugly monkey-patch! + # ------------------------------------------------------- return self.perspectivesMenu def __onPerspectiveSelected(self): '''slot to be called by the actions in the perspectivesMenu''' - pname = self.sender().text() + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + # pname = self.sender().text() # <-- this fails because of added "&" + pname = self.sender().perspective_name # <-- this was monkey-patched + # ------------------------------------------------------- self.loadPerspective(name=pname) def splashScreen(self): @@ -384,39 +402,45 @@ def __createActions(self): '''initializes the application-wide actions''' self.quitApplicationAction = Qt.QAction( Qt.QIcon.fromTheme("process-stop"), 'Exit Application', self) - self.quitApplicationAction.triggered[()].connect(self.close) + self.quitApplicationAction.triggered.connect(self.close) self.changeTangoHostAction = Qt.QAction(Qt.QIcon.fromTheme( "network-server"), 'Change Tango Host ...', self) - self.changeTangoHostAction.triggered[()].connect(self._onChangeTangoHostAction) + self.changeTangoHostAction.triggered.connect(self._onChangeTangoHostAction) # make this action invisible since it is deprecated self.changeTangoHostAction.setVisible(False) self.loadPerspectiveAction = Qt.QAction(Qt.QIcon.fromTheme( "document-open"), 'Load Perspective ...', self) - self.loadPerspectiveAction.triggered[()].connect(self.loadPerspective) + self.loadPerspectiveAction.triggered.connect( + partial(self.loadPerspective, name=None, settings=None)) self.savePerspectiveAction = Qt.QAction(Qt.QIcon.fromTheme( "document-save"), 'Save Perspective ...', self) - self.savePerspectiveAction.triggered[()].connect(self.savePerspective) + self.savePerspectiveAction.triggered.connect( + partial(self.savePerspective, name=None)) self.deletePerspectiveAction = Qt.QAction( Qt.QIcon("actions:edit-delete.svg"), 'Delete Perspective ...', self) - self.deletePerspectiveAction.triggered[()].connect(self.removePerspective) + self.deletePerspectiveAction.triggered.connect( + partial(self.removePerspective, name=None, settings=None)) self.exportSettingsFileAction = Qt.QAction( Qt.QIcon.fromTheme("document-save"), 'Export Settings ...', self) - self.exportSettingsFileAction.triggered[()].connect(self.exportSettingsFile) + self.exportSettingsFileAction.triggered.connect( + partial(self.exportSettingsFile, fname=None)) self.importSettingsFileAction = Qt.QAction( Qt.QIcon.fromTheme("document-open"), 'Import Settings ...', self) - self.importSettingsFileAction.triggered[()].connect(self.importSettingsFile) + self.importSettingsFileAction.triggered.connect( + partial(self.importSettingsFile, fname=None)) #self.resetSettingsAction = Qt.QAction(Qt.QIcon.fromTheme("edit-undo"),'Reset Settings', self) #self.connect(self.resetSettingsAction, Qt.SIGNAL("triggered()"), self.resetSettings) self.configurationAction = Qt.QAction(Qt.QIcon.fromTheme( "preferences-system"), 'Configurations ...', self) - self.configurationAction.triggered[()].connect(self.configurationDialog.show) + self.configurationAction.triggered.connect( + self.configurationDialog.show) #self.rpdb2Action = Qt.QAction("Spawn rpdb2", self) self.spawnRpdb2Shortcut = Qt.QShortcut(self) @@ -610,25 +634,19 @@ def loadSettings(self, settings=None, group=None, ignoreGeometry=False, factoryS if group is not None: settings.beginGroup(group) if not ignoreGeometry: - # With API2, from_qvariant is returning None instead - ba = Qt.from_qvariant(settings.value( - "MainWindow/Geometry"), 'toByteArray') or Qt.QByteArray() - # of an empty QByTeArray - # and this caused an exception later on. Hence the "or" + ba = settings.value("MainWindow/Geometry") or Qt.QByteArray() self.restoreGeometry(ba) # restore the Taurus config try: - ba = Qt.from_qvariant(settings.value( - "TaurusConfig"), 'toByteArray') or Qt.QByteArray() + ba = settings.value("TaurusConfig") or Qt.QByteArray() self.applyQConfig(ba) - except Exception, e: + except Exception as e: msg = 'Problem loading configuration from "%s". Some settings may not be restored.\n Details: %s' % ( - unicode(settings.fileName()), repr(e)) + str(settings.fileName()), repr(e)) self.error(msg) Qt.QMessageBox.warning( self, 'Error Loading settings', msg, Qt.QMessageBox.Ok) - ba = Qt.from_qvariant(settings.value( - "MainWindow/State"), 'toByteArray') or Qt.QByteArray() + ba = settings.value("MainWindow/State") or Qt.QByteArray() self.restoreState(ba) # hide all dockwidgets (so that they are shown only if they were # present in the settings) @@ -637,8 +655,7 @@ def loadSettings(self, settings=None, group=None, ignoreGeometry=False, factoryS for d in dockwidgets: r = self.restoreDockWidget(d) d.hide() - ba = Qt.from_qvariant(settings.value( - "MainWindow/State"), 'toByteArray') or Qt.QByteArray() + ba = settings.value("MainWindow/State") or Qt.QByteArray() self.restoreState(ba) if group is not None: @@ -659,16 +676,17 @@ def saveSettings(self, group=None): if group is not None: settings.beginGroup(group) # main window geometry - settings.setValue("MainWindow/State", Qt.QVariant(self.saveState())) - settings.setValue("MainWindow/Geometry", - Qt.QVariant(self.saveGeometry())) + settings.setValue("MainWindow/State", self.saveState()) + settings.setValue("MainWindow/Geometry", self.saveGeometry()) # store the config dict - settings.setValue("TaurusConfig", Qt.QVariant(self.createQConfig())) + settings.setValue("TaurusConfig", self.createQConfig()) if group is not None: settings.endGroup() self.info('MainWindow settings saved in "%s"' % settings.fileName()) + @Qt.pyqtSlot() + @Qt.pyqtSlot('QString') def savePerspective(self, name=None): '''Stores current state of the application as a perspective with the given name @@ -676,18 +694,26 @@ def savePerspective(self, name=None): ''' perspectives = self.getPerspectivesList() if name is None: - name, ok = Qt.QInputDialog.getItem(self, "Save Perspective", "Store current settings as the following perspective:", - perspectives, 0, True) + name, ok = Qt.QInputDialog.getItem( + self, "Save Perspective", + "Store current settings as the following perspective:", + perspectives, 0, True + ) if not ok: return if name in perspectives: - ans = Qt.QMessageBox.question(self, "Overwrite perspective?", "overwrite existing perspective %s?" % unicode(name), - Qt.QMessageBox.Yes, Qt.QMessageBox.No) + ans = Qt.QMessageBox.question( + self, "Overwrite perspective?", + "overwrite existing perspective %s?" % str(name), + Qt.QMessageBox.Yes, Qt.QMessageBox.No + ) if ans != Qt.QMessageBox.Yes: return self.saveSettings(group="Perspectives/%s" % name) self.updatePerspectivesMenu() + @Qt.pyqtSlot() + @Qt.pyqtSlot('QString') def loadPerspective(self, name=None, settings=None): '''Loads the settings saved for the given perspective. It emits a 'perspectiveChanged' signal with name as its parameter @@ -726,6 +752,8 @@ def getPerspectivesList(self, settings=None): settings.endGroup() return names + @Qt.pyqtSlot() + @Qt.pyqtSlot('QString') def removePerspective(self, name=None, settings=None): '''removes the given perspective from the settings @@ -753,24 +781,30 @@ def removePerspective(self, name=None, settings=None): settings.endGroup() self.updatePerspectivesMenu() + @Qt.pyqtSlot() + @Qt.pyqtSlot('QString') def exportSettingsFile(self, fname=None): '''copies the current settings file into the given file name. :param fname: (str) name of output file. If None given, a file dialog will be shown. ''' if fname is None: - fname = unicode(Qt.QFileDialog.getSaveFileName(self, 'Choose file where the current settings should be saved', - '', "Ini files (*.ini);;All files (*)")) + fname, _ = compat.getSaveFileName( + self, 'Choose file where the current settings should be saved', + '', "Ini files (*.ini);;All files (*)" + ) if not fname: return self.saveSettings() ok = Qt.QFile.copy(self.getQSettings().fileName(), fname) if ok: - self.info('MainWindow settings saved in "%s"' % unicode(fname)) + self.info('MainWindow settings saved in "%s"' % str(fname)) else: - msg = 'Settings could not be exported to %s' % unicode(fname) + msg = 'Settings could not be exported to %s' % str(fname) Qt.QMessageBox.warning(self, 'Export error', msg) + @Qt.pyqtSlot() + @Qt.pyqtSlot('QString') def importSettingsFile(self, fname=None): ''' loads settings (including importing all perspectives) from a given ini @@ -779,8 +813,10 @@ def importSettingsFile(self, fname=None): :param fname: (str) name of ini file. If None given, a file dialog will be shown. ''' if fname is None: - fname = unicode(Qt.QFileDialog.getOpenFileName(self, 'Select a ini-format settings file', - '', "Ini files (*.ini);;All files (*)")) + fname, _ = compat.getOpenFileName( + self, 'Select a ini-format settings file', + '', "Ini files (*.ini);;All files (*)" + ) if not fname: return s = Qt.QSettings(fname, Qt.QSettings.IniFormat) @@ -935,8 +971,8 @@ def resetHelpManualURI(self): self.setHelpManualURI(uri) def showHelpAbout(self): - appname = unicode(Qt.qApp.applicationName()) - appversion = unicode(Qt.qApp.applicationVersion()) + appname = str(Qt.qApp.applicationName()) + appversion = str(Qt.qApp.applicationVersion()) from taurus.core import release abouttext = "%s %s\n\nUsing %s %s" % ( appname, appversion, release.name, release.version) @@ -961,7 +997,7 @@ def checkSingleInstance(self, key=None): if key is None: from taurus.core.util.user import getSystemUserName username = getSystemUserName() - appname = unicode(Qt.QApplication.applicationName()) + appname = str(Qt.QApplication.applicationName()) key = "__socket_%s-%s__" % (username, appname) from taurus.external.qt import QtNetwork socket = QtNetwork.QLocalSocket(self) diff --git a/lib/taurus/qt/qtgui/container/taurusscrollarea.py b/lib/taurus/qt/qtgui/container/taurusscrollarea.py index f120e5d02..2dab74870 100644 --- a/lib/taurus/qt/qtgui/container/taurusscrollarea.py +++ b/lib/taurus/qt/qtgui/container/taurusscrollarea.py @@ -25,14 +25,16 @@ """This module provides basic taurus scroll area widget""" -__all__ = ["TaurusScrollArea"] - -__docformat__ = 'restructuredtext' +from __future__ import absolute_import from taurus.external.qt import Qt from taurus.qt.qtgui.base import TaurusBaseComponent +from .taurusbasecontainer import TaurusBaseContainer + -from taurusbasecontainer import TaurusBaseContainer +__all__ = ["TaurusScrollArea"] + +__docformat__ = 'restructuredtext' class TaurusScrollArea(Qt.QScrollArea, TaurusBaseContainer): diff --git a/lib/taurus/qt/qtgui/container/tauruswidget.py b/lib/taurus/qt/qtgui/container/tauruswidget.py index f91fb9baf..e09887bb6 100644 --- a/lib/taurus/qt/qtgui/container/tauruswidget.py +++ b/lib/taurus/qt/qtgui/container/tauruswidget.py @@ -25,13 +25,16 @@ """This module provides basic taurus container widget""" +from __future__ import absolute_import + +from taurus.external.qt import Qt +from .taurusbasecontainer import TaurusBaseContainer + + __all__ = ["TaurusWidget"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt -from taurusbasecontainer import TaurusBaseContainer - class TaurusWidget(Qt.QWidget, TaurusBaseContainer): """This is a Qt.QWidget that additionally accepts a model property. diff --git a/lib/taurus/qt/qtgui/dialog/taurusinputdialog.py b/lib/taurus/qt/qtgui/dialog/taurusinputdialog.py index f965aa9c2..2bf93d434 100644 --- a/lib/taurus/qt/qtgui/dialog/taurusinputdialog.py +++ b/lib/taurus/qt/qtgui/dialog/taurusinputdialog.py @@ -24,13 +24,15 @@ ############################################################################# """This module provides a set of dialog based widgets""" +from __future__ import print_function + +from taurus.external.qt import Qt + __all__ = ["TaurusInputDialog", "get_input"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt - def get_input(input_data, parent=None, input_panel_klass=None): """Static convenience function to get an input from the user using a @@ -199,7 +201,7 @@ def main(): data_type='Text', key="Memo", default_value="By default a memo is\na long thing") for d in [d1, d2, d3, d4, d5, d6, d7, d8]: - print get_input(input_data=d, title=d['prompt']) + print(get_input(input_data=d, title=d['prompt'])) if __name__ == "__main__": main() diff --git a/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py b/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py index 1d8142e73..cef60f27b 100644 --- a/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py +++ b/lib/taurus/qt/qtgui/dialog/taurusmessagebox.py @@ -25,20 +25,25 @@ """This module provides a set of dialog based widgets""" -__all__ = ["TaurusMessageBox", "protectTaurusMessageBox", - "ProtectTaurusMessageBox", "TaurusExceptHookMessageBox"] +from future import standard_library +standard_library.install_aliases() -__docformat__ = 'restructuredtext' +from builtins import object import sys from taurus.external.qt import Qt - from taurus.core.util.excepthook import BaseExceptHook from taurus.core.util.log import LogExceptHook from taurus.core.util.wrap import wraps +__all__ = ["TaurusMessageBox", "protectTaurusMessageBox", + "ProtectTaurusMessageBox", "TaurusExceptHookMessageBox"] + +__docformat__ = 'restructuredtext' + + class TaurusMessageBox(Qt.QDialog): """A panel intended to display a taurus error. Example:: @@ -334,11 +339,18 @@ def py_tg_serv_exc(): try: PyTango.Except.throw_exception('TangoException', 'A simple tango exception', 'right here') - except PyTango.DevFailed, df1: + except PyTango.DevFailed as df1: try: import traceback - import StringIO - origin = StringIO.StringIO() + # --------------------------------------------------------------- + # workaround for unicode issues on py2 when using io instead of + # StringIO + try: + import StringIO as io # py2 + except ImportError: + import io # py3 + # ---------------------------------------------------------------- + origin = io.StringIO() traceback.print_stack(file=origin) origin.seek(0) origin = origin.read() diff --git a/lib/taurus/qt/qtgui/display/demo/qpixmapwidgetdemo.py b/lib/taurus/qt/qtgui/display/demo/qpixmapwidgetdemo.py index 1ec7c3b20..b72f721ee 100644 --- a/lib/taurus/qt/qtgui/display/demo/qpixmapwidgetdemo.py +++ b/lib/taurus/qt/qtgui/display/demo/qpixmapwidgetdemo.py @@ -88,12 +88,12 @@ def __init__(self, parent=None): aspect_ratio_widget.addItems( ["Ignore", "Keep", "Keep by expanding"]) transformation_widget.addItems(["Fast", "Smooth"]) - halign_widget.addItem("Left", Qt.QVariant(Qt.Qt.AlignLeft)) - halign_widget.addItem("Center", Qt.QVariant(Qt.Qt.AlignHCenter)) - halign_widget.addItem("Right", Qt.QVariant(Qt.Qt.AlignRight)) - valign_widget.addItem("Top", Qt.QVariant(Qt.Qt.AlignTop)) - valign_widget.addItem("Center", Qt.QVariant(Qt.Qt.AlignVCenter)) - valign_widget.addItem("Bottom", Qt.QVariant(Qt.Qt.AlignBottom)) + halign_widget.addItem("Left", Qt.Qt.AlignLeft) + halign_widget.addItem("Center", Qt.Qt.AlignHCenter) + halign_widget.addItem("Right", Qt.Qt.AlignRight) + valign_widget.addItem("Top", Qt.Qt.AlignTop) + valign_widget.addItem("Center", Qt.Qt.AlignVCenter) + valign_widget.addItem("Bottom", Qt.Qt.AlignBottom) pixmap_widget.textChanged.connect(self.changePixmap) aspect_ratio_widget.currentIndexChanged.connect(self.changeAspectRatio) @@ -134,9 +134,7 @@ def changeTransformationMode(self, i): def changeAlignment(self, i): halign = self.w_halign.itemData(self.w_halign.currentIndex()) - halign = Qt.from_qvariant(halign, int) valign = self.w_valign.itemData(self.w_valign.currentIndex()) - valign = Qt.from_qvariant(valign, int) self.w.alignment = halign | valign panel = Qt.QWidget() diff --git a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py index 3bdafe6da..93136d3d5 100644 --- a/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py +++ b/lib/taurus/qt/qtgui/display/demo/tauruslabeldemo.py @@ -97,8 +97,8 @@ def __init__(self, parent=None): model_widget.textChanged.connect(w.setModel) format_widget.textChanged.connect(w.setFormat) - fg_widget.currentIndexChanged[str].connect(w.setFgRole) - bg_widget.currentIndexChanged[str].connect(w.setBgRole) + fg_widget.currentIndexChanged['QString'].connect(w.setFgRole) + bg_widget.currentIndexChanged['QString'].connect(w.setBgRole) prefix_widget.textChanged.connect(w.setPrefixText) suffix_widget.textChanged.connect(w.setSuffixText) model_index_widget.textChanged.connect(w.setModelIndex) diff --git a/lib/taurus/qt/qtgui/display/demo/tauruslcddemo.py b/lib/taurus/qt/qtgui/display/demo/tauruslcddemo.py index e55ea38c0..91b674863 100644 --- a/lib/taurus/qt/qtgui/display/demo/tauruslcddemo.py +++ b/lib/taurus/qt/qtgui/display/demo/tauruslcddemo.py @@ -88,8 +88,8 @@ def __init__(self, parent=None): model_widget.textChanged.connect(w.setModel) model_index_widget.textChanged.connect(w.setModelIndex) - fg_widget.currentIndexChanged[str].connect(w.setFgRole) - bg_widget.currentIndexChanged[str].connect(w.setBgRole) + fg_widget.currentIndexChanged['QString'].connect(w.setFgRole) + bg_widget.currentIndexChanged['QString'].connect(w.setBgRole) model_widget.setText("sys/tg_test/1/double_scalar") fg_widget.setCurrentIndex(0) diff --git a/lib/taurus/qt/qtgui/display/demo/taurusleddemo.py b/lib/taurus/qt/qtgui/display/demo/taurusleddemo.py index 0df9382c6..796b1ccba 100644 --- a/lib/taurus/qt/qtgui/display/demo/taurusleddemo.py +++ b/lib/taurus/qt/qtgui/display/demo/taurusleddemo.py @@ -94,10 +94,10 @@ def __init__(self, parent=None): inverted_widget.toggled.connect(w.setLedInverted) model_widget.textChanged.connect(w.setModel) - fg_widget.currentIndexChanged[str].connect(w.setFgRole) + fg_widget.currentIndexChanged['QString'].connect(w.setFgRole) model_index_widget.textChanged.connect(w.setModelIndex) - on_color_widget.currentIndexChanged[str].connect(w.setOnColor) - off_color_widget.currentIndexChanged[str].connect(w.setOffColor) + on_color_widget.currentIndexChanged['QString'].connect(w.setOnColor) + off_color_widget.currentIndexChanged['QString'].connect(w.setOffColor) inverted_widget.setChecked(False) model_widget.setText("sys/tg_test/1/state") diff --git a/lib/taurus/qt/qtgui/display/qfallback.py b/lib/taurus/qt/qtgui/display/qfallback.py index ec8e8aae2..a1e771497 100644 --- a/lib/taurus/qt/qtgui/display/qfallback.py +++ b/lib/taurus/qt/qtgui/display/qfallback.py @@ -58,7 +58,7 @@ def __init__(self, replaces=None, parent=None, *args, **kwargs): self.replaces = replaces self.exc_info = exc_info = kwargs.get("exc_info") layout = Qt.QVBoxLayout(self) - layout.setMargin(2) + layout.setContentsMargins(2, 2, 2, 2) layout.setSpacing(2) self.setLayout(layout) self.label = Qt.QLabel(self) diff --git a/lib/taurus/qt/qtgui/display/qled.py b/lib/taurus/qt/qtgui/display/qled.py index fd5bce969..0cb1e040f 100644 --- a/lib/taurus/qt/qtgui/display/qled.py +++ b/lib/taurus/qt/qtgui/display/qled.py @@ -98,7 +98,7 @@ def isLedColorValid(self, name): :type color: str :return: True is the given color name is valid or False otherwise :rtype: bool""" - return LedColor.has_key(name.upper()) + return name.upper() in LedColor def _refresh(self): """internal usage only""" @@ -289,8 +289,8 @@ def resetBlinkingInterval(self): class QLedOld(Qt.QLabel): ledDirPattern = ":leds/images%(size)d" - def __init__(self, parent=None, ledsize=LedSize.SMALL, ledcolor=LedColor.GREEN): - + def __init__(self, parent=None, ledsize=LedSize.SMALL, + ledcolor=LedColor.GREEN): Qt.QLabel.__init__(self, parent) self.ledsize = ledsize diff --git a/lib/taurus/qt/qtgui/display/qpixmapwidget.py b/lib/taurus/qt/qtgui/display/qpixmapwidget.py index 38a0bdcd5..81f54154e 100644 --- a/lib/taurus/qt/qtgui/display/qpixmapwidget.py +++ b/lib/taurus/qt/qtgui/display/qpixmapwidget.py @@ -25,12 +25,15 @@ """This module contains a pure Qt widget that displays an image""" +from __future__ import absolute_import + +from taurus.external.qt import Qt + + __all__ = ["QPixmapWidget"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt - class QPixmapWidget(Qt.QWidget): """This widget displays an image (pixmap). By default the pixmap is @@ -79,11 +82,11 @@ def paintEvent(self, paintEvent): vAlign = align & Qt.Qt.AlignVertical_Mask x, y = 0, 0 if hAlign & Qt.Qt.AlignHCenter: - x = (w - pw) / 2 + x = (w - pw) // 2 elif hAlign & Qt.Qt.AlignRight: x = w - pw if vAlign & Qt.Qt.AlignVCenter: - y = (h - ph) / 2 + y = (h - ph) // 2 elif vAlign & Qt.Qt.AlignBottom: y = h - ph x, y = max(0, x), max(0, y) @@ -225,8 +228,9 @@ def resetAlignment(self): def demo(): "QPixmap Widget" - import demo.qpixmapwidgetdemo - return demo.qpixmapwidgetdemo.main() + from .demo import qpixmapwidgetdemo # after futurize stage1 + return qpixmapwidgetdemo.main() + def main(): diff --git a/lib/taurus/qt/qtgui/display/qsevensegment.py b/lib/taurus/qt/qtgui/display/qsevensegment.py index 8287f8820..191723045 100644 --- a/lib/taurus/qt/qtgui/display/qsevensegment.py +++ b/lib/taurus/qt/qtgui/display/qsevensegment.py @@ -27,12 +27,15 @@ qsevensegmentdisplay.py """ +from __future__ import print_function + +from taurus.external.qt import Qt + + __all__ = ['Q7SegDigit'] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt - POLY = Qt.QPolygonF P = Qt.QPointF @@ -133,7 +136,7 @@ class Q7SegDigit(Qt.QWidget): DftWidth = 300 DftHeight = 300 - DftAspectRatio = DftWidth / DftHeight + DftAspectRatio = DftWidth // DftHeight DftUseFrame = True def __init__(self, parent=None, **kwargs): @@ -247,7 +250,7 @@ def _paintSegment(self, painter): pens, brushes = self._pens[idx], self._brushes[idx] - for i in xrange(7): + for i in range(7): seg = Qt.QPainterPath() seg.addPolygon(geom[i]) painter.setPen(pens[i]) @@ -501,11 +504,11 @@ def __init__(self, qt_parent=None): def __init(self): l = Qt.QHBoxLayout() l.setSpacing(10) - l.setMargin(5) + l.setContentsMargins(5, 5, 5, 5) self.setLayout(l) self._digits = [] - for i in xrange(5): + for i in range(5): d = Q7SegDigit() d.setUseFrame(False) d.setValue(i) @@ -574,7 +577,7 @@ def main2(): dw = Q7SegDigit() dw.setValue(int(sys.argv[1])) dw.setVisible(True) - print dw + print(dw) a.exec_() diff --git a/lib/taurus/qt/qtgui/display/tauruslabel.py b/lib/taurus/qt/qtgui/display/tauruslabel.py index 5fe0f1ead..999f09bc0 100644 --- a/lib/taurus/qt/qtgui/display/tauruslabel.py +++ b/lib/taurus/qt/qtgui/display/tauruslabel.py @@ -24,12 +24,12 @@ ############################################################################# """This module provides a set of basic Taurus widgets based on QLabel""" +from __future__ import absolute_import -__all__ = ["TaurusLabel"] - -__docformat__ = 'restructuredtext' +from builtins import str +from builtins import object -import operator +import collections import re from taurus.core.taurusbasetypes import (TaurusElementType, TaurusEventType, @@ -41,6 +41,11 @@ from taurus.qt.qtgui.base import TaurusConfigurationControllerHelper from taurus.qt.qtgui.base import updateLabelBackground + +__all__ = ["TaurusLabel"] + +__docformat__ = 'restructuredtext' + _QT_PLUGIN_INFO = { 'module': 'taurus.qt.qtgui.display', 'group': 'Taurus Display', @@ -113,9 +118,7 @@ def _updateToolTip(self, label): return toolTip = label.getFormatedToolTip() if self._trimmedText: - toolTip = u"

Value: %s


%s" %\ - (unicode(self._text, errors='replace'), - unicode(str(toolTip), errors='replace')) + toolTip = u"

Value: %s


%s" % (self._text, toolTip) label.setToolTip(toolTip) _updateBackground = updateLabelBackground @@ -344,7 +347,7 @@ def setModelIndex(self, modelIndex): return if type(mi_value) == int: mi_value = mi_value, - if not operator.isSequenceType(mi_value): + if not isinstance(mi_value, collections.Sequence): return self._modelIndex = mi_value self._modelIndexStr = mi @@ -487,8 +490,15 @@ def displayValue(self, v): else: value = self._permanentText - attr = self.getModelObj() - dev = attr.getParent() + dev = None + attr = None + + modeltype = self.getModelType() + if modeltype == TaurusElementType.Device: + dev = self.getModelObj() + elif modeltype == TaurusElementType.Attribute: + attr = self.getModelObj() + dev = attr.getParent() try: v = value.format(dev=dev, attr=attr) @@ -641,8 +651,8 @@ def getQtDesignerPluginInfo(cls): def demo(): "Label" - import demo.tauruslabeldemo - return demo.tauruslabeldemo.main() + from .demo import tauruslabeldemo + return tauruslabeldemo.main() def main(): diff --git a/lib/taurus/qt/qtgui/display/tauruslcd.py b/lib/taurus/qt/qtgui/display/tauruslcd.py index 3c63c3167..dcc395ad5 100644 --- a/lib/taurus/qt/qtgui/display/tauruslcd.py +++ b/lib/taurus/qt/qtgui/display/tauruslcd.py @@ -25,11 +25,12 @@ """This module provides a Taurus widget based on QLCDNumber""" -__all__ = ["TaurusLCD"] +from __future__ import absolute_import -__docformat__ = 'restructuredtext' +from builtins import str +from builtins import object -import operator +import collections from taurus.core.taurusbasetypes import (TaurusElementType, TaurusEventType, AttrQuality, TaurusDevState) @@ -40,6 +41,11 @@ from taurus.qt.qtgui.base import TaurusConfigurationControllerHelper from taurus.qt.qtgui.base import updateLabelBackground + +__all__ = ["TaurusLCD"] + +__docformat__ = 'restructuredtext' + _QT_PLUGIN_INFO = { 'module': 'taurus.qt.qtgui.display', 'group': 'Taurus Display', @@ -106,7 +112,7 @@ def _getDisplayValue(self, widget, valueObj, idx, write): for i in idx: value = value[i] return widget.displayValue(value) - except Exception, e: + except Exception as e: return widget.getNoneValue() @@ -278,7 +284,7 @@ def setModelIndex(self, modelIndex): return if type(mi_value) == int: mi_value = mi_value, - if not operator.isSequenceType(mi_value): + if not isinstance(mi_value, collections.Sequence): return self._modelIndex = mi_value self._modelIndexStr = mi @@ -383,8 +389,8 @@ def getQtDesignerPluginInfo(cls): def demo(): "LCD" - import demo.tauruslcddemo - return demo.tauruslcddemo.main() + from .demo import tauruslcddemo + return tauruslcddemo.main() def main(): diff --git a/lib/taurus/qt/qtgui/display/taurusled.py b/lib/taurus/qt/qtgui/display/taurusled.py index f837f0495..b4195a7a6 100644 --- a/lib/taurus/qt/qtgui/display/taurusled.py +++ b/lib/taurus/qt/qtgui/display/taurusled.py @@ -25,20 +25,25 @@ ############################################################################# """This module provides a set of basic Taurus widgets based on QLed""" +from __future__ import absolute_import -__all__ = ["TaurusLed"] - -__docformat__ = 'restructuredtext' +from builtins import str +from builtins import object import weakref -import operator +import collections from taurus.external.qt import Qt from taurus.core import DataFormat, AttrQuality, DataType from taurus.qt.qtgui.base import TaurusBaseWidget -from qled import QLed +from .qled import QLed + + +__all__ = ["TaurusLed"] + +__docformat__ = 'restructuredtext' _QT_PLUGIN_INFO = { 'module': 'taurus.qt.qtgui.display', @@ -344,7 +349,7 @@ def setModelIndex(self, modelIndex): return if type(mi_value) == int: mi_value = mi_value, - if not operator.isSequenceType(mi_value): + if not isinstance(mi_value, collections.Sequence): return self._modelIndex = mi_value self._modelIndexStr = mi @@ -448,8 +453,8 @@ def getQtDesignerPluginInfo(cls): def demo(): "Led" - import demo.taurusleddemo - return demo.taurusleddemo.main() + from .demo import taurusleddemo + return taurusleddemo.main() def main(): diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/builder.py b/lib/taurus/qt/qtgui/extra_guiqwt/builder.py index cebd30898..ffb06e990 100644 --- a/lib/taurus/qt/qtgui/extra_guiqwt/builder.py +++ b/lib/taurus/qt/qtgui/extra_guiqwt/builder.py @@ -24,24 +24,26 @@ ############################################################################# """Extension of :mod:`guiqwt.builder`""" - -__all__ = ["TaurusPlotItemBuilder", "make"] - -__docformat__ = 'restructuredtext' +from __future__ import absolute_import import guiqwt.builder +import numpy -from curve import TaurusCurveItem, TaurusTrendItem -from image import (TaurusImageItem, TaurusRGBImageItem, TaurusEncodedImageItem, +from .curve import TaurusCurveItem, TaurusTrendItem +from .image import (TaurusImageItem, TaurusRGBImageItem, TaurusEncodedImageItem, TaurusEncodedRGBImageItem, TaurusXYImageItem) from guiqwt.curve import CurveParam from guiqwt.image import ImageParam, XYImageItem from guiqwt.styles import XYImageParam from guiqwt.config import _ from guiqwt.histogram import lut_range_threshold -import numpy from taurus.core.units import Quantity +__all__ = ["TaurusPlotItemBuilder", "make"] + +__docformat__ = 'restructuredtext' + + class TaurusPlotItemBuilder(guiqwt.builder.PlotItemBuilder): '''extension of :class:`guiqwt.builder.PlotItemBuilder` to provide tauruscurve and taurusimage items''' diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/curve.py b/lib/taurus/qt/qtgui/extra_guiqwt/curve.py index fc93efc3d..18f3d6bf7 100644 --- a/lib/taurus/qt/qtgui/extra_guiqwt/curve.py +++ b/lib/taurus/qt/qtgui/extra_guiqwt/curve.py @@ -24,8 +24,9 @@ ############################################################################# """Extension of :mod:`guiqwt.curve`""" +from __future__ import print_function -__all__ = ["TaurusCurveItem"] +from builtins import next from taurus.external.qt import Qt from taurus.qt.qtgui.base import TaurusBaseComponent @@ -37,6 +38,8 @@ from taurus.core.util.containers import ArrayBuffer import numpy +__all__ = ["TaurusCurveItem"] + class TaurusCurveItem(CurveItem, TaurusBaseComponent): '''A CurveItem that autoupdates its values & params when x or y components change''' @@ -356,11 +359,11 @@ def taurusCurveMain(): elif n == 2: mx, my = mx_my else: - print "Invalid model: %s\n" % mx_my + print("Invalid model: %s\n" % mx_my) parser.print_help(sys.stderr) sys.exit(1) # cycle colors - style = make.style.next() + style = next(make.style) color = style[0] linestyle = style[1:] plot.add_item(make.curve(mx, my, color=color, diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py b/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py index d212d74d0..8dc49bdec 100644 --- a/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py +++ b/lib/taurus/qt/qtgui/extra_guiqwt/curvesmodel.py @@ -26,7 +26,15 @@ """ curvesmodel Model and view for new CurveItem configuration """ + +from __future__ import print_function +from builtins import next +from builtins import str +from builtins import range +from builtins import object + __all__ = ['TaurusCurveItemTableModel', 'CurveItemConf', 'CurveItemConfDlg'] + #raise UnimplementedError('Under Construction!') import copy @@ -42,7 +50,7 @@ from taurus.qt.qtgui.extra_guiqwt.styles import TaurusCurveParam import guiqwt -__guiqwt_version = map(int, guiqwt.__version__.split('.')[:3]) +__guiqwt_version = list(map(int, guiqwt.__version__.split('.')[:3])) if __guiqwt_version <= [2, 3, 1]: import taurus.external.qt.Qwt5 as qwt else: @@ -54,7 +62,7 @@ # set some named constants # columns: NUMCOLS = 3 -X, Y, TITLE = range(NUMCOLS) +X, Y, TITLE = list(range(NUMCOLS)) SRC_ROLE = Qt.Qt.UserRole + 1 @@ -108,7 +116,7 @@ def __init__(self, taurusparam=None, curveparam=None, axesparam=None): self.taurusparam = taurusparam if curveparam is None: curveparam = CurveParam() - style = make.style.next() # cycle through colors and linestyles + style = next(make.style) # cycle through colors and linestyles update_style_attr(style, curveparam) curveparam.line.width = 2 self.curveparam = curveparam @@ -173,78 +181,76 @@ def columnCount(self, index=Qt.QModelIndex()): def data(self, index, role=Qt.Qt.DisplayRole): if not index.isValid() or not (0 <= index.row() < self.rowCount()): - return Qt.QVariant() + return None row = index.row() column = index.column() # Display Role if role == Qt.Qt.DisplayRole: if column == X: - return Qt.QVariant(str(self.curves[row].x.display)) + return str(self.curves[row].x.display) elif column == Y: - return Qt.QVariant(str(self.curves[row].y.display)) + return str(self.curves[row].y.display) elif column == TITLE: - return Qt.QVariant(str(self.curves[row].curveparam.label)) + return str(self.curves[row].curveparam.label) else: - return Qt.QVariant() + return None elif role == Qt.Qt.DecorationRole: if column == X: - return Qt.QVariant(self.curves[row].x.icon) + return self.curves[row].x.icon elif column == Y: - return Qt.QVariant(self.curves[row].y.icon) + return self.curves[row].y.icon else: - return Qt.QVariant() + return None elif role == Qt.Qt.TextColorRole: if column == X: - Qt.QVariant( - Qt.QColor(self.curves[row].x.ok and 'green' or 'red')) + Qt.QColor(self.curves[row].x.ok and 'green' or 'red') elif column == Y: - Qt.QVariant( - Qt.QColor(self.curves[row].y.ok and 'green' or 'red')) + Qt.QColor(self.curves[row].y.ok and 'green' or 'red') else: - return Qt.QVariant() + return None elif role == SRC_ROLE: if column == X: - return Qt.QVariant(str(self.curves[row].taurusparam.xModel)) + return str(self.curves[row].taurusparam.xModel) elif column == Y: - return Qt.QVariant(str(self.curves[row].taurusparam.yModel)) + return str(self.curves[row].taurusparam.yModel) else: - return Qt.QVariant() + return None elif role == Qt.Qt.ToolTipRole: if column == X: - return Qt.QVariant(str(self.curves[row].taurusparam.xModel)) + return str(self.curves[row].taurusparam.xModel) elif column == Y: - return Qt.QVariant(str(self.curves[row].taurusparam.yModel)) + return str(self.curves[row].taurusparam.yModel) else: - return Qt.QVariant() + return None if role == Qt.Qt.EditRole: if column == X: - return Qt.QVariant(str(self.curves[row].taurusparam.xModel)) + return str(self.curves[row].taurusparam.xModel) elif column == Y: - return Qt.QVariant(str(self.curves[row].taurusparam.yModel)) + return str(self.curves[row].taurusparam.yModel) elif column == TITLE: - return Qt.QVariant(str(self.curves[row].curveparam.label)) + return str(self.curves[row].curveparam.label) else: - return Qt.QVariant() - return Qt.QVariant() + return None + return None def headerData(self, section, orientation, role=Qt.Qt.DisplayRole): if role == Qt.Qt.TextAlignmentRole: if orientation == Qt.Qt.Horizontal: - return Qt.QVariant(int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter)) - return Qt.QVariant(int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter)) + return int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter) + return int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter) if role != Qt.Qt.DisplayRole: - return Qt.QVariant() + return None # So this is DisplayRole... if orientation == Qt.Qt.Horizontal: if section == X: - return Qt.QVariant("X source") + return "X source" elif section == Y: - return Qt.QVariant("Y Source") + return "Y Source" elif section == TITLE: - return Qt.QVariant("Title") - return Qt.QVariant() + return "Title" + return None else: - return Qt.QVariant(str(section + 1)) + return str(section + 1) def flags(self, index): # use this to set the editable flag when fix is selected if not index.isValid(): @@ -261,7 +267,6 @@ def setData(self, index, value=None, role=Qt.Qt.EditRole): row = index.row() curve = self.curves[row] column = index.column() - value = Qt.from_qvariant(value, str) if column == X: curve.taurusparam.xModel = value curve.x.processSrc(value) @@ -288,10 +293,11 @@ def insertRows(self, position=None, rows=1, parentindex=None): def removeRows(self, position, rows=1, parentindex=None): if parentindex is None: parentindex = Qt.QModelIndex() + self.beginResetModel() self.beginRemoveRows(parentindex, position, position + rows - 1) self.curves = self.curves[:position] + self.curves[position + rows:] self.endRemoveRows() - self.reset() + self.endResetModel() return True def clearAll(self): @@ -315,31 +321,28 @@ def dropMimeData(self, data, action, row, column, parent): column = parent.columnCount() if data.hasFormat(TAURUS_ATTR_MIME_TYPE): self.setData(self.index(row, column), - value=Qt.QVariant(str(data.data(TAURUS_ATTR_MIME_TYPE)))) + value=str(data.data(TAURUS_ATTR_MIME_TYPE))) return True elif data.hasFormat(TAURUS_MODEL_LIST_MIME_TYPE): models = str(data.data(TAURUS_MODEL_LIST_MIME_TYPE)).split() if len(models) == 1: - self.setData(self.index(row, column), - value=Qt.QVariant(models[0])) + self.setData(self.index(row, column), value=models[0]) return True else: self.insertRows(row, len(models)) for i, m in enumerate(models): - self.setData(self.index(row + i, column), - value=Qt.QVariant(m)) + self.setData(self.index(row + i, column), value=m) return True elif data.hasText(): - self.setData(self.index(row, column), Qt.QVariant(data.text())) + self.setData(self.index(row, column), data.text()) return True return False def mimeData(self, indexes): mimedata = Qt.QAbstractTableModel.mimeData(self, indexes) if len(indexes) == 1: - # data = Qt.from_qvariant(self.data(indexes[0], str) - # mimedata.setData(TAURUS_ATTR_MIME_TYPE, data) - data = Qt.from_qvariant(self.data(indexes[0], role=SRC_ROLE), str) + data = self.data(indexes[0], role=SRC_ROLE) + # mimedata.setData(TAURUS_ATTR_MIME_TYPE, data) mimedata.setText(data) return mimedata # mimedata.setData() @@ -452,8 +455,7 @@ def onModelsAdded(self, models): rowcount = self.model.rowCount() self.model.insertRows(rowcount, nmodels) for i, m in enumerate(models): - self.model.setData(self.model.index( - rowcount + i, Y), value=Qt.QVariant(m)) + self.model.setData(self.model.index(rowcount + i, Y), value=m) title = self.model.data(self.model.index( rowcount + i, Y)) # the display data self.model.setData(self.model.index( @@ -488,7 +490,7 @@ def onApply(self): self.applied.emit() def onReload(self): - print "RELOAD!!! (todo)" + print("RELOAD!!! (todo)") # diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/image.py b/lib/taurus/qt/qtgui/extra_guiqwt/image.py index 04d1783dd..1781043fc 100644 --- a/lib/taurus/qt/qtgui/extra_guiqwt/image.py +++ b/lib/taurus/qt/qtgui/extra_guiqwt/image.py @@ -69,7 +69,7 @@ def handleEvent(self, evt_src, evt_type, evt_value): # TODO: units should be used for setting some title in the colorbar try: v = self.filterData(v) - except Exception, e: + except Exception as e: self.info('Ignoring event. Reason: %s', e.message) return # this is the range of the z axis (color scale) @@ -143,7 +143,7 @@ def filterData(self, data): try: fmt, decoded_data = codec.decode(data) - except Exception, e: + except Exception as e: self.info('Decoder error: %s', e.message) raise e diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/plot.py b/lib/taurus/qt/qtgui/extra_guiqwt/plot.py index 32f2db748..2ec85ac93 100644 --- a/lib/taurus/qt/qtgui/extra_guiqwt/plot.py +++ b/lib/taurus/qt/qtgui/extra_guiqwt/plot.py @@ -26,10 +26,12 @@ """ Extension of :mod:`guiqwt.plot` """ -__all__ = ["TaurusCurveDialog", "TaurusTrendDialog", "TaurusImageDialog"] +from builtins import next +from builtins import str import copy +from future.utils import string_types from guiqwt.plot import ImageDialog, CurveDialog import taurus.core @@ -42,6 +44,9 @@ from taurus.qt.qtgui.extra_guiqwt.curve import TaurusCurveItem, TaurusTrendParam, TaurusTrendItem +__all__ = ["TaurusCurveDialog", "TaurusTrendDialog", "TaurusImageDialog"] + + class TaurusCurveDialog(CurveDialog, TaurusBaseWidget): '''A taurus dialog for showing 1D data. It behaves as a regular :class:`guiqwt.plot.CurveDialog` but it also offers @@ -51,7 +56,7 @@ class TaurusCurveDialog(CurveDialog, TaurusBaseWidget): .. seealso:: :class:`TaurusCurveWidget` ''' _modifiableByUser = True - modelChanged = Qt.pyqtSignal([], ['QStringList'], [str]) + modelChanged = Qt.pyqtSignal([], ['QStringList'], ['QString']) def __init__(self, parent=None, designMode=False, toolbar=True, **kwargs): '''see :class:`guiqwt.plot.CurveDialog` for other valid initialization parameters''' @@ -82,7 +87,7 @@ def getModelClass(self): def _splitModel(self, modelNames): '''convert str to list if needed (commas and whitespace are considered as separators)''' - if isinstance(modelNames, (basestring, Qt.QString)): + if isinstance(modelNames, string_types): modelNames = str(modelNames).replace(',', ' ') modelNames = modelNames.split() return modelNames @@ -152,7 +157,7 @@ def addModels(self, modelNames): else: self.warning('Invalid model "%s" (Skipping)' % mx_my) # cycle styles - style = self.style.next() + style = next(self.style) color = style[0] linestyle = style[1:] # add the item @@ -198,7 +203,7 @@ class TaurusTrendDialog(CurveDialog, TaurusBaseWidget): ''' _modifiableByUser = True - modelChanged = Qt.pyqtSignal([], ['QStringList'], [str]) + modelChanged = Qt.pyqtSignal([], ['QStringList'], ['QString']) def __init__(self, parent=None, designMode=False, taurusparam=None, toolbar=True, **kwargs): '''see :class:`guiqwt.plot.CurveDialog` for other valid initialization parameters''' @@ -237,7 +242,7 @@ def getTaurusTrendItems(self): def _splitModel(self, modelNames): '''convert str to list if needed (commas and whitespace are considered as separators)''' - if isinstance(modelNames, (basestring, Qt.QString)): + if isinstance(modelNames, string_types): modelNames = str(modelNames).replace(',', ' ') modelNames = modelNames.split() return modelNames @@ -289,7 +294,7 @@ def addModels(self, modelNames): # create and attach new TaurusCurveItems for m in modelNames: # cycle styles - style = self.style.next() + style = next(self.style) # add the item item = make.ttrend(m, color=style[0], linestyle=style[ 1:], linewidth=2, taurusparam=copy.deepcopy(self.defaultTaurusparam)) diff --git a/lib/taurus/qt/qtgui/extra_guiqwt/scales.py b/lib/taurus/qt/qtgui/extra_guiqwt/scales.py index e51a4cad1..d73af7213 100644 --- a/lib/taurus/qt/qtgui/extra_guiqwt/scales.py +++ b/lib/taurus/qt/qtgui/extra_guiqwt/scales.py @@ -26,9 +26,7 @@ """ scales.py: Custom scales used by taurus.qt.qtgui.plot module """ -__all__ = ["DateTimeScaleEngine", "DeltaTimeScaleEngine", "FixedLabelsScaleEngine", - "FancyScaleDraw", "TaurusTimeScaleDraw", "DeltaTimeScaleDraw", - "FixedLabelsScaleDraw"] +from __future__ import print_function import numpy from datetime import datetime, timedelta @@ -36,13 +34,17 @@ from taurus.external.qt import Qt import guiqwt -__guiqwt_version = map(int, guiqwt.__version__.split('.')[:3]) +__guiqwt_version = list(map(int, guiqwt.__version__.split('.')[:3])) if __guiqwt_version <= [2, 3, 1]: import taurus.external.qt.Qwt5 as qwt else: import qwt +__all__ = ["DateTimeScaleEngine", "DeltaTimeScaleEngine", "FixedLabelsScaleEngine", + "FancyScaleDraw", "TaurusTimeScaleDraw", "DeltaTimeScaleDraw", + "FixedLabelsScaleDraw"] + def _getDefaultAxisLabelsAlignment(axis, rotation): '''return a "smart" alignment for the axis labels depending on the axis @@ -225,7 +227,7 @@ def divideScale(self, x1, x2, maxMajSteps, maxMinSteps, stepSize): elif dx > 2: # 2s format = "%H:%M:%S" - majticks = range(int(x1) + 1, int(x2)) + majticks = list(range(int(x1) + 1, int(x2))) else: # less than 2s (show microseconds) scaleDiv = qwt.QwtLinearScaleEngine.divideScale( @@ -326,8 +328,8 @@ def label(self, val): t = datetime.fromtimestamp(val) try: # If the scaleDiv was created by a DateTimeScaleEngine it has a _datetimeLabelFormat s = t.strftime(self._datetimeLabelFormat) - except AttributeError, e: - print "Warning: cannot get the datetime label format (Are you using a DateTimeScaleEngine?)" + except AttributeError as e: + print("Warning: cannot get the datetime label format (Are you using a DateTimeScaleEngine?)") s = t.isoformat(' ') return qwt.QwtText(s) diff --git a/lib/taurus/qt/qtgui/extra_nexus/taurusnexuswidget.py b/lib/taurus/qt/qtgui/extra_nexus/taurusnexuswidget.py index 69e862afe..c5715f193 100644 --- a/lib/taurus/qt/qtgui/extra_nexus/taurusnexuswidget.py +++ b/lib/taurus/qt/qtgui/extra_nexus/taurusnexuswidget.py @@ -27,16 +27,19 @@ nexusWidget.py: """ -__all__ = ["TaurusNexusBrowser"] +from builtins import str import numpy import posixpath +from functools import partial +from taurus.external.qt import Qt, compat from PyMca5.PyMcaGui.io.hdf5 import HDF5Widget, HDF5Info, HDF5DatasetTable -from taurus.external.qt import Qt from taurus.qt.qtgui.container import TaurusWidget -from taurus.qt.qtgui.plot import TaurusPlot + + +__all__ = ["TaurusNexusBrowser"] class NeXusInfoWidget(Qt.QTabWidget): @@ -102,17 +105,19 @@ def __init__(self, *args, **kwargs): # connections self.__fileModel.sigFileAppended.connect(self.treeWidget.fileAppended) self.treeWidget.sigHDF5WidgetSignal.connect(self.onHDF5WidgetSignal) - self.openFileAction.triggered[()].connect(self.openFile) + self.openFileAction.triggered.connect( + partial(self.openFile, fname=None)) self.togglePreviewAction.toggled.connect(self.__previewStack.setVisible) # configuration self.registerConfigProperty( self.togglePreviewAction.isChecked, self.togglePreviewAction.setChecked, 'showPreview') + @Qt.pyqtSlot() + @Qt.pyqtSlot('QString') def openFile(self, fname=None): if fname is None: - fname = unicode(Qt.QFileDialog.getOpenFileName( - self, "Choose NeXus File", "/home/cpascual/local/tmp/scantest.h5")) # @TODO!! + fname, _ = compat.getOpenFileName(self, 'Choose NeXus File', '') if fname: self.__nexusFile = self.__fileModel.openFile(fname) @@ -129,8 +134,13 @@ def neXusPreviewWidgetFactory(self, ddict): node = ddict['name'] data = self.__nexusFile[node] if len(data.shape) == 1 and isinstance(data[0], (numpy.floating, numpy.integer, int, float)): - w = TaurusPlot() - w.attachRawData({"x": numpy.arange(len(data)), "y": data}) + try: + import pyqtgraph as pg + w = pg.PlotWidget() + w.plot(data) + except ImportError: + w = HDF5DatasetTable.HDF5DatasetTable() + w.setDataset(data) else: w = HDF5DatasetTable.HDF5DatasetTable() w.setDataset(data) diff --git a/lib/taurus/qt/qtgui/graphic/jdraw/jdraw.py b/lib/taurus/qt/qtgui/graphic/jdraw/jdraw.py index caf8faac6..5f99ead13 100644 --- a/lib/taurus/qt/qtgui/graphic/jdraw/jdraw.py +++ b/lib/taurus/qt/qtgui/graphic/jdraw/jdraw.py @@ -25,9 +25,10 @@ """This module contains the graphics factory for the jdraw file format""" -__all__ = ["TaurusJDrawGraphicsFactory"] +from __future__ import absolute_import -__docformat__ = 'restructuredtext' +from builtins import str +from builtins import range import os import traceback @@ -40,6 +41,11 @@ TaurusGraphicsScene, TaurusGraphicsItem) +__all__ = ["TaurusJDrawGraphicsFactory"] + +__docformat__ = 'restructuredtext' + + LINESTYLE_JDW2QT = {0: Qt.Qt.SolidLine, 1: Qt.Qt.DotLine, 2: Qt.Qt.DashLine, @@ -180,7 +186,7 @@ def getPolylineObj(self, params): polygon = Qt.QPolygonF() p = params.get('summit') - for i in xrange(0, len(p), 2): + for i in range(0, len(p), 2): polygon.append(Qt.QPointF(p[i], p[i + 1])) item.setPolygon(polygon) @@ -189,7 +195,7 @@ def getPolylineObj(self, params): def getSplineObj(self, params): item = self.getGraphicsItem('Spline', params) p = params.get('summit') - p = [Qt.QPointF(p[i], p[i + 1]) for i in xrange(0, len(p), 2)] + p = [Qt.QPointF(p[i], p[i + 1]) for i in range(0, len(p), 2)] item.setControlPoints(p) isClosed = params.get('isClosed', True) item.setClose(isClosed) @@ -212,8 +218,8 @@ def readLabelObj(self, item, params): # it is parsed as a float vAlignment = int(params.get('vAlignment', 0)) hAlignment = int(params.get('hAlignment', 0)) - assert(vAlignment in VALIGNMENT.keys()) - assert(hAlignment in ALIGNMENT.keys()) + assert(vAlignment in VALIGNMENT) + assert(hAlignment in ALIGNMENT) vAlignment = VALIGNMENT[vAlignment] hAlignment = ALIGNMENT[hAlignment] item.setAlignment(hAlignment | vAlignment) @@ -241,7 +247,7 @@ def readLabelObj(self, item, params): if txt: if any(isinstance(txt, t) for t in (list, tuple, set)): # Parsing several lines of text txt = '\n'.join(txt) - item.setPlainText(Qt.QString(txt)) + item.setPlainText(str(txt)) item._currText = txt def getGroupObj(self, params): @@ -371,9 +377,9 @@ def set_common_params(self, item, params): pen.setWidth(lineWidth) pen.setStyle(LINESTYLE_JDW2QT[params.get("lineStyle", 0)]) item.setPen(pen) - except AttributeError, ae: + except AttributeError as ae: pass - except Exception, e: + except Exception as e: self.warning('jdraw.set_common_params(%s(%s)).(foreground,width,style) failed!: \n\t%s' % ( type(item).__name__, name, traceback.format_exc())) @@ -419,5 +425,5 @@ def set_item_filling(self, item, pattern=Qt.Qt.Dense4Pattern, expand=False): return if __name__ == "__main__": - import jdraw_view + from . import jdraw_view jdraw_view.jdraw_view_main() diff --git a/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_parser.py b/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_parser.py index 524378164..5175be052 100644 --- a/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_parser.py +++ b/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_parser.py @@ -27,17 +27,17 @@ from __future__ import absolute_import -__all__ = ["new_parser", "parse"] - import os import re -import imp from ply import lex from ply import yacc from taurus.core.util.log import Logger + +__all__ = ["new_parser", "parse"] + tokens = ('NUMBER', 'SYMBOL', 'LBRACKET', 'RBRACKET', 'TWOP', 'COMMA', 'JDFILE', 'GLOBAL', 'JDLINE', 'JDRECTANGLE', 'JDROUNDRECTANGLE', 'JDGROUP', 'JDELLIPSE', 'JDBAR', 'JDSWINGOBJECT', 'JDLABEL', 'JDPOLYLINE', @@ -325,7 +325,7 @@ def new_parser(optimize=None, debug=0, outputdir=None): try: p = yacc.yacc(tabmodule=jdraw_yacctab, debugfile=None, write_tables=1, **common_kwargs) - except Exception, e: + except Exception as e: msg = ('Error creating jdraw parser.\n' + 'HINT: Try removing jdraw_lextab.* and jdraw_yacctab.* from %s' % outputdir) diff --git a/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_view.py b/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_view.py index e98dd1a5c..f8dcd4c88 100644 --- a/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_view.py +++ b/lib/taurus/qt/qtgui/graphic/jdraw/jdraw_view.py @@ -24,22 +24,29 @@ ############################################################################# """This module contains the graphics view widget for jdraw files""" +from __future__ import absolute_import -__all__ = ["TaurusJDrawSynopticsView"] - -__docformat__ = 'restructuredtext' +from builtins import str import os import traceback + +from future.utils import string_types + import taurus -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat from taurus.core.taurusbasetypes import TaurusElementType from taurus.core import taurushelper from taurus.qt.qtgui.graphic.taurusgraphic import parseTangoUri, TaurusGraphicsItem, SynopticSelectionStyle from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_MODEL_MIME_TYPE from taurus.qt.qtgui.base import TaurusBaseWidget -import jdraw_parser +from . import jdraw_parser + + +__all__ = ["TaurusJDrawSynopticsView"] + +__docformat__ = 'restructuredtext' class TaurusJDrawSynopticsView(Qt.QGraphicsView, TaurusBaseWidget): @@ -74,7 +81,7 @@ class TaurusJDrawSynopticsView(Qt.QGraphicsView, TaurusBaseWidget): allows to configure custom context menus for graphic items using a list of tuples. Empty tuples will insert separators in the menu. ''' - itemsChanged = Qt.pyqtSignal(str, dict) + itemsChanged = Qt.pyqtSignal('QString', dict) modelsChanged = Qt.pyqtSignal(list) graphicItemSelected = Qt.pyqtSignal('QString') graphicSceneClicked = Qt.pyqtSignal('QPoint') @@ -120,8 +127,8 @@ def update(self): self.emitColors() def openJDraw(self): - ifile = unicode(Qt.QFileDialog.getOpenFileName( - self, 'Load JDraw File', '', 'JDraw File (*.jdw)')) + ifile, _ = compat.getOpenFileName( + self, 'Load JDraw File', '', 'JDraw File (*.jdw)') if not ifile: return fileName = ifile.split("/") @@ -158,6 +165,7 @@ def get_item_colors(self, emit=False): self.warning('Unable to emitColors: %s' % traceback.format_exc()) return item_colors + @Qt.pyqtSlot(object) @Qt.pyqtSlot('QString') def selectGraphicItem(self, item_name): self.scene().selectGraphicItem(item_name) @@ -268,7 +276,7 @@ def repaint(self): # self.fitting() def getGraphicsFactory(self, delayed=False): - import jdraw + from . import jdraw # self.parent()) return jdraw.TaurusJDrawGraphicsFactory(self, alias=(self.alias or None), delayed=delayed) @@ -317,17 +325,12 @@ def getModelMimeData(self): mimeData = Qt.QMimeData() if model: taurusType = taurushelper.getValidTypesForName(model, False) + model = str(model).encode('utf8') if TaurusElementType.Device in taurusType: - self.debug('getMimeData(): DeviceModel at %s: %s', - self.mousePos, model) mimeData.setData(TAURUS_DEV_MIME_TYPE, model) if TaurusElementType.Attribute in taurusType: - self.debug('getMimeData(): AttributeModel at %s: %s', - self.mousePos, model) mimeData.setData(TAURUS_ATTR_MIME_TYPE, model) else: - self.debug('getMimeData(): UnknownModel at %s: %s', - self.mousePos, model) mimeData.setData(TAURUS_MODEL_MIME_TYPE, model) except: self.debug( @@ -440,7 +443,7 @@ def getQtDesignerPluginInfo(cls): model = Qt.pyqtProperty("QString", getModel, setModel) def setSelectionStyle(self, selectionStyle): - if isinstance(selectionStyle, (Qt.QString, basestring)): + if isinstance(selectionStyle, string_types): selectionStyle = str(selectionStyle).upper() try: selectionStyle = SynopticSelectionStyle[selectionStyle] @@ -488,9 +491,6 @@ def jdraw_view_main(): # print '%s setModel(%s)'%(time.ctime(),sys.argv[1]) form.setModel(sys.argv[1]) form.setWindowTitle(sys.argv[1].rsplit('.', 1)[0]) - #def kk(*args):print("\tgraphicItemSelected(%s)"%str(args)) - #form.connect(form,Qt.SIGNAL("graphicItemSelected(QString)"), kk) - # form.fitting() sys.exit(app.exec_()) if __name__ == "__main__": diff --git a/lib/taurus/qt/qtgui/graphic/taurusgraphic.py b/lib/taurus/qt/qtgui/graphic/taurusgraphic.py index 7373eac44..eb242535a 100755 --- a/lib/taurus/qt/qtgui/graphic/taurusgraphic.py +++ b/lib/taurus/qt/qtgui/graphic/taurusgraphic.py @@ -25,9 +25,38 @@ """ taurusgraphic.py: """ +from __future__ import print_function # TODO: Tango-centric +from future import standard_library +standard_library.install_aliases() +from builtins import str +from builtins import range +from builtins import object + +import re +import os +import subprocess +import traceback +import collections + +from future.utils import string_types +from queue import Queue + +from taurus import Manager +from taurus.core import AttrQuality, DataType +from taurus.core.util.containers import CaselessDefaultDict +from taurus.core.util.log import Logger, deprecation_decorator +from taurus.core.taurusdevice import TaurusDevice +from taurus.core.taurusattribute import TaurusAttribute +from taurus.core.util.enumeration import Enumeration +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, + ExternalAppAction, TaurusWidgetFactory) + + __all__ = ['SynopticSelectionStyle', 'parseTangoUri', 'QEmitter', # TODO: QEmitter should probably be removed (kept priv) @@ -55,27 +84,6 @@ __docformat__ = 'restructuredtext' -import re -import os -import subprocess -import traceback -import operator -import types - -import Queue - -from taurus import Manager -from taurus.core import AttrQuality, DataType -from taurus.core.util.containers import CaselessDefaultDict -from taurus.core.util.log import Logger, deprecation_decorator -from taurus.core.taurusdevice import TaurusDevice -from taurus.core.taurusattribute import TaurusAttribute -from taurus.core.util.enumeration import Enumeration -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, - ExternalAppAction, TaurusWidgetFactory) - SynopticSelectionStyle = Enumeration("SynopticSelectionStyle", [ # A blue ellipse is displayed around the selected objects @@ -140,12 +148,12 @@ def run(self): p = self.parent() while True: item = p.getQueue().get(True) - if type(item) in types.StringTypes: + if type(item) in (str,): if item == "exit": break else: continue - if not operator.isSequenceType(item): + if not isinstance(item, collections.Sequence): item = (item,) # @todo: Unless the call to boundingRect() has a side effect, this line is useless.. probably related to todo in _updateView() item_rects = [i.boundingRect() for i in item] @@ -214,12 +222,12 @@ def __init__(self, parent=None, strt=True): self.debug = self.info = self.warning = self.error = lambda l: self.logger.warning( l) except: - print 'Unable to initialize TaurusGraphicsSceneLogger: %s' % traceback.format_exc() + print('Unable to initialize TaurusGraphicsSceneLogger: %s' % traceback.format_exc()) try: if parent and parent.panelClass() is not None: defaultClass = parent.panelClass() - if defaultClass and isinstance(defaultClass, str): + if defaultClass and isinstance(defaultClass, string_types): self.panel_launcher = self.getClass(defaultClass) if self.panel_launcher is None: self.panel_launcher = ExternalAppAction( @@ -260,7 +268,7 @@ def showNewPanel(self, args=None, standAlone=False): clName.actionTriggered(clParam if isinstance( clParam, (list, tuple)) else [clParam]) else: - if isinstance(clName, str): + if isinstance(clName, string_types): klass = self.getClass(clName) if klass is None: self.warning("%s Class not found!" % clName) @@ -371,7 +379,7 @@ def getItemByName(self, item_name, strict=None): if not target.endswith('$'): target += '$' result = [] - for k in self._itemnames.keys(): + for k in list(self._itemnames.keys()): if re.match(target.lower(), k.lower()): #self.debug('getItemByName(%s): _itemnames[%s]: %s'%(target,k,self._itemnames[k])) result.extend(self._itemnames[k]) @@ -432,14 +440,15 @@ def addMenuAction(menu, k, action, last_was_separator=False): if k: configDialogAction = menu.addAction(k) if action: - configDialogAction.triggered[()].connect(lambda dev=obj_name, act=action: act(dev)) + configDialogAction.triggered.connect( + lambda dev=obj_name, act=action: act(dev)) else: configDialogAction.setEnabled(False) last_was_separator = False elif not last_was_separator: menu.addSeparator() last_was_separator = True - except Exception, e: + except Exception as e: self.warning('Unable to add Menu Action: %s:%s' % (k, e)) return last_was_separator if (mouseEvent.button() == Qt.Qt.RightButton): @@ -575,7 +584,7 @@ def _displaySelectionAsEllipse(self, items): if item not in self._selectedItems: self._selectedItems.append(item) retval = True - except Exception, e: + except Exception as e: self.warning('selectGraphicsItem(%s) failed! %s' % (getattr(item, '_name', item), str(e))) self.warning(traceback.format_exc()) @@ -683,12 +692,12 @@ def getSelectionMark(self, picture=None, w=10, h=10): SelectionMark = picture SelectionMark.setRect(0, 0, w, h) SelectionMark.hide() - elif operator.isCallable(picture): + elif hasattr(picture, '__call__'): SelectionMark = picture() else: if isinstance(picture, Qt.QPixmap): pixmap = picture - elif isinstance(picture, basestring) or isinstance(picture, Qt.QString): + elif isinstance(picture, string_types): picture = str(picture) pixmap = Qt.QPixmap(os.path.realpath(picture)) SelectionMark = Qt.QGraphicsPixmapItem() @@ -829,7 +838,7 @@ def getAllChildren(self, item, klass=None): def start(self): if self.updateThread: return - self.updateQueue = Queue.Queue() + self.updateQueue = Queue() self.updateThread = TaurusGraphicsUpdateThread(self) self.updateThread.start() # Qt.QThread.HighPriority) @@ -983,7 +992,7 @@ def updateSplinePath(self): path.lineTo(cp[1]) else: path.moveTo(cp[0]) - for i in xrange(1, nb_points - 1, 3): + for i in range(1, nb_points - 1, 3): p1 = cp[i + 0] p2 = cp[i + 1] end = cp[i + 2] @@ -1272,7 +1281,9 @@ def paint(self, painter, option, widget): class TaurusRoundRectItem(Qt.QGraphicsPathItem): def __init__(self, name=None, parent=None, scene=None): - Qt.QGraphicsPathItem.__init__(self, parent, scene) + Qt.QGraphicsPathItem.__init__(self, parent) + if scene is not None: + scene.addItem(self) self.__rect = None self.setCornerWidth(0, 0) @@ -1436,7 +1447,7 @@ def paint(self, painter, option, widget): } -class TaurusBaseGraphicsFactory: +class TaurusBaseGraphicsFactory(object): def __init__(self): pass diff --git a/lib/taurus/qt/qtgui/help/assistant.py b/lib/taurus/qt/qtgui/help/assistant.py index dbcc055de..64545ad06 100644 --- a/lib/taurus/qt/qtgui/help/assistant.py +++ b/lib/taurus/qt/qtgui/help/assistant.py @@ -39,13 +39,15 @@ app.exec_() """ -__all__ = ["Assistant", "Widgets"] - +from builtins import object from taurus.external.qt import Qt -class Widgets: +__all__ = ["Assistant", "Widgets"] + + +class Widgets(object): contents = "contents" index = "index" bookmarks = "bookmarks" diff --git a/lib/taurus/qt/qtgui/help/helppanel.py b/lib/taurus/qt/qtgui/help/helppanel.py index e90e8229b..d08b49127 100644 --- a/lib/taurus/qt/qtgui/help/helppanel.py +++ b/lib/taurus/qt/qtgui/help/helppanel.py @@ -51,7 +51,7 @@ def setHelpEngine(self, help_engine): def loadResource(self, type, url): if url.scheme() == "qthelp": if self.__help_engine: - return Qt.QVariant(self.__help_engine.fileData(url)) + return self.__help_engine.fileData(url) return Qt.QTextBrowser.loadResource(self, type, url) diff --git a/lib/taurus/qt/qtgui/icon/__init__.py b/lib/taurus/qt/qtgui/icon/__init__.py index d660f66a4..cbffb2896 100644 --- a/lib/taurus/qt/qtgui/icon/__init__.py +++ b/lib/taurus/qt/qtgui/icon/__init__.py @@ -28,5 +28,7 @@ sources of icons. """ -from icon import * -from catalog import QIconCatalog +from __future__ import absolute_import + +from .icon import * +from .catalog import QIconCatalog diff --git a/lib/taurus/qt/qtgui/icon/catalog.py b/lib/taurus/qt/qtgui/icon/catalog.py index 3d271b98d..58cb72934 100644 --- a/lib/taurus/qt/qtgui/icon/catalog.py +++ b/lib/taurus/qt/qtgui/icon/catalog.py @@ -25,6 +25,10 @@ This module provides an icon catalog widget """ +from __future__ import print_function + +from builtins import str + import os import hashlib from taurus.qt.qtgui.application import TaurusApplication @@ -57,7 +61,7 @@ def __build_catalog(self, prefix, columns=10): for path in Qt.QDir.searchPaths(prefix): if not os.path.exists(path): - print " %s not found. Skipping.!" % path + print(" %s not found. Skipping.!" % path) continue for fname in os.listdir(path): @@ -108,7 +112,14 @@ def onClick(self): """Reimplemented :class:`GraphicalChoiceWidget` """ # From all alternatives, extract the one with the shortest name - chosen = self.sender().text() + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + # self._chosen = str(self.sender().text()) + # it fails due to added "&" + chosen = self.sender()._id # <-- this was monkey-patched + # ------------------------------------------------------- + alts = chosen.splitlines() alts = sorted(alts, key=lambda s: len(s.split()[0])) name, absname = alts[0].split() @@ -136,7 +147,7 @@ class QIconCatalog(Qt.QTabWidget): application. """ - iconSelected = Qt.pyqtSignal(str) + iconSelected = Qt.pyqtSignal('QString') def __init__(self, parent=None): Qt.QTabWidget.__init__(self) diff --git a/lib/taurus/qt/qtgui/icon/icon.py b/lib/taurus/qt/qtgui/icon/icon.py index d7ead72e9..0299849f6 100644 --- a/lib/taurus/qt/qtgui/icon/icon.py +++ b/lib/taurus/qt/qtgui/icon/icon.py @@ -68,7 +68,7 @@ __3DQS = Qt.QSize(3 * __DW, __DH) # Indexes for the map below -__IDX_ELEM_TYPE_ICON, __IDX_ELEM_TYPE_SIZE, __IDX_ELEM_TYPE_TOOLTIP = range(3) +__IDX_ELEM_TYPE_ICON, __IDX_ELEM_TYPE_SIZE, __IDX_ELEM_TYPE_TOOLTIP = list(range(3)) # New default role map # Elements are: icon theme, preferred size, description/tooltip @@ -102,7 +102,7 @@ } # Indexes for the map below -__IDX_STATE_ICON, __IDX_STATE_TOOLTIP = range(2) +__IDX_STATE_ICON, __IDX_STATE_TOOLTIP = list(range(2)) _STATE_MAP = { TaurusDevState.Ready: ("status:available.svg", "Element ready"), @@ -135,7 +135,7 @@ def registerPathFiles(pathfilenames): for filename in pathfilenames: try: pathmap = json.load(open(filename)) - except Exception, e: + except Exception as e: __LOGGER.error('Error registering "%s": %r', filename, e) pathmap = [] diff --git a/lib/taurus/qt/qtgui/input/choicedlg.py b/lib/taurus/qt/qtgui/input/choicedlg.py index 3e50fc322..b8f2cdfa0 100644 --- a/lib/taurus/qt/qtgui/input/choicedlg.py +++ b/lib/taurus/qt/qtgui/input/choicedlg.py @@ -24,13 +24,16 @@ ########################################################################### """This package provides a dialog for graphically choosing a Taurus class""" +from __future__ import print_function +from future.builtins import str + +from taurus.external.qt import Qt + __all__ = ["GraphicalChoiceDlg", "GraphicalChoiceWidget"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt - class GraphicalChoiceDlg(Qt.QDialog): ''' @@ -119,7 +122,7 @@ def getChoice(parent=None, title='', msg='', choices=None, pixmaps=None, iconSiz class GraphicalChoiceWidget(Qt.QScrollArea): '''A widget that presents a 2D grid of buttons''' - choiceMade = Qt.pyqtSignal(str) + choiceMade = Qt.pyqtSignal('QString') def __init__(self, parent=None, designMode=False, choices=None, pixmaps=None, iconSize=128, defaultPixmap=None, horizontalScrollBarPolicy=Qt.Qt.ScrollBarAsNeeded, @@ -192,10 +195,20 @@ def setChoice(self, row, col, text, pixmap=None, tooltip=None): button.setToolTip(tooltip) button.clicked.connect(self.onClick) self.gridLayout.addWidget(button, row, col, Qt.Qt.AlignCenter) + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + button._id = text # <-- ugly monkey-patch! + # ------------------------------------------------------- def onClick(self): '''slot called when a button is clicked''' - self._chosen = unicode(self.sender().text()) + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + # self._chosen = str(self.sender().text()) # <-- fails due to added "&" + self._chosen = self.sender()._id # <-- this was monkey-patched + # ------------------------------------------------------- self.choiceMade.emit(self._chosen) def getChosen(self): @@ -255,7 +268,7 @@ def main(): for k in row: pixmaps[k] = getCachedPixmap('snapshot:%s.png' % k) - print GraphicalChoiceDlg.getChoice(parent=None, title='Panel chooser', msg='Choose the type of Panel:', choices=choices, pixmaps=pixmaps) + print(GraphicalChoiceDlg.getChoice(parent=None, title='Panel chooser', msg='Choose the type of Panel:', choices=choices, pixmaps=pixmaps)) sys.exit() diff --git a/lib/taurus/qt/qtgui/input/qwheel.py b/lib/taurus/qt/qtgui/input/qwheel.py index b71db84f8..7965ea317 100755 --- a/lib/taurus/qt/qtgui/input/qwheel.py +++ b/lib/taurus/qt/qtgui/input/qwheel.py @@ -25,9 +25,8 @@ """This module provides an arrow based widget.""" -__all__ = ["QWheelEdit"] - -__docformat__ = 'restructuredtext' +from builtins import map +from builtins import range import os import math @@ -36,6 +35,12 @@ from taurus.external.qt import Qt from taurus.core.units import Q_ + +__all__ = ["QWheelEdit"] + +__docformat__ = 'restructuredtext' + + class _ArrowButton(Qt.QPushButton): """Private class to be used by QWheelEdit for an arrow button""" @@ -89,14 +94,14 @@ def getPixmap(self): pm = Qt.QPixmapCache.find(_DownArrowButton.ArrowPixmapKey) if pm is None: pm = Qt.QPixmap(self.ArrowPixmapName) - pm = pm.transformed(Qt.QMatrix().rotate(180)) + pm = pm.transformed(Qt.QTransform().rotate(180)) Qt.QPixmapCache.insert(_DownArrowButton.ArrowPixmapKey, pm) return pm class _DigitLabel(Qt.QLabel): """A private single digit label to be used by QWheelEdit widget""" - PixmapKeys = map(str, xrange(10)) + ['blank', 'minus', 'point'] + PixmapKeys = list(map(str, range(10))) + ['blank', 'minus', 'point'] def __init__(self, lbl, parent=None): Qt.QLabel.__init__(self, parent) @@ -223,7 +228,7 @@ def _getMinPossibleValue(self): @return (float) the minimum possible value """ decmax = 0 - for i in xrange(self.getDecDigitCount()): + for i in range(self.getDecDigitCount()): decmax += 9 * math.pow(10, -(i + 1)) return -math.pow(10.0, self.getIntDigitCount()) + 1 - decmax @@ -236,7 +241,7 @@ def _getMaxPossibleValue(self): @return (float) the maximum possible value """ decmax = 0 - for i in xrange(self.getDecDigitCount()): + for i in range(self.getDecDigitCount()): decmax += 9 * math.pow(10, -(i + 1)) return math.pow(10.0, self.getIntDigitCount()) - 1 + decmax @@ -247,7 +252,7 @@ def _build(self): l = self.layout() l.setSpacing(0) - l.setMargin(0) + l.setContentsMargins(0, 0, 0, 0) id = self.getIntDigitCount() dd = self.getDecDigitCount() @@ -270,7 +275,7 @@ def _build(self): l.setColumnMinimumWidth(0, _ArrowButton.ButtonSize) l.setColumnStretch(0, 1) - for i in xrange(id): + for i in range(id): col = i + 1 d = _DigitLabel('0') up = _UpArrowButton(id - i - 1) @@ -293,7 +298,7 @@ def _build(self): self._digitLabels.append(dotLabel) l.addWidget(dotLabel, 1, id + 1) - for i in xrange(id, digits): + for i in range(id, digits): col = i + 1 if showDot: col += 1 @@ -325,6 +330,15 @@ def _build(self): self.getDecDigitCount()) ed.setVisible(False) self._editor = ed + + # set the minimum height for the widget + # (otherwise the hints seem to be ignored by the layouts) + min_height = max(ed.minimumSizeHint().height(), + signLabel.minimumSizeHint().height()) + if self.getShowArrowButtons(): + min_height += 2 * _ArrowButton.ButtonSize + self.setMinimumHeight(min_height) + self.clearWarning() def _clear(self): @@ -442,7 +456,7 @@ def _updateValue(self, trigValueChanged=True, trigValueEdited=True): if len(v_str) > len(self._digitLabels): # do auto adjust if '.' in v_str: - dc = map(len, v_str.split('.')) + dc = list(map(len, v_str.split('.'))) else: dc = len(v_str), 0 self._setDigits(*dc) @@ -767,8 +781,8 @@ def hideEditWidget(self): self.setFocus() def wheelEvent(self, evt): - numDegrees = evt.delta() / 8 - numSteps = numDegrees / 15 + numDegrees = evt.delta() // 8 + numSteps = numDegrees // 15 #w = Qt.QApplication.focusWidget() w = self.focusWidget() if not isinstance(w, _DigitLabel): @@ -864,6 +878,7 @@ def resetShowArrowButtons(self): def main(): + import taurus.qt.qtgui.icon # otherwise the arrows don't show in the demo global arrowWidget def resetAll(): diff --git a/lib/taurus/qt/qtgui/input/tauruscombobox.py b/lib/taurus/qt/qtgui/input/tauruscombobox.py index 9baebcbca..d7ec36029 100644 --- a/lib/taurus/qt/qtgui/input/tauruscombobox.py +++ b/lib/taurus/qt/qtgui/input/tauruscombobox.py @@ -108,7 +108,6 @@ def getValue(self): func = bool else: return None - new_value = Qt.from_qvariant(new_value, func) return new_value def setValue(self, value): @@ -116,7 +115,7 @@ def setValue(self, value): Set the value for the widget to display, not the value of the attribute. """ - index = self.findData(Qt.QVariant(value)) + index = self.findData(value) self._setCurrentIndex(index) def updateStyle(self): @@ -180,7 +179,7 @@ def addValueNames(self, names): bs = self.blockSignals(True) try: for k, v in names: - self.addItem(k, Qt.QVariant(v)) + self.addItem(k, v) # Ok, now we should see if the current value matches any # of the newly added names. This is kinda a refresh: @@ -201,7 +200,7 @@ def getValueString(self, value, default='UNKNOWN(%s)'): a '%s' placeholder which will be substituted with str(value). It defaults to 'UNKNOWN(%s)'. """ - item = self.findData(Qt.QVariant(value)) + item = self.findData(value) if item < 0: if '%s' in default: return default % str(value) diff --git a/lib/taurus/qt/qtgui/input/tauruslineedit.py b/lib/taurus/qt/qtgui/input/tauruslineedit.py index 9d3928c8e..135460ae5 100755 --- a/lib/taurus/qt/qtgui/input/tauruslineedit.py +++ b/lib/taurus/qt/qtgui/input/tauruslineedit.py @@ -27,6 +27,8 @@ This module provides a set of basic taurus widgets based on QLineEdit """ +from builtins import bytes +from builtins import str import sys import numpy from taurus.external.qt import Qt @@ -65,6 +67,7 @@ class TaurusValueLineEdit(Qt.QLineEdit, TaurusBaseWritableWidget): """ + _bytesEncoding = sys.stdin.encoding def __init__(self, qt_parent=None, designMode=False): name = self.__class__.__name__ @@ -85,6 +88,7 @@ def __init__(self, qt_parent=None, designMode=False): def _updateValidator(self, value): """This method sets a validator depending on the data type""" + val = None if isinstance(value.wvalue, Quantity): val = self.validator() if not isinstance(val, PintValidator): @@ -105,6 +109,7 @@ def _updateValidator(self, value): else: self.setValidator(None) self.debug("Validator disabled") + return val def __decimalDigits(self, fmt): """returns the number of decimal digits from a format string @@ -200,8 +205,8 @@ def wheelEvent(self, evt): return Qt.QLineEdit.wheelEvent(self, evt) evt.accept() - numDegrees = evt.delta() / 8 - numSteps = numDegrees / 15 + numDegrees = evt.delta() // 8 + numSteps = numDegrees // 15 self._stepBy(numSteps) def keyPressEvent(self, evt): @@ -244,7 +249,11 @@ def setValue(self, v): # Other fragments are ignored by setValue if self.modelFragmentName == "wvalue.magnitude": try: - units = self.validator().units + validator = self.validator() + if validator is None: + value = self.getModelValueObj() + validator = self._updateValidator(value) + units = validator.units v = v.to(units).magnitude except Exception as e: self.debug('Cannot enforce fragment. Reason: %r', e) @@ -284,7 +293,7 @@ def getValue(self): else: return numpy.array(eval(text), dtype=str).tolist() elif model_type == DataType.Bytes: - return bytes(text) + return bytes(text, self._bytesEncoding) else: raise TypeError('Unsupported model type "%s"' % model_type) except Exception as e: diff --git a/lib/taurus/qt/qtgui/input/taurusspinbox.py b/lib/taurus/qt/qtgui/input/taurusspinbox.py index 500171949..e7edf2ccc 100644 --- a/lib/taurus/qt/qtgui/input/taurusspinbox.py +++ b/lib/taurus/qt/qtgui/input/taurusspinbox.py @@ -27,13 +27,16 @@ This module provides a set of basic taurus widgets based on QAbstractSpinBox """ +from __future__ import absolute_import + from taurus.external.qt import Qt -from tauruslineedit import TaurusValueLineEdit +from .tauruslineedit import TaurusValueLineEdit from taurus.qt.qtgui.icon import getStandardIcon from taurus.core.units import Quantity from taurus.qt.qtgui.util import PintValidator + __all__ = ["TaurusValueSpinBox", "TaurusValueSpinBoxEx"] __docformat__ = 'restructuredtext' @@ -217,7 +220,6 @@ class TaurusValueSpinBoxEx(Qt.QWidget): def __init__(self, qt_parent=None, designMode=False): Qt.QWidget.__init__(self, qt_parent) layout = Qt.QGridLayout() - layout.setMargin(0) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) diff --git a/lib/taurus/qt/qtgui/input/tauruswheel.py b/lib/taurus/qt/qtgui/input/tauruswheel.py index 3070b61f9..69901fd7e 100644 --- a/lib/taurus/qt/qtgui/input/tauruswheel.py +++ b/lib/taurus/qt/qtgui/input/tauruswheel.py @@ -25,16 +25,18 @@ """This module provides a set of basic taurus widgets based on QWheelEdit""" -__all__ = ["TaurusWheelEdit"] - -__docformat__ = 'restructuredtext' +from __future__ import absolute_import -import taurus from taurus.external.qt import Qt from taurus.core.taurusbasetypes import TaurusEventType from taurus.qt.qtgui.base import TaurusBaseWritableWidget -from qwheel import QWheelEdit +from .qwheel import QWheelEdit + + +__all__ = ["TaurusWheelEdit"] + +__docformat__ = 'restructuredtext' class TaurusWheelEdit(QWheelEdit, TaurusBaseWritableWidget): diff --git a/lib/taurus/qt/qtgui/model/qbasemodel.py b/lib/taurus/qt/qtgui/model/qbasemodel.py index e5b79cda8..a43db5394 100644 --- a/lib/taurus/qt/qtgui/model/qbasemodel.py +++ b/lib/taurus/qt/qtgui/model/qbasemodel.py @@ -528,7 +528,7 @@ def _setPerspective(self, perspective): # reversed qmodel_class, qmodel_proxy_classes = qmodel_classes[ -1], qmodel_classes[-2::-1] - qmodel = qmodel_class(self) + qmodel = qmodel_class(parent=self) qmodel_source = qmodel if self._proxyModel is None: # applies the chain of proxies for qmodel_proxy_class in qmodel_proxy_classes: diff --git a/lib/taurus/qt/qtgui/panel/qdataexportdialog.py b/lib/taurus/qt/qtgui/panel/qdataexportdialog.py index dd09e26bb..bdd5db549 100755 --- a/lib/taurus/qt/qtgui/panel/qdataexportdialog.py +++ b/lib/taurus/qt/qtgui/panel/qdataexportdialog.py @@ -26,15 +26,18 @@ """DataExportDlg.py: A Qt dialog for showing and exporting x-y Ascii data from one or more curves""" -__all__ = ["QDataExportDialog"] +from __future__ import print_function import os.path from datetime import datetime -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat from taurus.qt.qtgui.util.ui import UILoadable +__all__ = ["QDataExportDialog"] + + @UILoadable class QDataExportDialog(Qt.QDialog): """ @@ -59,7 +62,7 @@ def __init__(self, parent=None, datadict=None, sortedNames=None): # connections self.exportBT.clicked.connect(self.exportData) - self.dataSetCB.currentIndexChanged[str].connect(self.onDataSetCBChange) + self.dataSetCB.currentIndexChanged['QString'].connect(self.onDataSetCBChange) self.setDataSets(datadict, sortedNames) @@ -75,7 +78,7 @@ def setDataSets(self, datadict, sortedNames=None): self.datadict = datadict self.dataSetCB.clear() self.dataSetCB.insertItems(0, sortedNames) - if len(self.datadict.keys()) > 1: + if len(self.datadict) > 1: self.dataSetCB.insertItems( 0, [self.allInSingleFile, self.allInMultipleFiles]) @@ -104,8 +107,8 @@ def exportCurrentData(self, set=None, ofile=None, verbose=True, AllowCloseAfter= #**lazy** sanitising of the set to *suggest* it as a filename name = set.replace('*', '').replace('/', '_').replace('\\', '_') name += ".dat" - ofile = Qt.QFileDialog.getSaveFileName( self, 'Export File Name', - name, 'All Files (*)') + ofile, _ = compat.getSaveFileName(self, 'Export File Name', name, + 'All Files (*)') if not ofile: return False try: @@ -123,9 +126,9 @@ def exportCurrentData(self, set=None, ofile=None, verbose=True, AllowCloseAfter= else: for x,y in zip(xdata, ydata): text+="%r\t%r\n" % (x, y) - print >> ofile, str(text) + print(str(text), file=ofile) else: - print >> ofile, str(self.dataTE.toPlainText()) + print(str(self.dataTE.toPlainText()), file=ofile) except: Qt.QMessageBox.warning(self, "File saving failed", @@ -147,7 +150,7 @@ def exportAllData(self, preffix=None): if preffix is not given, the user is prompted for a directory path""" if preffix is None: outputdir = Qt.QFileDialog.getExistingDirectory( - self, 'Export Directory', Qt.QString()) + self, 'Export Directory', '') if not outputdir: return False preffix = os.path.join(str(outputdir), "set") diff --git a/lib/taurus/qt/qtgui/panel/qdoublelist.py b/lib/taurus/qt/qtgui/panel/qdoublelist.py index 76a051e00..5d7b69a54 100644 --- a/lib/taurus/qt/qtgui/panel/qdoublelist.py +++ b/lib/taurus/qt/qtgui/panel/qdoublelist.py @@ -29,14 +29,20 @@ items from one to the other """ -__all__ = ["QDoubleListDlg"] +from __future__ import print_function -__docformat__ = 'restructuredtext' +from builtins import str +from builtins import range from taurus.external.qt import Qt from taurus.qt.qtgui.util.ui import UILoadable +__all__ = ["QDoubleListDlg"] + +__docformat__ = 'restructuredtext' + + @UILoadable(with_ui='ui') class QDoubleListDlg(Qt.QDialog): '''Generic dialog providing two lists. Items can be moved from one to the other @@ -101,14 +107,14 @@ def getAll1(self): :return: (list) ''' - return [unicode(self.ui.list1.item(row).text()) for row in xrange(self.ui.list1.count())] + return [str(self.ui.list1.item(row).text()) for row in range(self.ui.list1.count())] def getAll2(self): '''returns a copy the items in the second list :return: (list) ''' - return [unicode(self.ui.list2.item(row).text()) for row in xrange(self.ui.list2.count())] + return [str(self.ui.list2.item(row).text()) for row in range(self.ui.list2.count())] # note, for the moment we do not make it available in designer because it does not # behave well as a widget (only as a dialog) (e.g., it closes if ESC is pressed @@ -132,9 +138,9 @@ def main(): list1=['11', '22'], list2=['123', '33']) result = dlg.exec_() - print "Result", result - print "list1", dlg.getAll1() - print "list2", dlg.getAll2() + print("Result", result) + print("list1", dlg.getAll1()) + print("list2", dlg.getAll2()) if __name__ == "__main__": diff --git a/lib/taurus/qt/qtgui/panel/report/albareport.py b/lib/taurus/qt/qtgui/panel/report/albareport.py index 6e5591ae8..e9a3c5d8c 100644 --- a/lib/taurus/qt/qtgui/panel/report/albareport.py +++ b/lib/taurus/qt/qtgui/panel/report/albareport.py @@ -25,13 +25,17 @@ """This module provides a panel to display taurus messages""" +from taurus.external.qt import Qt +from taurus.qt.qtgui.panel.report.basicreport import (SendMailDialog, + SMTPReportHandler) + + +__package__ = 'taurus.qt.qtgui.panel.report' + __all__ = ["TicketReportHandler"] __docformat__ = 'restructuredtext' -from taurus.external.qt import Qt -from basicreport import SendMailDialog, SMTPReportHandler - class SendTicketDialog(SendMailDialog): diff --git a/lib/taurus/qt/qtgui/panel/taurusconfigeditor.py b/lib/taurus/qt/qtgui/panel/taurusconfigeditor.py index 1f0316aa3..1f6b66607 100644 --- a/lib/taurus/qt/qtgui/panel/taurusconfigeditor.py +++ b/lib/taurus/qt/qtgui/panel/taurusconfigeditor.py @@ -27,12 +27,11 @@ taurusconfigeditor.py: """ -__all__ = ["QConfigEditor"] - -__docformat__ = 'restructuredtext' +from future import standard_library +standard_library.install_aliases() -from taurus.external.qt import Qt -import cPickle as pickle +from taurus.external.qt import Qt, compat +import pickle import os import tempfile from taurus.qt.qtcore.configuration import BaseConfigurableClass @@ -40,10 +39,15 @@ import shutil +__all__ = ["QConfigEditor"] + +__docformat__ = 'restructuredtext' + + class QConfigEditorModel(Qt.QStandardItemModel): '''A custom Model for QConfigEditor''' - showError = Qt.pyqtSignal(str, str) + showError = Qt.pyqtSignal('QString', 'QString') def __init__(self, parent=None, designMode=False): super(Qt.QStandardItemModel, self).__init__() @@ -57,13 +61,12 @@ def __init__(self, parent=None, designMode=False): def setData(self, index, value, role=Qt.Qt.DisplayRole): '''see :meth:`Qt.QAbstractTableModel.setData`''' - idx_data_str = Qt.from_qvariant(index.data(), str) - value_str = Qt.from_qvariant(value, str) - if idx_data_str == value_str: + idx_data_str = index.data() + if idx_data_str == value: return False #self.itemFromIndex(index).setData(value, role) try: - self.valueChanged(value_str, index) + self.valueChanged(value, index) except: self.showError.emit('Wrong value!', 'The value you entered is wrong. The old value will be restored.') @@ -77,9 +80,9 @@ def loadFile(self, iniFileName): :param iniFileName: (str) ''' - self.originalFile = unicode(iniFileName) + self.originalFile = str(iniFileName) self._file = tempfile.NamedTemporaryFile() - self._temporaryFile = unicode(self._file.name) + self._temporaryFile = str(self._file.name) shutil.copyfile(self.originalFile, self._temporaryFile) @@ -97,7 +100,7 @@ def deleteBranch(self): ''' tmpindex = self._toDeleteIndex item = self.itemFromIndex(tmpindex) - path = Qt.from_qvariant(item.data(Qt.Qt.UserRole), str) + path = item.data(Qt.Qt.UserRole) self._delete = False self._configurationDictionaries = self.removeBranch( self._configurationDictionaries, path) @@ -108,8 +111,8 @@ def deleteBranch(self): group = str(path).split(';', 1)[0] itemToMark = self.itemFromIndex(tmpindex.parent()) while(itemToMark is not None): - itemToMark.setData(Qt.QVariant( - Qt.QFont("Arial", 10, Qt.QFont.Bold)), Qt.Qt.FontRole) + itemToMark.setData( + Qt.QFont("Arial", 10, Qt.QFont.Bold), Qt.Qt.FontRole) itemToMark = self.itemFromIndex(itemToMark.index().parent()) self.markedItems.append(self._toDeleteIndex.parent()) @@ -138,7 +141,7 @@ def removeBranch(self, dict, path): key = val[0] dict[key] = self.removeBranch(dict[key], path) if self._delete == True: - if not dict.has_key('__orderedConfigNames__'): + if '__orderedConfigNames__' not in dict: return dict dict['__orderedConfigNames__'] = self.removeBranch( dict['__orderedConfigNames__'], path) @@ -150,7 +153,7 @@ def removeBranch(self, dict, path): return dict dict.remove(val[0]) return dict - if not dict.has_key('__orderedConfigNames__'): + if '__orderedConfigNames__' not in dict: self._delete = True dict.pop(val[0]) return dict @@ -165,7 +168,7 @@ def valueChanged(self, value, index): :param index: (QModelIndex) index of the model ''' changedItem = self.itemFromIndex(index) - path = Qt.from_qvariant(changedItem.data(Qt.Qt.UserRole), str) + path = changedItem.data(Qt.Qt.UserRole) self._configurationDictionaries = self.changeTreeValue( self._configurationDictionaries, path, value) try: @@ -177,14 +180,14 @@ def valueChanged(self, value, index): self.itemFromIndex(index.sibling(index.row(), 1) ).setText(str(type(eval(value)))) - changedItem.setData(Qt.QVariant( - 'Value has been changed. Old value: ' + str(changedItem.text())), + changedItem.setData( + 'Value has been changed. Old value: ' + str(changedItem.text()), Qt.Qt.ToolTipRole) - itemToMark.setData(Qt.QVariant(Qt.QIcon.fromTheme('emblem-important')), + itemToMark.setData(Qt.QIcon.fromTheme('emblem-important'), Qt.Qt.DecorationRole) while(itemToMark is not None): - itemToMark.setData(Qt.QVariant( - Qt.QFont("Arial", 10, Qt.QFont.Bold)), Qt.Qt.FontRole) + itemToMark.setData( + Qt.QFont("Arial", 10, Qt.QFont.Bold), Qt.Qt.FontRole) itemToMark = self.itemFromIndex(itemToMark.index().parent()) self.saveSettings(group) @@ -232,7 +235,7 @@ def fillTopLevel(self): configdict = self.getTaurusConfigFromSettings() if configdict is not None: mainConfig[None] = configdict - item.setData(Qt.QVariant('None'), Qt.Qt.UserRole) + item.setData('None', Qt.Qt.UserRole) self.fillTaurusConfig(item, configdict) self._settings.beginGroup("Perspectives") self.perspectives = self._settings.childGroups() @@ -240,7 +243,7 @@ def fillTopLevel(self): item = Qt.QStandardItem(name) item.setEditable(False) # item.setSelectable(False) - path = Qt.QVariant("Perspectives/" + name) + path = "Perspectives/" + name item.setData(path, Qt.Qt.UserRole) root.appendRow(item) self._settings.beginGroup(name) @@ -272,8 +275,8 @@ def fillTaurusConfig(self, item, configdict): child.setEditable(False) item.appendRow(child) - txt = Qt.from_qvariant(item.data(Qt.Qt.UserRole), str) - path = Qt.QVariant(txt + ";__itemConfigurations__;" + k) + txt = item.data(Qt.Qt.UserRole) + path = txt + ";__itemConfigurations__;" + k child.setData(path, Qt.Qt.UserRole) # recursive call to fill all nodes self.fillTaurusConfig(child, value) @@ -286,8 +289,8 @@ def fillTaurusConfig(self, item, configdict): item.appendRow([child, typeV, valueV]) - txt = Qt.from_qvariant(item.data(Qt.Qt.UserRole), str) - path = Qt.QVariant(txt + ";__itemConfigurations__;" + k) + txt = item.data(Qt.Qt.UserRole) + path = txt + ";__itemConfigurations__;" + k child.setEditable(False) typeV.setEditable(False) @@ -312,8 +315,8 @@ def fillTaurusConfig(self, item, configdict): if BaseConfigurableClass.isTaurusConfig(value): child.setEditable(False) item.appendRow(child) - txt = Qt.from_qvariant(item.data(Qt.Qt.UserRole), str) - path = Qt.QVariant(txt + ";" + k) + txt = item.data(Qt.Qt.UserRole) + path = txt + ";" + k child.setData(path, Qt.Qt.UserRole) # recursive call to fill all nodes self.fillTaurusConfig(child, value) @@ -323,8 +326,8 @@ def fillTaurusConfig(self, item, configdict): typeV.setForeground(Qt.QBrush(Qt.QColor('gray'))) child.setForeground(Qt.QBrush(Qt.QColor('gray'))) item.appendRow([child, typeV, valueV]) - txt = Qt.from_qvariant(item.data(Qt.Qt.UserRole), str) - path = Qt.QVariant(txt + ";" + k) + txt = item.data(Qt.Qt.UserRole) + path = txt + ";" + k child.setData(path, Qt.Qt.UserRole) child.setEditable(False) @@ -342,11 +345,11 @@ def getTaurusConfigFromSettings(self, key='TaurusConfig'): :returns (dict) ''' result = None - qstate = Qt.from_qvariant(self._settings.value(key), 'toByteArray') + qstate = self._settings.value(key) if qstate is not None and not qstate.isNull(): try: result = pickle.loads(qstate.data()) - except Exception, e: + except Exception as e: msg = 'problems loading TaurusConfig: \n%s' % repr(e) Qt.QMessageBox.critical(None, 'Error loading settings', msg) return result @@ -377,8 +380,10 @@ def saveSettings(self, group=None): self._settings.beginGroup(group) # store the config dict - self._settings.setValue("TaurusConfig", Qt.QVariant( - Qt.QByteArray(pickle.dumps(self._configurationDictionaries[group])))) + self._settings.setValue( + "TaurusConfig", + Qt.QByteArray(pickle.dumps(self._configurationDictionaries[group])) + ) if group is not None: self._settings.endGroup() #self.info('MainWindow settings saved in "%s"'%self._settings.fileName()) @@ -400,9 +405,9 @@ def clearChanges(self): for index in self.markedItems: itemToMark = self.itemFromIndex(index) while(itemToMark is not None): - itemToMark.setData(Qt.QVariant( - Qt.QFont("Arial", 10, Qt.QFont.Normal)), Qt.Qt.FontRole) - itemToMark.setData(Qt.QVariant(), Qt.Qt.DecorationRole) + itemToMark.setData(Qt.QFont("Arial", 10, Qt.QFont.Normal), + Qt.Qt.FontRole) + itemToMark.setData(None, Qt.Qt.DecorationRole) itemToMark = self.itemFromIndex(itemToMark.index().parent()) @@ -434,7 +439,7 @@ def contextMenuEvent(self, event): '''Reimplemented from :meth:`QWidget.contextMenuEvent`''' self.tree._toDeleteIndex = self.treeview.selectedIndexes()[0] - text = Qt.from_qvariant(self.tree._toDeleteIndex.data(), str) + text = self.tree._toDeleteIndex.data() if self.tree._toDeleteIndex.column() in [1, 2] or text in ['LAST', '[custom]'] or text in self.tree.perspectives: return menu = Qt.QMenu() @@ -465,7 +470,7 @@ def loadFile(self, iniFileName=None): path = Qt.QDir.homePath() else: path = self.tree.originalFile - iniFileName = Qt.QFileDialog.getOpenFileName( + iniFileName, _ = compat.getOpenFileName( self, 'Select a settings file', path, 'Ini Files (*.ini)') if not iniFileName: return diff --git a/lib/taurus/qt/qtgui/panel/taurusdemo.py b/lib/taurus/qt/qtgui/panel/taurusdemo.py index de0ef0272..405a76a62 100644 --- a/lib/taurus/qt/qtgui/panel/taurusdemo.py +++ b/lib/taurus/qt/qtgui/panel/taurusdemo.py @@ -23,8 +23,8 @@ ## ############################################################################# +from __future__ import print_function import sys -import operator import taurus.core.util import taurus.qt.qtgui.util @@ -54,20 +54,20 @@ def __init__(self, parent=None): continue internal_widget_module = sys.modules[internal_widget_module_name] if hasattr(internal_widget_module, "demo"): - if operator.isCallable(internal_widget_module.demo): + if hasattr(internal_widget_module.demo, '__call__'): demos[internal_widget_module_name] = internal_widget_module.demo groups = set() - for demo_name in demos.keys(): + for demo_name in demos: parts = demo_name.split(".") group = parts[-2] groups.add(group) - for group in groups: + for group in sorted(groups): self.addGroup(group) - for demo_name in sorted(demos.keys()): + for demo_name in sorted(demos): demo_func = demos[demo_name] parts = demo_name.split(".") group = parts[-2] @@ -75,10 +75,10 @@ def __init__(self, parent=None): continue try: self.addDemo(demo_func.__doc__, demo_func, group) - except Exception, e: - print 80 * "-" - print "Problems adding demo", demo_name - print e + except Exception as e: + print(80 * "-") + print("Problems adding demo", demo_name) + print(e) def addGroup(self, name): g = Qt.QGroupBox(name) @@ -96,7 +96,7 @@ def addDemo(self, name, f, group): button = Qt.QPushButton(name, self) button._f = f layout.addWidget(button, row, 0) - button.connect(button, Qt.SIGNAL("clicked()"), self.go) + button.clicked.connect(self.go) def go(self): b = self.sender() @@ -111,12 +111,12 @@ def go(self): dialog.setLayout(layout) layout.addWidget(w) dialog.exec_() - except Exception, e: + except Exception as e: if dialog is not None: dialog.done(0) dialog.hide() dialog = None - print str(e) + print(str(e)) return d = Qt.QErrorMessage() d.showMessage(str(e)) diff --git a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py index 870482001..63dd49262 100644 --- a/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusdevicepanel.py @@ -27,13 +27,14 @@ TaurusDevicePanel.py: """ -__all__ = ["TaurusDevicePanel", "TaurusDevPanel"] - -__docformat__ = 'restructuredtext' +from builtins import str +from future.utils import string_types import re import traceback +from future.utils import string_types + import taurus from taurus.external.qt import Qt @@ -43,12 +44,17 @@ from taurus.core.taurusdevice import TaurusDevice from taurus.qt.qtgui.container import TaurusWidget, TaurusMainWindow from taurus.qt.qtgui.display import TaurusLabel -from taurus.qt.qtgui.display import TaurusLed from taurus.qt.qtgui.panel.taurusform import TaurusForm from taurus.qt.qtgui.panel.taurusform import TaurusCommandsForm from taurus.qt.qtgui.util.ui import UILoadable from taurus.qt.qtgui.icon import getCachedPixmap + +__all__ = ["TaurusDevicePanel", "TaurusDevPanel"] + +__docformat__ = 'restructuredtext' + + ############################################################################### # TaurusDevicePanel (from Vacca) @@ -81,7 +87,7 @@ def get_regexp_dict(dct, key, default=None): # TODO: Tango-centric if default is not None: return default else: - raise Exception('KeyNotFound:%s' % k) + raise Exception('KeyNotFound:%s' % key) def get_eqtype(dev): # TODO: Tango-centric @@ -98,7 +104,7 @@ def str_to_filter(seq): # TODO: Tango-centric f = eval(seq) except: f = seq - if isinstance(f, basestring): + if isinstance(f, string_types): return {'.*': [f]} elif isinstance(f, list): return {'.*': f} @@ -217,7 +223,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('value') + self._statelabel.setBgRole('rvalue') self._stateframe.layout().addWidget(self._statelabel, 0, 1, Qt.Qt.AlignCenter) self._statusframe = Qt.QScrollArea(self) @@ -271,7 +277,7 @@ def __init__(self, parent=None, model=None, palette=None, bound=True): def loadConfigFile(self, ifile=None): self.info('In TaurusDevicePanel.loadConfigFile(%s)' % ifile) - if isinstance(ifile, file) or isinstance(ifile, str) and not ifile.endswith('.py'): + if isinstance(ifile, file) or isinstance(ifile, string_types) and not ifile.endswith('.py'): TaurusWidget.loadConfigFile(self, ifile) else: from imp import load_source @@ -353,7 +359,7 @@ def setModel(self, model, pixmap=None): filters = get_regexp_dict( TaurusDevicePanel._attribute_filter, model, ['.*']) if hasattr(filters, 'keys'): - filters = filters.items() # Dictionary! + filters = list(filters.items()) # Dictionary! if filters and isinstance(filters[0], (list, tuple)): # Mapping self._attrs = [] for tab, attrs in filters: @@ -472,7 +478,7 @@ def get_comms_form(self, device, form=None, parent=None): form.setViewFilters([lambda c: str(c.cmd_name).lower() not in ( 'state', 'status') and any(searchCl(s[0], str(c.cmd_name)) for s in params)]) form.setDefaultParameters(dict((k, v) for k, v in ( - params if not hasattr(params, 'items') else params.items()) if v)) + params if not hasattr(params, 'items') else list(params.items())) if v)) for wid in form._cmdWidgets: if not hasattr(wid, 'getCommand') or not hasattr(wid, 'setDangerMessage'): continue @@ -551,8 +557,6 @@ def createActions(self): self._ui.attrDW.toggleViewAction()) self.showCommandsAction = self.viewMenu.addAction( self._ui.commandsDW.toggleViewAction()) - self.showTrendAction = self.viewMenu.addAction( - self._ui.trendDW.toggleViewAction()) def setTangoHost(self, host): '''extended from :class:setTangoHost''' diff --git a/lib/taurus/qt/qtgui/panel/taurusform.py b/lib/taurus/qt/qtgui/panel/taurusform.py index 1f959352b..13572f4a0 100644 --- a/lib/taurus/qt/qtgui/panel/taurusform.py +++ b/lib/taurus/qt/qtgui/panel/taurusform.py @@ -25,11 +25,13 @@ """This module contains taurus Qt form widgets""" -__all__ = ["TaurusAttrForm", "TaurusCommandsForm", "TaurusForm"] - -__docformat__ = 'restructuredtext' +from __future__ import print_function +from __future__ import absolute_import from datetime import datetime +from functools import partial + +from future.utils import string_types, binary_type from taurus.external.qt import Qt @@ -40,7 +42,11 @@ TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_MODEL_MIME_TYPE) from taurus.qt.qtgui.container import TaurusWidget, TaurusScrollArea from taurus.qt.qtgui.button import QButtonBox, TaurusCommandButton -from taurusmodelchooser import TaurusModelChooser +from .taurusmodelchooser import TaurusModelChooser + +__all__ = ["TaurusAttrForm", "TaurusCommandsForm", "TaurusForm"] + +__docformat__ = 'restructuredtext' def _normalize_model_name_case(modelname): @@ -132,7 +138,7 @@ def __init__(self, parent=None, self.chooseModelsAction = Qt.QAction('Modify Contents', self) self.addAction(self.chooseModelsAction) - self.chooseModelsAction.triggered[()].connect(self.chooseModels) + self.chooseModelsAction.triggered.connect(self.chooseModels) self.showButtonsAction = Qt.QAction('Show Buttons', self) self.showButtonsAction.setCheckable(True) @@ -142,7 +148,7 @@ def __init__(self, parent=None, self.changeLabelsAction = Qt.QAction('Change labels (all items)', self) self.addAction(self.changeLabelsAction) - self.changeLabelsAction.triggered[()].connect(self.onChangeLabelsAction) + self.changeLabelsAction.triggered.connect(self.onChangeLabelsAction) self.compactModeAction = Qt.QAction('Compact mode (all items)', self) self.compactModeAction.setCheckable(True) @@ -151,7 +157,7 @@ def __init__(self, parent=None, self.setFormatterAction = Qt.QAction('Set formatter (all items)', self) self.addAction(self.setFormatterAction) - self.setFormatterAction.triggered[()].connect(self.onSetFormatter) + self.setFormatterAction.triggered.connect(self.onSetFormatter) self.resetModifiableByUser() self.setSupportedMimeTypes([TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE, @@ -174,7 +180,9 @@ def __len__(self): def _splitModel(self, modelNames): '''convert str to list if needed (commas and whitespace are considered as separators)''' - if isinstance(modelNames, (basestring, Qt.QString)): + if isinstance(modelNames, binary_type): + modelNames = modelNames.decode() + if isinstance(modelNames, string_types): modelNames = str(modelNames).replace(',', ' ') modelNames = modelNames.split() return modelNames @@ -216,7 +224,7 @@ def chooseModels(self): if self.__modelChooserDlg is None: self.__modelChooserDlg = Qt.QDialog(self) self.__modelChooserDlg.setWindowTitle( - "%s - Model Chooser" % unicode(self.windowTitle())) + "%s - Model Chooser" % str(self.windowTitle())) self.__modelChooserDlg.modelChooser = TaurusModelChooser() layout = Qt.QVBoxLayout() layout.addWidget(self.__modelChooserDlg.modelChooser) @@ -386,7 +394,7 @@ def onSetFormatter(self): format = TaurusWidget.onSetFormatter(self) if format is not None: for item in self.getItems(): - rw = item.readWidget() + rw = item.readWidget(followCompact=True) if hasattr(rw, 'setFormat'): rw.setFormat(format) return format @@ -397,9 +405,8 @@ def setFormat(self, format): """ TaurusWidget.setFormat(self, format) for item in self.getItems(): - rw = item.readWidget() - if hasattr(rw, 'setFormat'): - rw.setFormat(format) + if hasattr(item, 'setFormat'): + item.setFormat(format) def setCompact(self, compact): self._compact = compact @@ -678,7 +685,7 @@ def _updateCommandWidgets(self, *args): return for f in self.getViewFilters(): - commands = filter(f, commands) + commands = list(filter(f, commands)) self._clearFrame() @@ -865,7 +872,7 @@ def _updateAttrWidgets(self): return attrlist = sorted(dev.attribute_list_query(), key=self._sortKey) for f in self.getViewFilters(): - attrlist = filter(f, attrlist) + attrlist = list(filter(f, attrlist)) attrnames = [] devname = self.getModelName() for a in attrlist: @@ -1014,7 +1021,7 @@ def test4(): class DummyCW(TaurusValue): def setModel(self, model): - print "!!!!! IN DUMMYCW.SETMODEL", model + print("!!!!! IN DUMMYCW.SETMODEL", model) TaurusValue.setModel(self, model + '/double_scalar') models = ['sys/database/2', 'sys/tg_test/1', 'sys/tg_test/1/short_spectrum', @@ -1072,15 +1079,16 @@ def taurusFormMain(): quitApplicationAction = Qt.QAction( Qt.QIcon.fromTheme("process-stop"), 'Close Form', dialog) - quitApplicationAction.triggered[()].connect(dialog.close) + quitApplicationAction.triggered.connect(dialog.close) saveConfigAction = Qt.QAction("Save current settings...", dialog) saveConfigAction.setShortcut(Qt.QKeySequence.Save) - saveConfigAction.triggered[()].connect(dialog.saveConfigFile) - + saveConfigAction.triggered.connect( + partial(dialog.saveConfigFile, ofile=None)) loadConfigAction = Qt.QAction("&Retrieve saved settings...", dialog) loadConfigAction.setShortcut(Qt.QKeySequence.Open) - loadConfigAction.triggered[()].connect(dialog.loadConfigFile) + loadConfigAction.triggered.connect( + partial(dialog.loadConfigFile, ifile=None)) dialog.addActions( (saveConfigAction, loadConfigAction, quitApplicationAction)) diff --git a/lib/taurus/qt/qtgui/panel/taurusinputpanel.py b/lib/taurus/qt/qtgui/panel/taurusinputpanel.py index 7feb94381..6df5d2462 100644 --- a/lib/taurus/qt/qtgui/panel/taurusinputpanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusinputpanel.py @@ -24,17 +24,23 @@ ############################################################################# """This module provides an Input panel (usually used inside a TaurusDialog)""" +from __future__ import print_function -__all__ = ["TaurusInputPanel"] - -__docformat__ = 'restructuredtext' +from builtins import str +from builtins import object import collections import numpy +from future.utils import string_types + from taurus.external.qt import Qt from taurus.qt.qtgui.util.ui import UILoadable +__all__ = ["TaurusInputPanel"] + +__docformat__ = 'restructuredtext' + @UILoadable(with_ui='_ui') class TaurusInputPanel(Qt.QWidget): @@ -120,7 +126,7 @@ def create_single_input_panel(self, input_data): self.setText(input_data['prompt']) data_type = input_data.get('data_type', 'String') - is_seq = not isinstance(data_type, (str, unicode)) and \ + is_seq = not isinstance(data_type, string_types) and \ isinstance(data_type, collections.Sequence) if is_seq: panel, getter = self.create_selection_panel(input_data) @@ -160,7 +166,7 @@ def _create_combobox_panel(self, input_data): self._ui.inputWidget = combobox = Qt.QComboBox() items = input_data['data_type'] for item in items: - is_seq = not isinstance(item, (str, unicode)) and \ + is_seq = not isinstance(item, string_types) and \ isinstance(item, collections.Sequence) if is_seq: text, userData = item @@ -172,7 +178,7 @@ def _create_combobox_panel(self, input_data): def _get_combobox_value(self): combo = self._ui.inputWidget - return Qt.from_qvariant(combo.itemData(combo.currentIndex())) + return combo.itemData(combo.currentIndex()) def _create_radiobutton_panel(self, input_data): panel = self._create_group_panel(input_data) @@ -182,7 +188,7 @@ def _create_radiobutton_panel(self, input_data): self._ui.inputWidget = buttongroup = Qt.QButtonGroup() buttongroup.setExclusive(True) for item in items: - is_seq = not isinstance(item, (str, unicode)) and \ + is_seq = not isinstance(item, string_types) and \ isinstance(item, collections.Sequence) if is_seq: text, userData = item @@ -209,7 +215,7 @@ def _create_multi_selection_panel(self, input_data): default_value = input_data.get('default_value') if default_value is None: default_value = () - dft_is_seq = not isinstance(default_value, (str, unicode)) and \ + dft_is_seq = not isinstance(default_value, string_types) and \ isinstance(default_value, collections.Sequence) if not dft_is_seq: default_value = default_value, @@ -218,14 +224,14 @@ def _create_multi_selection_panel(self, input_data): listwidget.setSelectionMode(Qt.QAbstractItemView.MultiSelection) for item in items: - is_seq = not isinstance(item, (str, unicode)) and \ + is_seq = not isinstance(item, string_types) and \ isinstance(item, collections.Sequence) if is_seq: text, userData = item else: text, userData = str(item), item item_widget = Qt.QListWidgetItem(text, listwidget) - item_widget.setData(Qt.Qt.UserRole, Qt.to_qvariant(userData)) + item_widget.setData(Qt.Qt.UserRole, userData) if userData in default_value: item_widget.setSelected(True) layout.addWidget(listwidget) @@ -233,7 +239,7 @@ def _create_multi_selection_panel(self, input_data): def _get_multi_selection_value(self): listwidget = self._ui.inputWidget - return [Qt.from_qvariant(item.data(Qt.Qt.UserRole)) for item in listwidget.selectedItems()] + return [item.data(Qt.Qt.UserRole) for item in listwidget.selectedItems()] def _create_group_panel(self, input_data): title = input_data.get('key', '') @@ -402,7 +408,7 @@ def main(): class Listener(object): def on_accept(self): - print "user selected", self.panel.value() + print("user selected", self.panel.value()) d = dict(prompt="What's your favourite car brand?", data_type=["Mazda", "Skoda", "Citroen", diff --git a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py index 9b1ab110b..ae0554a8c 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py +++ b/lib/taurus/qt/qtgui/panel/taurusmessagepanel.py @@ -25,10 +25,9 @@ """This module provides a panel to display taurus messages""" -__all__ = ["TaurusMessagePanel", "TaurusMessageErrorHandler", - "TangoMessageErrorHandler", "MacroServerMessageErrorHandler"] - -__docformat__ = 'restructuredtext' +from future import standard_library +standard_library.install_aliases() +from builtins import object import sys import traceback @@ -47,6 +46,12 @@ from taurus.qt.qtgui.util.ui import UILoadable +__all__ = ["TaurusMessagePanel", "TaurusMessageErrorHandler", + "TangoMessageErrorHandler", "MacroServerMessageErrorHandler"] + +__docformat__ = 'restructuredtext' + + class TaurusMessageErrorHandler(object): """This class is designed to handle a generic error into a :class:`TaurusMessagePanel`""" @@ -107,7 +112,7 @@ def setError(self, err_type=None, err_value=None, err_traceback=None): formatter = HtmlFormatter() style = formatter.get_style_defs() html = html_orig.format(style=style) - for de in err_value: + for de in err_value.args: e_html = """
{reason}: {desc}
{origin}
""" origin, reason, desc = de.origin, de.reason, de.desc if reason.startswith("PyDs_") and pygments is not None: @@ -116,7 +121,7 @@ def setError(self, err_type=None, err_value=None, err_traceback=None): origin = "
%s
" % origin html += e_html.format(desc=desc, origin=origin, reason=reason) html += "" - msgbox.setText(err_value[0].desc) + msgbox.setText(err_value.args[0].desc) msgbox.setDetailedHtml(html) exc_info = "".join(traceback.format_exception(err_type, err_value, @@ -204,7 +209,7 @@ def get_report_handlers(): elem, _ = os.path.splitext(elem) _is_report_handler = functools.partial( is_report_handler, abs_file=full_elem) - report_lib = __import__(elem, globals(), locals(), [], -1) + report_lib = __import__(elem, globals(), locals(), [], 0) for name, obj in inspect.getmembers(report_lib, _is_report_handler): _REPORT_HANDLERS[name] = obj finally: @@ -279,13 +284,12 @@ def _initReportCombo(self): report_handlers = get_report_handlers() combo = self.reportComboBox() for name, report_handler in report_handlers.items(): - name = Qt.QVariant(name) combo.addItem(report_handler.Label, name) def _onReportTriggered(self, index): report_handlers = get_report_handlers() combo = self.reportComboBox() - name = Qt.from_qvariant(combo.itemData(index), str) + name = combo.itemData(index) report_handler = report_handlers[name] report = report_handler(self) app = Qt.QApplication.instance() @@ -586,11 +590,18 @@ def py_tg_serv_exc(): try: PyTango.Except.throw_exception( 'TangoException', 'A simple tango exception', 'right here') - except PyTango.DevFailed, df1: + except PyTango.DevFailed as df1: try: import traceback - import StringIO - origin = StringIO.StringIO() + # --------------------------------------------------------------- + # workaround for unicode issues on py2 when using io instead of + # StringIO + try: + import StringIO as io # py2 + except ImportError: + import io # py3 + # ---------------------------------------------------------------- + origin = io.StringIO() traceback.print_stack(file=origin) origin.seek(0) origin = origin.read() diff --git a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py index 58fb9adff..e505715e5 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py +++ b/lib/taurus/qt/qtgui/panel/taurusmodelchooser.py @@ -26,8 +26,8 @@ """ AttributeChooser.py: widget for choosing (a list of) attributes from a tango DB """ - -__all__ = ["TaurusModelSelectorTree", "TaurusModelChooser"] +from __future__ import print_function +from __future__ import absolute_import import sys from taurus.external.qt import Qt @@ -35,7 +35,10 @@ from taurus.qt.qtgui.container import TaurusWidget from taurus.qt.qtgui.tree import TaurusDbTreeWidget from taurus.core.util.containers import CaselessList -from taurusmodellist import TaurusModelList +from .taurusmodellist import TaurusModelList + + +__all__ = ["TaurusModelSelectorTree", "TaurusModelChooser"] class TaurusModelSelectorTree(TaurusWidget): @@ -355,7 +358,7 @@ def main(args): host = None app = Qt.QApplication(args) - print TaurusModelChooser.modelChooserDlg(host=host) + print(TaurusModelChooser.modelChooserDlg(host=host)) sys.exit() if __name__ == "__main__": diff --git a/lib/taurus/qt/qtgui/panel/taurusmodellist.py b/lib/taurus/qt/qtgui/panel/taurusmodellist.py index b1369fbce..fef186b5e 100644 --- a/lib/taurus/qt/qtgui/panel/taurusmodellist.py +++ b/lib/taurus/qt/qtgui/panel/taurusmodellist.py @@ -26,19 +26,25 @@ """ itemsmodel Model and view for new CurveItem configuration """ -__all__ = ['TaurusModelModel', 'TaurusModelItem', 'TaurusModelList'] +from builtins import object #raise UnimplementedError('Under Construction!') import copy +from future.utils import string_types + from taurus.external.qt import Qt import taurus from taurus.core.taurushelper import getSchemeFromName from taurus.core.taurusbasetypes import TaurusElementType from taurus.core.taurusexception import TaurusException -from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE +from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_LIST_MIME_TYPE +from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE +from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_MIME_TYPE from taurus.qt.qtgui.icon import getElementTypeIcon -from taurus.qt.qtcore.util.signal import baseSignal + + +__all__ = ['TaurusModelModel', 'TaurusModelItem', 'TaurusModelList'] # set some named constants SRC_ROLE = Qt.Qt.UserRole + 1 @@ -142,24 +148,24 @@ def rowCount(self, index=Qt.QModelIndex()): def data(self, index, role=Qt.Qt.DisplayRole): '''reimplemented from :class:`Qt.QAbstractListModel`''' if not index.isValid() or not (0 <= index.row() < self.rowCount()): - return Qt.QVariant() + return None row = index.row() # Display Role if role == Qt.Qt.DisplayRole: - return Qt.QVariant(Qt.QString(self.items[row].display)) + return str(self.items[row].display) elif role == Qt.Qt.DecorationRole: - return Qt.QVariant(self.items[row].icon) + return self.items[row].icon elif role == Qt.Qt.TextColorRole: if not self.items[row].src: - return Qt.QVariant(Qt.QColor('gray')) - return Qt.QVariant(Qt.QColor(self.items[row].ok and 'green' or 'red')) + return Qt.QColor('gray') + return Qt.QColor(self.items[row].ok and 'green' or 'red') elif role == SRC_ROLE: - return Qt.QVariant(Qt.QString(self.items[row].src)) + return str(self.items[row].src) elif role == Qt.Qt.ToolTipRole: - return Qt.QVariant(Qt.QString(self.items[row].src)) + return str(self.items[row].src) if role == Qt.Qt.EditRole: - return Qt.QVariant(Qt.QString(self.items[row].src)) - return Qt.QVariant() + return str(self.items[row].src) + return None def flags(self, index): '''reimplemented from :class:`Qt.QAbstractListModel`''' @@ -172,7 +178,6 @@ def setData(self, index, value=None, role=Qt.Qt.EditRole): if index.isValid() and (0 <= index.row() < self.rowCount()): row = index.row() item = self.items[row] - value = Qt.from_qvariant(value, unicode) if role == Qt.Qt.EditRole: item.src = value elif role == Qt.Qt.DisplayRole: @@ -188,7 +193,7 @@ def insertRows(self, position=None, rows=1, parentindex=None, items=None): if parentindex is None: parentindex = Qt.QModelIndex() if items is None: - slice = [TaurusModelItem() for i in xrange(rows)] + slice = [TaurusModelItem() for i in range(rows)] else: slice = list(items) # note that the rows parameter is ignored if items is passed @@ -202,10 +207,11 @@ def removeRows(self, position, rows=1, parentindex=None): '''reimplemented from :class:`Qt.QAbstractListModel`''' if parentindex is None: parentindex = Qt.QModelIndex() + self.beginResetModel() self.beginRemoveRows(parentindex, position, position + rows - 1) self.items = self.items[:position] + self.items[position + rows:] self.endRemoveRows() - self.reset() + self.endResetModel() return True def clearAll(self): @@ -255,7 +261,7 @@ def insertItems(self, row, items): for e in items: if isinstance(e, TaurusModelItem): itemobjs.append(e) - elif isinstance(e, basestring): + elif isinstance(e, string_types): itemobjs.append(TaurusModelItem(src=e)) else: # assuming it is a sequence of arguments that can be passed to the constructor of TaurusModelItem itemobjs.append(TaurusModelItem(*e)) @@ -265,9 +271,8 @@ def mimeData(self, indexes): '''reimplemented from :class:`Qt.QAbstractListModel`''' mimedata = Qt.QAbstractListModel.mimeData(self, indexes) if len(indexes) == 1: - # mimedata.setData(TAURUS_ATTR_MIME_TYPE, - # Qt.from_qvariant(self.data(indexes[0]), str))) - txt = Qt.from_qvariant(self.data(indexes[0], role=SRC_ROLE), str) + # mimedata.setData(TAURUS_ATTR_MIME_TYPE, self.data(indexes[0])) + txt = self.data(indexes[0], role=SRC_ROLE) mimedata.setText(txt) return mimedata # mimedata.setData() @@ -330,14 +335,13 @@ def _onEditDisplay(self): idx = selected[0] else: return - value = Qt.from_qvariant(self._model.data( - idx, role=Qt.Qt.DisplayRole), str) - src = Qt.from_qvariant(self._model.data(idx, role=SRC_ROLE), str) + value = self._model.data(idx, role=Qt.Qt.DisplayRole) + src = self._model.data(idx, role=SRC_ROLE) value, ok = Qt.QInputDialog.getText( self, "Display Value", "Display value for %s?" % src, Qt.QLineEdit.Normal, value) if not ok: return - self._model.setData(idx, Qt.QVariant(value), role=Qt.Qt.DisplayRole) + self._model.setData(idx, value, role=Qt.Qt.DisplayRole) def _onSelectionChanged(self, selected, deselected): '''updates the status of the actions that depend on the selection''' @@ -424,7 +428,7 @@ def getModelList(self): .. seealso:: :meth:`getModelItems` ''' - return [unicode(s.src) for s in self.getModelItems()] + return [str(s.src) for s in self.getModelItems()] @classmethod def getQtDesignerPluginInfo(cls): diff --git a/lib/taurus/qt/qtgui/panel/taurusvalue.py b/lib/taurus/qt/qtgui/panel/taurusvalue.py index c052cef25..815328288 100644 --- a/lib/taurus/qt/qtgui/panel/taurusvalue.py +++ b/lib/taurus/qt/qtgui/panel/taurusvalue.py @@ -35,6 +35,9 @@ __docformat__ = 'restructuredtext' +from future.utils import string_types +from future.builtins import str + import weakref import re from taurus.external.qt import Qt @@ -99,13 +102,8 @@ def setModel(self, model): config = self.taurusValueBuddy().getLabelConfig() TaurusLabel.setModel(self, '%s#%s' % (fullname, config)) elif elementtype == TaurusElementType.Device: - # @TODO: tango-centric! - # TaurusLabel.setModel(self, '%s/state#dev_alias'%fullname) - # - # The following is a workaround to avoid tango-centricity, but - # it has the drawback that the model is not set (e.g., no tooltip) devName = self.taurusValueBuddy().getModelObj().getSimpleName() - TaurusLabel.setModel(self, None) + TaurusLabel.setModel(self, model) self.setText(devName) _BCK_COMPAT_TAGS = {'': '{attr.name}', @@ -160,16 +158,18 @@ def contextMenuEvent(self, event): event.accept() def getModelMimeData(self): - '''reimplemented to use the taurusValueBuddy model instead of its own model''' + """ + reimplemented to use the taurusValueBuddy model instead of its own + model + """ mimeData = TaurusLabel.getModelMimeData(self) - mimeData.setData(TAURUS_MODEL_MIME_TYPE, - self.taurusValueBuddy().getModelName()) + _modelname = str(self.taurusValueBuddy().getModelName()) + modelname = _modelname.encode(encoding='utf8') + mimeData.setData(TAURUS_MODEL_MIME_TYPE, modelname) if self.taurusValueBuddy().getModelType() == TaurusElementType.Device: - mimeData.setData(TAURUS_DEV_MIME_TYPE, - self.taurusValueBuddy().getModelName()) + mimeData.setData(TAURUS_DEV_MIME_TYPE, modelname) elif self.taurusValueBuddy().getModelType() == TaurusElementType.Attribute: - mimeData.setData(TAURUS_ATTR_MIME_TYPE, - self.taurusValueBuddy().getModelName()) + mimeData.setData(TAURUS_ATTR_MIME_TYPE, modelname) return mimeData @classmethod @@ -442,16 +442,21 @@ def onSetFormatter(self): """ Reimplemented to call onSetFormatter of the read widget (if provided) """ - if hasattr(self._readWidget, 'onSetFormatter'): - return self._readWidget.onSetFormatter() + rw = self.readWidget(followCompact=True) + if hasattr(rw, 'onSetFormatter'): + return rw.onSetFormatter() def setFormat(self, format): """ Reimplemented to call setFormat of the read widget (if provided) """ TaurusBaseWidget.setFormat(self, format) - if hasattr(getattr(self, '_readWidget', None), 'setFormat'): - return self._readWidget.setFormat(format) + try: + rw = self.readWidget(followCompact=True) + except AttributeError: + return + if hasattr(rw, 'setFormat'): + rw.setFormat(format) def getAllowWrite(self): return self._allowWrite @@ -831,7 +836,7 @@ def updateReadWidget(self): try: klass = self.readWidgetClassFactory(self.readWidgetClassID) self._readWidget = self._newSubwidget(self._readWidget, klass) - except Exception, e: + except Exception as e: self._destroyWidget(self._readWidget) self._readWidget = Qt.QLabel('[Error]') msg = 'Error creating read widget:\n' + str(e) @@ -1084,13 +1089,29 @@ def resetExtraWidgetClass(self): self.extraWidgetClassID = 'Auto' def setCompact(self, compact): + + # don't do anything if it is already done if compact == self._compact: return + + #do not switch to compact mode if the write widget is None + if compact and self.writeWidget() is None: + self.debug('No write widget. Ignoring setCompact(True)') + return + + # Backup the current RW format + rw = self.readWidget(followCompact=True) + format = rw.getFormat() + self._compact = compact if self.getModel(): self.updateReadWidget() self.updateWriteWidget() + # Apply the format to the new RW + rw = self.readWidget(followCompact=True) + rw.setFormat(format) + def isCompact(self): return self._compact @@ -1129,7 +1150,8 @@ def createConfig(self, allowUnpickable=False): for key in ('LabelWidget', 'ReadWidget', 'WriteWidget', 'UnitsWidget', 'CustomWidget', 'ExtraWidget'): # calls self.getLabelWidgetClass, self.getReadWidgetClass,... classID = getattr(self, 'get%sClass' % key)() - if isinstance(classID, (str, Qt.QString)) or allowUnpickable: + if (isinstance(classID, string_types) + or allowUnpickable): #configdict[key] = classID configdict[key] = {'classid': classID} widget = getattr(self, key[0].lower() + key[1:])() @@ -1156,7 +1178,7 @@ def applyConfig(self, configdict, **kwargs): widget_configdict = configdict[key] getattr(self, 'set%sClass' % key)( widget_configdict.get('classid', None)) - if widget_configdict.has_key('delegate'): + if 'delegate' in widget_configdict: widget = getattr(self, key[0].lower() + key[1:])() if isinstance(widget, BaseConfigurableClass): widget.applyConfig(widget_configdict[ diff --git a/lib/taurus/qt/qtgui/panel/ui/TaurusDevPanel.ui b/lib/taurus/qt/qtgui/panel/ui/TaurusDevPanel.ui index f4eaf77fb..a155f1ba5 100644 --- a/lib/taurus/qt/qtgui/panel/ui/TaurusDevPanel.ui +++ b/lib/taurus/qt/qtgui/panel/ui/TaurusDevPanel.ui @@ -1,3 +1,4 @@ + TaurusDevPanel @@ -29,8 +30,8 @@ - - + + true @@ -51,8 +52,8 @@ - - + + true @@ -60,52 +61,17 @@ - - - Trends - - - 8 - - - - - - - - - - - TaurusWidget - QWidget -
taurus.qt.qtgui.container
-
- - TaurusPlot - QwtPlot -
taurus.qt.qtgui.plot
-
- - TaurusTrend - TauPlot -
taurus.qt.qtgui.plot
-
- - QwtPlot - QFrame -
qwt_plot.h
-
TaurusCommandsForm - TauWidget + QWidget
taurus.qt.qtgui.panel
1
TaurusAttrForm - TauWidget + QWidget
taurus.qt.qtgui.panel
1
@@ -113,4 +79,3 @@
- diff --git a/lib/taurus/qt/qtgui/plot/__init__.py b/lib/taurus/qt/qtgui/plot/__init__.py old mode 100644 new mode 100755 index 9a8011352..1d4326cb6 --- a/lib/taurus/qt/qtgui/plot/__init__.py +++ b/lib/taurus/qt/qtgui/plot/__init__.py @@ -24,20 +24,43 @@ ############################################################################# """ -Taurus Widget Plot module -========================== +Taurus Plot module (old) +======================== + +This module is now deprecated. + +If available, it exposes the plot API now implemented in +`taurus.qt.qtgui.qwt5`. Otherwise it tries to provide a minimal API +(essentially the TaurusPlot and TaurusTrend classes from taurus_pyqtgraph) +to help with the transition to another plotting module such as +taurus.qt.qtgui.tpg. + +Note that if you really want to continue using the old Qwt5-based widgets and +avoid deprecation warnings, you can import the old API from +taurus.qt.qtgui.qwt5 + +Note that `PyQwt5 module `_ +only works with Python2 and PyQt4 and is no longer supported, +so taurus is moving to other modules for plotting (pyqtgraph, silx, ...) -This module is part of Taurus Widgets. It contains specialized widgets for 2D plotting -in Taurus. It depends on the `PyQwt module `_ """ -from .qwtdialog import TaurusPlotConfigDialog -from .scales import * -from .taurusplot import * -from .taurustrend import * -from .arrayedit import ArrayEditor -from .taurusarrayedit import TaurusArrayEditor -from .curvesAppearanceChooserDlg import CurveAppearanceProperties, CurvesAppearanceChooser -from .curveprops import CurvePropertiesView -from .monitor import TaurusMonitorTiny -from .curveStatsDlg import CurveStatsDialog +from __future__ import absolute_import +from taurus.core.util import log as __log + +__log.deprecated(dep='taurus.qt.qtgui.plot', rel='4.5', + alt='taurus.qt.qtgui.tpg or taurus.qt.qtgui.qwt5') + +try: + from taurus.qt.qtgui.qwt5 import * +except: + try: + from taurus.qt.qtgui.tpg import TaurusPlot, TaurusTrend + __log.info('plot: Using taurus.qt.qtgui.tpg to provide a minimal API ' + + 'to facilitate the transition') + except: + __log.info('plot: Cannot import taurus.qt.qtgui.tpg to provide a ' + + 'minimal API for transition') + + + diff --git a/lib/taurus/qt/qtgui/qwt5/__init__.py b/lib/taurus/qt/qtgui/qwt5/__init__.py new file mode 100644 index 000000000..38e0ee7ce --- /dev/null +++ b/lib/taurus/qt/qtgui/qwt5/__init__.py @@ -0,0 +1,47 @@ +#!/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 . +## +############################################################################# + +""" +Taurus Widget qwt5 (former `plot`) module +========================================== + + +This module provides the Qwt5-based widgets previously provided by +`taurus.qt.qtgui.plot`. +Note that `PyQwt5 module `_ +only works with Python2 and PyQt4 and is no longer supported, +so taurus is moving to other modules for plotting (pyqtgraph, silx, ...) +""" + +from .qwtdialog import TaurusPlotConfigDialog +from .scales import * +from .taurusplot import * +from .taurustrend import * +from .arrayedit import ArrayEditor +from .taurusarrayedit import TaurusArrayEditor +from .curvesAppearanceChooserDlg import CurveAppearanceProperties, CurvesAppearanceChooser +from .curveprops import CurvePropertiesView +from .monitor import TaurusMonitorTiny +from .curveStatsDlg import CurveStatsDialog diff --git a/lib/taurus/qt/qtgui/plot/arrayedit.py b/lib/taurus/qt/qtgui/qwt5/arrayedit.py similarity index 96% rename from lib/taurus/qt/qtgui/plot/arrayedit.py rename to lib/taurus/qt/qtgui/qwt5/arrayedit.py index 984733a27..f3c36dfc6 100644 --- a/lib/taurus/qt/qtgui/plot/arrayedit.py +++ b/lib/taurus/qt/qtgui/qwt5/arrayedit.py @@ -26,12 +26,13 @@ """ arrayedit.py: Widget for editing a spectrum/array via control points """ +from __future__ import absolute_import - +from builtins import range import numpy from taurus.external.qt import Qt, Qwt5 from taurus.qt.qtgui.util.ui import UILoadable -from curvesAppearanceChooserDlg import CurveAppearanceProperties +from .curvesAppearanceChooserDlg import CurveAppearanceProperties @UILoadable @@ -155,11 +156,11 @@ def __init__(self, parent=None): # Qt.QTimer.singleShot(0, ) # connections - self.addCPointsBT.clicked[()].connect(self._addCPointsDialog.show) - self._addCPointsDialog.editBT.clicked[()].connect(self.showEditCPointsDialog) - self._addCPointsDialog.cleanBT.clicked[()].connect(self.resetCorrection) - self._addCPointsDialog.addSingleCPointBT.clicked[()].connect(self.onAddSingleCPointBT) - self._addCPointsDialog.addRegEspCPointsBT.clicked[()].connect(self.onAddRegEspCPointsBT) + self.addCPointsBT.clicked.connect(self._addCPointsDialog.show) + self._addCPointsDialog.editBT.clicked.connect(self.showEditCPointsDialog) + self._addCPointsDialog.cleanBT.clicked.connect(self.resetCorrection) + self._addCPointsDialog.addSingleCPointBT.clicked.connect(self.onAddSingleCPointBT) + self._addCPointsDialog.addRegEspCPointsBT.clicked.connect(self.onAddRegEspCPointsBT) def plot1MousePressEvent(self, event): self.plotMousePressEvent(event, self.plot1) @@ -231,10 +232,10 @@ def makeControllerVisible(self, ctrl=None): def connectToController(self, ctrl): ctrl.selected.connect(self.changeCPointSelection) ctrl.corrSB.valueChanged.connect(self.onCorrSBChanged) - ctrl.lCopyBT.clicked[()].connect(self.onLCopy) - ctrl.rCopyBT.clicked[()].connect(self.onRCopy) - ctrl.lScaleBT.clicked[()].connect(self.onLScale) - ctrl.rScaleBT.clicked[()].connect(self.onRScale) + ctrl.lCopyBT.clicked.connect(self.onLCopy) + ctrl.rCopyBT.clicked.connect(self.onRCopy) + ctrl.lScaleBT.clicked.connect(self.onLScale) + ctrl.rScaleBT.clicked.connect(self.onRScale) def onAddSingleCPointBT(self): x = self._addCPointsDialog.singleCPointXSB.value() @@ -265,7 +266,7 @@ def showEditCPointsDialog(self): new_xp = numpy.zeros(table.rowCount()) new_corrp = numpy.zeros(table.rowCount()) try: - for i in xrange(table.rowCount()): + for i in range(table.rowCount()): new_xp[i] = float(table.item(i, 0).text()) new_corrp[i] = float(table.item(i, 1).text()) self.setCorrection(new_xp, new_corrp) diff --git a/lib/taurus/qt/qtgui/plot/curveStatsDlg.py b/lib/taurus/qt/qtgui/qwt5/curveStatsDlg.py similarity index 95% rename from lib/taurus/qt/qtgui/plot/curveStatsDlg.py rename to lib/taurus/qt/qtgui/qwt5/curveStatsDlg.py index 86d2a6bdc..807b31b80 100644 --- a/lib/taurus/qt/qtgui/plot/curveStatsDlg.py +++ b/lib/taurus/qt/qtgui/qwt5/curveStatsDlg.py @@ -29,6 +29,7 @@ for a QwtPlot-derived widget (like Taurusplot) """ +from builtins import range from taurus.external.qt import Qt, Qwt5 from datetime import datetime from taurus.qt.qtgui.util.ui import UILoadable @@ -68,7 +69,7 @@ def __init__(self, parent=None): cbs = (self.ui.npointsStatCB, self.ui.minStatCB, self.ui.maxStatCB, self.ui.meanStatCB, self.ui.stdStatCB, self.ui.rmsStatCB) - self._checkboxToColMap = dict(zip(cbs, xrange(len(self.statColumns)))) + self._checkboxToColMap = dict(zip(cbs, range(len(self.statColumns)))) self.minPicker = Qwt5.QwtPlotPicker(Qwt5.QwtPlot.xBottom, Qwt5.QwtPlot.yLeft, @@ -113,7 +114,7 @@ def __init__(self, parent=None): refreshAction = Qt.QAction(Qt.QIcon.fromTheme( 'view-refresh'), "Refresh available curves", self.ui.statsTW) refreshAction.setShortcut(Qt.Qt.Key_F5) - refreshAction.triggered[()].connect(self.refreshCurves) + refreshAction.triggered.connect(self.refreshCurves) self.ui.statsTW.addAction(refreshAction) # connections @@ -133,7 +134,8 @@ def __init__(self, parent=None): def _timestamptToQDateTime(self, ts): dt = datetime.fromtimestamp(ts) - return Qt.QDateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond / 1000) + return Qt.QDateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, + dt.second, dt.microsecond // 1000) def _QDateTimeToTimestamp(self, qdt): return qdt.toTime_t() + qdt.time().msec() / 1000. @@ -233,7 +235,7 @@ def getSelectedRows(self): '''returns a list of row numbers corresponding to the selected rows of the table''' selected = [] for rg in self.ui.statsTW.selectedRanges(): - for row in xrange(rg.topRow(), rg.topRow() + rg.rowCount()): + for row in range(rg.topRow(), rg.topRow() + rg.rowCount()): selected.append(row) return selected @@ -249,21 +251,21 @@ def onCalculate(self): xmin, xmax = None, None if self.ui.minCB.isChecked(): if plot.getXIsTime(): - xmin = self.ui.minDTE.dateTime().toTime_t() + self.ui.minDTE.time().msec() / \ - 1000. + xmin = (self.ui.minDTE.dateTime().toTime_t() + + self.ui.minDTE.time().msec() / 1000.) else: xmin = self.ui.minSB.value() if self.ui.maxCB.isChecked(): if plot.getXIsTime(): - xmax = self.ui.maxDTE.dateTime().toTime_t() + self.ui.maxDTE.time().msec() / \ - 1000. + xmax = (self.ui.maxDTE.dateTime().toTime_t() + + self.ui.maxDTE.time().msec() / 1000.) else: xmax = self.ui.maxSB.value() limits = xmin, xmax selectedRows = self.getSelectedRows() if len(selectedRows) == 0: - selectedRows = range(len(self.curveNames)) + selectedRows = list(range(len(self.curveNames))) selectedCurves = [self.curveNames[i] for i in selectedRows] statsdict = plot.getCurveStats( limits=limits, curveNames=selectedCurves) diff --git a/lib/taurus/qt/qtgui/plot/curveprops.py b/lib/taurus/qt/qtgui/qwt5/curveprops.py similarity index 85% rename from lib/taurus/qt/qtgui/plot/curveprops.py rename to lib/taurus/qt/qtgui/qwt5/curveprops.py index ecfaee425..e5a4054f2 100755 --- a/lib/taurus/qt/qtgui/plot/curveprops.py +++ b/lib/taurus/qt/qtgui/qwt5/curveprops.py @@ -26,12 +26,14 @@ """ curveprops: Model and view for curve properties """ -__all__ = ['CurveConf', 'CurvesTableModel', - 'ExtendedSelectionModel', 'CurvePropertiesView'] + #raise NotImplementedError('Under Construction!') +from __future__ import absolute_import +from builtins import str +from builtins import object + import copy -import re from taurus.external.qt import Qt, Qwt5 import taurus @@ -40,16 +42,19 @@ from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_ATTR_MIME_TYPE from taurus.qt.qtgui.util.ui import UILoadable -from curvesAppearanceChooserDlg import NamedLineStyles, ReverseNamedLineStyles, \ +from .curvesAppearanceChooserDlg import NamedLineStyles, ReverseNamedLineStyles, \ NamedCurveStyles, ReverseNamedCurveStyles, \ NamedSymbolStyles, ReverseNamedSymbolStyles, \ NamedColors, CurveAppearanceProperties +__all__ = ['CurveConf', 'CurvesTableModel', + 'ExtendedSelectionModel', 'CurvePropertiesView'] + # set some named constants # columns: NUMCOLS = 4 -X, Y, TITLE, VIS = range(NUMCOLS) +X, Y, TITLE, VIS = list(range(NUMCOLS)) SRC_ROLE = Qt.Qt.UserRole + 1 PROPS_ROLE = Qt.Qt.UserRole + 2 @@ -73,7 +78,7 @@ def setSrc(self, src): def processSrc(self, src): '''returns src,display,icon,ok''' - src = unicode(src) + src = str(src) # empty if src == '': return '', '', Qt.QIcon(), True @@ -134,93 +139,90 @@ def columnCount(self, index=Qt.QModelIndex()): def data(self, index, role=Qt.Qt.DisplayRole): if not index.isValid() or not (0 <= index.row() < self.rowCount()): - return Qt.QVariant() + return None row = index.row() column = index.column() # Display Role if role == Qt.Qt.DisplayRole: if column == X: - return Qt.QVariant(Qt.QString(self.curves[row].x.display)) + return str(self.curves[row].x.display) elif column == Y: - return Qt.QVariant(Qt.QString(self.curves[row].y.display)) + return str(self.curves[row].y.display) elif column == TITLE: - return Qt.QVariant(Qt.QString(self.curves[row].title)) + return str(self.curves[row].title) elif column == VIS: - return Qt.QVariant(Qt.QString(self.curves[row].vis)) + return str(self.curves[row].vis) else: - return Qt.QVariant() + return None elif role == Qt.Qt.DecorationRole: if column == X: - return Qt.QVariant(self.curves[row].x.icon) + return self.curves[row].x.icon elif column == Y: - return Qt.QVariant(self.curves[row].y.icon) + return self.curves[row].y.icon elif column == TITLE: - return Qt.QVariant(Qt.QColor(self.curves[row].properties.lColor or 'black')) + return Qt.QColor(self.curves[row].properties.lColor or 'black') else: - return Qt.QVariant() + return None elif role == Qt.Qt.TextColorRole: if column == X: - Qt.QVariant( - Qt.QColor(self.curves[row].x.ok and 'green' or 'red')) + Qt.QColor(self.curves[row].x.ok and 'green' or 'red') elif column == Y: - Qt.QVariant( - Qt.QColor(self.curves[row].y.ok and 'green' or 'red')) + Qt.QColor(self.curves[row].y.ok and 'green' or 'red') else: - return Qt.QVariant() + return None elif role == SRC_ROLE: if column == X: - return Qt.QVariant(Qt.QString(self.curves[row].x.src)) + return str(self.curves[row].x.src) elif column == Y: - return Qt.QVariant(Qt.QString(self.curves[row].y.src)) + return str(self.curves[row].y.src) else: - return Qt.QVariant() + return None elif role == PROPS_ROLE: return self.curves[row].properties elif role == Qt.Qt.ToolTipRole: if column == X: - return Qt.QVariant(Qt.QString(self.curves[row].x.src)) + return str(self.curves[row].x.src) elif column == Y: - return Qt.QVariant(Qt.QString(self.curves[row].y.src)) + return str(self.curves[row].y.src) else: - return Qt.QVariant() + return None if role == Qt.Qt.EditRole: if column == X: - return Qt.QVariant(Qt.QString(self.curves[row].x.src)) + return str(self.curves[row].x.src) elif column == Y: - return Qt.QVariant(Qt.QString(self.curves[row].y.src)) + return str(self.curves[row].y.src) elif column == TITLE: - return Qt.QVariant(Qt.QString(self.curves[row].title)) + return str(self.curves[row].title) else: - return Qt.QVariant() + return None # Alignment # elif role == Qt.Qt.TextAlignmentRole: -# return QVariant(int(Qt.AlignHCenter|Qt.AlignVCenter)) +# return int(Qt.AlignHCenter|Qt.AlignVCenter) # Text Color # elif role == Qt.Qt.TextColorRole: -# return Qt.QVariant(Qt.QColor(self.curves[row].properties.lColor or -# 'black')) - return Qt.QVariant() +# return Qt.QColor(self.curves[row].properties.lColor or 'black') + return None def headerData(self, section, orientation, role=Qt.Qt.DisplayRole): if role == Qt.Qt.TextAlignmentRole: if orientation == Qt.Qt.Horizontal: - return Qt.QVariant(int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter)) - return Qt.QVariant(int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter)) + return int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter) + return int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter) if role != Qt.Qt.DisplayRole: - return Qt.QVariant() + return None # So this is DisplayRole... if orientation == Qt.Qt.Horizontal: if section == X: - return Qt.QVariant("X source") + return "X source" elif section == Y: - return Qt.QVariant("Y Source") + return "Y Source" elif section == TITLE: - return Qt.QVariant("Title") + return "Title" elif section == VIS: - return Qt.QVariant("Shown at") - return Qt.QVariant() + return "Shown at" + return None else: - return Qt.QVariant(Qt.QString.number(section + 1)) + return str(section + 1) def flags(self, index): # use this to set the editable flag when fix is selected if not index.isValid(): @@ -242,7 +244,6 @@ def setData(self, index, value=None, role=Qt.Qt.EditRole): row, 0), self.index(row, self.ncolumns - 1)) else: column = index.column() - value = Qt.from_qvariant(value, unicode) if column == X: curve.x.setSrc(value) elif column == Y: @@ -269,10 +270,11 @@ def insertRows(self, position=None, rows=1, parentindex=None): def removeRows(self, position, rows=1, parentindex=None): if parentindex is None: parentindex = Qt.QModelIndex() + self.beginResetModel() self.beginRemoveRows(parentindex, position, position + rows - 1) self.curves = self.curves[:position] + self.curves[position + rows:] self.endRemoveRows() - self.reset() + self.endResetModel() return True def mimeTypes(self): @@ -293,31 +295,28 @@ def dropMimeData(self, data, action, row, column, parent): column = parent.columnCount() if data.hasFormat(TAURUS_ATTR_MIME_TYPE): self.setData(self.index(row, column), - value=Qt.QVariant(str(data.data(TAURUS_ATTR_MIME_TYPE)))) + value=str(data.data(TAURUS_ATTR_MIME_TYPE))) return True elif data.hasFormat(TAURUS_MODEL_LIST_MIME_TYPE): models = str(data.data(TAURUS_MODEL_LIST_MIME_TYPE)).split() if len(models) == 1: - self.setData(self.index(row, column), - value=Qt.QVariant(models[0])) + self.setData(self.index(row, column), value=models[0]) return True else: self.insertRows(row, len(models)) for i, m in enumerate(models): - self.setData(self.index(row + i, column), - value=Qt.QVariant(m)) + self.setData(self.index(row + i, column), value=m) return True elif data.hasText(): - self.setData(self.index(row, column), Qt.QVariant(data.text())) + self.setData(self.index(row, column), data.text()) return True return False def mimeData(self, indexes): mimedata = Qt.QAbstractTableModel.mimeData(self, indexes) if len(indexes) == 1: - # txt = Qt.from_qvariant(self.data(indexes[0], str) - # mimedata.setData(TAURUS_ATTR_MIME_TYPE, txt) - txt = Qt.from_qvariant(self.data(indexes[0], role=SRC_ROLE), str) + txt = self.data(indexes[0], role=SRC_ROLE) + # mimedata.setData(TAURUS_ATTR_MIME_TYPE, txt) mimedata.setText(txt) return mimedata # mimedata.setData() @@ -356,14 +355,14 @@ def __init__(self, parent=None, designMode=False): self.loadUi() self.ui.sStyleCB.insertItems(0, sorted(NamedSymbolStyles.values())) - self.ui.lStyleCB.insertItems(0, NamedLineStyles.values()) - self.ui.cStyleCB.insertItems(0, NamedCurveStyles.values()) + self.ui.lStyleCB.insertItems(0, list(NamedLineStyles.values())) + self.ui.cStyleCB.insertItems(0, list(NamedCurveStyles.values())) self.ui.sColorCB.addItem("") self.ui.lColorCB.addItem("") for color in NamedColors: icon = self._colorIcon(color) - self.ui.sColorCB.addItem(icon, "", Qt.QVariant(Qt.QColor(color))) - self.ui.lColorCB.addItem(icon, "", Qt.QVariant(Qt.QColor(color))) + self.ui.sColorCB.addItem(icon, "", Qt.QColor(color)) + self.ui.lColorCB.addItem(icon, "", Qt.QColor(color)) self._emptyProps = CurveAppearanceProperties() self.showProperties(self._emptyProps) @@ -512,22 +511,24 @@ def showProperties(self, prop, blockSignals=True): if prop.sColor is None: index = 0 else: - index = self.ui.sColorCB.findData( - Qt.QVariant(Qt.QColor(prop.sColor))) + index = self.ui.sColorCB.findData(Qt.QColor(prop.sColor)) if index == -1: # if the color is not one of the supported colors, add it to the combobox index = self.ui.sColorCB.count() # set the index to what will be the added one - self.ui.sColorCB.addItem(self._colorIcon( - Qt.QColor(prop.sColor)), "", Qt.QVariant(Qt.QColor(prop.sColor))) + self.ui.sColorCB.addItem(self._colorIcon(Qt.QColor(prop.sColor)), + "", + Qt.QColor(prop.sColor) + ) self.ui.sColorCB.setCurrentIndex(index) if prop.lColor is None: index = 0 else: - index = self.ui.lColorCB.findData( - Qt.QVariant(Qt.QColor(prop.lColor))) + index = self.ui.lColorCB.findData(Qt.QColor(prop.lColor)) if index == -1: # if the color is not one of the supported colors, add it to the combobox index = self.ui.lColorCB.count() # set the index to what will be the added one - self.ui.lColorCB.addItem(self._colorIcon( - Qt.QColor(prop.lColor)), "", Qt.QVariant(Qt.QColor(prop.lColor))) + self.ui.lColorCB.addItem(self._colorIcon(Qt.QColor(prop.lColor)), + "", + Qt.QColor(prop.lColor) + ) self.ui.lColorCB.setCurrentIndex(index) # set the Fill Checkbox. The prop.sFill value can be in 3 states: True, # False and None diff --git a/lib/taurus/qt/qtgui/plot/curvesAppearanceChooserDlg.py b/lib/taurus/qt/qtgui/qwt5/curvesAppearanceChooserDlg.py similarity index 93% rename from lib/taurus/qt/qtgui/plot/curvesAppearanceChooserDlg.py rename to lib/taurus/qt/qtgui/qwt5/curvesAppearanceChooserDlg.py index 51d6a0c2f..724a058fd 100644 --- a/lib/taurus/qt/qtgui/plot/curvesAppearanceChooserDlg.py +++ b/lib/taurus/qt/qtgui/qwt5/curvesAppearanceChooserDlg.py @@ -29,6 +29,9 @@ for a QwtPlot-derived widget (like Taurusplot) """ +from __future__ import print_function + +from builtins import object import copy from taurus.external.qt import Qt, Qwt5 @@ -45,7 +48,7 @@ Qt.Qt.DashDotDotLine: ".._..", } ReverseNamedLineStyles = {} -for k, v in NamedLineStyles.iteritems(): +for k, v in NamedLineStyles.items(): ReverseNamedLineStyles[v] = k NamedCurveStyles = {None: "", @@ -56,7 +59,7 @@ Qwt5.QwtPlotCurve.Dots: "Dots" } ReverseNamedCurveStyles = {} -for k, v in NamedCurveStyles.iteritems(): +for k, v in NamedCurveStyles.items(): ReverseNamedCurveStyles[v] = k NamedSymbolStyles = { @@ -80,7 +83,7 @@ } ReverseNamedSymbolStyles = {} -for k, v in NamedSymbolStyles.iteritems(): +for k, v in NamedSymbolStyles.items(): ReverseNamedSymbolStyles[v] = k NamedColors = ["Black", "Red", "Blue", "Magenta", @@ -112,8 +115,8 @@ def __init__(self, parent=None, curvePropDict={}, showButtons=False, autoApply=F self.loadUi() self.autoApply = autoApply self.sStyleCB.insertItems(0, sorted(NamedSymbolStyles.values())) - self.lStyleCB.insertItems(0, NamedLineStyles.values()) - self.cStyleCB.insertItems(0, NamedCurveStyles.values()) + self.lStyleCB.insertItems(0, list(NamedLineStyles.values())) + self.cStyleCB.insertItems(0, list(NamedCurveStyles.values())) self.sColorCB.addItem("") self.lColorCB.addItem("") if not showButtons: @@ -121,8 +124,8 @@ def __init__(self, parent=None, curvePropDict={}, showButtons=False, autoApply=F self.resetBT.hide() for color in NamedColors: icon = self._colorIcon(color) - self.sColorCB.addItem(icon, "", Qt.QVariant(Qt.QColor(color))) - self.lColorCB.addItem(icon, "", Qt.QVariant(Qt.QColor(color))) + self.sColorCB.addItem(icon, "", Qt.QColor(color)) + self.lColorCB.addItem(icon, "", Qt.QColor(color)) self.__itemsDict = CaselessDict() self.setCurves(curvePropDict) # set the icon for the background button (stupid designer limitations @@ -163,11 +166,11 @@ def setCurves(self, curvePropDict): self._curvePropDictOrig = copy.deepcopy(curvePropDict) self.curvesLW.clear() self.__itemsDict = CaselessDict() - for name, prop in self.curvePropDict.iteritems(): + for name, prop in self.curvePropDict.items(): # create and insert the item - item = Qt.QListWidgetItem(Qt.QString(prop.title), self.curvesLW) + item = Qt.QListWidgetItem(str(prop.title), self.curvesLW) self.__itemsDict[name] = item - item.setData(self.NAME_ROLE, Qt.QVariant(Qt.QString(name))) + item.setData(self.NAME_ROLE, str(name)) item.setToolTip("Curve Name: %s" % name) item.setFlags(Qt.Qt.ItemIsEnabled | Qt.Qt.ItemIsSelectable | Qt.Qt.ItemIsUserCheckable | Qt.Qt.ItemIsDragEnabled | Qt.Qt.ItemIsEditable) @@ -175,7 +178,7 @@ def setCurves(self, curvePropDict): def onItemChanged(self, item): '''slot used when an item data has changed''' - name = Qt.from_qvariant(item.data(self.NAME_ROLE), str) + name = item.data(self.NAME_ROLE) previousTitle = self.curvePropDict[name].title currentTitle = item.text() if previousTitle != currentTitle: @@ -191,7 +194,7 @@ def updateTitles(self, newTitlesDict=None): ''' if newTitlesDict is None: return - for name, title in newTitlesDict.iteritems(): + for name, title in newTitlesDict.items(): self.curvePropDict[name].title = title self.__itemsDict[name].setText(title) @@ -204,7 +207,7 @@ def getSelectedCurveNames(self): :return: (string_list) the names of the selected curves ''' - return [Qt.from_qvariant(item.data(self.NAME_ROLE), str) for item in self.curvesLW.selectedItems()] + return [item.data(self.NAME_ROLE) for item in self.curvesLW.selectedItems()] def showProperties(self, prop=None): '''Updates the dialog to show the given properties. @@ -232,20 +235,24 @@ def showProperties(self, prop=None): if prop.sColor is None: index = 0 else: - index = self.sColorCB.findData(Qt.QVariant(Qt.QColor(prop.sColor))) + index = self.sColorCB.findData(Qt.QColor(prop.sColor)) if index == -1: # if the color is not one of the supported colors, add it to the combobox index = self.sColorCB.count() # set the index to what will be the added one - self.sColorCB.addItem(self._colorIcon( - Qt.QColor(prop.sColor)), "", Qt.QVariant(Qt.QColor(prop.sColor))) + self.sColorCB.addItem(self._colorIcon(Qt.QColor(prop.sColor)), + "", + Qt.QColor(prop.sColor) + ) self.sColorCB.setCurrentIndex(index) if prop.lColor is None: index = 0 else: - index = self.lColorCB.findData(Qt.QVariant(Qt.QColor(prop.lColor))) + index = self.lColorCB.findData(Qt.QColor(prop.lColor)) if index == -1: # if the color is not one of the supported colors, add it to the combobox index = self.lColorCB.count() # set the index to what will be the added one - self.lColorCB.addItem(self._colorIcon( - Qt.QColor(prop.lColor)), "", Qt.QVariant(Qt.QColor(prop.lColor))) + self.lColorCB.addItem(self._colorIcon(Qt.QColor(prop.lColor)), + "", + Qt.QColor(prop.lColor) + ) self.lColorCB.setCurrentIndex(index) # set the Fill Checkbox. The prop.sFill value can be in 3 states: True, # False and None @@ -411,7 +418,7 @@ def __init__(self, sStyle=None, sSize=None, sColor=None, sFill=None, - curvestyle is one of Qwt5.QwtPlotCurve.CurveStyle - axis is one of Qwt5.QwtPlot.Axis - title is something that Qwt5.QwtText() accepts in its constructor - (i.e. a QwtText, QString or any basestring) + (i.e. a QwtText or a string type) """ self.sStyle = sStyle self.sSize = sSize @@ -430,10 +437,10 @@ def __init__(self, sStyle=None, sSize=None, sColor=None, sFill=None, def _print(self): """Just for debug""" - print "-" * 77 + print("-" * 77) for k in self.propertyList: - print k + "= ", self.__getattribute__(k) - print "-" * 77 + print(k + "= ", self.__getattribute__(k)) + print("-" * 77) @staticmethod def inConflict_update_a(a, b): diff --git a/lib/taurus/qt/qtgui/plot/monitor.py b/lib/taurus/qt/qtgui/qwt5/monitor.py similarity index 95% rename from lib/taurus/qt/qtgui/plot/monitor.py rename to lib/taurus/qt/qtgui/qwt5/monitor.py index fca92c3dd..deab6d7f6 100644 --- a/lib/taurus/qt/qtgui/plot/monitor.py +++ b/lib/taurus/qt/qtgui/qwt5/monitor.py @@ -27,8 +27,10 @@ monitor.py: Specialized mini-trend widget to monitor some scalar value """ +from __future__ import print_function + from taurus.external.qt import Qt -from taurus.qt.qtgui.plot import TaurusTrend +from taurus.qt.qtgui.qwt5 import TaurusTrend class TaurusMonitorTiny(TaurusTrend): @@ -109,7 +111,7 @@ def event(self, event): elif a.startswith('-config='): CONFIG = a.split('=')[-1] elif a.startswith('-'): # whatever other argument starting by "-" - print "\n Usage: \n%s [-xe|-xt] [-config=configfilename] [model1 [model2] ...]\n" % sys.argv[0] + print("\n Usage: \n%s [-xe|-xt] [-config=configfilename] [model1 [model2] ...]\n" % sys.argv[0]) sys.exit(1) else: # anything that is not a parameter is interpreted as a model MODELS.append(a) diff --git a/lib/taurus/qt/qtgui/plot/qwtdialog.py b/lib/taurus/qt/qtgui/qwt5/qwtdialog.py similarity index 96% rename from lib/taurus/qt/qtgui/plot/qwtdialog.py rename to lib/taurus/qt/qtgui/qwt5/qwtdialog.py index d40f62814..83441e6df 100644 --- a/lib/taurus/qt/qtgui/plot/qwtdialog.py +++ b/lib/taurus/qt/qtgui/qwt5/qwtdialog.py @@ -26,15 +26,19 @@ """ qwtdialog.py: Dialogs for Taurusplot """ - -__all__ = ["TaurusPlotConfigDialog"] +from __future__ import print_function +from __future__ import absolute_import import time +from functools import partial from taurus.external.qt import Qt, Qwt5 from taurus.qt.qtgui.util.ui import UILoadable +__all__ = ["TaurusPlotConfigDialog"] + + # class TaurusPlotConfigCapable: # """This class is aimed to act as an interface for class TaurusPlot. Every class that uses # TaurusPlotConfigDialog class (as TaurusPlot does) should inherit TaurusPlotConfigCapable class @@ -72,7 +76,7 @@ def __init__(self, parent=None, flags=Qt.Qt.WindowFlags()): # insert the CurvesAppearanceWidget #(@TODO:must be changed to be done directly in the ui, but I couldn't make the widget available to TaurusDesigner) - from curvesAppearanceChooserDlg import CurvesAppearanceChooser + from .curvesAppearanceChooserDlg import CurvesAppearanceChooser layout = Qt.QVBoxLayout() self.curvesAppearanceChooser = CurvesAppearanceChooser(None) layout.addWidget(self.curvesAppearanceChooser) @@ -165,7 +169,7 @@ def __init__(self, parent=None, flags=Qt.Qt.WindowFlags()): elif scaleType == Qwt5.QwtScaleTransformation.Log10: axes[axis].setCurrentIndex(1) else: - raise TypeError, "TaurusPlotConfigDialog::__init__(): unexpected axis scale type (linear or logarihtmic expected)" + raise TypeError("TaurusPlotConfigDialog::__init__(): unexpected axis scale type (linear or logarihtmic expected)") self.ui.xModeComboBox.setEnabled(not self.parent.getXIsTime()) # determine which axes are visible @@ -217,8 +221,10 @@ def __init__(self, parent=None, flags=Qt.Qt.WindowFlags()): # self.connect(self.curvesAppearanceChooser, # Qt.SIGNAL("controlChanged"),self.apply) #"autoapply" mode for *all* # the curve appearance controls - self.curvesAppearanceChooser.assignToY1BT.clicked[()].connect(self.setCurvesYAxis) - self.curvesAppearanceChooser.assignToY2BT.clicked[()].connect(self.setCurvesYAxis) + self.curvesAppearanceChooser.assignToY1BT.clicked.connect( + partial(self.setCurvesYAxis, curvesNamesList=None, axis=None)) + self.curvesAppearanceChooser.assignToY2BT.clicked.connect( + partial(self.setCurvesYAxis, curvesNamesList=None, axis=None)) self.curvesAppearanceChooser.bckgndBT.clicked.connect(self.changeBackgroundColor) self.curvesAppearanceChooser.changeTitlesBT.clicked.connect(self.onChangeTitles) self.curvesAppearanceChooser.CurveTitleEdited.connect(self.onCurveTitleEdited) @@ -277,14 +283,14 @@ def str2deltatime(self, strtime): 24, 'w': 3600 * 24 * 7, 'y': 3600 * 24 * 365} if strtime.lower() == "now": return time.time() - if strtime[-1] in timeunits.keys(): + if strtime[-1] in timeunits: try: return float(strtime[:-1]) * timeunits[strtime[-1]] - except Exception, e: - print '[str2deltatime] No format could be applied to "%s"' % strtime + except Exception as e: + print('[str2deltatime] No format could be applied to "%s"' % strtime) return None else: - print '[str2deltatime] Non-supported unit "%s"' % strtime[-1] + print('[str2deltatime] Non-supported unit "%s"' % strtime[-1]) return None def strtime2epoch(self, strtime): @@ -333,12 +339,12 @@ def strtime2epoch(self, strtime): t = (lt[0], lt[1], lt[2], t[3], t[ 4], lt[5], lt[6], lt[7], lt[8]) break - except Exception, e: + except Exception as e: pass if t: return time.mktime(t) else: - print 'No format could be applied to "%s"' % strtime + print('No format could be applied to "%s"' % strtime) return None def validate(self): @@ -557,7 +563,7 @@ def onCurveTitleEdited(self, name, newTitle): '''slot used when a curve title is edited :param name: (str) curve name - :param name: (QString) new title + :param name: (str) new title ''' newTitlesDict = self.parent.setCurvesTitle(newTitle, [name]) self.curvesAppearanceChooser.updateTitles(newTitlesDict) diff --git a/lib/taurus/qt/qtgui/plot/qwtplot.py b/lib/taurus/qt/qtgui/qwt5/qwtplot.py similarity index 94% rename from lib/taurus/qt/qtgui/plot/qwtplot.py rename to lib/taurus/qt/qtgui/qwt5/qwtplot.py index abb62408b..e7f2225f4 100644 --- a/lib/taurus/qt/qtgui/plot/qwtplot.py +++ b/lib/taurus/qt/qtgui/qwt5/qwtplot.py @@ -23,9 +23,9 @@ ## ############################################################################# -print "*" * 77 -print \ - """ +from __future__ import print_function +print("*" * 77) +print(""" If you are seeing this, it is because you tried to access qwtplot.py directly. All funcionality has been moved to taurusplot.py, taurustrend.py and scales.py @@ -34,5 +34,5 @@ If you were trying to launch a stand-alone Taurusplot or TaurusTrend from a script, you should update such script. -""" -print "*" * 77 +""") +print("*" * 77) diff --git a/lib/taurus/qt/qtgui/plot/scales.py b/lib/taurus/qt/qtgui/qwt5/scales.py similarity index 98% rename from lib/taurus/qt/qtgui/plot/scales.py rename to lib/taurus/qt/qtgui/qwt5/scales.py index e28993ef2..1297ef81a 100644 --- a/lib/taurus/qt/qtgui/plot/scales.py +++ b/lib/taurus/qt/qtgui/qwt5/scales.py @@ -26,9 +26,8 @@ """ scales.py: Custom scales used by taurus.qt.qtgui.plot module """ -__all__ = ["DateTimeScaleEngine", "DeltaTimeScaleEngine", "FixedLabelsScaleEngine", - "FancyScaleDraw", "TaurusTimeScaleDraw", "DeltaTimeScaleDraw", - "FixedLabelsScaleDraw"] + +from __future__ import print_function import numpy from datetime import datetime, timedelta @@ -36,6 +35,11 @@ from taurus.external.qt import Qt, Qwt5 +__all__ = ["DateTimeScaleEngine", "DeltaTimeScaleEngine", "FixedLabelsScaleEngine", + "FancyScaleDraw", "TaurusTimeScaleDraw", "DeltaTimeScaleDraw", + "FixedLabelsScaleDraw"] + + def _getDefaultAxisLabelsAlignment(axis, rotation): '''return a "smart" alignment for the axis labels depending on the axis and the label rotation @@ -217,7 +221,7 @@ def divideScale(self, x1, x2, maxMajSteps, maxMinSteps, stepSize): elif dx > 2: # 2s format = "%H:%M:%S" - majticks = range(int(x1) + 1, int(x2)) + majticks = list(range(int(x1) + 1, int(x2))) else: # less than 2s (show microseconds) scaleDiv = Qwt5.QwtLinearScaleEngine.divideScale( @@ -318,8 +322,8 @@ def label(self, val): t = datetime.fromtimestamp(val) try: # If the scaleDiv was created by a DateTimeScaleEngine it has a _datetimeLabelFormat s = t.strftime(self._datetimeLabelFormat) - except AttributeError, e: - print "Warning: cannot get the datetime label format (Are you using a DateTimeScaleEngine?)" + except AttributeError: + print("Warning: cannot get the datetime label format (Are you using a DateTimeScaleEngine?)") s = t.isoformat(' ') return Qwt5.QwtText(s) diff --git a/lib/taurus/qt/qtgui/plot/taurusarrayedit.py b/lib/taurus/qt/qtgui/qwt5/taurusarrayedit.py similarity index 91% rename from lib/taurus/qt/qtgui/plot/taurusarrayedit.py rename to lib/taurus/qt/qtgui/qwt5/taurusarrayedit.py index 2af8463d1..41b4eb9a8 100644 --- a/lib/taurus/qt/qtgui/plot/taurusarrayedit.py +++ b/lib/taurus/qt/qtgui/qwt5/taurusarrayedit.py @@ -24,13 +24,14 @@ ############################################################################# -from taurus.external.qt import Qt +from __future__ import absolute_import +from taurus.external.qt import Qt, compat import taurus import numpy from taurus.qt.qtgui.container import TaurusWidget -from arrayedit import ArrayEditor - +from .arrayedit import ArrayEditor +from functools import partial class TaurusArrayEditor(TaurusWidget): @@ -56,10 +57,10 @@ def __init__(self, parent=None, designMode=False): layout.addWidget(self.fromAttrBT, 1, 2) layout.addWidget(self.toAttrBT, 1, 3) - self.fromFileBT.clicked[()].connect(self.onFromFile) - self.toFileBT.clicked[()].connect(self.onToFile) - self.fromAttrBT.clicked[()].connect(self.onFromAttr) - self.toAttrBT.clicked[()].connect(self.onToAttr) + self.fromFileBT.clicked.connect(self._onFromFile) + self.toFileBT.clicked.connect(self.onToFile) + self.fromAttrBT.clicked.connect(partial(self.onFromAttr, quiet=False)) + self.toAttrBT.clicked.connect(partial(self.onToAttr, quiet=False)) def arrayEditor(self): return self._arrayEditor @@ -92,6 +93,10 @@ def setModel(self, model): return ok + def _onFromFile(self): + """dummy, just to be used as an unambiguous slot""" + self.onFromFile() + def onFromFile(self, filename=None, **kwargs): '''imports Master curve from a two-column ASCII file. The first colum will be interpreted to be the abcissas. @@ -102,7 +107,7 @@ def onFromFile(self, filename=None, **kwargs): skiprows=0, usecols=None, unpack=False} see help from numpy.loadtxt for more info on the kwargs''' if filename is None: - filename = Qt.QFileDialog.getOpenFileName( + filename, _ = compat.getOpenFileName( self, 'Choose input file', '', 'Ascii file (*)') if not filename: return False @@ -137,7 +142,7 @@ def onFromAttr(self, quiet=False): x = numpy.arange(y.size) else: x = numpy.array(self._xAttr.read().rvalue) - except Exception, e: + except Exception as e: self.error('Error reading from attribute(s): %s' % (str(e))) if not quiet: Qt.QMessageBox.warning( @@ -182,7 +187,7 @@ def onToAttr(self, quiet=False): if self._xAttr is not None and numpy.any(self._xAttr.read(cache=False).wvalue != x): raise IOError('Unexpected Write error: %s' % self._xAttr.getFullName()) - except Exception, e: + except Exception as e: self.error('Error writing to attribute(s): %s' % (str(e))) if not quiet: Qt.QMessageBox.warning( @@ -200,7 +205,7 @@ def getQtDesignerPluginInfo(cls): :return: (dict) a map with pertinent designer information""" ret = TaurusWidget.getQtDesignerPluginInfo() - ret['module'] = 'taurus.qt.qtgui.plot' + ret['module'] = 'taurus.qt.qtgui.qwt5' ret['group'] = 'Taurus Input' ret['icon'] = 'designer:arrayedit.png' ret['container'] = False diff --git a/lib/taurus/qt/qtgui/plot/taurusplot.py b/lib/taurus/qt/qtgui/qwt5/taurusplot.py similarity index 96% rename from lib/taurus/qt/qtgui/plot/taurusplot.py rename to lib/taurus/qt/qtgui/qwt5/taurusplot.py index efa3c9481..7680c93fa 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplot.py +++ b/lib/taurus/qt/qtgui/qwt5/taurusplot.py @@ -26,15 +26,24 @@ """ taurusplot.py: Generic graphical plotting widget for Taurus """ -__all__ = ["TaurusCurve", "TaurusCurveMarker", - "TaurusXValues", "TaurusPlot", "isodatestr2float"] + +from __future__ import print_function +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() +from builtins import next +from builtins import str +from builtins import range +from builtins import object import os import copy from datetime import datetime import time import numpy -from taurus.external.qt import Qt, Qwt5 +from future.utils import string_types +from functools import partial +from taurus.external.qt import Qt, Qwt5, compat import taurus import taurus.core @@ -46,9 +55,13 @@ from taurus.qt.qtcore.util.signal import baseSignal from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_ATTR_MIME_TYPE from taurus.qt.qtgui.base import TaurusBaseComponent, TaurusBaseWidget -from taurus.qt.qtgui.plot import TaurusPlotConfigDialog, FancyScaleDraw,\ +from taurus.qt.qtgui.qwt5 import TaurusPlotConfigDialog, FancyScaleDraw,\ DateTimeScaleEngine, FixedLabelsScaleEngine, FixedLabelsScaleDraw -from curvesAppearanceChooserDlg import CurveAppearanceProperties +from .curvesAppearanceChooserDlg import CurveAppearanceProperties + + +__all__ = ["TaurusCurve", "TaurusCurveMarker", + "TaurusXValues", "TaurusPlot", "isodatestr2float"] def isodatestr2float(s, sep='_'): @@ -540,7 +553,7 @@ def handleEvent(self, src, evt_type, val): ''' if self.isRawData: #self.warning('fireEvent of a RawData curve has been called by %s'%repr(self.sender())) - raise StandardError('called handleEvent of a RawData curve') + raise Exception('called handleEvent of a RawData curve') return model = src if src is not None else self.getModelObj() if model is None: @@ -559,7 +572,7 @@ def handleEvent(self, src, evt_type, val): return try: self.setXYFromModel(value) - except Exception, e: + except Exception as e: self._onDroppedEvent(reason=str(e)) return self._updateMarkers() @@ -910,8 +923,8 @@ def getStats(self, limits=None, inclusive=(True, True), imin=None, imax=None, ig if imax is None: imax = data.size() - x = numpy.array([data.x(i) for i in xrange(imin, imax)]) - y = numpy.array([data.y(i) for i in xrange(imin, imax)]) + x = numpy.array([data.x(i) for i in range(imin, imax)]) + y = numpy.array([data.y(i) for i in range(imin, imax)]) if limits is not None: xmin, xmax = limits @@ -1185,11 +1198,11 @@ def __initActions(self): self._dataInspectorAction.toggled[bool].connect(self.toggleDataInspectorMode) self._setFormatterAction = Qt.QAction("Set Formatter...", None) - self._setFormatterAction.triggered[()].connect(self.onSetFormatter) + 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) + self._curveStatsAction.triggered.connect(self.onCurveStatsAction) self._pauseAction = Qt.QAction("&Pause", None) self._pauseAction.setShortcuts([Qt.Qt.Key_P, Qt.Qt.Key_Pause]) @@ -1199,28 +1212,31 @@ def __initActions(self): self._autoscaleAllAxisAction = Qt.QAction("Autoscale all axes", None) self._autoscaleAllAxisAction.setShortcut(Qt.Qt.Key_Escape) - self._autoscaleAllAxisAction.triggered[()].connect(self.autoScaleAllAxes) + self._autoscaleAllAxisAction.triggered.connect(self.autoScaleAllAxes) self._toggleZoomAxisAction = Qt.QAction("Toggle Zoom-aware axis", None) self._toggleZoomAxisAction.setShortcut(Qt.Qt.Key_Z) - self._toggleZoomAxisAction.triggered[()].connect(self.toggleZoomer) + self._toggleZoomAxisAction.triggered.connect( + partial(self.toggleZoomer, axis=None)) self._configDialogAction = Qt.QAction("Plot configuration...", None) self._configDialogAction.setShortcut(Qt.QKeySequence("Alt+C")) - self._configDialogAction.triggered[()].connect(self.showConfigDialog) + self._configDialogAction.triggered.connect(self.showConfigDialog) self._inputDataAction = Qt.QAction("Input data selection...", None) self._inputDataAction.setShortcut(Qt.QKeySequence.New) - self._inputDataAction.triggered[()].connect(self.showDataImportDlg) + self._inputDataAction.triggered.connect(self.showDataImportDlg) self._saveConfigAction = Qt.QAction("Save current settings...", None) self._saveConfigAction.setShortcut(Qt.QKeySequence.Save) - self._saveConfigAction.triggered[()].connect(self.saveConfig) + self._saveConfigAction.triggered.connect( + partial(self.saveConfig, ofile=None, curvenames=None)) self._loadConfigAction = Qt.QAction( "&Retrieve saved settings...", None) self._loadConfigAction.setShortcut(Qt.QKeySequence.Open) - self._loadConfigAction.triggered[()].connect(self.loadConfig) + self._loadConfigAction.triggered.connect( + partial(self.loadConfig, ifile=None)) self._showLegendAction = Qt.QAction("Show &Legend", None) self._showLegendAction.setShortcut(Qt.QKeySequence("Ctrl+L")) @@ -1240,21 +1256,24 @@ def __initActions(self): self._showMinAction.toggled[bool].connect(self.showMinPeaks) self._printAction = Qt.QAction("&Print plot...", None) - self._printAction.triggered[()].connect(self.exportPrint) + self._printAction.triggered.connect(self.exportPrint) self._exportPdfAction = Qt.QAction("Export plot to PD&F...", None) - self._exportPdfAction.triggered[()].connect(self.exportPdf) + self._exportPdfAction.triggered.connect( + partial(self.exportPdf, fileName=None)) self._exportAsciiAction = Qt.QAction("Export data to &ASCII...", None) - self._exportAsciiAction.triggered[()].connect(self.exportAscii) + self._exportAsciiAction.triggered.connect( + partial(self.exportAscii, curves=None)) self._setCurvesTitleAction = Qt.QAction( "Change Curves Titles...", None) - self._setCurvesTitleAction.triggered[()].connect(self.changeCurvesTitlesDialog) + self._setCurvesTitleAction.triggered.connect( + partial(self.changeCurvesTitlesDialog, curveNamesList=None)) self._closeWindowAction = Qt.QAction( Qt.QIcon.fromTheme("process-stop"), 'Close Plot', self) - self._closeWindowAction.triggered[()].connect(self.close) + self._closeWindowAction.triggered.connect(self.close) # add all actions and limit the scope of the key shortcuts to the # widget (default is Window) @@ -1276,8 +1295,7 @@ def __initActions(self): def setFormat(self, format): """Reimplemented from TaurusBaseComponent""" - targetCurveNames = self.curves.iterkeys() - for name in targetCurveNames: + for name in self.curves: curve = self.curves.get(name, None) w = getattr(curve, 'owner', curve) w.setFormat(format) @@ -1343,7 +1361,7 @@ def getCurveTitle(self, curvename): if curve is None: title = None else: - title = unicode(curve.title().text()) + title = str(curve.title().text()) finally: self.curves_lock.release() return title @@ -1358,7 +1376,7 @@ def getCurveNames(self): ''' self.curves_lock.acquire() try: - ret = copy.deepcopy(self.curves.keys()) + ret = copy.deepcopy(list(self.curves.keys())) finally: self.curves_lock.release() return ret @@ -1393,7 +1411,9 @@ def sortCurves(self, ordered=None): try: if ordered is None: orderedObjs = sorted( - self.curves.values(), key=lambda curve: curve.titleText(compiled=True)) + self.curves.values(), + key=lambda curve: curve.titleText(compiled=True) + ) else: #current = self.curves.keys() # if len(ordered) != len(current) or set(map(str.lower,current)) - set(map(str.lower, ordered)): @@ -1428,7 +1448,7 @@ def toggleZoomer(self, axis=None): for z in (self._zoomer1, self._zoomer2): z.setEnabled(z.yAxis() == axis) self._zoomer = self.getZoomers(axis)[0] - self.debug('Now Zooming on %s' % unicode(self.getAxisName(axis))) + self.debug('Now Zooming on %s' % str(self.getAxisName(axis))) return self._zoomer.yAxis() def getAxisName(self, axis): @@ -1439,7 +1459,7 @@ def getAxisName(self, axis): :return: (unicode) ''' - name = unicode(self.axisTitle(axis).text()) + name = str(self.axisTitle(axis).text()) if name == '': name = self._axesnames[axis] return name @@ -1449,7 +1469,7 @@ def setPaused(self, paused=True): :param paused: (bool) if True, the plot will be paused ''' - for c in self.curves.itervalues(): + for c in self.curves.values(): c.setPaused(paused) self._isPaused = paused @@ -1462,7 +1482,7 @@ def isPaused(self): def __debug(self, *args, **kwargs): '''put code here that you want to debug''' - print "!!!!!!!!!!!!!!!1", self.pos() + print("!!!!!!!!!!!!!!!1", self.pos()) Qt.QToolTip.showText(self.mapToGlobal(self.pos()), "ASDASDASDASD DASDAS ASDA", self) @@ -1522,7 +1542,7 @@ def setAxisCustomLabels(self, axis, pos_and_labels, rotation=0, alignment=None): If None given, it will be autocalculated ''' - positions, labels = zip(*pos_and_labels) # "unzipping" + positions, labels = list(zip(*pos_and_labels)) # "unzipping" positions = list(positions) self.setAxisScaleEngine(axis, FixedLabelsScaleEngine(positions)) @@ -1573,7 +1593,7 @@ def getPlot(self): TaurusPlot.getPlot()''' self.info( 'DEPRECATION WARNING!: Calling TaurusPlot.getPlot() is deprecated. Use the TaurusPlot object itself instead') - print self.sender() + print(self.sender()) return self def getCurve(self, name): @@ -1660,7 +1680,7 @@ def showMaxPeaks(self, show): self.curves_lock.acquire() try: self._showMaxPeaks = show - for curveName in self.curves.iterkeys(): + for curveName in self.curves.keys(): curve = self.curves.get(str(curveName)) if show: curve.showMaxPeak(True) @@ -1681,7 +1701,7 @@ def showMinPeaks(self, show): self.curves_lock.acquire() try: self._showMinPeaks = show - for curveName in self.curves.iterkeys(): + for curveName in self.curves.keys(): curve = self.curves.get(str(curveName)) if show: curve.showMinPeak(True) @@ -1747,7 +1767,7 @@ def toggleCurveState(self, curve): try: # get the key in the self.curves directory curveName = None - for curveName, c in self.curves.iteritems(): + for curveName, c in self.curves.items(): if c is curve: break axis = curve.yAxis() @@ -1812,11 +1832,11 @@ def attachRawData(self, rawdata, properties=None, id=None): properties = CurveAppearanceProperties( lColor=self._curvePens.next().color(), lWidth=2) # Deprecation Warning: - if rawdata.has_key("pen") or rawdata.has_key("style"): + if "pen" in rawdata or "style" in rawdata: raise DeprecationWarning( "'pen' or 'style' are no longer supported. Use the properties parameter instead") - if rawdata.has_key("name"): - if rawdata.has_key("title"): + if "name" in rawdata: + if "title" in rawdata: self.error( 'Inconsistence: both "name" and "title" passed for rawdata. Use "title" only') else: @@ -1866,7 +1886,7 @@ def attachRawData(self, rawdata, properties=None, id=None): name = id self.curves_lock.acquire() try: - if self.curves.has_key(name): + if name in self.curves: curve = self.curves.get(name) if curve.isRawData: self.detachRawData(name) @@ -1928,7 +1948,7 @@ def clearAllRawData(self): """ self.curves_lock.acquire() try: - names = [name for name in self.curves.keys() if self.curves[ + names = [name for name in self.curves if self.curves[ name].isRawData] finally: self.curves_lock.release() @@ -1947,10 +1967,10 @@ def getCurveData(self, curvename, numpy=False): """ self.curves_lock.acquire() try: - if self.curves.has_key(curvename): + if curvename in self.curves: data = self.curves[curvename].data() - x = [data.x(i) for i in xrange(data.size())] - y = [data.y(i) for i in xrange(data.size())] + x = [data.x(i) for i in range(data.size())] + y = [data.y(i) for i in range(data.size())] else: self.error("Curve '%s' not found" % curvename) raise KeyError() @@ -1986,7 +2006,7 @@ def updateCurves(self, names): xnames.append(xname) ynames.append(yname) - del_curves = [name for name in self.curves.keys() + del_curves = [name for name in self.curves if name not in ynames] # if all curves were removed, reset the color palette @@ -1997,7 +2017,7 @@ def updateCurves(self, names): xname = xnames[i] name = str(name) self.debug('updating curve %s' % name) - if not self.curves.has_key(name): + if name not in self.curves: curve = TaurusCurve(name, xname, self, optimized=self.isOptimizationEnabled()) curve.attach(self) @@ -2008,7 +2028,7 @@ def updateCurves(self, names): curve.attachMaxMarker(self) if self._showMinPeaks: curve.attachMinMarker(self) - curve.setPen(self._curvePens.next()) + curve.setPen(next(self._curvePens)) curve.setUseParentModel(self.getUseParentModel()) curve.setTitleText(self.getDefaultCurvesTitle()) curve.registerDataChanged(self, self.curveDataChanged) @@ -2212,20 +2232,20 @@ def _axisContextMenu(self, axis=None): autoScaleThisAxis = lambda: self.setAxisAutoScale(axis=axis) autoscaleAction = menu.addAction("AutoScale %s" % axisname) - autoscaleAction.triggered[()].connect(autoScaleThisAxis) + autoscaleAction.triggered.connect(autoScaleThisAxis) if not self.getXIsTime(): switchThisAxis = lambda: self.setAxisScaleType( axis=axis, scale=None) switchThisAxisAction = menu.addAction( "Toggle linear/log for %s" % axisname) - switchThisAxisAction.triggered[()].connect(switchThisAxis) + switchThisAxisAction.triggered.connect(switchThisAxis) if axis in (Qwt5.QwtPlot.yLeft, Qwt5.QwtPlot.yRight): zoomOnThisAxis = lambda: self.toggleZoomer(axis=axis) zoomOnThisAxisAction = menu.addAction( "Zoom-to-region acts on %s" % axisname) - zoomOnThisAxisAction.triggered[()].connect(zoomOnThisAxis) + zoomOnThisAxisAction.triggered.connect(zoomOnThisAxis) elif axis in (Qwt5.QwtPlot.xBottom, Qwt5.QwtPlot.xTop): if self.isXDynScaleSupported(): @@ -2258,7 +2278,7 @@ def getCurveAppearancePropertiesDict(self): self.curves_lock.acquire() try: propdict = {} - for name, curve in self.curves.iteritems(): + for name, curve in self.curves.items(): propdict[name] = copy.deepcopy(curve.getAppearanceProperties()) finally: self.curves_lock.release() @@ -2276,7 +2296,7 @@ def setCurveAppearanceProperties(self, propDict): """ self.curves_lock.acquire() try: - for name, prop in propDict.iteritems(): + for name, prop in propDict.items(): c = self.curves[name] c.setAppearanceProperties(copy.deepcopy(prop)) visible = getattr(prop, 'visible', True) @@ -2325,7 +2345,7 @@ def _createMiscDict(self): miscdict = {'defaultCurvesTitle': self.getDefaultCurvesTitle(), 'canvasBackground': self.canvasBackground(), 'orderedCurveNames': self.getCurveNamesSorted(), - 'plotTitle': unicode(self.title().text()), + 'plotTitle': str(self.title().text()), 'formatter': self.getFormat()} if self.isWindow(): miscdict["Geometry"] = self.saveGeometry() @@ -2395,7 +2415,7 @@ def createConfig(self, allowUnpickable=False, curvenames=None, **kwargs): self.curves_lock.acquire() try: if curvenames is None: - curvenames = self.curves.keys() + curvenames = list(self.curves) curvenames = self._lowerIfInsensitive(curvenames) for name in curvenames: curve = self.curves.get(name) @@ -2404,7 +2424,7 @@ def createConfig(self, allowUnpickable=False, curvenames=None, **kwargs): rawdatadict[name] = curve.getRawData() else: tangodict[name] = curve.getModel() - except Exception, e: + except Exception as e: self.error( 'Exception while gathering curves configuration info' + str(e)) finally: @@ -2431,7 +2451,7 @@ def applyConfig(self, configdict, **kwargs): self.attachRawData(rd) # for backwards compatibility, if the ordered list of models is not # stored, it uses the unsorted dict values - models = configdict.get("model", configdict["TangoCurves"].values()) + models = configdict.get("model", list(configdict["TangoCurves"].values())) self.addModels(models) # set curve properties self.setCurveAppearanceProperties(configdict["CurveProp"]) @@ -2484,10 +2504,13 @@ def saveConfig(self, ofile=None, curvenames=None): :return: (str) file name used """ - import cPickle as pickle + import pickle if ofile is None: - ofile = str(Qt.QFileDialog.getSaveFileName(self, 'Save Taurusplot Configuration', - 'TaurusplotConfig.pck', 'TaurusPlot Curve Properties File (*.pck)')) + ofile, _ = compat.getSaveFileName( + self, 'Save Taurusplot Configuration', + 'TaurusplotConfig.pck', + 'TaurusPlot Curve Properties File (*.pck)' + ) if not ofile: return if not isinstance(ofile, file): @@ -2504,10 +2527,11 @@ def loadConfig(self, ifile=None): :return: (str) file name used """ - import cPickle as pickle + import pickle if ifile is None: - ifile = str(Qt.QFileDialog.getOpenFileName( - self, 'Load Taurusplot Configuration', '', 'TaurusPlot Curve Properties File (*.pck)')) + ifile, _ = compat.getOpenFileName( + self, 'Load Taurusplot Configuration', '', + 'TaurusPlot Curve Properties File (*.pck)') if not ifile: return if not isinstance(ifile, file): @@ -2668,7 +2692,7 @@ def __updateCurvesData(self): '''call safeSetData again on all curves to force a refiltering in case the scale changed its type''' self.curves_lock.acquire() try: - for c in self.curves.itervalues(): + for c in self.curves.values(): c.safeSetData() finally: self.curves_lock.release() @@ -2682,7 +2706,7 @@ def exportPdf(self, fileName=None): a file name. """ if fileName is None: - fileName = Qt.QFileDialog.getSaveFileName( + fileName, _ = compat.getSaveFileName( self, 'Export File Name', 'plot.pdf', 'PDF Documents (*.pdf)') fileName = str(fileName) if fileName: @@ -2778,7 +2802,7 @@ def importAscii(self, filenames=None, xcol=None, **kwargs): .. seealso:: :meth:`numpy.loadtxt` ''' if filenames is None: - filenames = Qt.QFileDialog.getOpenFileNames( + filenames, _ = compat.getOpenFileNames( self, 'Choose input files', '', 'Ascii file (*)') if not filenames: return False @@ -2799,7 +2823,7 @@ def importAscii(self, filenames=None, xcol=None, **kwargs): else: rawdata["x"] = M[:, xcol] - for col in xrange(M.shape[1]): + for col in range(M.shape[1]): if col == xcol: continue # ignore the xcol (it has already been set) rawdata["y"] = M[:, col] @@ -2894,8 +2918,8 @@ def setCurvesYAxis(self, curvesNamesList, axis): :param axis: (Qwt5.QwtPlot.Axis) the axis """ if not Qwt5.QwtPlot.axisValid(axis): - raise ValueError, "TaurusPlot::setCurvesYAxis. Invalid axis ID: " + \ - repr(axis) + raise ValueError("TaurusPlot::setCurvesYAxis. Invalid axis ID: " + \ + repr(axis)) self.curves_lock.acquire() try: for curveName in curvesNamesList: @@ -2924,7 +2948,7 @@ def autoShowYAxes(self): try: # get a list of *unique* axes with visible curves attached axes = list( - set([curve.yAxis() for curve in self.curves.itervalues() if curve.isVisible()])) + set([curve.yAxis() for curve in self.curves.values() if curve.isVisible()])) n = len(axes) if n == 0: @@ -2983,7 +3007,7 @@ def pickDataPoint(self, pos, scope=20, showMarker=True, targetCurveNames=None): self.curves_lock.acquire() try: if targetCurveNames is None: - targetCurveNames = self.curves.iterkeys() + targetCurveNames = self.curves.keys() for name in targetCurveNames: curve = self.curves.get(name, None) if curve is None: @@ -2991,7 +3015,7 @@ def pickDataPoint(self, pos, scope=20, showMarker=True, targetCurveNames=None): if not curve.isVisible(): continue data = curve.data() - for i in xrange(data.size()): + for i in range(data.size()): point = Qt.QPoint(self.transform(curve.xAxis(), data.x( i)), self.transform(curve.yAxis(), data.y(i))) if scopeRect.contains(point): @@ -3083,7 +3107,7 @@ def onCurveStatsAction(self): then shows curve statistics on that range. ''' if getattr(self, '_curveStatsDialog', None) is None: - from taurus.qt.qtgui.plot import CurveStatsDialog + from taurus.qt.qtgui.qwt5 import CurveStatsDialog self._curveStatsDialog = CurveStatsDialog(self) self._curveStatsDialog.closed.connect(self._onCurveStatsDialogClosed) self._curveStatsDialog.finished.connect(self._onCurveStatsDialogClosed) @@ -3184,7 +3208,7 @@ def getCurveStats(self, limits=None, curveNames=None): for name in curveNames: curve = self.curves.get(name, None) stats[name] = curve.getStats(limits=limits) - stats[name]['title'] = unicode(curve.title().text()) + stats[name]['title'] = str(curve.title().text()) finally: self.curves_lock.release() return stats @@ -3244,7 +3268,7 @@ def resetGridWidth(self): #-~-~-~-~-~-~-~-~-~-~-~-~ def _splitModel(self, modelNames): '''convert str to list if needed (commas and whitespace are considered as separators)''' - if isinstance(modelNames, (basestring, Qt.QString)): + if isinstance(modelNames, string_types): modelNames = str(modelNames).replace(',', ' ') modelNames = modelNames.split() return modelNames @@ -3459,7 +3483,7 @@ def setCurvesTitle(self, titletext, curveNamesList=None): try: if curveNamesList is None: curveNamesList = [ - n for n, c in self.curves.iteritems() if not c.isRawData] + n for n, c in self.curves.items() if not c.isRawData] newTitlesDict = CaselessDict() for curveName in curveNamesList: curve = self.curves.get(curveName) @@ -3626,7 +3650,7 @@ def setOptimizationEnabled(self, enable): self._optimizationEnabled = enable # make sure that already-created curves are also optimized try: - for curveName in self.curves.iterkeys(): + for curveName in self.curves: curve = self.curves.get(str(curveName)) curve.setPaintAttribute(curve.PaintFiltered, enable) curve.setPaintAttribute(curve.ClipPolygons, enable) @@ -3654,7 +3678,7 @@ def getQtDesignerPluginInfo(cls): :return: (dict) a map with pertinent designer information""" ret = TaurusBaseWidget.getQtDesignerPluginInfo() - ret['module'] = 'taurus.qt.qtgui.plot' + ret['module'] = 'taurus.qt.qtgui.qwt5' ret['group'] = 'Taurus Display' ret['icon'] = 'designer:qwtplot.png' return ret @@ -3728,16 +3752,16 @@ def main(): w.setModel(models) if options.export_file is not None: - curves = dict.fromkeys(w.trendSets.keys(), 0) + curves = dict.fromkeys(w.trendSets, 0) def exportIfAllCurves(curve, trend=w, counters=curves): curve = str(curve) - print '*' * 10 + ' %s: Event received for %s ' % (datetime.now().isoformat(), curve) + '*' * 10 + print('*' * 10 + ' %s: Event received for %s ' % (datetime.now().isoformat(), curve) + '*' * 10) if curve in counters: counters[curve] += 1 if all(counters.values()): trend.exportPdf(options.export_file) - print '*' * 10 + ' %s: Exported to : %s ' % (datetime.now().isoformat(), options.export_file) + '*' * 10 + print('*' * 10 + ' %s: Exported to : %s ' % (datetime.now().isoformat(), options.export_file) + '*' * 10) trend.close() return if not curves: diff --git a/lib/taurus/qt/qtgui/plot/taurusplotconf.py b/lib/taurus/qt/qtgui/qwt5/taurusplotconf.py similarity index 96% rename from lib/taurus/qt/qtgui/plot/taurusplotconf.py rename to lib/taurus/qt/qtgui/qwt5/taurusplotconf.py index 6d0b490a6..d6a8e5095 100644 --- a/lib/taurus/qt/qtgui/plot/taurusplotconf.py +++ b/lib/taurus/qt/qtgui/qwt5/taurusplotconf.py @@ -26,8 +26,8 @@ """ TaurusPlotConf: widget for configurating the contents and appearance of a TaurusPlot """ - -__all__ = ['TaurusPlotConfDlg'] +from __future__ import print_function +from __future__ import absolute_import raise NotImplementedError('Under Construction!') @@ -35,13 +35,16 @@ from taurus.external.qt import Qt, Qwt5 from taurus.qt.qtgui.util.ui import UILoadable -import curveprops +from . import curveprops try: import taurus.qt.qtgui.extra_nexus as extra_nexus except: extra_nexus = None +__all__ = ['TaurusPlotConfDlg'] + + @UILoadable(with_ui='ui') class TaurusPlotConfDlg(Qt.QWidget): ''' A configuration dialog for TaurusPlot. @@ -121,23 +124,23 @@ def __replaceWidget(self, new, old, layout=None): return new def onModelsAdded(self, models): - print models + print(models) nmodels = len(models) rowcount = self.model.rowCount() self.model.insertRows(rowcount, nmodels) for i, m in enumerate(models): self.model.setData(self.model.index(rowcount + i, curveprops.Y), - value=Qt.QVariant(m)) + value=m) def onApply(self): - print "APPLY!!! (todo)" + print("APPLY!!! (todo)") curveConfs = self.model.dumpData() for c in curveConfs: - print repr(c) + print(repr(c)) def onReload(self): - print "RELOAD!!! (todo)" + print("RELOAD!!! (todo)") class demo(Qt.QDialog): @@ -200,7 +203,7 @@ def onRem(self): def onData(self): cmds = self.model.dumpData() - print self.model.curves + print(self.model.curves) def main1(): diff --git a/lib/taurus/qt/qtgui/plot/taurustrend.py b/lib/taurus/qt/qtgui/qwt5/taurustrend.py similarity index 97% rename from lib/taurus/qt/qtgui/plot/taurustrend.py rename to lib/taurus/qt/qtgui/qwt5/taurustrend.py index a52e42e01..df069d4de 100644 --- a/lib/taurus/qt/qtgui/plot/taurustrend.py +++ b/lib/taurus/qt/qtgui/qwt5/taurustrend.py @@ -26,7 +26,9 @@ """ taurustrend.py: Generic trend widget for Taurus """ -__all__ = ["ScanTrendsSet", "TaurusTrend", "TaurusTrendsSet"] + +from __future__ import print_function +from builtins import str from datetime import datetime import time @@ -34,13 +36,17 @@ import re import gc import weakref +from functools import partial from taurus.external.qt import Qt, Qwt5 import taurus.core from taurus.core.taurusattribute import TaurusAttribute from taurus.core.util.containers import CaselessDict, CaselessList, ArrayBuffer from taurus.qt.qtgui.base import TaurusBaseComponent -from taurus.qt.qtgui.plot import TaurusPlot +from taurus.qt.qtgui.qwt5 import TaurusPlot + + +__all__ = ["ScanTrendsSet", "TaurusTrend", "TaurusTrendsSet"] def getArchivedTrendValues(*args, **kwargs): @@ -112,7 +118,7 @@ def __init__(self, name, parent=None, curves=None): self._orderedCurveNames = [] else: self._curves = curves - self._orderedCurveNames = curves.keys() + self._orderedCurveNames = list(curves) self._titleText = None self.setModel(name) @@ -183,7 +189,7 @@ def compileTitles(self, basetitle): ntrends = len(self._curves) if '' in basetitle: ret = [basetitle.replace('', "%i" % i) - for i in xrange(ntrends)] + for i in range(ntrends)] else: ret = [basetitle] * ntrends return ret @@ -338,7 +344,7 @@ def _updateHistory(self, model, value): v = value.rvalue try: self._yBuffer.append(v) - except Exception, e: + except Exception as e: self.warning('Problem updating history (%s=%s):%s', model, v, e) value = None @@ -354,7 +360,7 @@ def _updateHistory(self, model, value): if self.parent().getXDynScale() or not self.parent().axisAutoScale(Qwt5.QwtPlot.xBottom): try: getArchivedTrendValues(self, model, insert=True) - except Exception, e: + except Exception as e: import traceback self.warning('%s: reading from archiving failed: %s' % ( datetime.now().isoformat('_'), traceback.format_exc())) @@ -438,7 +444,7 @@ def handleEvent(self, evt_src, evt_type, evt_value): try: self._xValues, self._yValues = self._updateHistory( model=model or self.getModel(), value=value) - except Exception, e: + except Exception as e: self._onDroppedEvent(reason=str(e)) raise @@ -451,7 +457,7 @@ def handleEvent(self, evt_src, evt_type, evt_value): c._xValues, c._yValues = self._xValues, self._yValues[:, i] c._updateMarkers() - self.dataChanged.emit(Qt.QString(self.getModel())) + self.dataChanged.emit(str(self.getModel())) def _checkDataDimensions(self, value): ''' @@ -471,7 +477,7 @@ def _checkDataDimensions(self, value): # them to the TrendSet name = self.getModelName() rawdata = {'x': numpy.zeros(0), 'y': numpy.zeros(0)} - for i in xrange(ntrends): + for i in range(ntrends): subname = "%s[%i]" % (name, i) self.parent().attachRawData(rawdata, id=subname) self.addCurve(subname, self.parent().curves[subname]) @@ -779,7 +785,7 @@ def _createTrends(self, datadesc): curve.setAppearanceProperties(prop) self.addCurve(name, curve) self.parent().autoShowYAxes() - self.dataChanged.emit(Qt.QString(self.getModel())) + self.dataChanged.emit(str(self.getModel())) def _scanLineReceived(self, recordData): '''Receives a recordData dictionary and updates the curves associated to it @@ -837,7 +843,7 @@ def _scanLineReceived(self, recordData): c._yValues = numpy.append(c._yValues, v) c._updateMarkers() - self.dataChanged.emit(Qt.QString(self.getModel())) + self.dataChanged.emit(str(self.getModel())) def connectWithQDoor(self, qdoor): '''connects this ScanTrendsSet to a QDoor @@ -941,12 +947,14 @@ def __initActions(self): self._usePollingBufferAction.toggled.connect(self.setUsePollingBuffer) self._setForcedReadingPeriodAction = Qt.QAction( "Set forced reading period...", None) - self._setForcedReadingPeriodAction.triggered[()].connect(self.setForcedReadingPeriod) + self._setForcedReadingPeriodAction.triggered.connect( + partial(self.setForcedReadingPeriod, msec=None, tsetnames=None)) self._clearBuffersAction = Qt.QAction("Clear Buffers", None) - self._clearBuffersAction.triggered[()].connect(self.clearBuffers) + self._clearBuffersAction.triggered.connect(self.clearBuffers) self._setMaxBufferSizeAction = Qt.QAction( "Change buffers size...", None) - self._setMaxBufferSizeAction.triggered[()].connect(self.setMaxDataBufferSize) + self._setMaxBufferSizeAction.triggered.connect( + partial(self.setMaxDataBufferSize, maxSize=None)) self._autoClearOnScanAction = Qt.QAction( "Auto-clear on new scans", None) self._autoClearOnScanAction.setCheckable(True) @@ -1125,7 +1133,7 @@ def clearBuffers(self): not remove the models, it simply removes all stored data)''' self.curves_lock.acquire() try: - for ts in self.trendSets.itervalues(): + for ts in self.trendSets.values(): ts.clearTrends(replot=False) finally: self.curves_lock.release() @@ -1175,7 +1183,7 @@ def updateCurves(self, names): raise ValueError( 'composed ("X|Y") models are not supported by TaurusTrend') # create a new TrendSet if not already there - if not self.trendSets.has_key(name): + if name not in self.trendSets: # check if the model name is of scan type and provides a # door matchScan = re.search(r"scan:\/\/(.*)", name) @@ -1279,7 +1287,7 @@ def getCurveTitle(self, name, index=None): index = 0 else: return tset.compiledTitle - title = unicode(tset[index].title().text()) + title = str(tset[index].title().text()) finally: self.curves_lock.release() return title @@ -1294,7 +1302,7 @@ def changeCurvesTitlesDialog(self, curveNamesList=None): and it will also be used as default for newly created ones) - :return: (caselessDict or None) The return value will be + :return: (caselessDict or None) The return value will be `None` if `curveNamesList` is None. Otherwise it will be a dictionary with key=curvename and value=newtitle. @@ -1328,7 +1336,7 @@ def changeCurvesTitlesDialog(self, curveNamesList=None): newTitlesDict = CaselessDict() for curveName in curveNamesList: curvetitle = titletext - for ts in self.trendSets.itervalues(): + for ts in self.trendSets.values(): if curveName in ts: curvetitle = ts.compileBaseTitle(curvetitle) curvetitle = curvetitle.replace( @@ -1392,7 +1400,7 @@ def curveDataChanged(self, name): self.xBottom, currmin + step, currmax + step) finally: self.curves_lock.release() - self.dataChanged.emit(Qt.QString(name)) + self.dataChanged.emit(str(name)) if not self.xIsTime: self.replot() else: @@ -1440,7 +1448,7 @@ def setPaused(self, paused=True): .. seealso:: :meth:`TaurusBaseComponent.setPaused` ''' - for ts in self.trendSets.itervalues(): + for ts in self.trendSets.values(): ts.setPaused(paused) self._isPaused = paused @@ -1478,7 +1486,7 @@ def createConfig(self, tsnames=None, **kwargs): miscdict["MaxBufferSize"] = self.getMaxDataBufferSize() self.curves_lock.acquire() try: - for tsname, ts in self.trendSets.iteritems(): + for tsname, ts in self.trendSets.items(): if tsname in tsnames: # store a dict containing just model names (key and value # are the same) @@ -1513,7 +1521,7 @@ def applyConfig(self, configdict, **kwargs): self.attachRawData(rd) # for backwards compatibility, if the ordered list of models is not # stored, it uses the unsorted dict values - models = configdict.get("model", configdict["TrendSets"].values()) + models = configdict.get("model", list(configdict["TrendSets"].values())) self.addModels(models) for m in models: tset = self.trendSets[m] @@ -1538,7 +1546,7 @@ def getQtDesignerPluginInfo(cls): :return: (dict) a map with pertinent designer information""" return { - 'module': 'taurus.qt.qtgui.plot', + 'module': 'taurus.qt.qtgui.qwt5', 'group': 'Taurus Display', 'icon': 'designer:qwtplot.png', 'container': False} @@ -1689,7 +1697,7 @@ def setMaxDataBufferSize(self, maxSize=None): self.curves_lock.acquire() try: - for n, ts in self.trendSets.iteritems(): + for n, ts in self.trendSets.items(): try: ts.setMaxDataBufferSize(maxSize) except ValueError: @@ -1926,16 +1934,16 @@ def main(): w.setModel(models) # export option if options.export_file is not None: - curves = dict.fromkeys(w.trendSets.keys(), 0) + curves = dict.fromkeys(w.trendSets, 0) def exportIfAllCurves(curve, trend=w, counters=curves): curve = str(curve) - print '*' * 10 + ' %s: Event received for %s ' % (datetime.now().isoformat(), curve) + '*' * 10 + print('*' * 10 + ' %s: Event received for %s ' % (datetime.now().isoformat(), curve) + '*' * 10) if curve in counters: counters[curve] += 1 if all(counters.values()): trend.exportPdf(options.export_file) - print '*' * 10 + ' %s: Exported to : %s ' % (datetime.now().isoformat(), options.export_file) + '*' * 10 + print('*' * 10 + ' %s: Exported to : %s ' % (datetime.now().isoformat(), options.export_file) + '*' * 10) trend.close() return if not curves: diff --git a/lib/taurus/qt/qtgui/plot/ui/AddCPointsDialog.ui b/lib/taurus/qt/qtgui/qwt5/ui/AddCPointsDialog.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/AddCPointsDialog.ui rename to lib/taurus/qt/qtgui/qwt5/ui/AddCPointsDialog.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/ArrayEditor.ui b/lib/taurus/qt/qtgui/qwt5/ui/ArrayEditor.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/ArrayEditor.ui rename to lib/taurus/qt/qtgui/qwt5/ui/ArrayEditor.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/ControllerBox.ui b/lib/taurus/qt/qtgui/qwt5/ui/ControllerBox.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/ControllerBox.ui rename to lib/taurus/qt/qtgui/qwt5/ui/ControllerBox.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/CurvePropertiesView.ui b/lib/taurus/qt/qtgui/qwt5/ui/CurvePropertiesView.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/CurvePropertiesView.ui rename to lib/taurus/qt/qtgui/qwt5/ui/CurvePropertiesView.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/CurveStatsDialog.ui b/lib/taurus/qt/qtgui/qwt5/ui/CurveStatsDialog.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/CurveStatsDialog.ui rename to lib/taurus/qt/qtgui/qwt5/ui/CurveStatsDialog.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/CurvesAppearanceChooser.ui b/lib/taurus/qt/qtgui/qwt5/ui/CurvesAppearanceChooser.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/CurvesAppearanceChooser.ui rename to lib/taurus/qt/qtgui/qwt5/ui/CurvesAppearanceChooser.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/EditCPointsDialog.ui b/lib/taurus/qt/qtgui/qwt5/ui/EditCPointsDialog.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/EditCPointsDialog.ui rename to lib/taurus/qt/qtgui/qwt5/ui/EditCPointsDialog.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/TaurusPlotConfDlg.ui b/lib/taurus/qt/qtgui/qwt5/ui/TaurusPlotConfDlg.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/TaurusPlotConfDlg.ui rename to lib/taurus/qt/qtgui/qwt5/ui/TaurusPlotConfDlg.ui diff --git a/lib/taurus/qt/qtgui/plot/ui/TaurusPlotConfigDialog.ui b/lib/taurus/qt/qtgui/qwt5/ui/TaurusPlotConfigDialog.ui similarity index 100% rename from lib/taurus/qt/qtgui/plot/ui/TaurusPlotConfigDialog.ui rename to lib/taurus/qt/qtgui/qwt5/ui/TaurusPlotConfigDialog.ui diff --git a/lib/taurus/qt/qtgui/resource/__init__.py b/lib/taurus/qt/qtgui/resource/__init__.py index ef0a65b2d..2aec1eba9 100644 --- a/lib/taurus/qt/qtgui/resource/__init__.py +++ b/lib/taurus/qt/qtgui/resource/__init__.py @@ -28,10 +28,12 @@ based on taurus.qt.qtgui.icon for bck-compat) """ +from __future__ import absolute_import + from taurus.core.util.log import deprecated as __deprecated __deprecated(dep='taurus.qt.qtgui.resource', alt='taurus.qt.qtgui.icon', rel='4.0') -from taurus_resource_utils import * +from .taurus_resource_utils import * diff --git a/lib/taurus/qt/qtgui/table/qdictionary.py b/lib/taurus/qt/qtgui/table/qdictionary.py index dd47a808e..9063e1425 100644 --- a/lib/taurus/qt/qtgui/table/qdictionary.py +++ b/lib/taurus/qt/qtgui/table/qdictionary.py @@ -24,25 +24,27 @@ ############################################################################# """This module provides basic python dictionary/list editor widgets""" - -__all__ = ["QDictionaryEditor", "QListEditor"] - -__docformat__ = 'restructuredtext' +from __future__ import print_function import sys -import taurus -import numpy +from future.utils import string_types + from taurus.core.util.containers import SortedDict from taurus.external.qt import Qt from taurus.qt.qtgui.container import TaurusBaseContainer, TaurusWidget from taurus.qt.qtcore.util.properties import join, djoin + +__all__ = ["QDictionaryEditor", "QListEditor"] + +__docformat__ = 'restructuredtext' + ############################################################################### # Methods borrowed from fandango modules def isString(seq): - if isinstance(seq, basestring): + if isinstance(seq, string_types): return True # It matches most python str-like classes if any(s in str(type(seq)).lower() for s in ('vector', 'array', 'list',)): return False @@ -86,8 +88,8 @@ def dict2array(dct): def expand(d, level): # ,nrows=nrows,ncols=ncols): # self.debug('\texpand(%s(%s),%s)'%(type(d),d,level)) - items = d.items() if isinstance(d, SortedDict) else sorted( - d.items() if hasattr(d, 'items') else d) + items = list(d.items()) if isinstance(d, SortedDict) else sorted( + list(d.items()) if hasattr(d, 'items') else d) for k, v in items: zero = data['nrows'] data[(data['nrows'], level)] = k diff --git a/lib/taurus/qt/qtgui/table/qlogtable.py b/lib/taurus/qt/qtgui/table/qlogtable.py index afbc1b394..3c61cb8d6 100644 --- a/lib/taurus/qt/qtgui/table/qlogtable.py +++ b/lib/taurus/qt/qtgui/table/qlogtable.py @@ -26,10 +26,10 @@ """This module provides Qt table widgets which display logging messages from the python :mod:`logging` module""" -__all__ = ["QLoggingTableModel", "QLoggingTable", "QLoggingWidget", - "QRemoteLoggingTableModel"] +from __future__ import absolute_import -__docformat__ = 'restructuredtext' +from operator import attrgetter +from builtins import range import logging import logging.handlers @@ -47,9 +47,15 @@ from taurus.qt.qtgui.model import FilterToolBar from taurus.qt.qtgui.util import ActionFactory -from qtable import QBaseTableWidget +from .qtable import QBaseTableWidget + + +__all__ = ["QLoggingTableModel", "QLoggingTable", "QLoggingWidget", + "QRemoteLoggingTableModel"] -LEVEL, TIME, MSG, NAME, ORIGIN = range(5) +__docformat__ = 'restructuredtext' + +LEVEL, TIME, MSG, NAME, ORIGIN = list(range(5)) HORIZ_HEADER = 'Level', 'Time', 'Message', 'By', 'Origin' __LEVEL_BRUSH = { @@ -76,19 +82,10 @@ def getBrushForLevel(level): elevel = taurus.Error elif level <= taurus.Critical: elevel = taurus.Critical - f, g = map(Qt.QBrush, __LEVEL_BRUSH[elevel]) + f, g = list(map(Qt.QBrush, __LEVEL_BRUSH[elevel])) return f, g -def _origin_cmp(rec1, rec2): - c1 = cmp(rec1.process, rec2.process) - if c1 == 0: - c2 = cmp(rec1.thread, rec2.thread) - if c2 == 0: - return cmp(rec1.name, rec2.name) - return c2 - return c1 - gethostname = memoized(socket.gethostname) @@ -119,7 +116,7 @@ def _get_record_origin_tooltip(rec): host, procName, procID, threadName, threadID = _get_record_origin(rec) pathname, filename, modulename, funcname, lineno = _get_record_trace(rec) timestamp = str(datetime.datetime.fromtimestamp(rec.created)) - bgcolor, fgcolor = map(Qt.QBrush.color, getBrushForLevel(rec.levelno)) + bgcolor, fgcolor = list(map(Qt.QBrush.color, getBrushForLevel(rec.levelno))) bgcolor = "#%02x%02x%02x" % ( bgcolor.red(), bgcolor.green(), bgcolor.blue()) fgcolor = "#%02x%02x%02x" % ( @@ -150,7 +147,7 @@ class QLoggingTableModel(Qt.QAbstractTableModel, logging.Handler): DftColSize = Qt.QSize(80, 20), Qt.QSize(200, 20), \ Qt.QSize(300, 20), Qt.QSize(180, 20), Qt.QSize(240, 20), - def __init__(self, capacity=500000, freq=0.25): + def __init__(self, parent=None, capacity=500000, freq=0.25): super(Qt.QAbstractTableModel, self).__init__() logging.Handler.__init__(self) self._capacity = capacity @@ -164,17 +161,13 @@ def __init__(self, capacity=500000, freq=0.25): # --------------------------------- def sort(self, column, order=Qt.Qt.AscendingOrder): - if column == LEVEL: - f = lambda a, b: cmp(a.levelno, b.levelno) - elif column == TIME: - f = lambda a, b: cmp(a.created, b.created) - elif column == MSG: - f = lambda a, b: cmp(a.msg, b.msg) - elif column == NAME: - f = lambda a, b: cmp(a.name, b.name) - elif column == ORIGIN: - f = _origin_cmp - self._records = sorted(self._records, cmp=f, + column2key_map = {LEVEL: attrgetter('levelno'), + TIME: attrgetter('created'), + MSG: attrgetter('msg'), + NAME: attrgetter('name'), + ORIGIN: attrgetter('process', 'thread', 'name'), + } + self._records = sorted(self._records, key=column2key_map[column], reverse=order == Qt.Qt.DescendingOrder) def rowCount(self, index=Qt.QModelIndex()): @@ -188,74 +181,74 @@ def getRecord(self, index): def data(self, index, role=Qt.Qt.DisplayRole): if not index.isValid() or not (0 <= index.row() < len(self._records)): - return Qt.QVariant() + return None record = self.getRecord(index) column = index.column() if role == Qt.Qt.DisplayRole: if column == LEVEL: - return Qt.QVariant(record.levelname) + return record.levelname elif column == TIME: dt = datetime.datetime.fromtimestamp(record.created) - return Qt.QVariant(str(dt)) - # return Qt.QVariant(dt.strftime("%Y-%m-%d %H:%m:%S.%f")) + return str(dt) + # return dt.strftime("%Y-%m-%d %H:%m:%S.%f") elif column == MSG: - return Qt.QVariant(record.getMessage()) + return record.getMessage() elif column == NAME: - return Qt.QVariant(record.name) + return record.name elif column == ORIGIN: - return Qt.QVariant(_get_record_origin_str(record)) + return _get_record_origin_str(record) elif role == Qt.Qt.TextAlignmentRole: if column in (LEVEL, MSG): - return Qt.QVariant(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter) - return Qt.QVariant(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter) + return Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter + return Qt.Qt.AlignRight | Qt.Qt.AlignVCenter elif role == Qt.Qt.BackgroundRole: if column == LEVEL: - return Qt.QVariant(getBrushForLevel(record.levelno)[0]) + return getBrushForLevel(record.levelno)[0] elif role == Qt.Qt.ForegroundRole: if column == LEVEL: - return Qt.QVariant(getBrushForLevel(record.levelno)[1]) + return getBrushForLevel(record.levelno)[1] elif role == Qt.Qt.ToolTipRole: - return Qt.QVariant(_get_record_origin_tooltip(record)) + return _get_record_origin_tooltip(record) elif role == Qt.Qt.SizeHintRole: return self._getSizeHint(column) # elif role == Qt.Qt.StatusTipRole: # elif role == Qt.Qt.CheckStateRole: elif role == Qt.Qt.FontRole: - return Qt.QVariant(self.DftFont) - return Qt.QVariant() + return self.DftFont + return None def _getSizeHint(self, column): - return Qt.QVariant(QLoggingTableModel.DftColSize[column]) + return QLoggingTableModel.DftColSize[column] def headerData(self, section, orientation, role=Qt.Qt.DisplayRole): if role == Qt.Qt.TextAlignmentRole: if orientation == Qt.Qt.Horizontal: - return Qt.QVariant(int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter)) - return Qt.QVariant(int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter)) + return int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter) + return int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter) elif role == Qt.Qt.SizeHintRole: if orientation == Qt.Qt.Vertical: - return Qt.QVariant(Qt.QSize(50, 20)) + return Qt.QSize(50, 20) else: return self._getSizeHint(section) elif role == Qt.Qt.FontRole: - return Qt.QVariant(Qt.QFont("Mono", 8)) + return Qt.QFont("Mono", 8) elif role == Qt.Qt.ToolTipRole: if section == LEVEL: - return Qt.QVariant("log level") + return "log level" elif section == TIME: - return Qt.QVariant("log time stamp") + return "log time stamp" elif section == MSG: - return Qt.QVariant("log message") + return "log message" elif section == NAME: - return Qt.QVariant("object who recorded the log") + return "object who recorded the log" elif section == ORIGIN: - return Qt.QVariant("the host, process and thread where the " - "log was executed from") + return ("the host, process and thread where the" + + " log was executed from") if role != Qt.Qt.DisplayRole: - return Qt.QVariant() + return None if orientation == Qt.Qt.Horizontal: - return Qt.QVariant(HORIZ_HEADER[section]) - return Qt.QVariant(int(section + 1)) + return HORIZ_HEADER[section] + return int(section + 1) def insertRows(self, position, rows=1, index=Qt.QModelIndex()): self.beginInsertRows(Qt.QModelIndex(), position, position + rows - 1) @@ -345,7 +338,7 @@ def rowsInserted(self, index, start, end): """Overwrite of slot rows inserted to do proper resize and scroll to bottom if desired""" Qt.QTableView.rowsInserted(self, index, start, end) - for i in xrange(start, end + 1): + for i in range(start, end + 1): self.resizeRowToContents(i) if start == 0: self.resizeColumnsToContents() @@ -376,8 +369,7 @@ def __init__(self, view=None, parent=None, designMode=False): self._logLevelComboBox = logLevelComboBox = Qt.QComboBox() levels = "Trace", "Debug", "Info", "Warning", "Error", "Critical" for level in levels: - logLevelComboBox.addItem( - level, Qt.QVariant(getattr(taurus, level))) + logLevelComboBox.addItem(level, getattr(taurus, level)) logLevelComboBox.setCurrentIndex(0) logLevelComboBox.currentIndexChanged.connect(self.onLogLevelChanged) logLevelComboBox.setToolTip("Filter by log level") @@ -405,12 +397,12 @@ def getLogLevelComboBox(self): def getLogLevel(self): combo = self.getLogLevelComboBox() - return Qt.from_qvariant(combo.itemData(combo.currentIndex())) + return combo.itemData(combo.currentIndex()) def setLogLevel(self, level): combo = self.getLogLevelComboBox() for i in range(combo.count()): - l = Qt.from_qvariant(combo.itemData(i)) + l = combo.itemData(i) if l == level: combo.setCurrentIndex(i) @@ -489,7 +481,11 @@ def createViewWidget(self, klass=None): klass = QLoggingTable view = QBaseTableWidget.createViewWidget(self, klass=klass) hh = view.horizontalHeader() - hh.setSectionResizeMode(MSG, Qt.QHeaderView.Stretch) + if hh.length() > 0: + try: + hh.setSectionResizeMode(MSG, Qt.QHeaderView.Stretch) + except AttributeError: # PyQt4 + hh.setResizeMode(MSG, Qt.QHeaderView.Stretch) view.setShowGrid(False) view.sortByColumn(TIME, Qt.Qt.AscendingOrder) return view @@ -562,7 +558,7 @@ def fill_log(): import time import random - for i in xrange(10): + for i in range(10): taurus.info("Hello world %04d" % i) loggers = ["Object%02d" % (i + 1) for i in range(10)] diff --git a/lib/taurus/qt/qtgui/table/taurusdbtable.py b/lib/taurus/qt/qtgui/table/taurusdbtable.py index 4dc111d64..75b27c308 100644 --- a/lib/taurus/qt/qtgui/table/taurusdbtable.py +++ b/lib/taurus/qt/qtgui/table/taurusdbtable.py @@ -28,16 +28,19 @@ # todo: tango-centric!!! -__all__ = ["TaurusDbTableWidget"] - -__docformat__ = 'restructuredtext' +from __future__ import absolute_import from taurus.external.qt import Qt from taurus.core.taurusbasetypes import TaurusElementType from taurus.qt.qtcore.model import * from taurus.core.taurusauthority import TaurusAuthority from taurus.qt.qtgui.icon import getElementTypeIcon, getElementTypeIconName -from taurustable import TaurusBaseTableWidget +from .taurustable import TaurusBaseTableWidget + + +__all__ = ["TaurusDbTableWidget"] + +__docformat__ = 'restructuredtext' class TaurusDbTableWidget(TaurusBaseTableWidget): diff --git a/lib/taurus/qt/qtgui/table/taurusdevicepropertytable.py b/lib/taurus/qt/qtgui/table/taurusdevicepropertytable.py index 6cab80c41..74f5565bc 100755 --- a/lib/taurus/qt/qtgui/table/taurusdevicepropertytable.py +++ b/lib/taurus/qt/qtgui/table/taurusdevicepropertytable.py @@ -29,13 +29,18 @@ # todo: tango-centric -__all__ = ["TaurusPropTable"] +from __future__ import print_function +from future.utils import string_types +from builtins import str from taurus.external.qt import Qt, QtCore, QtGui from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.core.taurusdevice import TaurusDevice +__all__ = ["TaurusPropTable"] + + class TaurusPropTable(QtGui.QTableWidget, TaurusBaseWidget): ''' This widget will show a list of properties of device and the list of values. @@ -54,7 +59,7 @@ def __init__(self, parent=None, designMode=False): self.defineStyle() self.db = None - except Exception, e: + except Exception as e: self.traceback() #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- @@ -147,7 +152,7 @@ def setTable(self, dev_name): if USE_TABLES: self.setPropertyValue(value, i, 1) else: - if not isinstance(value, str): # not something like an string + if not isinstance(value, string_types): # not something like an string # adding new lines in between elements in the list value = '\n'.join(str(v) for v in value) self.setText(str(value), i, 1) @@ -181,15 +186,22 @@ def defineStyle(self): headerItem1.setText(QtGui.QApplication.translate( "PLCTabWidget", "Value", None, QtGui.QApplication.UnicodeUTF8)) self.setHorizontalHeaderItem(1, headerItem1) - self.horizontalHeader().setSectionResizeMode( - QtGui.QHeaderView.ResizeToContents) # .Stretch) + hh = self.horizontalHeader() + if hh.length() > 0: + try: + hh.setSectionResizeMode(hh.ResizeToContents) + except AttributeError: # PyQt4 + hh.setResizeMode(hh.ResizeToContents) def updateStyle(self): self.resizeRowsToContents() - self.horizontalHeader().setSectionResizeMode( - QtGui.QHeaderView.ResizeToContents) # .Stretch) - # self.resizeColumnsToContents() - pass + hh = self.horizontalHeader() + if hh.length() > 0: + try: + hh.setSectionResizeMode(hh.ResizeToContents) + except AttributeError: # PyQt4 + hh.setResizeMode(hh.ResizeToContents) + # self.resizeColumnsToContents() def contextMenuEvent(self, event): ''' This function is called when right clicking on qwt plot area. A pop up menu will be @@ -197,11 +209,11 @@ def contextMenuEvent(self, event): self.info('TaurusPropTable.contextMenuEvent()') menu = Qt.QMenu(self) configDialogAction = menu.addAction("Add new property") - configDialogAction.triggered[()].connect(self.addProperty) + configDialogAction.triggered.connect(self.addProperty) configDialogAction = menu.addAction("Delete property") - configDialogAction.triggered[()].connect(self.deleteProperty) + configDialogAction.triggered.connect(self.deleteProperty) configDialogAction = menu.addAction("Edit property") - configDialogAction.triggered[()].connect(self.editProperty) + configDialogAction.triggered.connect(self.editProperty) menu.addSeparator() menu.exec_(event.globalPos()) del menu @@ -229,7 +241,7 @@ def addProperty(self): text, ok = QtGui.QInputDialog.getText( self, 'New Property', 'Property name:') if ok: - text1 = unicode(text) + text1 = str(text) new_prop_name = str(text1) new_prop_value = '0' dict1 = {new_prop_name: [new_prop_value]} @@ -268,7 +280,7 @@ def editProperty(self): new_text, ok = QtGui.QInputDialog.getText( self, 'Rename', 'Write new name of property:') if ok: - new_text = unicode(new_text) + new_text = str(new_text) new_text = str(new_text) list = [prop_name] dict = {new_text: [prop_value]} @@ -288,7 +300,7 @@ def editProperty(self): self.setNewPropertyValue(new_text) def setNewPropertyValue(self, new_text): - new_text = unicode(new_text) + new_text = str(new_text) new_text = str(new_text) values = {self.prop_name2: new_text.replace('\r', '').split('\n')} self.db.put_device_property(self.dev_name, values) @@ -331,9 +343,9 @@ def setPropertyValue(self, value, i, j): ''' This method inserts a new table widget inside the cell @deprecated ... use setText() and editProperty() event call instead!!! ''' - if len(value) == 1 and isinstance(value[0], str): + if len(value) == 1 and isinstance(value[0], string_types): value = value[0] - if isinstance(value, str): # and '\n' in value: + if isinstance(value, string_types): # and '\n' in value: value = value.split('\n') if False: # isinstance(value,str): item = QtGui.QTableWidgetItem() diff --git a/lib/taurus/qt/qtgui/table/taurusgrid.py b/lib/taurus/qt/qtgui/table/taurusgrid.py index 6a4c5c0b2..a061968da 100644 --- a/lib/taurus/qt/qtgui/table/taurusgrid.py +++ b/lib/taurus/qt/qtgui/table/taurusgrid.py @@ -33,23 +33,26 @@ # This module needs a total cleanup. Both re. code conventions and algorithms. # --cpascual 20140827 +from __future__ import print_function +from future.utils import string_types +from future import standard_library +standard_library.install_aliases() +from builtins import str + + __all__ = ["TaurusGrid"] __docformat__ = 'restructuredtext' import re -import operator -import traceback -import Queue -from functools import partial +from queue import Queue from taurus.external.qt import Qt, QtGui, QtCore import taurus -from taurus.qt.qtcore.util.emitter import (modelSetter, TaurusEmitterThread, +from taurus.qt.qtcore.util.emitter import (modelSetter, SingletonWorker, MethodModel) from taurus.core.taurusmanager import TaurusManager -from taurus.core.util.log import Logger from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.panel import TaurusValue @@ -73,7 +76,7 @@ def get_all_models(expressions, limit=1000): Move this method to taurus.core.tango.search ''' # print( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) - if isinstance(expressions, str): + if isinstance(expressions, string_types): # if any(re.match(s,expressions) for s in ('\{.*\}','\(.*\)','\[.*\]')): ##self.debug( 'evaluating expressions ....') # expressions = list(eval(expressions)) @@ -81,8 +84,7 @@ def get_all_models(expressions, limit=1000): ##self.debug( 'expressions as string separated by commas ...') expressions = expressions.split(',') - elif any(isinstance(expressions, klass) for klass in - (QtCore.QStringList, list, tuple, dict)): + elif isinstance(expressions, (list, tuple, dict)): # self.debug( 'expressions converted from list ...') expressions = list(str(e) for e in expressions) @@ -128,7 +130,7 @@ def get_all_models(expressions, limit=1000): taurus_dp.attribute_list_query( ) if re_match_low(attribute, att.name)] targets.extend(dev + '/' + att for att in attrs) - except Exception, e: + except Exception as e: # self.warning( 'ERROR! TaurusGrid.get_all_models(): Unable to get attributes for device %s: %s' % (dev,str(e))) pass else: @@ -147,7 +149,7 @@ def get_readwrite_models(expressions, limit=1000): For each device only the good attributes are read. ''' # self.debug( 'In TaurusGrid.get_all_models(%s:"%s") ...' % (type(expressions),expressions)) - if isinstance(expressions, str): + if isinstance(expressions, string_types): if any(re.match(s, expressions) for s in ('\{.*\}', '\(.*\)', '\[.*\]')): # self.trace( 'evaluating expressions ....') @@ -156,8 +158,7 @@ def get_readwrite_models(expressions, limit=1000): # 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 isinstance(expressions, (list, tuple, dict)): expressions = list(str(e) for e in expressions) taurus_db = taurus.Authority() @@ -195,7 +196,7 @@ def get_readwrite_models(expressions, limit=1000): ) if re_match_low(attribute, att.name) and att.isReadOnly()] targets.extend(dev + '/' + att for att in attrs) - except Exception, e: + except Exception as e: pass else: targets.append(dev + '/' + attribute) @@ -274,7 +275,7 @@ def __init__(self, parent=None, designMode=False): self.hideLabels = False self.defineStyle() - self.modelsQueue = Queue.Queue() + self.modelsQueue = Queue() self.__modelsThread = None if not designMode: self.modelsThread @@ -361,10 +362,6 @@ def attach(self): # 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 - # - # Typical code is: - # self.connect(self, QtCore.SIGNAL('valueChangedDueToEvent(QString)'), - # self.setTextValue) ret = TaurusBaseWidget.attach(self) @@ -379,12 +376,6 @@ def detach(self): # ---------------------------------------------------------------------- # Write your own code here after detaching the widget from the model - # - # Typical code is: - # self.emit(QtCore.SIGNAL('valueChangedDueToEvent(QString)'), - # QtCore.QString(value_str)) - # self.disconnect(self, QtCore.SIGNAL('valueChangedDueToEvent(QString)'), - # self.setTextValue) # by default disable widget when dettached self.setEnabled(False) @@ -432,8 +423,7 @@ def setModel(self, model, devsInRows=False, delayed=False, append=False, if isinstance(model, dict): self.load(model) else: - model = isinstance(model, (str, QtCore.QString)) and [ - model] or list(model) + model = isinstance(model, string_types) and [model] or list(model) self.trace('#' * 80) self.trace('In TaurusGrid.setModel(%s)' % str(model)[:100]) @@ -526,7 +516,7 @@ def parse_labels(self, text): try: labels = eval(text) return labels - except Exception, e: + except Exception as e: self.warning( 'ERROR! Unable to parse labels property: %s' % str(e)) return [] @@ -546,7 +536,7 @@ def setRowLabels(self, rows): section = self.rows[i] self.table.setVerticalHeaderItem( i, QtGui.QTableWidgetItem(section)) - except Exception, e: + except Exception as e: self.debug("setRowLabels(): Exception! %s" % e) # self.create_widgets_table(self._columnsNames) @@ -567,7 +557,7 @@ def setColumnLabels(self, columns): equipment = self.columns[i] self.table.setHorizontalHeaderItem( i, QtGui.QTableWidgetItem(equipment)) - except Exception, e: + except Exception as e: self.debug("setColumnLabels(): Exception! %s" % e) # self.create_widgets_table(self._columnsNames) @@ -659,12 +649,12 @@ def create_widgets_dict(self, models): self.row_labels = sorted( list(set(m.split('/')[0].upper() for m in models if m.count('/') >= 2))) - self.row_labels = zip(self.row_labels, self.row_labels) + self.row_labels = list(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))) - self.column_labels = zip(self.column_labels, self.column_labels) + self.column_labels = list(zip(self.column_labels, self.column_labels)) # for m in models: # if m.count('/')<2: @@ -794,7 +784,14 @@ def create_widgets_table(self, models): for i in range(len(self.rows)): section = self.rows[i] checkbox = QtGui.QCheckBox(section) - if checkbox.text() == 'Others': + + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + checkbox._id = section # <-- ugly monkey-patch! + # ------------------------------------------------------- + + if section == 'Others': checkbox.setChecked(False) if not self._show_others: checkbox.hide() @@ -815,7 +812,14 @@ def create_widgets_table(self, models): for i in range(len(self.columns)): column = self.columns[i] checkbox = QtGui.QCheckBox(column) - if checkbox.text() == 'Others': + + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + checkbox._id = column # <-- ugly monkey-patch! + # ------------------------------------------------------- + + if column == 'Others': checkbox.setChecked(False) if not self._show_others: checkbox.hide() @@ -839,7 +843,12 @@ def show_hide_rows(self): """ for checkbox in self.rows_frame.children(): if isinstance(checkbox, QtGui.QCheckBox): - table_row = self.rows.index(checkbox.text()) + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + # checkbox.text() <-- fails due to added "& + table_row = self.rows.index(checkbox._id) + # ------------------------------------------------------- if checkbox.isChecked(): self.table.showRow(table_row) else: @@ -851,7 +860,12 @@ def show_hide_columns(self): """ for checkbox in self.columns_frame.children(): if isinstance(checkbox, QtGui.QCheckBox): - table_col = self.columns.index(checkbox.text()) + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + # checkbox.text() <-- fails due to added "& + table_col = self.columns.index(checkbox._id) + # ------------------------------------------------------- if checkbox.isChecked(): self.table.showColumn(table_col) else: @@ -861,16 +875,26 @@ 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) + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + # checkbox.text() <-- fails due to added "& + and checkbox._id == '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) + # ------------------------------------------------------- + # Work around for https://bugs.kde.org/show_bug.cgi?id=345023 + # TODO: make better solution for this + # checkbox.text() <-- fails due to added "& + and checkbox._id == 'Others'): + # ------------------------------------------------------- if self._show_others: checkbox.show() else: @@ -902,11 +926,20 @@ def build_table(self, values): # table.resizeColumnsToContents() # table.resizeRowsToContents() - table.horizontalHeader().setSectionResizeMode(QtGui.QHeaderView.Stretch) + hh = table.horizontalHeader() + if hh.length() > 0: + try: + hh.setSectionResizeMode(hh.Stretch) + except AttributeError: # PyQt4 + hh.setResizeMode(hh.Stretch) # table.verticalHeader().setSectionResizeMode(QtGui.QHeaderView.Stretch) # table.horizontalHeader().setSectionResizeMode(QtGui.QHeaderView.ResizeToContents) - table.verticalHeader().setSectionResizeMode( - QtGui.QHeaderView.ResizeToContents) + vh = table.verticalHeader() + if vh.length() > 0: + try: + vh.setSectionResizeMode(vh.ResizeToContents) + except AttributeError: # PyQt4 + hh.setResizeMode(vh.ResizeToContents) return table @@ -1022,9 +1055,9 @@ def sysargs_to_dict(defaults=[]): from taurus.qt.qtgui.application import TaurusApplication if len(sys.argv) < 2: - print "The format of the call is something like:" - print '\t/usr/bin/python taurusgrid.py grid.pickle.file' - 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' + print("The format of the call is something like:") + print('\t/usr/bin/python taurusgrid.py grid.pickle.file') + 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 = TaurusApplication(sys.argv[0:1]) @@ -1037,7 +1070,7 @@ def sysargs_to_dict(defaults=[]): except: args = sysargs_to_dict( ['model', 'rows', 'cols', 'others', 'rowframe', 'colframe']) - print "args = %s" % args + print("args = %s" % args) if args.get('rows'): gui.setRowLabels(args['rows']) if args.get('cols'): @@ -1048,6 +1081,6 @@ def sysargs_to_dict(defaults=[]): gui.showColumnFrame('colframe' in args and args['colframe'] and True) gui.showOthers('others' in args and args['others'] or True) - print "current TaurusGrid model= %s" % (gui.getModel()) + print("current TaurusGrid model= %s" % (gui.getModel())) gui.show() sys.exit(app.exec_()) diff --git a/lib/taurus/qt/qtgui/table/taurustable.py b/lib/taurus/qt/qtgui/table/taurustable.py index 2d8e00aba..1b2cd5985 100644 --- a/lib/taurus/qt/qtgui/table/taurustable.py +++ b/lib/taurus/qt/qtgui/table/taurustable.py @@ -26,13 +26,16 @@ """This module provides a base widget that can be used to display a taurus model in a table widget""" +from __future__ import absolute_import + +from taurus.qt.qtgui.model import TaurusBaseModelWidget +from .qtable import QBaseTableWidget + + __all__ = ["TaurusBaseTableWidget"] __docformat__ = 'restructuredtext' -from taurus.qt.qtgui.model import TaurusBaseModelWidget -from qtable import QBaseTableWidget - class TaurusBaseTableWidget(QBaseTableWidget, TaurusBaseModelWidget): """A class:`taurus.qt.qtgui.tree.QBaseTableWidget` that connects to a diff --git a/lib/taurus/qt/qtgui/table/taurusvaluestable.py b/lib/taurus/qt/qtgui/table/taurusvaluestable.py index d0ec7183c..567ea37cf 100755 --- a/lib/taurus/qt/qtgui/table/taurusvaluestable.py +++ b/lib/taurus/qt/qtgui/table/taurusvaluestable.py @@ -23,9 +23,7 @@ ## ############################################################################# -__all__ = ["TaurusValuesTable"] - -__docformat__ = 'restructuredtext' +from builtins import str from taurus.external.qt import Qt from taurus.core.units import Quantity @@ -41,6 +39,11 @@ from taurus.core.util.enumeration import Enumeration +__all__ = ["TaurusValuesTable"] + +__docformat__ = 'restructuredtext' + + def _value2Quantity(value, units): ''' Creates a Quantity from value and forces units if the vaule is unitless @@ -58,7 +61,7 @@ def _value2Quantity(value, units): class TaurusValuesIOTableModel(Qt.QAbstractTableModel): typeCastingMap = {'f': float, 'b': bool, - 'u': int, 'i': int, 'S': str, 'U': unicode} + 'u': int, 'i': int, 'S': str, 'U': str} # Need to have an array dataChanged = Qt.pyqtSignal('QModelIndex', 'QModelIndex') @@ -100,7 +103,7 @@ def data(self, index, role=Qt.Qt.DisplayRole): else: tabledata = self._wtabledata if not index.isValid() or not (0 <= index.row() < len(tabledata)): - return Qt.QVariant() + return None elif role == Qt.Qt.DisplayRole: value = None rc = (index.row(), index.column()) @@ -115,10 +118,10 @@ def data(self, index, role=Qt.Qt.DisplayRole): value = value.magnitude # cast the value to a standard python type value = self.typeCastingMap[tabledata.dtype.kind](value) - return Qt.QVariant(value) + return value elif role == Qt.Qt.DecorationRole: - if (self._modifiedDict.has_key((index.row(), index.column()))) and\ - (self._writeMode): + if ((index.row(), index.column()) in self._modifiedDict + and self._writeMode): if self.getAttr().type in [DataType.Integer, DataType.Float]: value = self._modifiedDict[(index.row(), index.column())] if not self.inAlarmRange(value): @@ -127,46 +130,46 @@ def data(self, index, role=Qt.Qt.DisplayRole): icon = Qt.QIcon.fromTheme('emblem-important') else: icon = Qt.QIcon.fromTheme('document-save') - return Qt.QVariant(icon) + return icon elif role == Qt.Qt.EditRole: value = None - if self._modifiedDict.has_key((index.row(), index.column())) and\ - (self._writeMode): + if ((index.row(), index.column()) in self._modifiedDict + and self._writeMode): value = self._modifiedDict[(index.row(), index.column())] else: value = tabledata[index.row(), index.column()] if tabledata.dtype == bool: value = bool(value) - return Qt.QVariant(value) + return value elif role == Qt.Qt.BackgroundRole: if self._writeMode: - return Qt.QVariant(Qt.QColor(22, 223, 21, 50)) + return Qt.QColor(22, 223, 21, 50) else: - return Qt.QVariant(Qt.QColor('white')) + return Qt.QColor('white') elif role == Qt.Qt.ForegroundRole: - if self._modifiedDict.has_key((index.row(), index.column())) and\ - (self._writeMode): + if ((index.row(), index.column()) in self._modifiedDict + and self._writeMode): if self.getAttr().type in [DataType.Integer, DataType.Float]: value = self._modifiedDict[(index.row(), index.column())] if not self.inAlarmRange(value): - return Qt.QVariant(Qt.QColor('blue')) + return Qt.QColor('blue') else: - return Qt.QVariant(Qt.QColor('orange')) + return Qt.QColor('orange') else: - return Qt.QVariant(Qt.QColor('blue')) - return Qt.QVariant(Qt.QColor('black')) + return Qt.QColor('blue') + return Qt.QColor('black') elif role == Qt.Qt.FontRole: - if self._modifiedDict.has_key((index.row(), index.column())) and\ - (self._writeMode): - return Qt.QVariant(Qt.QFont("Arial", 10, Qt.QFont.Bold)) + if ((index.row(), index.column()) in self._modifiedDict + and self._writeMode): + return Qt.QFont("Arial", 10, Qt.QFont.Bold) elif role == Qt.Qt.ToolTipRole: - if self._modifiedDict.has_key((index.row(), index.column())) and\ - (self._writeMode): + if ((index.row(), index.column()) in self._modifiedDict + and self._writeMode): value = str(self._modifiedDict[(index.row(), index.column())]) msg = 'Original value: %s.\nNew value that will be saved: %s' %\ (str(tabledata[index.row(), index.column()]), value) - return Qt.QVariant(msg) - return Qt.QVariant() + return msg + return None def getAttr(self): return self._attr @@ -191,7 +194,8 @@ def setAttr(self, attr): repr(attr.data_format)) if (self._rowCount != rows) or (self._columnCount != columns): - self.reset() + self.beginResetModel() + self.endResetModel() self._rowCount = rows self._columnCount = columns @@ -243,7 +247,7 @@ def removeValue(self, index): :param index: (QModelIndex) table index ''' - if self._modifiedDict.has_key((index.row(), index.column())): + if (index.row(), index.column()) in self._modifiedDict: self._modifiedDict.pop((index.row(), index.column())) def flags(self, index): @@ -266,7 +270,7 @@ def getModifiedWriteData(self): if kind in 'SU': table = table.tolist() # we want to allow the strings to be larger than the original ones for (r, c), v in self._modifiedDict.items(): - table[r][c] = Qt.from_qvariant(v, str) + table[r][c] = v table = numpy.array(table, dtype=str) else: for k, v in self._modifiedDict.items(): @@ -275,7 +279,6 @@ def getModifiedWriteData(self): q = _value2Quantity(v, units) table[k] = q elif kind == 'b': - # TODO: This does not work Qt.from_qvariant(v, bool) if str(v) == "true": table[k] = True else: @@ -485,13 +488,13 @@ def setEditorData(self, editor, index): self._initialText = None if index.model().getType() == bool: editor.addItems(['true', 'false']) - a = str(Qt.from_qvariant(index.data(), bool)).lower() + a = str(index.data()).lower() self._initialText = a editor.setCurrentIndex(editor.findText(a)) else: data = index.model().data(index, Qt.Qt.EditRole) - self._initialText = Qt.from_qvariant(data, str) + self._initialText = data editor.setText(str(self._initialText)) def setModelData(self, editor, model, index): @@ -515,11 +518,19 @@ def setModelData(self, editor, model, index): text = editor.text() text = str(text) if(text != self._initialText) & (text != ""): - model.addValue(index, Qt.QVariant(text)) + model.addValue(index, text) hh = self.parent().horizontalHeader() - hh.setSectionResizeMode(Qt.QHeaderView.Fixed) + if hh.length() > 0: + try: + hh.setSectionResizeMode(hh.Fixed) + except AttributeError: # PyQt4 + hh.setResizeMode(hh.Fixed) vh = self.parent().verticalHeader() - vh.setSectionResizeMode(Qt.QHeaderView.Fixed) + if vh.length() > 0: + try: + vh.setSectionResizeMode(vh.Fixed) + except AttributeError: # PyQt4 + hh.setResizeMode(vh.Fixed) index.model().editedIndex = None @@ -658,7 +669,7 @@ def _initActions(self): self.chooseModelAction = Qt.QAction("Choose &Model", self) self.chooseModelAction.setEnabled(self.isModifiableByUser()) self.addAction(self.chooseModelAction) - self.chooseModelAction.triggered[()].connect(self.chooseModel) + self.chooseModelAction.triggered.connect(self.chooseModel) def getModelClass(self): '''see :meth:`TaurusWidget.getModelClass`''' @@ -723,9 +734,17 @@ def handleEvent(self, evt_src, evt_type, evt_value): model.setWriteMode(self._writeMode) hh = self._tableView.horizontalHeader() - hh.setSectionResizeMode(Qt.QHeaderView.Fixed) + if hh.length() > 0: + try: + hh.setSectionResizeMode(hh.Fixed) + except AttributeError: # PyQt4 + hh.setResizeMode(hh.Fixed) vh = self._tableView.verticalHeader() - vh.setSectionResizeMode(Qt.QHeaderView.Fixed) + if vh.length() > 0: + try: + vh.setSectionResizeMode(vh.Fixed) + except AttributeError: # PyQt4 + hh.setResizeMode(vh.Fixed) if self.defaultWriteMode == "r": isWritable = False else: @@ -748,7 +767,7 @@ def contextMenuEvent(self, event): index = self._tableView.selectedIndexes()[0] if index.isValid(): val = self._tableView.model().getReadValue(index) - if self._tableView.model().getModifiedDict().has_key((index.row(), index.column())): + if (index.row(), index.column()) in self._tableView.model().getModifiedDict(): menu.addAction(Qt.QIcon.fromTheme( 'edit-undo'), "Reset to original value (%s) " % repr(val), self._tableView.removeChange) menu.addSeparator() diff --git a/lib/taurus/qt/qtgui/taurusgui/PermanentCustomPanelsDlg.py b/lib/taurus/qt/qtgui/taurusgui/PermanentCustomPanelsDlg.py index 59210b00a..972e97766 100644 --- a/lib/taurus/qt/qtgui/taurusgui/PermanentCustomPanelsDlg.py +++ b/lib/taurus/qt/qtgui/taurusgui/PermanentCustomPanelsDlg.py @@ -28,6 +28,7 @@ PermanentCustomPanelDlg.py: """ +from builtins import object from taurus.external.qt import Qt diff --git a/lib/taurus/qt/qtgui/taurusgui/__init__.py b/lib/taurus/qt/qtgui/taurusgui/__init__.py index 3bb7e6ab8..021658dd6 100644 --- a/lib/taurus/qt/qtgui/taurusgui/__init__.py +++ b/lib/taurus/qt/qtgui/taurusgui/__init__.py @@ -49,14 +49,19 @@ prevail). """ +from __future__ import absolute_import -__docformat__ = 'restructuredtext' - -import utils -from paneldescriptionwizard import * -from taurusgui import * -from appsettingswizard import * +from . import utils +from .paneldescriptionwizard import * +from .taurusgui import * +from .appsettingswizard import * try: - from macrolistener import * + # this import is left here for bck-compat, but will be removed + # TODO: remove this + from sardana.taurus.qt.qtgui.macrolistener import (MacroBroker, + DynamicPlotManager) except ImportError: - pass # allow for sardana not being installed + pass + + +__docformat__ = 'restructuredtext' diff --git a/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py b/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py index 0d5d8d658..066b6899f 100644 --- a/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py +++ b/lib/taurus/qt/qtgui/taurusgui/appsettingswizard.py @@ -32,8 +32,9 @@ time and those customizations will also be stored, this file defines what a user will find when launching the GUI for the first time. """ +from __future__ import print_function -__all__ = ["AppSettingsWizard", "ExternalAppEditor"] +from builtins import str import os import re @@ -45,7 +46,7 @@ from lxml import etree from taurus import tauruscustomsettings -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat import taurus.qt.qtgui.panel import taurus.qt.qtgui.taurusgui.paneldescriptionwizard import taurus.qt.qtgui.input @@ -54,6 +55,9 @@ from taurus.qt.qtgui.util import ExternalAppAction +__all__ = ["AppSettingsWizard", "ExternalAppEditor"] + + class BooleanWidget(Qt.QWidget): """ This class represents the simple boolean widget with two RadioButtons @@ -230,19 +234,19 @@ def _setupUI(self): self._projectDirBT.clicked.connect(self.onSelectDir) def onSelectDir(self): - dirname = unicode(Qt.QFileDialog.getExistingDirectory( + dirname = str(Qt.QFileDialog.getExistingDirectory( self, 'Choose the project directory', self._projectDirLE.text())) if not dirname: return self._projectDirLE.setText(dirname) def validatePage(self): - dirname = unicode(self._projectDirLE.text()) + dirname = str(self._projectDirLE.text()) if not os.path.exists(dirname): try: os.makedirs(dirname) - except Exception, e: + except Exception as e: Qt.QMessageBox.warning(self, 'Error creating project directory', 'Could not create the project directory.\nReason:%s' % repr( e), @@ -261,7 +265,7 @@ def validatePage(self): if option == Qt.QMessageBox.Yes: try: self.wizard().loadXml(fname) - except Exception, e: + except Exception as e: Qt.QMessageBox.warning(self, 'Error loading project configuration', 'Could not load the existing configuration.\nReason:%s' % repr( e), @@ -280,7 +284,7 @@ def validatePage(self): return True def _getProjectDir(self): - return unicode(self._projectDirLE.text()) + return str(self._projectDirLE.text()) class GeneralSettings(BasePage): @@ -467,8 +471,10 @@ def _getCustomLogo(self): return None def _selectImage(self): - fileName = Qt.QFileDialog.getOpenFileName(self, self.tr( - "Open File"), Qt.QDir.homePath(), self.tr("Images (*.png *.xpm *.jpg *.jpeg *.svg)")) + fileName, _ = compat.getOpenFileName( + self, self.tr("Open File"), Qt.QDir.homePath(), + self.tr("Images (*.png *.xpm *.jpg *.jpeg *.svg)") + ) self._customLogoLineEdit.setText(fileName) self._changeImage() @@ -576,10 +582,12 @@ def _setupUI(self): def _addSynoptic(self): pdir = self.wizard().__getitem__('projectDir') - fileNames = Qt.QFileDialog.getOpenFileNames(self, self.tr( - "Open File"), pdir, self.tr("JDW (*.jdw );; All files (*)")) + fileNames, _ = compat.getOpenFileNames( + self, self.tr("Open File"), pdir, + self.tr("JDW (*.jdw );; All files (*)") + ) for fileName in fileNames: - fileName = unicode(fileName) + fileName = str(fileName) if fileName not in self._synoptics: self._synoptics.append(fileName) self._refreshSynopticList() @@ -1000,8 +1008,10 @@ def _setDefaultText(self): self.checkData() def _selectExecFile(self): - filePath = Qt.QFileDialog.getOpenFileName(self, self.tr( - "Open File"), Qt.QDir.homePath(), self.tr("All files (*)")) + filePath, _ = compat.getOpenFileName( + self, self.tr("Open File"), + Qt.QDir.homePath(), self.tr("All files (*)") + ) if len(filePath): self._execFileLineEdit.setText(filePath) self._setDefaultText() @@ -1310,7 +1320,7 @@ def initializePage(self): def validatePage(self): try: self.createProject() - except Exception, e: + except Exception as e: Qt.QMessageBox.warning(self, 'Error creating project', 'Could not create project files. \nReason:%s' % repr( e), @@ -1333,8 +1343,8 @@ def createProject(self): datetime.datetime.now().isoformat()) # copy files for i in range(self._substTable.rowCount()): - src = unicode(self._substTable.item(i, 0).text()) - dst = os.path.join(install_dir, unicode( + src = str(self._substTable.item(i, 0).text()) + dst = os.path.join(install_dir, str( self._substTable.item(i, 1).text())) if os.path.normpath(src) != os.path.normpath(dst): shutil.copy(src, dst) @@ -1343,7 +1353,7 @@ def createProject(self): xmlcfgfilename = os.path.join(install_dir, self.wizard().getXmlConfigFileName()) f = open(xmlcfgfilename, 'w') - f.write(unicode(self._xml.toPlainText())) + f.write(str(self._xml.toPlainText())) f.close() logfile.write('XML Config file created: "%s"\n' % xmlcfgfilename) # write python config file @@ -1396,17 +1406,17 @@ def createProject(self): warnings = self.wizard().getProjectWarnings() if warnings: msg += '\n\nHowever, some fine-tuning may be needed. Please check the details:\n' - for short, long in warnings: - details += '- %s: %s\n\n' % (short, long) + for _short, _long in warnings: + details += '- %s: %s\n\n' % (_short, _long) logfile.write(msg + details) logfile.close() dlg = Qt.QMessageBox(Qt.QMessageBox.Information, 'Application project created', msg, Qt.QMessageBox.Ok, self) dlg.setDetailedText(details) dlg.exec_() - print - print msg + details - print + print() + print(msg + details) + print() class AppSettingsWizard(Qt.QWizard): @@ -1487,13 +1497,13 @@ def loadXml(self, fname): root = etree.fromstring(xml) # print self.Pages - for pageNumber in range(len(self.Pages.keys())): + for pageNumber in range(len(self.Pages)): self.page(pageNumber).fromXml(root) def getXml(self): try: return self.__getitem__("xml") - except Exception, e: + except Exception as e: return None def __setitem__(self, name, value): @@ -1505,7 +1515,7 @@ def __getitem__(self, name): if isinstance(p, BasePage): try: return p[name]() - except Exception, e: + except Exception as e: pass return self._item_funcs[name]() @@ -1543,6 +1553,9 @@ def _loadPages(self): try: from sardana.taurus.qt.qtgui.extra_macroexecutor.common import \ TaurusMacroConfigurationDialog + # try to instantiate the dialog (e.g. this fails if using Qt5 with + # versions of sardana which do not support it) + _ = TaurusMacroConfigurationDialog() self.SARDANA_INSTALLED = True except: self.SARDANA_INSTALLED = False @@ -1626,11 +1639,11 @@ def generateXml(self): refsrc = os.path.join(os.path.dirname(src), ref) refdst = self.substitutionName(refsrc, mod_dir) if ref != refdst: - short = 'Manual editing needed in "%s"' % dst - long = ('The synoptic file "%s" references a file that ' + _short = 'Manual editing needed in "%s"' % dst + _long = ('The synoptic file "%s" references a file that ' 'has been copied to the project dir in order to make the project portable. ' 'Please edit "%s" and replace "%s" by "%s"') % (dst, dst, ref, refdst) - self._projectWarnings.append((short, long)) + self._projectWarnings.append((_short, _long)) # macroserver page if self.SARDANA_INSTALLED and self.__getitem__("macroServerName"): diff --git a/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/__init__.py b/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/__init__.py index 1c3cba20c..931ce2d4b 100644 --- a/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/__init__.py +++ b/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/__init__.py @@ -23,4 +23,5 @@ ## ########################################################################### -from config import * +from __future__ import absolute_import +from .config import * diff --git a/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/config.py b/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/config.py index 95b224bc4..6e1d6df20 100644 --- a/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/config.py +++ b/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_example01/config.py @@ -74,48 +74,58 @@ #========================================================================= # Define panels to be shown. -# To define a panel, instantiate a PanelDescription object (see documentation -# for the gblgui_utils module) -#========================================================================= - -nxbrowser = PanelDescription('NeXus Browser', - classname='TaurusNeXusBrowser', - area=None) - -i0 = PanelDescription('BigInstrument', - classname='TaurusAttrForm', - model='sys/tg_test/1') - -i1 = PanelDescription('instrument1', - classname='TaurusForm', - model=['sys/tg_test/1/double_scalar', - 'sys/tg_test/1/short_scalar_ro', - 'sys/tg_test/1/float_spectrum_ro', - 'sys/tg_test/1/double_spectrum']) - -i2 = PanelDescription('instrument2', - classname='TaurusForm', - model=['sys/tg_test/1/wave', - 'sys/tg_test/1/boolean_scalar']) - -trend = PanelDescription('Trend', - classname='TaurusTrend', - model=['sys/tg_test/1/double_scalar']) - -connectionDemo = PanelDescription('Selected Instrument', - classname='PyQt4.Qt.QLineEdit', - sharedDataRead={ - 'SelectedInstrument': 'setText'}, - sharedDataWrite={'SelectedInstrument': 'textEdited'}) +# To define a panel, instantiate a PanelDescription object +#========================================================================= + +nxbrowser = PanelDescription( + 'NeXus Browser', + classname='taurus.qt.qtgui.extra_nexus.TaurusNeXusBrowser' +) + +i0 = PanelDescription( + 'BigInstrument', + classname='taurus.qt.qtgui.panel.TaurusAttrForm', + model='sys/tg_test/1' +) + +i1 = PanelDescription( + 'instrument1', + classname='taurus.qt.qtgui.panel.TaurusForm', + model=['sys/tg_test/1/double_scalar', + 'sys/tg_test/1/short_scalar_ro', + 'sys/tg_test/1/float_spectrum_ro', + 'sys/tg_test/1/double_spectrum'] +) + +i2 = PanelDescription( + 'instrument2', + classname='taurus.qt.qtgui.panel.TaurusForm', + model=['sys/tg_test/1/wave', + 'sys/tg_test/1/boolean_scalar'] +) + +trend = PanelDescription( + 'Trend', + classname='taurus.qt.qtgui.plot.TaurusTrend', + model=['sys/tg_test/1/double_scalar'] +) + +connectionDemo = PanelDescription( + 'Selected Instrument', + classname='taurus.external.qt.Qt.QLineEdit', # A pure Qt widget! + sharedDataRead={'SelectedInstrument': 'setText'}, + sharedDataWrite={'SelectedInstrument': 'textEdited'} +) #========================================================================= # Define custom toolbars to be shown. To define a toolbar, instantiate a # ToolbarDescription object (see documentation for the gblgui_utils module) #========================================================================= -dummytoolbar = ToolBarDescription('Empty Toolbar', - classname='QToolBar', - modulename='PyQt4.Qt') +dummytoolbar = ToolBarDescription( + 'Empty Toolbar', + classname='taurus.external.qt.Qt.QToolBar' +) # panictoolbar = ToolBarDescription('Panic Toolbar', # classname = 'PanicToolbar', @@ -127,9 +137,9 @@ # object (see documentation for the gblgui_utils module) #========================================================================= -mon2 = AppletDescription('Dummy Monitor', - classname='TaurusMonitorTiny', - model='eval:1000*rand(2)') +# mon2 = AppletDescription('Dummy Monitor', +# classname='TaurusMonitorTiny', +# model='eval:1000*rand(2)') #========================================================================= @@ -137,8 +147,8 @@ # To define an external application, instantiate an ExternalApp object # See TaurusMainWindow.addExternalAppLauncher for valid values of ExternalApp #========================================================================= -xterm = ExternalApp(cmdargs=['xterm', 'spock'], - text="Spock", icon='utilities-terminal') +xterm = ExternalApp( + cmdargs=['xterm', 'spock'], text="Spock", icon='utilities-terminal') hdfview = ExternalApp(["hdfview"]) pymca = ExternalApp(['pymca']) @@ -153,22 +163,23 @@ # MACROEDITORS_PATH = #========================================================================= -# Monitor widget (This is *obsolete* now, you can get the same result defining a -# custom applet with classname='TaurusMonitorTiny') +# Monitor widget (This is *obsolete* now, you can get the same result defining +# a custom applet with classname='TaurusMonitorTiny') #========================================================================= # MONITOR = ['sys/tg_test/1/double_scalar_rww'] #========================================================================= # Adding other widgets to the catalog of the "new panel" dialog. # pass a tuple of (classname,screenshot) -# -classname may contain the module name. +# -classname should contain the module name. # -screenshot can either be a resource URL, a file name (either relative to # the application dir or with an absolute path) or None #========================================================================= -EXTRA_CATALOG_WIDGETS = [('PyQt4.Qt.QLineEdit', 'logos:taurus.png'), # a resource - ('PyQt4.Qt.QSpinBox', 'images/syn2.jpg'), # relative - #('PyQt4.Qt.QTextEdit','/tmp/foo.png'), # absolute - ('PyQt4.Qt.QLabel', None)] # none +EXTRA_CATALOG_WIDGETS = [ + ('taurus.external.qt.Qt.QLineEdit', 'logos:taurus.png'), # a resource + ('taurus.external.qt.Qt.QSpinBox', 'images/syn2.jpg'), # relative + # ('taurus.external.Qt.QTextEdit','/tmp/foo.png'), # absolute + ('taurus.external.qt.Qt.QLabel', None)] # none #========================================================================= # Define one or more embedded consoles in the GUI. diff --git a/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_macrogui/__init__.py b/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_macrogui/__init__.py index 1c3cba20c..931ce2d4b 100644 --- a/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_macrogui/__init__.py +++ b/lib/taurus/qt/qtgui/taurusgui/conf/tgconf_macrogui/__init__.py @@ -23,4 +23,5 @@ ## ########################################################################### -from config import * +from __future__ import absolute_import +from .config import * diff --git a/lib/taurus/qt/qtgui/taurusgui/macrolistener.py b/lib/taurus/qt/qtgui/taurusgui/macrolistener.py deleted file mode 100644 index 6a831759b..000000000 --- a/lib/taurus/qt/qtgui/taurusgui/macrolistener.py +++ /dev/null @@ -1,600 +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 objects to manage macro-related tasks. Its primary use is -to be used within a TaurusGui for managing panels for: -- setting preferences in the sardana control system for data I/O -- displaying results of macro executions, including creating/removing panels for - plotting results of scans -- editing macros - -.. note:: This module will be moved to sardana.taurus at some point. -""" - - # TODO: move to sardana.taurus - -__all__ = ['MacroBroker', 'DynamicPlotManager'] -__docformat__ = 'restructuredtext' - -import datetime - -from taurus.core.util.containers import CaselessList -from taurus.external.qt import Qt -from taurus.qt.qtgui.base import TaurusBaseComponent - - -class ChannelFilter(object): - - def __init__(self, chlist): - self.chlist = tuple(chlist) - - def __call__(self, x): - return x in self.chlist - - -class DynamicPlotManager(Qt.QObject, TaurusBaseComponent): - '''This is a manager of plots related to the execution of macros. - It dynamically creates/removes plots according to the configuration made by - an ExperimentConfiguration widget. - - Currently it supports only 1D scan trends (2D scans are only half-baked) - - To use it simply instantiate it and pass it a door name as a model. You may - want to call :meth:`onExpConfChanged` to update the configuration being - used. - ''' - - newShortMessage = Qt.pyqtSignal(str) - - def __init__(self, parent=None): - Qt.QObject.__init__(self, parent) - TaurusBaseComponent.__init__(self, self.__class__.__name__) - - self.__panels = {} - - self._trends1d = {} - self._trends2d = {} - - def setModel(self, doorname): - '''reimplemented from :meth:`TaurusBaseComponent` - - :param doorname: (str) device name corresponding to a Door device. - ''' - TaurusBaseComponent.setModel(self, doorname) - # self._onDoorChanged(doorname) - if not doorname: - return - door = self.getModelObj() - if not isinstance(door, Qt.QObject): - msg = "Unexpected type (%s) for %s" % (repr(type(door)), doorname) - Qt.QMessageBox.critical( - self.parent(), 'Door connection error', msg) - return - - self._checkJsonRecorder() - - # read the expconf - expconf = door.getExperimentConfiguration() - self.onExpConfChanged(expconf) - - def _checkJsonRecorder(self): - '''Checks if JsonRecorder env var is set and offers to set it''' - door = self.getModelObj() - if 'JsonRecorder' not in door.getEnvironment(): - msg = ('JsonRecorder environment variable is not set, but it ' + - 'is needed for displaying trend plots.\n' + - 'Enable it globally for %s?') % door.fullname - result = Qt.QMessageBox.question(self.parent(), - 'JsonRecorder not set', msg, - Qt.QMessageBox.Yes | Qt.QMessageBox.No) - if result == Qt.QMessageBox.Yes: - door.putEnvironment('JsonRecorder', True) - self.info('JsonRecorder Enabled for %s' % door.fullname) - - def onExpConfChanged(self, expconf): - ''' - Slot to be called when experimental configuration changes. It should - remove the temporary panels and create the new ones needed. - - :param expconf: (dict) An Experiment Description dictionary. See - :meth:`sardana.taurus.qt.qtcore.tango.sardana. - 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 - if activeMntGrp not in expconf['MntGrpConfigs']: - self.warning( - "ActiveMntGrp '%s' is not defined" % - activeMntGrp) - return - mgconfig = expconf['MntGrpConfigs'][activeMntGrp] - channels = dict(getChannelConfigs(mgconfig, sort=False)) - - # classify by type of plot: - trends1d = {} - trends2d = {} - plots1d = {} - images = {} - - for chname, chdata in channels.items(): - ptype = chdata['plot_type'] - if ptype == PlotType.No: - continue - elif ptype == PlotType.Spectrum: - axes = tuple(chdata['plot_axes']) - # TODO: get default value from the channel. - ndim = chdata.get('ndim', 0) or 0 - if ndim == 0: # this is a trend - if axes in trends1d: - trends1d[axes].append(chname) - else: - trends1d[axes] = CaselessList([chname]) - elif ndim == 1: # a 1D plot (e.g. a spectrum) - pass # TODO: implement - else: - self.warning('Cannot create plot for %s', chname) - - elif ptype == PlotType.Image: - axes = tuple(chdata['plot_axes']) - # TODO: get default value from the channel. - ndim = chdata.get('ndim', 1) - if ndim == 0: # a mesh-like plot? - pass # TODO implement - elif ndim == 1: # a 2D trend - if axes in trends2d: - trends2d[axes].append(chname) - else: - trends2d[axes] = CaselessList([chname]) - elif ndim == 2: # a 2D plot (e.g. an image) - pass # TODO: implement - else: - self.warning('Cannot create plot for %s', chname) - - new1d, removed1d = self._updateTemporaryTrends1D(trends1d) - self.newShortMessage.emit("Changed panels (%i new, %i removed)" % (len(new1d), - len(removed1d))) -# self._updateTemporaryTrends2D(trends2d) - - def _updateTemporaryTrends1D(self, trends1d): - '''adds necessary trend1D panels and removes no longer needed ones - - :param trends1d: (dict) A dict whose keys are tuples of axes and - whose values are list of model names to plot - - :returns: (tuple) two lists new,rm:new contains the names of the new - panels and rm contains the names of the removed panels - ''' - from taurus.qt.qtgui.plot import TaurusTrend - newpanels = [] - for axes, plotables in trends1d.items(): - if not axes: - continue - if axes not in self._trends1d: - w = TaurusTrend() - w.setXIsTime(False) - w.setScanDoor(self.getModelObj().fullname) - # TODO: use a standard key for and - w.setScansXDataKey(axes[0]) - pname = u'Trend1D - %s' % ":".join(axes) - panel = self.createPanel(w, pname, registerconfig=False, - permanent=False) - try: # if the panel is a dockwidget, raise it - panel.raise_() - except: - pass - self._trends1d[axes] = pname - newpanels.append(pname) - - widget = self.getPanelWidget(self._trends1d[axes]) - flt = ChannelFilter(plotables) - widget.onScanPlotablesFilterChanged(flt) - - # remove trends that are no longer configured - removedpanels = [] - olditems = list(self._trends1d.items()) - for axes, name in olditems: - if axes not in trends1d: - removedpanels.append(name) - self.removePanel(name) - self._trends1d.pop(axes) - - return newpanels, removedpanels - - def _updateTemporaryTrends2D(self, trends2d): - '''adds necessary trend2D panels and removes no longer needed ones - - :param trends2d: (dict) A dict whose keys are tuples of axes and - whose values are list of model names to plot - - :returns: (tuple) two lists new,rm:new contains the names of the new - panels and rm contains the names of the removed panels - - ..note:: Not fully implemented yet - ''' - try: - from taurus.qt.qtgui.extra_guiqwt.taurustrend2d import \ - TaurusTrend2DDialog - from taurus.qt.qtgui.extra_guiqwt.image import TaurusTrend2DScanItem - except: - self.info('guiqwt extension cannot be loaded. ' + - '2D Trends will not be created') - raise - return - - for axes, plotables in trends2d.items(): - for chname in plotables: - pname = u'Trend2D - %s' % chname - if pname in self._trends2d: - self._trends2d[pname].widget().trendItem.clearTrend() - else: - axis = axes[0] - w = TaurusTrend2DDialog(stackMode='event') - plot = w.get_plot() - t2d = TaurusTrend2DScanItem(chname, axis, - self.getModelObj().fullname) - plot.add_item(t2d) - self.createPanel(w, pname, registerconfig=False, - permanent=False) - self._trends2d[(axes, chname)] = pname - - def createPanel(self, widget, name, **kwargs): - '''Creates a "panel" from a widget. In this basic implementation this - means that the widgets is shown as a non-modal top window - - :param widget: (QWidget) widget to be used for the panel - :param name: (str) name of the panel. Must be unique. - - Note: for backawards compatibility, this implementation accepts - arbitrary keyword arguments which are just ignored - ''' - widget.setWindowTitle(name) - widget.show() - self.__panels[name] = widget - - def getPanelWidget(self, name): - '''Returns the widget associated to a panel name - - :param name: (str) name of the panel. KeyError is raised if not found - - :return: (QWidget) - ''' - return self.__panels[name] - - def removePanel(self, name): - '''stop managing the given panel - - :param name: (str) name of the panel''' - widget = self.__panels.pop(name) - if hasattr(widget, 'setModel'): - widget.setModel(None) - widget.setParent(None) - widget.close() - - def removePanels(self, names=None): - '''removes panels. - - :param names: (seq) names of the panels to be removed. If None is - given (default), all the panels are removed. - ''' - if names is None: - names = self._trends1d.values() + self._trends2d.values() - # TODO: do the same for other temporary panels - for pname in names: - self.removePanel(pname) - - -class MacroBroker(DynamicPlotManager): - '''A manager of all macro-related panels of a TaurusGui. - - It creates, destroys and manages connections for the following objects: - - - Macro Configuration dialog - - Experiment Configuration panel - - Macro Executor panel - - Sequencer panel - - Macro description viewer - - Door output, result and debug panels - - Macro editor - - Macro "panic" button (to abort macros) - - Dynamic plots (see :class:`DynamicPlotManager`) - ''' - - def __init__(self, parent): - '''Passing the parent object (the main window) is mandatory''' - DynamicPlotManager.__init__(self, parent) - - self._createPermanentPanels() - - # connect the broker to shared data - Qt.qApp.SDM.connectReader("doorName", self.setModel) - Qt.qApp.SDM.connectReader("expConfChanged", self.onExpConfChanged) - Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage') - - def setModel(self, doorname): - ''' Reimplemented from :class:`DynamicPlotManager`.''' - # disconnect the previous door - door = self.getModelObj() - if door is not None: # disconnect it from *all* shared data providing - SDM = Qt.qApp.SDM - try: - SDM.disconnectWriter("macroStatus", door, "macroStatusUpdated") - except: - self.info("Could not disconnect macroStatusUpdated") - try: - SDM.disconnectWriter("doorOutputChanged", door, "outputUpdated") - except: - self.info("Could not disconnect outputUpdated") - try: - SDM.disconnectWriter("doorInfoChanged", door, "infoUpdated") - except: - self.info("Could not disconnect infoUpdated") - try: - SDM.disconnectWriter("doorWarningChanged", door, - "warningUpdated") - except: - self.info("Could not disconnect warningUpdated") - try: - SDM.disconnectWriter("doorErrorChanged", door, "errorUpdated") - except: - self.info("Could not disconnect errorUpdated") - try: - SDM.disconnectWriter("doorDebugChanged", door, "debugUpdated") - except: - self.info("Could not disconnect debugUpdated") - try: - SDM.disconnectWriter("doorResultChanged", door, "resultUpdated") - except: - self.info("Could not disconnect resultUpdated") - try: - SDM.disconnectWriter("expConfChanged", door, - "experimentConfigurationChanged") - except: - self.info("Could not disconnect experimentConfigurationChanged") - # set the model - DynamicPlotManager.setModel(self, doorname) - - # connect the new door - door = self.getModelObj() - if door is not None: - SDM = Qt.qApp.SDM - SDM.connectWriter("macroStatus", door, "macroStatusUpdated") - SDM.connectWriter("doorOutputChanged", door, "outputUpdated") - SDM.connectWriter("doorInfoChanged", door, "infoUpdated") - SDM.connectWriter("doorWarningChanged", door, "warningUpdated") - SDM.connectWriter("doorErrorChanged", door, "errorUpdated") - SDM.connectWriter("doorDebugChanged", door, "debugUpdated") - SDM.connectWriter("doorResultChanged", door, "resultUpdated") - SDM.connectWriter("expConfChanged", door, - "experimentConfigurationChanged") - - def _createPermanentPanels(self): - '''creates panels on the main window''' - from sardana.taurus.qt.qtgui.extra_macroexecutor import \ - TaurusMacroExecutorWidget, TaurusSequencerWidget, \ - TaurusMacroConfigurationDialog, TaurusMacroDescriptionViewer, \ - DoorOutput, DoorDebug, DoorResult - - from sardana.taurus.qt.qtgui.extra_sardana import \ - ExpDescriptionEditor, SardanaEditor - - mainwindow = self.parent() - - # Create macroconfiguration dialog & action - self.__macroConfigurationDialog = \ - TaurusMacroConfigurationDialog(mainwindow) - self.macroConfigurationAction = mainwindow.taurusMenu.addAction( - Qt.QIcon.fromTheme("preferences-system-session"), - "Macro execution configuration...", - self.__macroConfigurationDialog.show) - - SDM = Qt.qApp.SDM - SDM.connectReader("macroserverName", - self.__macroConfigurationDialog.selectMacroServer) - SDM.connectReader("doorName", - self.__macroConfigurationDialog.selectDoor) - SDM.connectWriter("macroserverName", self.__macroConfigurationDialog, - 'macroserverNameChanged') - SDM.connectWriter("doorName", self.__macroConfigurationDialog, - 'doorNameChanged') - - # Create ExpDescriptionEditor dialog - self.__expDescriptionEditor = ExpDescriptionEditor(plotsButton=False) - SDM.connectReader("doorName", self.__expDescriptionEditor.setModel) - mainwindow.createPanel(self.__expDescriptionEditor, - 'Experiment Config', - registerconfig=True, - icon=Qt.QIcon.fromTheme('preferences-system'), - permanent=True) - ############################### - # TODO: These lines can be removed once the door does emit - # "experimentConfigurationChanged" signals - SDM.connectWriter("expConfChanged", self.__expDescriptionEditor, - "experimentConfigurationChanged") - ################################ - - # put a Macro Executor - self.__macroExecutor = TaurusMacroExecutorWidget() - SDM.connectReader("macroserverName", self.__macroExecutor.setModel) - SDM.connectReader("doorName", self.__macroExecutor.onDoorChanged) - SDM.connectReader("macroStatus", - self.__macroExecutor.onMacroStatusUpdated) - SDM.connectWriter("macroName", self.__macroExecutor, - "macroNameChanged") - SDM.connectWriter("executionStarted", self.__macroExecutor, - "macroStarted") - SDM.connectWriter("plotablesFilter", self.__macroExecutor, - "plotablesFilterChanged") - SDM.connectWriter("shortMessage", self.__macroExecutor, - "shortMessageEmitted") - mainwindow.createPanel(self.__macroExecutor, 'Macros', - registerconfig=True, permanent=True) - - # put a Sequencer - self.__sequencer = TaurusSequencerWidget() - SDM.connectReader("macroserverName", self.__sequencer.setModel) - SDM.connectReader("doorName", self.__sequencer.onDoorChanged) - SDM.connectReader("macroStatus", self.__sequencer.onMacroStatusUpdated) - SDM.connectWriter("macroName", self.__sequencer.tree, - "macroNameChanged") - SDM.connectWriter("macroName", self.__sequencer, - "macroNameChanged") - SDM.connectWriter("executionStarted", self.__sequencer, - "macroStarted") - SDM.connectWriter("plotablesFilter", self.__sequencer, - "plotablesFilterChanged") - SDM.connectWriter("shortMessage", self.__sequencer, - "shortMessageEmitted") - mainwindow.createPanel(self.__sequencer, 'Sequences', - registerconfig=True, permanent=True) - - # puts a macrodescriptionviewer - self.__macroDescriptionViewer = TaurusMacroDescriptionViewer() - SDM.connectReader("macroserverName", - self.__macroDescriptionViewer.setModel) - SDM.connectReader("macroName", - self.__macroDescriptionViewer.onMacroNameChanged) - mainwindow.createPanel(self.__macroDescriptionViewer, - 'MacroDescription', registerconfig=True, - permanent=True) - - # puts a doorOutput - self.__doorOutput = DoorOutput() - SDM.connectReader("doorOutputChanged", - self.__doorOutput.onDoorOutputChanged) - SDM.connectReader("doorInfoChanged", - self.__doorOutput.onDoorInfoChanged) - SDM.connectReader("doorWarningChanged", - self.__doorOutput.onDoorWarningChanged) - SDM.connectReader("doorErrorChanged", - self.__doorOutput.onDoorErrorChanged) - mainwindow.createPanel(self.__doorOutput, 'DoorOutput', - registerconfig=False, permanent=True) - - # puts doorDebug - self.__doorDebug = DoorDebug() - SDM.connectReader("doorDebugChanged", - self.__doorDebug.onDoorDebugChanged) - mainwindow.createPanel(self.__doorDebug, 'DoorDebug', - registerconfig=False, permanent=True) - - # puts doorResult - self.__doorResult = DoorResult(mainwindow) - SDM.connectReader("doorResultChanged", - self.__doorResult.onDoorResultChanged) - mainwindow.createPanel(self.__doorResult, 'DoorResult', - registerconfig=False, permanent=True) - - # puts sardanaEditor - # self.__sardanaEditor = SardanaEditor() - # SDM.connectReader("macroserverName", self.__sardanaEditor.setModel) - # mainwindow.createPanel(self.__sardanaEditor, 'SardanaEditor', - # registerconfig=False, permanent=True) - - # add panic button for aborting the door - text = "Panic Button: stops the pool (double-click for abort)" - self.doorAbortAction = mainwindow.jorgsBar.addAction( - Qt.QIcon("actions:process-stop.svg"), - text, self.__onDoorAbort) - - # store beginning of times as a datetime - self.__lastAbortTime = datetime.datetime(1, 1, 1) - - # store doubleclick interval as a timedelta - td = datetime.timedelta(0, 0, 1000 * Qt.qApp.doubleClickInterval()) - self.__doubleclickInterval = td - - def __onDoorAbort(self): - '''slot to be called when the abort action is triggered. - It sends stop command to the pools (or abort if the action - has been triggered twice in less than self.__doubleclickInterval - - .. note:: An abort command is always preceded by an stop command - ''' - # decide whether to send stop or abort - now = datetime.datetime.now() - if now - self.__lastAbortTime < self.__doubleclickInterval: - cmd = 'abort' - else: - cmd = 'stop' - - door = self.getModelObj() - - # abort the door - door.command_inout('abort') - # send stop/abort to all pools - pools = door.macro_server.getElementsOfType('Pool') - for pool in pools.values(): - self.info('Sending %s command to %s' % (cmd, pool.getFullName())) - try: - pool.getObj().command_inout(cmd) - except: - self.info('%s command failed on %s', cmd, pool.getFullName(), - exc_info=1) - self.newShortMessage.emit("%s command sent to all pools" % - cmd) - self.__lastAbortTime = now - - def createPanel(self, widget, name, **kwargs): - ''' Reimplemented from :class:`DynamicPlotManager` to delegate panel - management to the parent widget (a TaurusGui)''' - mainwindow = self.parent() - return mainwindow.createPanel(widget, name, **kwargs) - - def getPanelWidget(self, name): - ''' Reimplemented from :class:`DynamicPlotManager` to delegate panel - management to the parent widget (a TaurusGui)''' - mainwindow = self.parent() - return mainwindow.getPanel(name).widget() - - def removePanel(self, name): - ''' Reimplemented from :class:`DynamicPlotManager` to delegate panel - management to the parent widget (a TaurusGui)''' - mainwindow = self.parent() - mainwindow.removePanel(name) - - def removeTemporaryPanels(self, names=None): - '''Remove temporary panels managed by this widget''' - # for now, the only temporary panels are the plots - DynamicPlotManager.removePanels(self, names=names) - - -if __name__ == "__main__": - import sys - from taurus.qt.qtgui.application import TaurusApplication - - app = TaurusApplication() - - b = DynamicPlotManager(None) - - b.setModel('door/cp1/1') - - print '...' - sys.exit(app.exec_()) diff --git a/lib/taurus/qt/qtgui/taurusgui/paneldescriptionwizard.py b/lib/taurus/qt/qtgui/taurusgui/paneldescriptionwizard.py index 9167d0e8a..a4fa08cae 100644 --- a/lib/taurus/qt/qtgui/taurusgui/paneldescriptionwizard.py +++ b/lib/taurus/qt/qtgui/taurusgui/paneldescriptionwizard.py @@ -23,11 +23,12 @@ ## ########################################################################### -__all__ = ["PanelDescriptionWizard"] """ paneldescriptionwizard.py: """ +from __future__ import print_function + from taurus.external.qt import Qt import sys import weakref @@ -44,6 +45,9 @@ import copy +__all__ = ["PanelDescriptionWizard"] + + class ExpertWidgetChooserDlg(Qt.QDialog): CHOOSE_TYPE_TXT = '(choose type)' @@ -99,7 +103,7 @@ def onModuleSelected(self): # We use this because __import__('x.y') returns x instead of y !! self.module = sys.modules[modulename] self.moduleNameLE.setStyleSheet('QLineEdit {color: green}') - except Exception, e: + except Exception as e: Logger().debug(repr(e)) self.moduleNameLE.setStyleSheet('QLineEdit {color: red}') return @@ -129,7 +133,7 @@ def getMemberDescription(self): membername = str(self.membersCB.currentText()) member = getattr(self.module, membername, None) result = {'modulename': self.module.__name__} - except Exception, e: + except Exception as e: Logger().debug('Cannot get member description: %s', repr(e)) return None if inspect.isclass(member): @@ -221,7 +225,7 @@ def __init__(self, parent=None, designMode=False, extraWidgets=None): Qt.QWizardPage.__init__(self, parent) TaurusBaseWidget.__init__(self, 'WidgetPage') if extraWidgets: - customWidgets, customWidgetScreenshots = zip(*extraWidgets) + customWidgets, customWidgetScreenshots = list(zip(*extraWidgets)) pixmaps = {} for k, s in extraWidgets: if s is None: @@ -304,10 +308,10 @@ def validatePage(self): raise ValueError # set the name now because it might have changed since the # PanelDescription was created - paneldesc.name = Qt.from_qvariant(self.field('panelname'), str) + paneldesc.name = self.field('panelname') # allow the wizard to proceed return True - except Exception, e: + except Exception as e: Qt.QMessageBox.warning( self, 'Invalid panel', 'The requested panel cannot be created. \nReason:\n%s' % repr(e)) return False @@ -406,7 +410,7 @@ def __init__(self, parent=None): def initializePage(self): try: widget = self.wizard().getPanelDescription().getWidget() - except Exception, e: + except Exception as e: Logger().debug(repr(e)) widget = None # prevent the user from changing the model if it was already set @@ -417,7 +421,7 @@ def initializePage(self): try: if isinstance(Qt.qApp.SDM, SharedDataManager): sdm = Qt.qApp.SDM - except Exception, e: + except Exception as e: Logger().debug(repr(e)) sdm = None #@todo set selection filter in modelChooser based on the widget's modelclass @@ -461,7 +465,7 @@ def validatePage(self): class CommTableModel(Qt.QAbstractTableModel): NUMCOLS = 3 - UID, R, W = range(NUMCOLS) + UID, R, W = list(range(NUMCOLS)) dataChanged = Qt.pyqtSignal(int, int) @@ -481,25 +485,25 @@ def columnCount(self, index=Qt.QModelIndex()): def headerData(self, section, orientation, role=Qt.Qt.DisplayRole): if role == Qt.Qt.TextAlignmentRole: if orientation == Qt.Qt.Horizontal: - return Qt.QVariant(int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter)) - return Qt.QVariant(int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter)) + return int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter) + return int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter) if role != Qt.Qt.DisplayRole: - return Qt.QVariant() + return None # So this is DisplayRole... if orientation == Qt.Qt.Horizontal: if section == self.UID: - return Qt.QVariant("Data UID") + return "Data UID" elif section == self.R: - return Qt.QVariant("Reader (slot)") + return "Reader (slot)" elif section == self.W: - return Qt.QVariant("Writer (signal)") - return Qt.QVariant() + return "Writer (signal)" + return None else: - return Qt.QVariant(Qt.QString('%i' % (section + 1))) + return str('%i' % (section + 1)) def data(self, index, role=Qt.Qt.DisplayRole): if not index.isValid() or not (0 <= index.row() < self.rowCount()): - return Qt.QVariant() + return None row = index.row() column = index.column() # Display Role @@ -510,8 +514,8 @@ def data(self, index, role=Qt.Qt.DisplayRole): text = '(enter UID)' else: text = '(not registered)' - return Qt.QVariant(Qt.QString(text)) - return Qt.QVariant() + return str(text) + return None def flags(self, index): return (Qt.Qt.ItemIsEnabled | Qt.Qt.ItemIsEditable | Qt.Qt.ItemIsDragEnabled | Qt.Qt.ItemIsDropEnabled | Qt.Qt.ItemIsSelectable) @@ -520,7 +524,6 @@ def setData(self, index, value=None, role=Qt.Qt.EditRole): if index.isValid() and (0 <= index.row() < self.rowCount()): row = index.row() column = index.column() - value = Qt.from_qvariant(value, str) self.__table[row][column] = value self.dataChanged.emit(index, index) return True @@ -541,10 +544,11 @@ def insertRows(self, position=None, rows=1, parentindex=None): def removeRows(self, position, rows=1, parentindex=None): if parentindex is None: parentindex = Qt.QModelIndex() + self.beginResetModel() self.beginRemoveRows(parentindex, position, position + rows - 1) self.__table = self.__table[:position] + self.__table[position + rows:] self.endRemoveRows() - self.reset() + self.endResetModel() return True @staticmethod @@ -554,7 +558,7 @@ def rowModel(uid='', slot='', signal=''): class CommItemDelegate(Qt.QStyledItemDelegate): NUMCOLS = 3 - UID, R, W = range(NUMCOLS) + UID, R, W = list(range(NUMCOLS)) def __init__(self, parent=None, widget=None, sdm=None): super(CommItemDelegate, self).__init__(parent) @@ -585,7 +589,7 @@ def setEditorData(self, editor, index): editor.setEditText('') def setModelData(self, editor, model, index): - model.setData(index, Qt.QVariant(editor.currentText())) + model.setData(index, editor.currentText()) class PanelDescriptionWizard(Qt.QWizard, TaurusBaseWidget): @@ -652,7 +656,7 @@ def test(): form = PanelDescriptionWizard() def kk(d): - print d + print(d) Qt.qApp.SDM = SharedDataManager(form) Qt.qApp.SDM.connectReader('111111', kk) Qt.qApp.SDM.connectWriter('222222', form, 'thisisasignalname') @@ -664,7 +668,7 @@ def kk(d): def test2(): from taurus.qt.qtgui.application import TaurusApplication app = TaurusApplication(sys.argv) - print ExpertWidgetChooserDlg.getDialog() + print(ExpertWidgetChooserDlg.getDialog()) sys.exit() @@ -675,7 +679,7 @@ def main(): form = Qt.QMainWindow() def kk(d): - print d + print(d) Qt.qApp.SDM = SharedDataManager(form) Qt.qApp.SDM.connectReader('someUID', kk) Qt.qApp.SDM.connectWriter('anotherUID', form, 'thisisasignalname') @@ -688,12 +692,11 @@ def kk(d): w = paneldesc.getWidget(sdm=Qt.qApp.SDM) form.setCentralWidget(w) form.setWindowTitle(paneldesc.name) - print Qt.qApp.SDM.info() + print(Qt.qApp.SDM.info()) sys.exit(app.exec_()) if __name__ == "__main__": - import sys # test2() main() diff --git a/lib/taurus/qt/qtgui/taurusgui/taurusgui.py b/lib/taurus/qt/qtgui/taurusgui/taurusgui.py index e5b4a22cb..7228bc607 100644 --- a/lib/taurus/qt/qtgui/taurusgui/taurusgui.py +++ b/lib/taurus/qt/qtgui/taurusgui/taurusgui.py @@ -25,22 +25,19 @@ """This package provides the TaurusGui class""" -__all__ = ["DockWidgetPanel", "TaurusGui"] - -__docformat__ = 'restructuredtext' - - +from builtins import str import os import sys import copy import weakref import inspect +from future.utils import string_types from lxml import etree import taurus from taurus import tauruscustomsettings -from taurus.external.qt import Qt +from taurus.external.qt import Qt, compat from taurus.qt.qtcore.configuration import BaseConfigurableClass from taurus.qt.qtcore.communication import SharedDataManager from taurus.qt.qtgui.util import TaurusWidgetFactory @@ -55,6 +52,11 @@ from taurus.qt.qtgui.taurusgui.utils import ExternalAppAction +__all__ = ["DockWidgetPanel", "TaurusGui"] + +__docformat__ = 'restructuredtext' + + @UILoadable(with_ui='ui') class AssociationDialog(Qt.QDialog): '''A dialog for viewing and editing the associations between instruments @@ -91,7 +93,7 @@ def refresh(self): self.onInstrumentChanged(self.ui.instrumentCB.currentText()) def onInstrumentChanged(self, instrumentname): - instrumentname = unicode(instrumentname) + instrumentname = str(instrumentname) panelname = self.associations.get(instrumentname) if panelname is None: self.ui.panelCB.setCurrentIndex(0) @@ -104,10 +106,10 @@ def onDialogButtonClicked(self, button): role = self.ui.buttonBox.buttonRole(button) if role in (Qt.QDialogButtonBox.AcceptRole, Qt.QDialogButtonBox.ApplyRole): if self.ui.panelCB.currentIndex() > 0: - panelname = unicode(self.ui.panelCB.currentText()) + panelname = str(self.ui.panelCB.currentText()) else: panelname = None - instrumentname = unicode(self.ui.instrumentCB.currentText()) + instrumentname = str(self.ui.instrumentCB.currentText()) self.associations[instrumentname] = panelname self.parent().setInstrumentAssociation(instrumentname, panelname) @@ -128,7 +130,7 @@ def __init__(self, parent, widget, name, mainwindow): self.setWidget(widget) # self._widget = self.widget() #keep a pointer that may change if the # widget changes - name = unicode(name) + name = str(name) self.setWindowTitle(name) self.setObjectName(name) self._custom = False @@ -162,7 +164,7 @@ def setWidgetFromClassName(self, classname, modulename=None): module = __import__(modulename, fromlist=['']) klass = getattr(module, classname) w = klass() - except Exception, e: + except Exception as e: raise RuntimeError( 'Cannot create widget from classname "%s". Reason: %s' % (classname, repr(e))) # set customwidgetmap if necessary @@ -191,7 +193,7 @@ def applyConfig(self, configdict, depth=-1): 'widgetClassName'), modulename=configdict.get('widgetModuleName', None)) if isinstance(self.widget(), BaseConfigurableClass): self.widget().applyConfig(configdict['widget']) - except Exception, e: + except Exception as e: self.info( 'Failed to set the widget for this panel. Reason: %s' % repr(e)) self.traceback(self.Debug) @@ -272,10 +274,10 @@ class TaurusGui(TaurusMainWindow): ''' - SelectedInstrument = Qt.pyqtSignal(str) - doorNameChanged = Qt.pyqtSignal(str) - macroserverNameChanged = Qt.pyqtSignal(str) - newShortMessage = Qt.pyqtSignal(str) + SelectedInstrument = Qt.pyqtSignal('QString') + doorNameChanged = Qt.pyqtSignal('QString') + macroserverNameChanged = Qt.pyqtSignal('QString') + newShortMessage = Qt.pyqtSignal('QString') IMPLICIT_ASSOCIATION = '__[IMPLICIT]__' @@ -500,7 +502,7 @@ def removeExternalApp(self, name=None): removed If None given, the user will be prompted ''' - apps = self.__external_app.keys() + self.__permanent_ext_apps + apps = list(self.__external_app.keys()) + self.__permanent_ext_apps if name is None: items = sorted(apps) msg1 = "Remove External application" @@ -510,13 +512,13 @@ def removeExternalApp(self, name=None): False) if not ok: return - name = unicode(name) + name = str(name) if name not in apps: msg = ('Cannot remove the external application "%s"' ' (not found)' % name) self.debug(msg) return - if name in self.__external_app.keys(): + if name in list(self.__external_app.keys()): self.__external_app.pop(name) else: self.__permanent_ext_apps.remove(name) @@ -570,12 +572,12 @@ def removePanel(self, name=None): ''' if name is None: items = sorted( - [n for n, p in self.__panels.iteritems() if p.isCustom()]) + [n for n, p in self.__panels.items() if p.isCustom()]) name, ok = Qt.QInputDialog.getItem(self, "Remove Panel", "Panel to be removed (only custom panels can be removed).\n Important: you may want to save the perspective afterwards,\n and maybe remove the panel from other perspectives as well", items, 0, False) if not ok: return - name = unicode(name) + name = str(name) if name not in self.__panels: self.debug('Cannot remove panel "%s" (not found)' % name) return @@ -626,7 +628,7 @@ def createPanel(self, widget, name, floating=False, registerconfig=True, custom= 'Deprecation warning: please note that the "area" argument is deprecated. See TaurusGui.createPanel doc') floating = not(floating) - name = unicode(name) + name = str(name) if name in self.__panels: self.info('Panel with name "%s" already exists. Reusing.' % name) return self.__panels[name] @@ -636,7 +638,7 @@ def createPanel(self, widget, name, floating=False, registerconfig=True, custom= # we will only place panels in this area self.addDockWidget(Qt.Qt.TopDockWidgetArea, panel) if len(self.__panels) != 0: - self.tabifyDockWidget(self.__panels.values()[-1], panel) + self.tabifyDockWidget(list(self.__panels.values())[-1], panel) panel.setFloating(floating) @@ -674,14 +676,14 @@ def getPanel(self, name): :return: (DockWidgetPanel) ''' - return self.__panels[unicode(name)] + return self.__panels[str(name)] def getPanelNames(self): '''returns the names of existing panels :return: (list) ''' - return copy.deepcopy(self.__panels.keys()) + return copy.deepcopy(list(self.__panels.keys())) def _setPermanentExternalApps(self, permExternalApps): '''creates empty panels for restoring custom panels. @@ -717,7 +719,7 @@ def _getPermanentCustomPanels(self): :return: (list) ''' - return [n for n, p in self.__panels.iteritems() if (p.isCustom() and p.isPermanent())] + return [n for n, p in self.__panels.items() if (p.isCustom() and p.isPermanent())] def updatePermanentCustomPanels(self, showAlways=True): ''' @@ -729,7 +731,7 @@ def updatePermanentCustomPanels(self, showAlways=True): # check if there are some newly created panels that may be made # permanent perm = self._getPermanentCustomPanels() - temp = [n for n, p in self.__panels.iteritems() if ( + temp = [n for n, p in self.__panels.items() if ( p.isCustom() and not p.isPermanent())] if len(temp) > 0 or showAlways: dlg = QDoubleListDlg(winTitle='Stored panels', @@ -765,7 +767,7 @@ def updatePermanentExternalApplications(self, showAlways=True): mainLabel=msg, label1='Temporary (to be discarded)', label2='Permanent (to be stored)', - list1=self.__external_app.keys(), + list1=list(self.__external_app.keys()), list2=self.__permanent_ext_apps) result = dlg.exec_() if result == Qt.QDialog.Accepted: @@ -820,7 +822,7 @@ def createMainSynoptic(self, synopticname): synoptic = TaurusJDrawSynopticsView() synoptic.setModel(jdwFileName) self.__synoptics.append(synoptic) - except Exception, e: + except Exception as e: # print repr(e) msg = 'Error loading synoptic file "%s".\nSynoptic won\'t be available' % jdwFileName self.error(msg) @@ -879,8 +881,8 @@ def createInstrumentsFromPool(self, macroservername): ms = taurus.Device(macroservername) instruments = ms.getElementsOfType('Instrument') if instruments is None: - raise - except Exception, e: + raise Exception() + except Exception as e: msg = 'Could not fetch Instrument list from "%s"' % macroservername self.error(msg) result = Qt.QMessageBox.critical(self, 'Initialization error', '%s\n\n%s' % ( @@ -990,7 +992,7 @@ def loadConfiguration(self, confname): else: # if confname is not a dir name, we assume it is a module name in the python path conf = self._importConfiguration(confname) self._confDirectory = os.path.dirname(conf.__file__) - except Exception, e: + except Exception: import traceback msg = 'Error loading configuration: %s' % traceback.format_exc() # repr(e) self.error(msg) @@ -1016,7 +1018,7 @@ def loadConfiguration(self, confname): xmlstring = xmlFile.read() xmlFile.close() xmlroot = etree.fromstring(xmlstring) - except Exception, e: + except Exception as e: msg = 'Error reading the XML file: "%s"' % xmlfname self.error(msg) self.traceback(level=taurus.Info) @@ -1100,7 +1102,7 @@ def loadConfiguration(self, confname): xmlroot, "MACRO_PANELS", True)) # macro infrastructure will only be created if MACROSERVER_NAME is set if MACRO_PANELS and MACROSERVER_NAME is not None: - from taurus.qt.qtgui.taurusgui import MacroBroker + from sardana.taurus.qt.qtgui.macrolistener import MacroBroker self.__macroBroker = MacroBroker(self) if MACROSERVER_NAME: self.macroserverNameChanged.emit(MACROSERVER_NAME) @@ -1119,7 +1121,7 @@ def loadConfiguration(self, confname): # Synoptics SYNOPTIC = getattr(conf, 'SYNOPTIC', None) - if isinstance(SYNOPTIC, basestring): # old config file style + if isinstance(SYNOPTIC, string_types): # old config file style self.warning( 'Deprecated usage of SYNOPTIC keyword (now it expects a list of paths). Please update your configuration file to: "SYNOPTIC=[\'%s\']".' % SYNOPTIC) SYNOPTIC = [SYNOPTIC] @@ -1191,7 +1193,7 @@ def loadConfiguration(self, confname): # create a panel self.createPanel(w, p.name, floating=p.floating, registerconfig=registerconfig, instrumentkey=instrumentkey, permanent=True) - except Exception, e: + except Exception as e: msg = 'Cannot create panel %s' % getattr( p, 'name', '__Unknown__') self.error(msg) @@ -1233,7 +1235,7 @@ def loadConfiguration(self, confname): if isinstance(w, BaseConfigurableClass): self.registerConfigDelegate(w, d.name) - except Exception, e: + except Exception as e: msg = 'Cannot add toolbar %s' % getattr( d, 'name', '__Unknown__') self.error(msg) @@ -1279,7 +1281,7 @@ def loadConfiguration(self, confname): # register the toolbar as delegate if it supports it if isinstance(w, BaseConfigurableClass): self.registerConfigDelegate(w, d.name) - except Exception, e: + except Exception as e: msg = 'Cannot add applet %s' % getattr( d, 'name', '__Unknown__') self.error(msg) @@ -1355,12 +1357,12 @@ def onShortMessage(self, msg): def hideAllPanels(self): '''hides all current panels''' - for panel in self.__panels.itervalues(): + for panel in self.__panels.values(): panel.hide() def showAllPanels(self): '''shows all current panels''' - for panel in self.__panels.itervalues(): + for panel in self.__panels.values(): panel.show() def onShowAssociationDialog(self): @@ -1391,7 +1393,7 @@ def setInstrumentAssociation(self, instrumentname, panelname): panel or None to remove the association for this instrument. ''' - instrumentname = unicode(instrumentname) + instrumentname = str(instrumentname) # remove a previous association if it exists oldpanelname = self.__instrumentToPanelMap.get(instrumentname, None) self.__panelToInstrumentMap.pop(oldpanelname, None) @@ -1426,12 +1428,12 @@ def setAllInstrumentAssociations(self, associationsdict, clearExisting=False): else: self.__instrumentToPanelMap.update(copy.deepcopy(associationsdict)) self.__panelToInstrumentMap = {} - for k, v in self.__instrumentToPanelMap.iteritems(): + for k, v in self.__instrumentToPanelMap.items(): self.__panelToInstrumentMap[v] = k def _onPanelVisibilityChanged(self, visible): if visible: - panelname = unicode(self.sender().objectName()) + panelname = str(self.sender().objectName()) instrumentname = self.__panelToInstrumentMap.get(panelname) if instrumentname is not None: self.SelectedInstrument.emit(instrumentname) @@ -1442,7 +1444,7 @@ def onSelectedInstrument(self, instrumentname): :param instrumentname: (str) The name that identifies the instrument. ''' - instrumentname = unicode(instrumentname) + instrumentname = str(instrumentname) panelname = self.getInstrumentAssociation(instrumentname) self.setFocusToPanel(panelname) @@ -1452,7 +1454,7 @@ def setFocusToPanel(self, panelname): :param panelname: (str) The name that identifies the panel. This name must be unique within the panels in the GUI. ''' - panelname = unicode(panelname) + panelname = str(panelname) try: panel = self.__panels[panelname] panel.show() @@ -1515,7 +1517,7 @@ def onExportCurrentPanelConfiguration(self, fname=None): f = open(self._xmlConfigFileName, 'r') xmlroot = etree.fromstring(f.read()) f.close() - except Exception, e: + except Exception as e: self.error('Cannot parse file "%s": %s', self._xmlConfigFileName, str(e)) return @@ -1530,7 +1532,7 @@ def onExportCurrentPanelConfiguration(self, fname=None): dlg = QDoubleListDlg(winTitle='Export Panels to XML', mainLabel='Select which of the custom panels you want to export as xml configuration', label1='Not Exported', label2='Exported', - list1=[n for n, p in self.__panels.iteritems() if p.isCustom()], list2=[]) + list1=[n for n, p in self.__panels.items() if p.isCustom()], list2=[]) result = dlg.exec_() if result != Qt.QDialog.Accepted: return @@ -1550,8 +1552,10 @@ def onExportCurrentPanelConfiguration(self, fname=None): # write to file while True: if fname is None: - fname = Qt.QFileDialog.getSaveFileName( - self, "Open File", fname or self._confDirectory, self.tr("XML files (*.xml)")) + fname, _ = compat.getSaveFileName( + self, "Open File", self._confDirectory, + self.tr("XML files (*.xml)") + ) if not fname: return fname = str(fname) @@ -1570,7 +1574,7 @@ def onExportCurrentPanelConfiguration(self, fname=None): f.write(xml) f.close() break - except Exception, e: + except Exception as e: msg = 'Cannot write to %s: %s' % (fname, str(e)) self.error(msg) Qt.QMessageBox.warning( diff --git a/lib/taurus/qt/qtgui/taurusgui/utils.py b/lib/taurus/qt/qtgui/taurusgui/utils.py index 73f95a34a..6812fd315 100644 --- a/lib/taurus/qt/qtgui/taurusgui/utils.py +++ b/lib/taurus/qt/qtgui/taurusgui/utils.py @@ -26,19 +26,21 @@ """This configuration contains base modules and classes that may be used by specific TaurusGui-based GUIs""" -__docformat__ = 'restructuredtext' +from builtins import object -import os -import sys from lxml import etree +from future.utils import string_types from taurus.qt.qtgui.util import ExternalAppAction from taurus.qt.qtgui.util import TaurusWidgetFactory from taurus.core.util.log import Logger + +__docformat__ = 'restructuredtext' + # this is here only for backwards compatibility. It should not be used at all -class Qt_Qt: +class Qt_Qt(object): LeftDockWidgetArea = 1 RightDockWidgetArea = 2 BottomDockWidgetArea = 3 @@ -223,9 +225,9 @@ def getWidget(self, sdm=None, setModel=True): w.setModel(self.model) # connect (if an sdm is given) if sdm is not None: - for dataUID, signalname in self.sharedDataWrite.iteritems(): + for dataUID, signalname in self.sharedDataWrite.items(): sdm.connectWriter(dataUID, w, signalname) - for dataUID, slotname in self.sharedDataRead.iteritems(): + for dataUID, slotname in self.sharedDataRead.items(): sdm.connectReader(dataUID, getattr(w, slotname)) # set the name w.name = self.name @@ -250,12 +252,12 @@ def toXml(self): floating.text = str(self._floating) sharedDataWrite = etree.SubElement(root, "sharedDataWrite") - for k, v in self._sharedDataWrite.iteritems(): + for k, v in self._sharedDataWrite.items(): item = etree.SubElement( sharedDataWrite, "item", datauid=k, signalName=v) sharedDataRead = etree.SubElement(root, "sharedDataRead") - for k, v in self._sharedDataRead.iteritems(): + for k, v in self._sharedDataRead.items(): item = etree.SubElement( sharedDataRead, "item", datauid=k, slotName=v) @@ -381,13 +383,13 @@ def fromPanel(panel): sharedDataWrite = None sharedDataRead = None model = getattr(panel.widget(), 'model', None) - if model is None or isinstance(model, basestring): + if model is None or isinstance(model, string_types): pass elif hasattr(model, '__iter__'): # if model is a sequence, convert to space-separated string try: model = " ".join(model) - except Exception, e: + except Exception as e: msg = ('Cannot convert %s to a space-separated string: %s' % (model, e)) Logger().debug(msg) diff --git a/lib/taurus/qt/qtgui/test/base.py b/lib/taurus/qt/qtgui/test/base.py index 576577546..678e27628 100644 --- a/lib/taurus/qt/qtgui/test/base.py +++ b/lib/taurus/qt/qtgui/test/base.py @@ -25,6 +25,8 @@ """Utilities for creating generic tests for Taurus widgets""" +from builtins import range +from builtins import object import time import taurus.core import unittest @@ -84,7 +86,7 @@ def assertMaxDeprecations(self, maximum, msg=None): self.assertTrue(deps <= maximum, msg) def processEvents(self, repetitions=1, sleep=0): - for i in xrange(repetitions): + for i in range(repetitions): time.sleep(sleep) self._app.processEvents() diff --git a/lib/taurus/qt/qtgui/tree/__init__.py b/lib/taurus/qt/qtgui/tree/__init__.py index 01bbccc33..7bb9a4af9 100644 --- a/lib/taurus/qt/qtgui/tree/__init__.py +++ b/lib/taurus/qt/qtgui/tree/__init__.py @@ -30,6 +30,3 @@ from .qtree import * from .taurustree import * from .taurusdbtree import * - -# taurusdevicetree should be removed from taurus or merged with taurusdbtree -# from .taurusdevicetree import * diff --git a/lib/taurus/qt/qtgui/tree/qtree.py b/lib/taurus/qt/qtgui/tree/qtree.py index 919e44995..3c0ecdc2c 100644 --- a/lib/taurus/qt/qtgui/tree/qtree.py +++ b/lib/taurus/qt/qtgui/tree/qtree.py @@ -236,7 +236,11 @@ def createViewWidget(self, klass=None): tree.clicked.connect(self._onClicked) tree.doubleClicked.connect(self._onDoubleClicked) h = tree.header() - h.setSectionResizeMode(0, Qt.QHeaderView.Stretch) + if h.length() > 0: + try: + h.setSectionResizeMode(0, h.Stretch) + except AttributeError: + h.setResizeMode(0, h.Stretch) return tree def treeView(self): diff --git a/lib/taurus/qt/qtgui/tree/taurusdbtree.py b/lib/taurus/qt/qtgui/tree/taurusdbtree.py index 4d26d8284..945b52f52 100644 --- a/lib/taurus/qt/qtgui/tree/taurusdbtree.py +++ b/lib/taurus/qt/qtgui/tree/taurusdbtree.py @@ -27,9 +27,7 @@ # todo: tango-centric!! -__all__ = ["TaurusDbTreeWidget"] - -__docformat__ = 'restructuredtext' +from __future__ import absolute_import from taurus.external.qt import Qt from taurus.core.taurusbasetypes import TaurusElementType @@ -37,7 +35,12 @@ from taurus.qt.qtcore.model import * from taurus.qt.qtgui.base import TaurusBaseWidget from taurus.qt.qtgui.icon import getElementTypeIcon, getElementTypeIconName -from taurustree import TaurusBaseTreeWidget +from .taurustree import TaurusBaseTreeWidget + + +__all__ = ["TaurusDbTreeWidget"] + +__docformat__ = 'restructuredtext' class TaurusDbTreeWidget(TaurusBaseTreeWidget): diff --git a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py b/lib/taurus/qt/qtgui/tree/taurusdevicetree.py deleted file mode 100644 index 9c57f0e92..000000000 --- a/lib/taurus/qt/qtgui/tree/taurusdevicetree.py +++ /dev/null @@ -1,1737 +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 . -## -############################################################################# - -""" -taurusdevicetree.py: -""" - -# @todo: This module is not being used anywhere in Taurus and depends on -# non-standard and non-provided modules. It is also quite specific and -# tango-centric, so it should be removed from Taurus or merged with -# Taurusdbtree - -# ,"SearchEdit"] #"TaurusTreeNode"] -__all__ = ["TaurusDevTree", "TaurusSearchTree", "TaurusDevTreeOptions"] - -import time -import os -import re -import traceback -from functools import partial - -# @todo: icons_dev_tree is not an included or standard module. -# Is anybody using it? If not, the following lines should be removed and -# the TaurusDevTree.setStateIcon method should be cleaned -try: - import icons_dev_tree -except: - icons_dev_tree = None - -from taurus.external.qt import Qt - -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.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 (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 -from taurus.qt.qtgui.container import TaurusWidget - -TREE_ITEM_MIME_TYPE = 'application/x-qabstractitemmodeldatalist' - -############################################################################### - - -class TaurusTreeNodeContainer(object): - """ - Interface that provides Node-focused methods to TaurusDevTree - This methods should be moved to TaurusDevTreeNode class when it will be able to retrieve currentItem from Tree. - """ - _icon_map = {} # A dictionary like {device_regexp:pixmap_url} - - addAttrSelected = Qt.pyqtSignal('QStringList') - removeAttrSelected = Qt.pyqtSignal('QStringList') - - def __init__(self): - raise Exception( - 'This class is just an interface, do not instantiate it!') - - @classmethod - def setIconMap(klass, filters): - """A dictionary like {device_regexp:pixmap_url}""" - klass._icon_map = filters - - @classmethod - def getIconMap(klass): - return klass._icon_map - - def createItem(self, parent, value, text=None): - self.debug('createItem(%s,%s)' % (value, text)) - USE_TREE_NODE = False - if USE_TREE_NODE: - item = TaurusTreeNode(parent) - else: - item = Qt.QTreeWidgetItem(parent) - if text is None: - text = value - item.isAttribute = False - item.DeviceName = '' - item.draggable = '' - item.setText(0, Qt.QApplication.translate( - '', text, None, Qt.QApplication.UnicodeUTF8)) - self.setNodeParent(item, parent) - item.adminNode = None - if not item.parentNode or '/' in text: - f = item.font(0) - if not item.parentNode: - f.setBold(True) - if '/' in text: - f.setItalic(True) - item.setFont(0, f) - item.parentTree = self # hook used to call external methods with item as single argument - self.item_index[value.strip().split()[0]] = item - try: - icon = self.getNodeIcon(item) - if icon: - item.setIcon(0, icon) - except: - pass - self.item_list.add(item) - return item - - ########################################################################### - # Item members methods - - def setNodeParent(self, node, parent): - """ Used to know which parent attributes must be expanded if found """ - node.parentNode = parent if isinstance( - parent, Qt.QTreeWidgetItem) else None - - def setNodeAdmin(self, node, admin): - """ Used to assign a controller to its controlled devices in the tree """ - node.adminNode = admin.getNodeText(admin) if isinstance( - admin, Qt.QTreeWidgetItem) else None - - def getNodeAdmin(self, node): - return node.adminNode(node) if isCallable(node.adminNode) else node.adminNode - - def getNodeText(self, node=None, full=False): - """ Get the text of the node as shown in the tree, @full allows to get the first word or the whole text""" - if node is None: - node = self.currentItem() - if hasattr(node, 'text'): - txt = str(node.text(0)).strip() - if not full: - return txt.split()[0] - return txt - else: - return '' - - def getNodeDeviceName(self, node=None): - if node is None: - node = self.currentItem() - return str(getattr(node, 'DeviceName', '')) or self.getNodeText(node) - - def getNodeParentName(self, node=None): - if node is None: - node = self.currentItem() - return self.getNodeText(node.parentNode) - - def getNodePath(self, node=None): - """ Returns all parent nodes prior to current """ - if node is None: - node = self.currentItem() - p, path, names = node.parentNode, [], [] - while p is not None: - path.insert(0, p) - names.insert(0, self.getNodeDeviceName(p)) - p = p.parentNode - return path - - def getNodeAlias(self, node=None): - if node is None: - node = self.currentItem() - alias = getattr(node, 'AttributeAlias', '') - return (alias or self.getNodeText(node)) - - def getNodeIcon(self, node=None): - #self.debug('TaurusDevTree.getNodeIcon(node) not implemented, overrided in subclasses') - - #self,url = node.parentTree,'' - if node is None: - node = self.getNode() - try: - name, url = self.getNodeText(node), '' - for k, v in self.getIconMap().items(): - if re.match(k.lower(), name.lower()): - url = v - if not url: - for k, v in self.getIconMap().items(): - if k.lower() in name.lower(): - url = v - # if name.count('/')==2: - # if any(a.startswith(name+'/') for a in getArchivedAttributes()): - #url = wdir('image/icons/clock.png') - # else: - #url = wdir('image/equips/icon-%s.gif'%name.split('/')[2].split('-')[0].lower()) - # elif name.count('/')==3: - #url = filterAttributes(name) or wdir('image/icons/closetab.png') - # else: - #url = wdir('image/equips/icon-%s.gif'%name.lower()) - except: - self.warning(traceback.format_exc()) - if not url or not os.path.isfile(url): - return None - else: - return Qt.QIcon(url) - - 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: - node = self.currentItem() - try: - name = self.getNodeText(node).lower() - drag = name - if node.isAttribute and getattr(node, 'DeviceName', '') and '/' not in name: - name = node.DeviceName + '/' + name - if name.count('/') == 2: # A Device Name - drag = name # +'/state' #False - elif name.count('/') == 3: # An Attribute Name - #dtype = PyTango.AttributeProxy(name).get_config().data_type - #if dtype in numtypes: self.debug('The attribute %s is a Numeric Attribute'%(name)) - drag = getattr(node, 'draggable', '') or name - # else: drag = False - self.debug('Node(%s,%s,%s): drag: %s' % - (name, node.isAttribute, node.DeviceName, drag)) - return drag.split()[0] - except: - import traceback - self.warning(traceback.format_exc()) - return False - - ########################################################################### - # Context Menu Actions - - @staticmethod - def setDefaultPanelClass(other): - TaurusTreeNodeContainer._defaultClass = other - - @staticmethod - def defaultPanelClass(): - if not hasattr(TaurusTreeNodeContainer, '_defaultClass'): - from taurus.qt.qtgui.panel import TaurusDevicePanel - TaurusTreeNodeContainer._defaultClass = TaurusDevicePanel - obj = TaurusTreeNodeContainer._defaultClass - return obj - - def showPanel(self): - '''Display widget taurusDevicePanel''' - device = self.getNodeText() - nameclass = self.defaultPanelClass()() - nameclass.setModel(device) - nameclass.show() - # nameclass.setSpectraAtkMode(True) - # Dialog is used to make new floating panels persistent - if isinstance(nameclass, TaurusWidget): - PopupDialog(self, nameclass) - - def showProperties(self): - '''Display widget TaurusPropTable''' - import taurus.qt.qtgui.table - device = self.getNodeText() - nameclass = taurus.qt.qtgui.table.TaurusPropTable() - nameclass.setModel(device) - nameclass.show() - # Dialog is used to make new floating panels persistent - PopupDialog(self, nameclass) - - def addToPlot(self): - """ This method will send a signal with the current selected node """ - items = self.getSelectedNodes() - for item in items: - attr = self.getNodeAlias(item) - self.trace('In addToPlot(%s->%s)' % (item.text(0), attr)) - self.addAttrToPlot(attr) - return - - def addAttrToPlot(self, attr): - """ This method will send a signal with the given attr name, in a separate method to be called with a pre-filled list """ - self.addAttrSelected.emit(Qt.QStringList([str(attr)])) - - def removeFromPlot(self): - """ This method will send a signal with the current selected node """ - items = self.getSelectedNodes() - for item in items: - item = self.currentItem() - attr = getattr(item, 'AttributeAlias', - '') or self.getNodeText(item) - self.removeAttrFromPlot(attr) - return - - def removeAttrFromPlot(self, attr): - """ This method will send a signal with the given attr name, in a separate method to be called with a pre-filled list """ - self.removeAttrSelected.emit(Qt.QStringList([str(attr)])) - - -############################################################################### - -class TaurusDevTree(TaurusTreeNodeContainer, Qt.QTreeWidget, TaurusBaseWidget): - ''' - This widget displays a list of servers, devices or instances. - To set a new Model use either setModel(filters), addModels(list), setFilters(...) or loadTree(filters) - setModel and loadTree are equivalent; adding a new branch to the tree - addModels merges the tree with new models - setFilters clears previous models and adds new one - ''' - - # TODO: tango-centric - __properties__ = ( - 'ModelInConfig', - 'modifiableByUser', - #'useParentModel', - 'Filters', - 'Source', - 'ShowAlias', - 'ShowColors', - 'ShowNotExported', - 'MaxDevices', - ) - __slots__ = ( - "setTangoHost", - #"setModel", - #"setFilters", - "addModels", - "setModelCheck", - "loadTree", # Applies regexp filters to database - "setTree", - "findInTree", - "setIcons", - "expandAll", - "expandNode", - "collapseNode", - ) - - TRACE_ALL = False - - refreshTree = Qt.pyqtSignal() - nodeFound = Qt.pyqtSignal() - deviceSelected = Qt.pyqtSignal('QString') - addAttrSelected = Qt.pyqtSignal('QStringList') - removeAttrSelected = Qt.pyqtSignal('QStringList') - - def __init__(self, parent=None, designMode=False): - name = "TaurusDevTree" - self._useParentModel = True - self._localModel = '' - self.call__init__wo_kw(Qt.QTreeWidget, parent) - self.call__init__(TaurusBaseWidget, name, designMode=designMode) - - self.setObjectName(name) - self._filters = [] - self.__attr_filter = None - self.__expand = 1 - self.collapsing_search = True - self.index = 0 - self._nameTopItem = "" - - # This is a list of regular expressions to exclude objects from - # searches - self.excludeFromSearch = [] - self.dictionary = {} - self.item_index = CaselessDict() - # NOTE: as several nodes may share the same name this list will be - # different from item_index.values()!!! - self.item_list = set() - self.setSelectionMode(self.ExtendedSelection) - - self.ContextMenu = [] - self.ExpertMenu = [] - - # The SingletonWorker Threads are used for expanding nodes and also for - # loading a new tree; both objects are the same thread, but read from - # different queues - self.__loader = None - self.__expander = None - - if not designMode: - self.Loader - self.Expander - - self.initConfig() - - # Signal - self.itemClicked.connect(self.deviceClicked) - self.nodeFound.connect(self.expandNode) - self.setDragDropMode(Qt.QAbstractItemView.DragDrop) - self.setModifiableByUser(True) - self.setModelInConfig(False) # We store Filters instead! - self.setAcceptDrops(True) - self.viewport().setAcceptDrops(True) - self.setDragEnabled(True) - self.setSupportedMimeTypes([ - TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE, TAURUS_ATTR_MIME_TYPE, - TAURUS_MODEL_MIME_TYPE, TREE_ITEM_MIME_TYPE, 'text/plain']) - - self.setTangoHost() - self.defineStyle() - - @property - def Loader(self): - loader = self.__loader - if loader is None: - loader = SingletonWorker(parent=self, name='TreeLoader', - cursor=True, start=True) - self.__loader = loader - return loader - - @property - def Expander(self): - expander = self.__expander - if expander is None: - expander = SingletonWorker(parent=self, name='NodeExpander', - method=lambda node, expand: node.setExpanded( - expand), - cursor=True, start=True) - self.__expander = expander - return expander - - def getConfig(self, name): - properties.get_property(self, name) - - def initConfig(self): - """ - Initializing the attributes that will be kept persitent as Qt settings. - e.g. for Filters property, the following attributes are created: - - - self.filters - - self._filters - - self.setFilters - - self.getFilters - - self.resetFilters - - """ - properties.set_property_methods(self, 'Models', 'QStringList', default='', - #setter = self.setFilters, - setter=self.addModels, # Not trivial!; it avoids QSettings erasing default model - #set_callback=lambda v,s=self:v and s.loadTree(v,clear=True), - #reset_callback=lambda s=self:s.setFilters(''), - qt=False, config=True - ) - properties.set_property_methods( - self, 'MaxDevices', 'int', default=150, qt=False, config=True) - properties.set_property_methods( - self, 'ShowAlias', 'bool', default=False, qt=False, config=True) - properties.set_property_methods( - self, 'ShowNotExported', 'bool', default=True, qt=False, config=True) - properties.set_property_methods( - self, 'ShowColors', 'bool', default=True, qt=False, config=True) - # properties.set_property_methods(self,'Expand','int',default=0) - - @staticmethod - def setDefaultAttrFilter(other): - TaurusDevTree._defattrfilter = staticmethod(other) - - @staticmethod - def defaultAttrFilter(): - if not hasattr(TaurusDevTree, '_defattrfilter'): - TaurusDevTree._defattrfilter = None - return TaurusDevTree._defattrfilter - - def setAttrFilter(self, other): - self._attrfilter = other - - def getAttrFilter(self): - if not isCallable(getattr(self, '_attrfilter', None)): - self._attrfilter = None - return self._attrfilter - - def matchAttrFilter(self, target): - def printf(s): - print(s) - if self.getAttrFilter() and isCallable(self._attrfilter): - return self._attrfilter(target, p=printf) - elif TaurusDevTree.defaultAttrFilter() and isCallable(TaurusDevTree._defattrfilter): - return TaurusDevTree._defattrfilter(target, p=printf) - else: - return True - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # TaurusBaseWidget over writing methods - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def sizeHint(self): - return Qt.QTreeWidget.sizeHint(self) - - def minimumSizeHint(self): - return Qt.QTreeWidget.minimumSizeHint(self) - - @classmethod - def getQtDesignerPluginInfo(cls): - ret = TaurusBaseWidget.getQtDesignerPluginInfo() - ret['module'] = 'taurus.qt.qtgui.tree' - ret['group'] = 'Taurus Views' - ret['icon'] = "designer:listview.png" - return ret - - def defineStyle(self): - self.setWindowTitle('TaurusDevTree') - self.setHeaderLabel( - 'Device Browser (right-click on any element to search/show options)') - self.setGeometry(Qt.QRect(90, 60, 256, 192)) - self.actionFindInTree = Qt.QAction(self) - self.actionFindInTree.setShortcut(Qt.QKeySequence.Find) - self.actionFindInTree.triggered[()].connect(self.findDialog) - #self.connect(self, Qt.SIGNAL("itemClicked"), self.clickedEvent) - from taurus.qt.qtgui.table.qdictionary import QDictionaryEditor, QListEditor - self.ExpertMenu.append( - ('Edit Model Filters', - lambda: QListEditor.main( - self._filters, - modal=True, - title='Edit Model Filters', - callback=lambda d: self.loadTree(d) - ) - # lambda:self.loadTree( - #str(Qt.QInputDialog.getText(None,'Set Tree Model','Enter a list of regexp separated by comma:',Qt.QLineEdit.Normal,','.join(str(f) for f in self._filters))[0]) - # or None) - )) - self.ExpertMenu.append( - ('Edit Tree', - lambda: QDictionaryEditor.main( - self.dictionary, modal=True, title='Edit Tree', callback=lambda d: self.setTree(d, clear=True)) - )) - self.ExpertMenu.append( - ('Expand All', - lambda: self.expandAll() - )) - self.ExpertMenu.append( - ('Collapse All', - lambda: self.collapseNode(ALL=True) - )) - self.ExpertMenu.append( - ('Save Config', - lambda: self.saveConfigFile() - )) - if not getattr(self, 'DeviceMenu', None): - self.DeviceMenu = {} - self.DeviceMenu.update({ - 'Show Properties': 'showProperties', - 'Refresh Tree': 'refreshTree', - }) - if not getattr(self, 'AttributeMenu', None): - self.AttributeMenu = [] - [self.AttributeMenu.append(a) for a in [ - ('Add to trends', 'addToPlot'), - ('Remove from trends', 'removeFromPlot'), - ] if a not in self.AttributeMenu] - try: - from PyTangoArchiving.widget.history import show_history - self.debug('Adding show_history from archiving...') - self.AttributeMenu.append(('Show History', show_history)) - except: - pass - - def trace(self, msg): - if self.TRACE_ALL or self.getLogLevel() in ('DEBUG', 40,): - # @TODO: use the taurus logger instead! ~~cpascual 20121121 - print 'TaurusDevTree.%s: %s' % (self.getLogLevel(), msg) - - def setTangoHost(self, tango_host=None): - self.db = taurus.Authority(tango_host) - - # model = Qt.pyqtProperty("QString", TaurusBaseWidget.getModel, - # TaurusBaseWidget.setModel, - # TaurusBaseWidget.resetModel) - - def getModel(self): - return self._filters - - def getModelClass(self): - return list # taurus.core.taurusauthority.TaurusAuthority - - def setModel(self, model): - TaurusBaseWidget.setModel(self, model) - - def setModelCheck(self, model): - # Called from TaurusBaseWidget.setModel() - self.trace('setModelCheck(%s)' % str(model)[:80]) - self.loadTree(model) - - @Qt.pyqtSlot('QStringList') - def addModels(self, modelNames): - '''Adds models to the existing ones: - :param modelNames: (sequence) the names of the models to be added - .. seealso:: :meth:`removeModels` - ''' - self.trace('In addModels(%s)' % str(modelNames)[:80]) - modelNames = split_model_list(modelNames) - self.setTree(self.getTangoDict(modelNames), clear=False) - if isSequence(modelNames): - self._filters = sorted( - set(split_model_list(self._filters) + modelNames)) - elif isMap(modelNames): - if isMap(self._filters): - self._filters.update(modelNames) - else: - self._filters = modelNames - - ########################################################################## - # Loading/Cleaning the tree - - # def loadTree(self,filters,clear=False): - #''' - # This method show a list of instances and devices depending on the given servers in QTProperty or in another widget, - # this method can be used to connect TauDevTree with another widget such as LineEdit. - #''' - #self.trace('In loadTree(%s)'%str(filters)) - #if clear: self.setWindowTitle('TaurusDevTree:%s'%str(filters)) - # self.setTree(self.getTangoDict(filters),clear=clear,alias=False) - - def loadTree(self, filters): - try: - if isString(filters): - try: - assert '{' in filters - filters = dict(filters) - except: - filters = split_model_list(filters) - self.trace('loadTree(%s)' % (filters)) - assert isMap(filters) or isSequence( - filters), "Filters have to be map, string or list type!" - # self._filters = filters - properties.set_property(self, 'Filters', filters) - if isSequence(filters): - self.setWindowTitle('TaurusDevTree:%s' % str(filters)) - dct = self.getTangoDict(filters) - else: # if isMap(filters): - self.setWindowTitle('TaurusDevTree:%s' % - ','.join(filters.keys())) - - def expand_dict(d): - return [x for v in d.values() for x in (expand_dict(v) if hasattr(v, 'values') else (v,))] - targets = [t.upper() for t in get_matching_devices( - ['*%s*' % f if '*' not in f else f for f in expand_dict(filters)])] - - def get_devs(f): - return dict.fromkeys(t for t in targets if matchCl(f, t)) - - def expand_filter(f): - return dict((k, expand_filter(v) if hasattr(v, 'values') else get_devs(v)) for k, v in f.items() if v) - dct = expand_filter(filters) - # self.Loader.next([self.setTree,dct,True]) - self.setTree(dct, clear=True) - except: - self.warning('TaurusDeviceTree.loadTree(%s):\n%s' % - (filters, traceback.format_exc())) - - def setTree(self, diction, clear=False): - """ - Initializes the tree from a dictionary {'Node0.0':{'Node1.0':None,'Node1.1':None}} - """ - K = len(str(dict(diction))) - self.trace('In setTree(%d) ...' % K) - self.debug(diction) - if diction is None: - return - if clear or self.dictionary: - if not clear: - # Merging new and old models - diction = djoin(diction, self.dictionary) - self.clear() - self.dictionary = diction - if len(diction): - self.setNodeTree(self, diction, alias=( - self.getShowAlias() or K < self.getMaxDevices() * 20)) - # Auto-Expand caused problems when loading filters from QSettings - if 0 < len(self.item_list) < self.getMaxDevices(): - self.expandAll(queue=False) - - def setNodeTree(self, parent, diction, alias=False): - """ - It has parent as argument to allow itself to be recursive - Initializes the node tree from a dictionary {'Node0.0':{'Node1.0':None,'Node1.1':None}} - """ - self.debug('In setNodeTree(%d,alias=%s) ...' % (len(diction), alias)) - if not hasattr(diction, 'keys'): - diction = dict.fromkeys(diction) - for node in sorted(diction.keys()): - assert int(self.index) < 10000000000, 'TooManyIterations!' - self.index = self.index + 1 - dev_alias = alias and str(node).count( - '/') == 2 and get_alias_for_device(node) - text = '%s (%s)' % (node, dev_alias) if dev_alias else node - if diction[node] and any(diction[node]): - item = self.createItem(parent, node, text) - self.setNodeTree(item, diction[node], alias) - else: - item = self.createItem(parent, node, text) - - def clear(self): - while not self.Expander.getQueue().empty(): - self.Expander.getQueue().get() - self.item_index.clear() - while self.item_list: - self.item_list.pop() - Qt.QTreeWidget.clear(self) - - def refreshTree(self): - self.loadTree(self._filters) - self.refreshTree.emit() - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # @name Methods for building server/devices/attributes tree - # @{ - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def getTangoDict(self, filters): - self.trace('In TaurusDevTree.getTangoDict(%s(%s))' % - (type(filters), str(filters)[:80])) - if filters is None: - return - result = {} - filters = split_model_list(filters) - targets = get_matching_devices(filters) - targets = [t.upper() for t in targets] - domains = set(t.split('/')[0] for t in targets) - for d in domains: - families = set(t.split('/')[1] - for t in targets if t.startswith('%s/' % d)) - result[d] = dict((f, dict.fromkeys( - t for t in targets if t.startswith('%s/%s/' % (d, f)))) for f in families) - return result - - def addAttrToDev(self, my_device, expert=False, allow_types=None): - """ This command returns the list of attributes of a given device applying display level and type filters. - @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 - dct = {} - self.trace('In addAttrToDev(%s)' % my_device) - try: - proxy = PyTango.DeviceProxy(my_device) - timeout = proxy.get_timeout_millis() - proxy.set_timeout_millis(50) - proxy.ping() - list_attr = proxy.attribute_list_query() - proxy.set_timeout_millis(timeout) - - for aname, my_attr in sorted([(a.name, a) for a in list_attr]): - if allow_types and my_attr.data_type not in allow_types: - continue - if not expert and my_attr.disp_level == PyTango.DispLevel.EXPERT: - continue - label = aname == my_attr.label and aname.lower( - ) or "%s (%s)" % (aname.lower(), my_attr.label) - dct[str(my_device).lower() + '/' + label] = 0 - except PyTango.DevFailed, e: - self.warning('addAttrToDev(%s): %s' % (my_device, str(e))) - qmsg = Qt.QMessageBox(Qt.QMessageBox.Critical, '%s Error' % - my_device, '%s not available' % my_device, Qt.QMessageBox.Ok, self) - qmsg.show() - except Exception, e: - self.warning('addAttrToDev(%s): %s' % (my_device, str(e))) - qmsg = Qt.QMessageBox(Qt.QMessageBox.Critical, '%s Error' % - my_device, str(e), Qt.QMessageBox.Ok, self) - qmsg.show() - return dct - - def addAttrToNode(self, node=None, full=False): - node = node or self.currentItem() - dev = self.getNodeDeviceName(node) - self.trace('In addAttrToNode(%s)' % dev) - attrs = self.addAttrToDev(dev) - children = [str(node.child(i).text(0)).lower() - for i in range(node.childCount())] - for aname in sorted(attrs): - tag = aname.rsplit('/')[-1] - if tag.lower() in children: - continue - elif not full and not self.matchAttrFilter(aname): - continue - else: - natt = self.createItem(node, value=aname, text=tag) - natt.draggable = aname.split()[0].strip() - natt.isAttribute = True - natt.DeviceName = dev - icon = self.getNodeIcon(natt) - if icon: - natt.setIcon(0, icon) - # it gets all aliases for this device attributes - alias = getattr(node, 'AttributeAlias', {}) - if alias: - self.trace('Got aliases for %s: %s' % (aname, alias)) - [setattr(natt, 'AttributeAlias', v) - for k, v in alias.items() if k in aname.lower()] - else: - natt.AttributeAlias = aname.split()[0].strip() - node.setExpanded(True) - return - - ########################################################################### - # Node getters - - def getNode(self, target=None): - """ Gets currrent node or node by name or by regexp """ - if target is None: - return self.currentItem() - else: - nodes = self.getMatchingNodes(target, 1) - if not nodes: - return None - else: - return nodes[0] - return - - def getNodeByName(self, key): - return self.item_index[key] - - def getNodeList(self): - return self.item_index.keys() - - def getMatchingNodes(self, regexp, limit=0, all=False, exclude=None): - """ It returns all nodes matching the given expression. """ - result, regexp = [], str(regexp).lower() - exclude = exclude or [] - self.trace('In TauDevTree.getMatchingNodes(%s,%s,%s,%s)' % - (regexp, limit, all, exclude)) - if not all: - node = self.item_index.get(regexp, None) - if node is not None: - return [node] - regexp = re.compile(extend_regexp(regexp)) - for k, node in self.item_index.iteritems(): - nname = self.getNodeText(node, full=True).lower() - if (regexp.match(k) or regexp.match(nname)) and \ - (not exclude or not any(re.match(x.lower(), y) for x in exclude for y in (k.lower(), nname))): - result.append(node) - if not all and len(result) == 1: - break - if limit and len(result) >= limit: - break - return result - - def getSelectedNodes(self): - return self.selectedItems() - - def getAllNodes(self): - """ Returns a list with all node objects. """ - def get_child_nodes(dct, node, fun=None): - if fun: - fun(node) - dct.update([(str(node.text(0)), node)]) - for j in range(node.childCount()): - get_child_nodes(dct, node.child(j)) - return dct - dct = {} - for i in range(self.topLevelItemCount()): - get_child_nodes(dct, self.topLevelItem(i)) - return dct - - def unpackChildren(self): - """ removes all nodes from the tree and returns them in a list, used for resorting """ - allChildren = [] - nodes = self.getAllNodes().values() - - for node in nodes: - allChildren.extend(node.takeChildren()) - while self.topLevelItemCount(): - allChildren.append(self.takeTopLevelItem(0)) - return allChildren - - # @} - ########################################################################## - - ########################################################################### - # Expand/Collapse/Search nodes - - def collapseNode(self, ALL=False, filters='', fun=None): - """ Collapses the whole tree or from a given node. - @argin ALL tells whether to collapse from current item or the whole tree - @argin filters Allows to set a list of nodes to not be filtered - - """ - filters = str(filters).lower() - found = '' - self.debug('In TaurusTree.collapseAll(%s)' % filters) - todelete = [] - - def expand_child_nodes(node): - result = int(bool(filters)) - if fun: - fun(node) - if not node: - return '' - for j in range(node.childCount()): - child = node.child(j) - result = expand_child_nodes(child) - if filters and re.search(filters, str(child.text(0)).lower()): - self.debug('In TaurusTree.collapseAll(%s): %s matches!' % ( - filters, str(child.text(0)).lower())) - result = True - elif not result: - child.setExpanded(False) - # When collapsing all attribute lists are cleaned up - aname = '/'.join(['[0-9a-zA-Z\-\_]+'] * 4) - if re.match(aname, str(child.text(0))): - todelete.append((node, child)) - if not result: - node.setExpanded(False) - return result - if ALL: - for i in range(self.topLevelItemCount()): - found = expand_child_nodes(self.topLevelItem(i)) or found - else: - found = expand_child_nodes(self.currentItem()) or found - for node, child in todelete: # Pruning attribute nodes - node.removeChild(child) - del child - return found - - ########################################################################### - # New expand/search methods - - def expandNode(self, node=None, expand=True): - """ Needed to do threaded expansion of the tree """ - if node is None: - node = self.getNode() - if isinstance(node, (basestring, Qt.QString)): - name, node = str(node), self.getNode(node) - else: - name = self.getNodeText(node) - node.setExpanded(expand) - return expand - - def expandAll(self, queue=True): - self.findInTree('*', select=False, queue=queue) - - def findDialog(self): - self.findInTree(str(Qt.QInputDialog.getText( - self, 'Search ...', 'Write a part of the name', Qt.QLineEdit.Normal)[0])) - - @Qt.pyqtSlot('QString') - def findInTree(self, regexp, collapseAll=None, exclude=None, select=True, queue=True): - self.trace('In TauTree.findInTree(%s)' % regexp) - if collapseAll is None: - collapseAll = self.collapsing_search - regexp = str(regexp).lower().strip() - exclude = (lambda x: x if hasattr(x, '__iter__') else [x])( - exclude or self.excludeFromSearch or []) - if not regexp: - return - try: - t0 = time.time() - nodes = self.getMatchingNodes(regexp, all=True, exclude=exclude) - if len(nodes) > 150: - v = Qt.QMessageBox.warning(None, 'Device Tree Search', - 'Your search matches too many devices (%d) and may slow down the application.\nDo you want to continue?' % len( - nodes), - Qt.QMessageBox.Ok | Qt.QMessageBox.Cancel) - if v == Qt.QMessageBox.Cancel: - self.debug('Search cancelled by user.') - return - if nodes: - # It's good to have first node matched to be selected fast - if select: - nodes[0].setSelected(True) - self.setCurrentItem(nodes[0]) - # Searches must not trigger events! - self.emitSelected(self.getNodeDeviceName(nodes[0])) - self.debug('The selected node is %s' % - self.getNodeText(nodes[0])) - # Then proceed to expand/close the rest of nodes - parents = set( - parent for node in nodes for parent in self.getNodePath(node) if parent) - for item in self.item_list: - matched, expanded = item in parents, item.isExpanded() - if (matched and not expanded): - if queue: - self.Expander.getQueue().put((item, True)) - else: - item.setExpanded(True) - elif (not matched and expanded and self.collapsing_search): - if queue: - self.Expander.getQueue().put((item, False)) - else: - item.setExpanded(False) - if select: - self.scrollTo(self.indexFromItem( - nodes[0]), Qt.QAbstractItemView.PositionAtTop) # Center) - self.debug('\tfindInTree(%s): %d nodes found in %f s' % - (regexp, len(nodes), time.time() - t0)) - else: - if collapseAll: - if queue: - [self.Expander.getQueue().put((item, False)) - for item in self.item_list if item.isExpanded()] - else: - [item.setExpanded(False) - for item in self.item_list if item.isExpanded()] - self.debug('findInTree(%s): Node not found' % (regexp)) - if queue: - self.Expander.next() - except: - self.warning('findInTree(%s): failed' % (regexp)) - self.error(traceback.format_exc()) - - def sortCustom(self, order): - assert order and len(order), 'sortCustom(order) must not be empty' - allChildren = {} - while self.topLevelItemCount(): - it = self.takeTopLevelItem(0) - allChildren[str(it.text(0))] = it - - sorter = lambda k, ks=[re.compile(c) for c in order]: str( - (i for i, r in enumerate(ks) if r.match(k.lower())).next()) + str(k) - for c, it in sorted(allChildren.items(), key=lambda k: sorter(k[0])): - self.debug('tree.sortCustom(%s): %s inserted at %d' % - (order, it.text(0), self.topLevelItemCount())) - self.insertTopLevelItem(self.topLevelItemCount(), it) - return - - ########################################################################### - # Update node colors - - def setIcons(self, dct={}, root_name=None, regexps=True): - ''' - This method change the icons depending of the status of the devices - Dict is a dictionary with name of device and colors such as {name_device:color,name_device2:color2} - An alternative may be an icon name! - ''' - #secs = time.time() - #ID = int(100*random.random()) - state2color = lambda state: Qt.QColor( - DEVICE_STATE_PALETTE.number(state)) - #quality2color = lambda attr: Qt.QColor(ATTRIBUTE_QUALITY_PALETTE.number(quality)) - - def update_node(node, key, dct): - if hasattr(node, 'CustomForeground'): - node.setForeground(0, Qt.QBrush( - Qt.QColor(node.CustomForeground))) - if hasattr(node, 'CustomBackground'): - node.setBackground(0, Qt.QBrush( - Qt.QColor(node.CustomBackground))) - elif hasattr(node, 'StateBackground'): - node.setBackground(0, Qt.QBrush(state2color(dct[key]))) - if hasattr(node, 'CustomIcon'): - node.setIcon(0, Qt.QIcon(node.CustomIcon)) - else: - #key = str(node.text(0)).split(' ')[0] - if key.count('/') == 2: - self.setStateIcon(node, dct and dct[key] or '') - return - - if not isinstance(dct, dict): - dct = dict.fromkeys(dct, '') - nodes = self.getAllNodes() - for name, node in nodes.iteritems(): - name = str(name).split()[0] - if node.isHidden(): - continue - if regexps: - matches = [v for k, v in dct.items() if re.match( - k.lower(), name.lower())] - if matches: - update_node(node, name, {name: matches[0]}) - elif name in dct or not dct: - update_node(node, name, dct or {name: ''}) - return - - def setStateIcon(self, child, color): - if icons_dev_tree is None: - self.debug('In setStateIcon(...): Icons for states not available!') - return - if color == "#00ff00" or color in 'ON,OPEN,EXTRACT': - icon = Qt.QIcon(":/ICON_GREEN") - child.setIcon(0, icon) - elif color == "#ff0000" or color in 'OFF,FAULT': - icon = Qt.QIcon(":/ICON_RED") - child.setIcon(0, icon) - elif color == "#ff8c00" or color in 'ALARM': - icon = Qt.QIcon(":/ICON_ORANGE") - child.setIcon(0, icon) - elif color == "#ffffff" or color in 'CLOSE,INSERT': - icon = Qt.QIcon(":/ICON_WHITE") - child.setIcon(0, icon) - elif color == "#80a0ff" or color in 'MOVING,RUNNING': - icon = Qt.QIcon(":/ICON_BLUE") - child.setIcon(0, icon) - elif color == "#ffff00" or color in 'STANDBY': - icon = Qt.QIcon(":/ICON_YELLOW") - child.setIcon(0, icon) - elif color == "#cccc7a" or color in 'INIT': - icon = Qt.QIcon(":/ICON_BRAWN") - child.setIcon(0, icon) - elif color == "#ff00ff" or color in 'DISABLE': - icon = Qt.QIcon(":/ICON_PINK") - child.setIcon(0, icon) - elif color == "#808080f" or color in 'UNKNOWN': - icon = Qt.QIcon(":/ICON_GREY") - child.setIcon(0, icon) - else: - icon = Qt.QIcon(":/ICON_WHITE") - child.setIcon(0, icon) - - ########################################################################## - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # Event methods - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def deviceClicked(self, item, column): - self.trace("In TaurusDevTree.deviceClicked(%s)" % item.text(column)) - self.emitSelected(self.getNodeDeviceName()) - - def emitSelected(self, device_name=''): - '''QSIGNAL: this method is used to emit deviceSelected(QString) signal''' - self.trace("In TaurusDevTree.emitSelected(%s)" % device_name) - try: - #item = self.currentItem() - device_name = device_name or self.getNodeDeviceName() # item.text(0) - if str(device_name).count('/') != 2: - return - # Signal - self.trace('TaurusTree emit deviceSelected(%s) signal ...' % - device_name) - self.deviceSelected.emit(Qt.QString(device_name)) - except: - self.error(traceback.format_exc()) - pass - - def getModelMimeData(self): - '''Returns a MimeData object containing the model data. The default implementation - fills the `TAURUS_MODEL_MIME_TYPE`. If the widget's Model class is - Attribute or Device, it also fills `TAURUS_ATTR_MIME_TYPE` or - `TAURUS_DEV_MIME_TYPE`, respectively - ''' - mimeData = Qt.QMimeData() - node = self.currentItem() - draggable = self.getNodeDraggable(node) - if draggable: - slashes = draggable.count('/') - draggable.count(':') - # mimeData.setData('application/x-qabstractitemmodeldatalist',draggable) - if slashes == 3: - mimeData.setData(TAURUS_ATTR_MIME_TYPE, draggable) - elif slashes == 2: - mimeData.setData(TAURUS_DEV_MIME_TYPE, draggable) - else: - mimeData.setData(TAURUS_MODEL_MIME_TYPE, draggable) - return mimeData - - def checkHeaderClicked(self, position): - if self.itemAt(position) is self.headerItem(): - node = self.headerItem() - self.showNodeContextMenu(node, event) - #node.ContextMenu = ['Search ...'] - - def mouseMoveEvent(self, event): - ''' - copied from TaurusBaseWidget to provide drag events - It had to be rewritten as QTreeWidget does not allow drag events - ''' - self.debug('In TaurusDevTree.mouseMoveEvent') - if not self._dragEnabled or not event.buttons() & Qt.Qt.LeftButton: - return self.getQtClass().mouseMoveEvent(self, event) - if (event.pos() - self.dragStartPosition).manhattanLength() < Qt.QApplication.startDragDistance(): - return self.getQtClass().mouseMoveEvent(self, event) - # The mouseMoveEvent of QTreeWidget do not allow drag, commented - ret = None # self.getQtClass().mouseMoveEvent(self, event) #call the superclass - event.accept() # we make sure we accept after having called the superclass so that it is not propagated (many default implementations of mouseMoveEvent call event.ignore()) - drag = Qt.QDrag(self) - drag.setMimeData(self.getModelMimeData()) - drag.exec_(Qt.Qt.CopyAction, Qt.Qt.CopyAction) - return ret - - def mimeTypes(self): - return self.getSupportedMimeTypes() - - def dropEvent(self, event): - '''reimplemented to support dropping of modelnames in forms''' - self.debug('dropEvent(%s): %s,%s' % (event, event.mimeData(), - split_model_list(event.mimeData().formats()))) - if event.source() is self: - self.info('Internal drag/drop not allowed') - return - if any(s in event.mimeData().formats() for s in self.getSupportedMimeTypes()): - # mtype = - # self.handleMimeData(event.mimeData(),self.addModels)#lambda - # m:self.addModels('^%s$'%m)) - event.acceptProposedAction() - else: - self.warning('Invalid model in dropped data') - - # 3 - # @name Context Menu Actions - # @{ - - def contextMenuEvent(self, event): - ''' - This function is called when right clicking on TaurusDevTree area. - ''' - node = self.currentItem() - self.showNodeContextMenu(node, event) - return - - def showNodeContextMenu(self, node, event): - """ - A pop up menu will be shown with the available options. - Menus are managed using two tuple lists for each node: node.ContextMenu and node.ExpertMenu - """ - obj = self.getNodeDraggable(node) - position = event.globalPos() - self.debug('showNodeContextMenu(%s)' % obj) - if self.itemAt(position) is self.headerItem(): - node = self.headerItem() - #node.ContextMenu = ['Search ...'] - if node is None: - node = self - else: - if not hasattr(node, 'ContextMenu'): - node.ContextMenu = [] - # Creating default menu - if not 'Search ...' in [k for k, a in node.ContextMenu]: - # DEVICE NODE CONTEXT MENU - if obj.count('/') == 2: - - node.ContextMenu.append(("Open Panel", self.showPanel)) - node.ContextMenu.append( - ("Show Attributes", self.addAttrToNode)) - if self.getNodeAdmin(node): - node.ContextMenu.append(("Go to %s" % self.getNodeAdmin(node), - lambda p=self.getNodeAdmin( - node): p and self.findInTree(p) - )) - if not hasattr(node, 'ExpertMenu'): - setattr(node, 'ExpertMenu', self.ExpertMenu) # []) - if not 'Show Properties' in [k for k, a in node.ExpertMenu]: - node.ExpertMenu.append( - ("Show Properties", self.showProperties)) - - def test_device(): - device = str(self.getNodeDeviceName()) - if device: - comm = 'tg_devtest %s &' % device - os.system(comm) - else: - self.debug( - 'TaurusDevTree.TestDevice: Selected Device is None!') - node.ExpertMenu.append(("Test Device", test_device)) - node.ExpertMenu.append( - ("Show ALL Attributes", lambda s=self: s.addAttrToNode(full=True))) - node.ContextMenu.append(('', None)) - - # ATTRIBUTE NODE CONTEXT MENU - elif obj.count('/') == 3: - for k, v in self.AttributeMenu: - self.debug('Adding action %s' % k) - if type(v) is str and hasattr(self, v): - node.ContextMenu.append((k, getattr(self, v))) - else: - node.ContextMenu.append( - (k, lambda s=self.getNodeAlias(node): v(s))) - #node.ContextMenu.append(("add to Trends", self.addToPlot)) - #node.ContextMenu.append(("remove from Trends", self.removeFromPlot)) - node.ContextMenu.append(('', None)) - elif not hasattr(node, 'ExpertMenu'): - setattr(node, 'ExpertMenu', self.ExpertMenu) # []) - #node.ContextMenu.append(("Expand Node", self.expandNode)) - #node.ContextMenu.append(("Collapse Node", self.collapseNode)) - if node.isExpanded() and node.childCount() < 10 and all(self.getNodeText(node.child(j)).count('/') == 2 for j in range(node.childCount())): - node.ContextMenu.append(("Show Attributes", lambda n=node, s=self: [ - s.addAttrToNode(n.child(j)) for j in range(n.childCount())])) - node.ContextMenu.append(("Search ...", - lambda: self.findInTree(str(Qt.QInputDialog.getText( - self, 'Search ...', 'Write a part of the name', Qt.QLineEdit.Normal)[0])) - )) - #configDialogAction = menu.addAction("Refresh Tree") - #self.connect(configDialogAction, Qt.SIGNAL("triggered()"), self.refreshTree) - menu = Qt.QMenu(self) - - if hasattr(node, 'ContextMenu'): - last_was_separator = True - for t in (type(node.ContextMenu) is dict and node.ContextMenu.items() or node.ContextMenu): - try: - k, action = t - if k: - configDialogAction = menu.addAction(k) - if action: - configDialogAction.triggered.connect(action) - else: - configDialogAction.setEnabled(False) - last_was_separator = False - elif not last_was_separator: - menu.addSeparator() - last_was_separator = True - except Exception, e: - self.warning('Unable to add Menu Action: %s:%s' % (t, e)) - - if hasattr(node, 'ExpertMenu'): - menu.addSeparator() - expert = menu.addMenu('Expert') - # expert.addSeparator() - last_was_separator = True - for t in (type(node.ContextMenu) is dict and node.ExpertMenu.items() or node.ExpertMenu): - try: - k, action = t - if k: - configDialogAction = expert.addAction(k) - if action: - configDialogAction.triggered.connect(action) - else: - configDialogAction.setEnabled(False) - last_was_separator = False - elif not last_was_separator: - expert.addSeparator() - last_was_separator = True - except Exception, e: - self.warning('Unable to add Expert Action: %s:%s' % (t, e)) - # menu.addSeparator() - menu.exec_(event.globalPos()) - del menu - - -class PopupDialog(Qt.QDialog): - """ - This class create the dialog - Dialog is used to make new floating panels persistent - """ - - def __init__(self, parent=None, target=None): - Qt.QDialog.__init__(self, parent) - if target: - self.initComponents(target) - - def initComponents(self, newWidget, show=True): - widgetLayout = Qt.QVBoxLayout(self) - widgetLayout.setContentsMargins(10, 10, 10, 10) - widgetLayout.addWidget(newWidget) - self.setWindowTitle(newWidget.windowTitle()) - self.setModal(False) - self.setVisible(True) - if show: - self.exec_() - -########################################################################## - - -class TaurusTreeNode(Qt.QTreeWidgetItem, TaurusBaseComponent): - """ - Unused; one day should replace TaurusTreeNodeContainer and all methods moved here. - Base class for all Taurus Tree Node Items - """ - - #------------------------------------------------------------------------- - # Write your own code here to define the signals generated by this widget - # - - modelChanged = Qt.pyqtSignal('const QString &') - - def __init__(self, name=None, parent=None): - name = name or self.__class__.__name__ - self.call__init__wo_kw(Qt.QTreeWidgetItem, parent) - self.call__init__(TaurusBaseComponent, name, parent) - # self.defineStyle() - - # def defineStyle(self): - #""" Defines the initial style for the widget """ - # ----------------------------------------------------------------------- - # Write your own code here to set the initial style of your widget - ## - # self.updateStyle() - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # Mandatory methods to be implemented in any subclass of TaurusComponent - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def getParentTaurusComponent(self): - """ Returns a parent Taurus component or None if no parent TaurusBaseComponent - is found.""" - p = self.parentItem() - while p and not isinstance(p, TaurusTreeNode): - p = self.parentItem() - return p - - def updateStyle(self): - """ Method called when the component detects an event that triggers a change - in the style.""" - - # if self.scene(): - # self.scene().updateSceneItem(self) - - #---------------------------------------------------------------------- - # Write your own code here to update your widget style - - # send a repaint in the end - # self.repaint() - #if self._parent: self._parent.repaint() - - state2color = lambda state: Qt.QColor( - DEVICE_STATE_PALETTE.number(state)) - quality2color = lambda attr: Qt.QColor( - ATTRIBUTE_QUALITY_PALETTE.number(attr)) - v = self.getModelValueObj() - if isinstance(v, PyTango.DevState): # TODO: maybe change to Taurus.tango.DevState - # @TODO: node is undefined. Check code - node.setBackground(0, Qt.QBrush(state2color(v))) - if hasattr(v, 'quality'): - self.setForeground(0, Qt.QBrush(quality2color(v.quality))) - - def isReadOnly(self): - return True - - def __str__(self): - return self.log_name + "(" + self.modelName + ")" - - def getModelClass(self): - return taurus.core.taurusdevice.TaurusDevice - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # TaurusBaseComponent over writing - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - # def attach(self): - #"""Attaches the widget to the model""" - # 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 - ## - # Typical code is: - # self.connect(self, Qt.SIGNAL('valueChangedDueToEvent(QString)'), - # self.setTextValue) - #ret = TaurusBaseWidget.attach(self) - # by default enable/disable widget according to attach state - # self.setEnabled(ret) - # return ret - - # def detach(self): - #"""Detaches the widget from the model""" - # TaurusBaseWidget.detach(self) - - # ----------------------------------------------------------------------- - # Write your own code here after detaching the widget from the model - ## - # Typical code is: - # self.emit(Qt.SIGNAL('valueChangedDueToEvent(QString)'), - # Qt.QString(value_str)) - # self.disconnect(self, Qt.SIGNAL('valueChangedDueToEvent(QString)'), - # self.setTextValue) - # by default disable widget when dettached - # self.setEnabled(False) - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # QT properties - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - model = Qt.pyqtProperty("QString", TaurusBaseWidget.getModel, - TaurusBaseWidget.setModel, - TaurusBaseWidget.resetModel) - - useParentModel = Qt.pyqtProperty("bool", - TaurusBaseWidget.getUseParentModel, - TaurusBaseWidget.setUseParentModel, - TaurusBaseWidget.resetUseParentModel) - - #------------------------------------------------------------------------- - # Write your own code here for your own widget properties - - -class TaurusDevTreeOptions(Qt.QWidget): - """ This class provides a search(QString) signal to be connected to TaurusDevTree.findInTree slot """ - - search = Qt.pyqtSignal('QString') - loadTree = Qt.pyqtSignal('QString') - hideUnarchived = Qt.pyqtSignal() - hideUnexported = Qt.pyqtSignal() - - def __init__(self, parent=None, icon=None): - Qt.QWidget.__init__(self, parent) - - self.setLayout(Qt.QHBoxLayout()) - try: - self._pixmap = Qt.QPixmap(icon or 'image/icons/search.png') - self._label = Qt.QLabel(self) - self._label.setPixmap(self._pixmap) - self.layout().addWidget(self._label) - except: - pass - - self._edit = Qt.QLineEdit() - self._button = Qt.QPushButton() - self._button.setText('Search') - self._edit.returnPressed.connect(self._button.animateClick) - self._button.clicked.connect(self._emitSearch) - self.layout().addWidget(self._edit) - self.layout().addWidget(self._button) - - def connectWithTree(self, tree): - self.search.connect(tree.findInTree) - - def _emitSearch(self): - text = self._edit.text() - if text: - self.search.emit(text) - return - -SearchEdit = TaurusDevTreeOptions - -########################################################################## - - -class ServerBrowser(TaurusDevTree): - """ This class is used only when browsing by Server/Instance instead of Domain/Family/Member scheme """ - - def getDeviceDict(self, filters): - ''' - This method build a dictionary of instances and devices depending on the given servers,devices or instances in QTProperty or in another widget - --- filters is a string with names of devices/servers such as "LT/VC/ALL,LT02/VC/IP-01" or "modbus,pyplc" - --- filters is a list of devices such as ["LT/VC/ALL","LT02/VC/IP-01"] - ''' - self.trace('In TaurusDevTree.buildDictFromFilters(%s)' % filters) - self._filters = filters - # @TODO:QString and QStringList should not be used (They disappear in API2) - if type(filters) == type("") or isinstance(filters, Qt.QString): - filters = str(filters).split(',') - # @TODO:QString and QStringList should not be used (They disappear in API2) - elif isinstance(filters, Qt.QStringList): - filters = list(filters) - elif type(filters) != type([]): - self.debug("'filters' has to be a string or the list type!") - vals = {} - if not filters: - return vals - if filters[0].count('/') == 0: - self.debug( - 'In TaurusDevTree.buildDictFromFilters(%s): Getting Servers' % filters) - targets, addMe = self.db.get_server_name_list( - ), self.addInstToServ # Searching servers - elif filters[0].count('/') == 1: - self.debug( - 'In TaurusDevTree.buildDictFromFilters(%s): Getting Instances' % filters) - targets, addMe = self.db.get_server_list(), self.addDevToInst # Searching instances - elif filters[0].count('/') == 2: - self.debug( - 'In TaurusDevTree.buildDictFromFilters(%s): Getting Devices' % filters) - targets, addMe = self.db.get_device_exported( - "*"), lambda s: {s: {}} # self.addAttrToDev #Searching devices - else: - raise Exception('UnknownFilter!: %s' % filters[0]) - - for t in targets: - for f in filters: - f = str(f) - exp = f.replace( - '*', '.*').lower() if '*' in f and '.*' not in f else f.lower() - if re.match(exp, t.lower()): - self.debug('Adding node %s' % t) - vals[t] = addMe(t) - self.trace('Out of TaurusDevTree.getDeviceDict(%s)' % filters) - return vals - - def addInstToServ(self, my_server): - dict = {} - list_inst = self.get_instances_for_server(my_server) - lower_list_inst = [s.lower() for s in list_inst] - for my_inst in lower_list_inst: - if self._expand: - dict[my_inst] = self.addDevtoInst(my_inst) - else: - dict[my_inst] = 0 - return dict - - def addDevtoInst(self, my_inst, expand_attrs=False): - d = {} - list_dev = self.get_devices_for_instance(my_inst) - lower_list_dev = [s.lower() for s in list_dev] - for my_dev in lower_list_dev: - if self._expand: - d[my_dev] = self.addAttrToDev( - my_dev) if expand_attrs else {my_dev: {}} - else: - d[my_dev] = 0 - return d - - def addFamilyToDomain(self, prev, expand_attrs): - d = {} - #children = self.get_devices_for_instance(my_inst) - # @TODO: list_dev is undefined. Check code - lower_list_dev = [s.lower() for s in list_dev] - for my_dev in lower_list_dev: - if self._expand: - d[my_dev] = self.addAttrToDev( - my_dev) if expand_attrs else {my_dev: {}} - else: - d[my_dev] = 0 - return d - - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - # Methods for database commands - #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- - - def get_instances_for_server(self, server_name): - #executable_name = class_name - instances = self.db.get_instance_name_list(server_name) - return [server_name + '/' + instance for instance in instances] - - def get_devices_for_instance(self, instance_name): - devslist = self.db.get_device_class_list(instance_name) - return [dev for dev in devslist if '/' in dev and not dev.startswith('dserver')] - - # @} - -########################################################################## - - -class TaurusSearchTree(TaurusWidget): - """ - This Class is a mere wrapper providing a TaurusDevTree connected with an options panel and some optional checkboxes. - """ - __slots__ = ( - "setTangoHost", - #"setModel", - "addModels", - "setModelCheck", - "setTree", - "findInTree", - "expandAll", - "loadTree", - ) - - refreshTree = Qt.pyqtSignal() - nodeFound = Qt.pyqtSignal() - deviceSelected = Qt.pyqtSignal('QString') - addAttrSelected = Qt.pyqtSignal('QStringList') - removeAttrSelected = Qt.pyqtSignal('QStringList') - modelChanged = Qt.pyqtSignal('const QString &') - search = Qt.pyqtSignal('QString') - - @staticmethod - def method_forwarder(*args, **kwargs): - m, o = kwargs['method'], kwargs['object'] - # print 'Calling %s.%s'%(o,m) - getattr(o.__class__, m)(o, *args) - - def defineStyle(self): - #print('In TaurusSearchTree.defineStyle()') - self.setWindowTitle('TaurusDevTree') - self.setLayout(Qt.QVBoxLayout()) - self.edit = TaurusDevTreeOptions(self) - self.tree = TaurusDevTree(self) - self.layout().addWidget(self.edit) - self.layout().addWidget(self.tree) - self.registerConfigDelegate(self.tree) - # Slot forwarding ... - for k in TaurusDevTree.__dict__.keys(): - # if k in ['__init__','defineStyle']: continue - if k not in self.__slots__: - continue - try: - setattr(self, k, partial( - self.method_forwarder, method=k, object=self.tree)) - except Exception, e: - self.warning('Unable to add slot %s: %s' % (k, e)) - # Event forwarding ... - self.tree.refreshTree.connect(self.refreshTree) - self.tree.nodeFound.connect(self.nodeFound) - self.tree.deviceSelected[str].connect(self.deviceSelected) - self.tree.search[str].connect(self.search) - self.tree.addAttrSelected[str].connect(self.addAttrSelected) - self.tree.removeAttrSelected[str].connect(self.removeAttrSelected) - self.tree.modelChanged[str].connect(self.modelChanged) - #for signal in TaurusDevTree.__pyqtSignals__: - # getattr(self.tree, signal).connect( - # lambda args, f=self, s=signal: getattr(f, signal).emit(args)) - self.edit.connectWithTree(self) - return - - -########################################################################## - -def taurusDevTreeMain(): - import sys - from taurus.qt.qtgui.application import TaurusApplication - from taurus.core.util import argparse - - if False: - app = Qt.QApplication([]) - args = sys.argv[1:] - #args = app.get_command_line_args() - #app = TaurusApplication(sys.argv) - if not args: - args = ['database'] - else: - taurus.setLogLevel(taurus.Debug) - parser = argparse.get_taurus_parser() - parser.set_usage("%prog [options] devname [attrs]") - parser.set_description( - "Taurus Application inspired in Jive and Atk Panel") - parser.add_option("--config", "--config-file", dest="config_file", default=None, - help="use the given config file for initialization") - app = TaurusApplication(cmd_line_parser=parser, app_name="TaurusDevicePanel", - app_version=taurus.Release.version) - args = app.get_command_line_args() - options = app.get_command_line_options() - - form = TaurusSearchTree() - # try: - # if options.tango_host is None: - #options.tango_host = taurus.Authority().getNormalName() - # form.setTangoHost(options.tango_host) - # except: pass - - def trace(m): - print(m) - [setattr(form.tree, f, trace) - for f in ('info', 'warning', 'error', 'trace')] - - form.setLogLevel(taurus.Debug) - form.tree.setLogLevel(taurus.Debug) - # set a model list from the command line or launch the chooser - if options.config_file is not None: - form.tree.loadConfigFile(options.config_file) - if len(args) > 0: - models = args - form.setModel(models) - form.show() - sys.exit(app.exec_()) - -if __name__ == "__main__": - taurusDevTreeMain() diff --git a/lib/taurus/qt/qtgui/tree/taurustree.py b/lib/taurus/qt/qtgui/tree/taurustree.py index f6d2493ba..91b8d5e16 100644 --- a/lib/taurus/qt/qtgui/tree/taurustree.py +++ b/lib/taurus/qt/qtgui/tree/taurustree.py @@ -26,13 +26,16 @@ """This module provides a base widget that can be used to display a taurus model in a tree widget""" +from __future__ import absolute_import + +from taurus.qt.qtgui.model import TaurusBaseModelWidget +from .qtree import QBaseTreeWidget + + __all__ = ["TaurusBaseTreeWidget"] __docformat__ = 'restructuredtext' -from taurus.qt.qtgui.model import TaurusBaseModelWidget -from qtree import QBaseTreeWidget - class TaurusBaseTreeWidget(QBaseTreeWidget, TaurusBaseModelWidget): diff --git a/lib/taurus/qt/qtgui/util/taurusaction.py b/lib/taurus/qt/qtgui/util/taurusaction.py index cad0d5158..5c8ec4778 100644 --- a/lib/taurus/qt/qtgui/util/taurusaction.py +++ b/lib/taurus/qt/qtgui/util/taurusaction.py @@ -25,6 +25,21 @@ """This module is designed to provide a library of taurus Qt actions""" +from __future__ import absolute_import + +from builtins import str + +import os +import xml.dom.minidom + +from functools import partial +from future.utils import string_types + +from taurus.external.qt import Qt +from taurus.core.taurushelper import getSchemeFromName +from taurus.qt.qtcore.configuration import BaseConfigurableClass + + __all__ = ["ExternalAppAction", "TaurusMenu", "TaurusAction", @@ -39,13 +54,6 @@ __docformat__ = 'restructuredtext' -import os -import xml.dom.minidom - -from taurus.external.qt import Qt -from taurus.core.taurushelper import getSchemeFromName -from taurus.qt.qtcore.configuration import BaseConfigurableClass - class ExternalAppAction(Qt.QAction, BaseConfigurableClass): """ An specialized QAction for launching external applications @@ -68,7 +76,7 @@ def __init__(self, cmdargs, text=None, icon=None, parent=None, interactive=True) :param icon: (QIcon or any other object that can be passed to QIcon creator) see :class:`Qt.QAction` :param parent: (QObject) The parent object ''' - if isinstance(cmdargs, (basestring, Qt.QString)): + if isinstance(cmdargs, string_types): import shlex cmdargs = shlex.split(str(cmdargs)) self.path = os.path.realpath(cmdargs and cmdargs[0] or '') @@ -76,7 +84,7 @@ def __init__(self, cmdargs, text=None, icon=None, parent=None, interactive=True) text = os.path.basename(cmdargs and cmdargs[0] or '') if icon is None: icon = Qt.QIcon.fromTheme(self.DEFAULT_ICON_NAME) - elif isinstance(icon, basestring): + elif isinstance(icon, string_types): tmp = Qt.QIcon.fromTheme(icon) if not tmp.isNull(): icon = tmp @@ -86,7 +94,7 @@ def __init__(self, cmdargs, text=None, icon=None, parent=None, interactive=True) self.interactive = interactive self._process = [] self.setCmdArgs(cmdargs) - self.triggered.connect(self.actionTriggered) + self.triggered.connect(partial(self.actionTriggered, args=None)) self.setToolTip("Launches %s (external application)" % text) self.registerConfigProperty(self.cmdArgs, self.setCmdArgs, 'cmdArgs') @@ -100,7 +108,7 @@ def setCmdArgs(self, cmdargs, emitsignal=True): application. It can also be a string containing a command, which will be automatically converted to a list ''' - if isinstance(cmdargs, (basestring, Qt.QString)): + if isinstance(cmdargs, string_types): import shlex cmdargs = shlex.split(str(cmdargs)) self.__cmdargs = cmdargs @@ -116,7 +124,7 @@ def actionTriggered(self, args=None): import subprocess try: if args is not None: - if isinstance(args, (basestring, Qt.QString)): + if isinstance(args, string_types): import shlex args = shlex.split(str(args)) args = self.cmdArgs() + args @@ -130,10 +138,10 @@ def actionTriggered(self, args=None): else: return False except OSError: - err = "Error launching %s" % unicode(self.text()) + err = "Error launching %s" % str(self.text()) msg = "Cannot launch application:\n" + \ " ".join(self.__cmdargs) + \ - "\nHint: Check that %s is installed and in the path" % unicode( + "\nHint: Check that %s is installed and in the path" % str( self.text()) if self.interactive: Qt.QMessageBox.warning(self.parentWidget(), err, msg) @@ -171,7 +179,7 @@ def build(self, data): self.buildFromXML(m_node) def getActionFactory(self): - import taurusactionfactory + from . import taurusactionfactory return taurusactionfactory.ActionFactory() def buildFromXML(self, m_node): diff --git a/lib/taurus/qt/qtgui/util/taurusactionfactory.py b/lib/taurus/qt/qtgui/util/taurusactionfactory.py index 645167ba1..43015cbbb 100644 --- a/lib/taurus/qt/qtgui/util/taurusactionfactory.py +++ b/lib/taurus/qt/qtgui/util/taurusactionfactory.py @@ -25,15 +25,20 @@ """This module is designed to provide a factory class for taurus Qt actions """ -__all__ = ["ActionFactory"] +from __future__ import absolute_import -__docformat__ = 'restructuredtext' +from future.utils import string_types from taurus.core.util.log import Logger from taurus.core.util.singleton import Singleton from taurus.external.qt import Qt -import taurusaction +from . import taurusaction + + +__all__ = ["ActionFactory"] + +__docformat__ = 'restructuredtext' class ActionFactory(Singleton, Logger): @@ -129,7 +134,7 @@ def createAction(self, parent, text, shortcut=None, icon=None, tip=None, action.toggled.connect(toggled) action.setCheckable(True) if icon is not None: - if isinstance(icon, (str, unicode)): + if isinstance(icon, string_types): icon = Qt.QIcon.fromTheme(icon) action.setIcon(icon) if shortcut is not None: diff --git a/lib/taurus/qt/qtgui/util/tauruscolor.py b/lib/taurus/qt/qtgui/util/tauruscolor.py index 5e3d3cb07..ab53fcf2d 100644 --- a/lib/taurus/qt/qtgui/util/tauruscolor.py +++ b/lib/taurus/qt/qtgui/util/tauruscolor.py @@ -37,6 +37,7 @@ from taurus.core.util.colors import ColorPalette, \ DEVICE_STATE_DATA, ATTRIBUTE_QUALITY_DATA from taurus.core.taurusbasetypes import AttrQuality +from taurus.core.util.log import deprecation_decorator class QtColorPalette(ColorPalette): @@ -47,8 +48,6 @@ def __init__(self, dat, int_decoder_dict): self._qcolor_cache_bg = dict() self._qbrush_cache_fg = dict() self._qbrush_cache_bg = dict() - self._qvariant_cache_fg = dict() - self._qvariant_cache_bg = dict() def qbrush(self, stoq): # print stoq @@ -57,10 +56,10 @@ def qbrush(self, stoq): f = self._qbrush_cache_fg b = self._qbrush_cache_bg - if not f.has_key(name): + if name not in f: f[name] = Qt.QBrush(self.qcolor(stoq)[1]) - if not b.has_key(name): + if name not in b: b[name] = Qt.QBrush(self.qcolor(stoq)[0]) if name == 'None': b[name].setStyle(Qt.Qt.BDiagPattern) @@ -73,26 +72,18 @@ def qcolor(self, stoq): f = self._qcolor_cache_fg b = self._qcolor_cache_bg - if not f.has_key(name): + if name not in f: f[name] = Qt.QColor(self.number(name, True)) - if not b.has_key(name): + if name not in b: b[name] = Qt.QColor(self.number(name)) return (b[name], f[name]) + @deprecation_decorator(alt='QtColorPalette.qcolor()', rel='4.5') def qvariant(self, stoq): """Returns the color for the specified state or quality""" - name = self._decoder(stoq) - - f = self._qvariant_cache_fg - b = self._qvariant_cache_bg - if not f.has_key(name): - (back, fore) = self.qcolor(name) - f[name] = Qt.QVariant(fore) - b[name] = Qt.QVariant(back) - - return (b[name], f[name]) + return self.qcolor(stoq) diff --git a/lib/taurus/qt/qtgui/util/taurusropepatch.py b/lib/taurus/qt/qtgui/util/taurusropepatch.py index 59d9cb7a3..9697c66e7 100644 --- a/lib/taurus/qt/qtgui/util/taurusropepatch.py +++ b/lib/taurus/qt/qtgui/util/taurusropepatch.py @@ -39,7 +39,7 @@ def apply(): """Monkey patching rope for better performances""" import rope if rope.VERSION not in ('0.9.3', '0.9.2'): - raise ImportError, "rope %s can't be patched" % rope.VERSION + raise ImportError("rope %s can't be patched" % rope.VERSION) # Patching pycore.PyCore, so that forced builtin modules (i.e. modules # that were declared as 'extension_modules' in rope preferences) diff --git a/lib/taurus/qt/qtgui/util/tauruswidgetfactory.py b/lib/taurus/qt/qtgui/util/tauruswidgetfactory.py index cda926091..4b5c28205 100644 --- a/lib/taurus/qt/qtgui/util/tauruswidgetfactory.py +++ b/lib/taurus/qt/qtgui/util/tauruswidgetfactory.py @@ -27,6 +27,7 @@ tauruswidgetfactory.py: """ + __all__ = ["TaurusWidgetFactory", "getWidgetsOfType"] __docformat__ = 'restructuredtext' @@ -67,6 +68,8 @@ def getWidgetsOfType(widget, class_or_type_or_tuple): return widgets +# TODO: TaurusWidgetFactory should be deprecated in favor of using importlib + class TaurusWidgetFactory(Singleton, Logger): """The TaurusWidgetFactory is a utility class that provides information about all Qt widgets (Taurus and non Taurus) that are found in the @@ -121,9 +124,9 @@ def _buildWidgets(self, module_name, path, recursive=True): qt_ret[dir_name] = package, attr if issubclass(attr, taurus.qt.qtgui.base.TaurusBaseWidget): taurus_ret[dir_name] = package, attr - except Exception, e: + except Exception as e: pass - except Exception, e: + except Exception as e: return taurus_ret, qt_ret if not recursive: @@ -162,13 +165,13 @@ def _addExtraTaurusWidgetsPath(self, taurus_ret, qt_widgets, path): try: self.debug("Trying to find extra module %s", m_name) f, fname, data = imp.find_module(m_name, [path]) - except ImportError, ie: + except ImportError as ie: self.debug("Could not find extra module %s:%s", m_name, ie) continue try: self.debug("Trying to load extra module %s", m_name) mod = imp.load_module(m_name, f, fname, data) - except ImportError, ie: + except ImportError as ie: self.debug("Could not load extra module %s:%s", m_name, ie) continue dir_names = dir(mod) @@ -185,7 +188,7 @@ def _addExtraTaurusWidgetsPath(self, taurus_ret, qt_widgets, path): taurus_ret[dir_name] = qt_info['module'], attr qt_widgets[dir_name] = qt_info['module'], attr self.debug("registered taurus widget %s", dir_name) - except Exception, e: + except Exception as e: pass def getWidgets(self): @@ -195,7 +198,7 @@ def getTaurusWidgets(self): return self._taurus_widgets def getWidgetClassNames(self): - return self._qt_widgets.keys() + return list(self._qt_widgets.keys()) def getWidgetClasses(self): return [klass for mod_name, klass in self._qt_widgets.values()] @@ -204,7 +207,7 @@ def getWidgetClass(self, name): return self._qt_widgets[name][1] def getTaurusWidgetClassNames(self): - return self._taurus_widgets.keys() + return list(self._taurus_widgets.keys()) def getTaurusWidgetClasses(self): return [klass for mod_name, klass in self._taurus_widgets.values()] diff --git a/lib/taurus/qt/qtgui/util/test/test_ui/test_ui.py b/lib/taurus/qt/qtgui/util/test/test_ui/test_ui.py index ae6e1a137..b5726f906 100644 --- a/lib/taurus/qt/qtgui/util/test/test_ui/test_ui.py +++ b/lib/taurus/qt/qtgui/util/test/test_ui/test_ui.py @@ -25,13 +25,15 @@ """Unit tests for UILoadable decorator""" +from __future__ import absolute_import + import os.path import unittest from taurus.external.qt import Qt from taurus.qt.qtgui.util.ui import UILoadable from taurus.qt.qtgui.test import BaseWidgetTestCase -from mywidget3 import MyWidget3 +from .mywidget3 import MyWidget3 class UILoadableTestCase(unittest.TestCase): diff --git a/lib/taurus/qt/qtgui/util/ui.py b/lib/taurus/qt/qtgui/util/ui.py index d22ef8d18..b4bc93a23 100644 --- a/lib/taurus/qt/qtgui/util/ui.py +++ b/lib/taurus/qt/qtgui/util/ui.py @@ -25,9 +25,7 @@ """utilities to load ui files for widgets""" -__all__ = ["loadUi", - "UILoadable", - ] +from builtins import object import os import sys @@ -37,6 +35,11 @@ from taurus.external.qt import uic +__all__ = ["loadUi", + "UILoadable", + ] + + class __UI(object): pass diff --git a/lib/taurus/qt/qtgui/util/validator.py b/lib/taurus/qt/qtgui/util/validator.py index c5d80fe3c..92f875ebb 100755 --- a/lib/taurus/qt/qtgui/util/validator.py +++ b/lib/taurus/qt/qtgui/util/validator.py @@ -88,7 +88,7 @@ def setBottom(self, bottom): """ self._bottom = Quantity(bottom) - def _validate(self, input, pos): + def validate(self, input, pos): """Reimplemented from :class:`QValidator` to validate if the input string is a representation of a quantity within the set bottom and top limits @@ -112,12 +112,3 @@ def _validate(self, input, pos): except DimensionalityError: return Qt.QValidator.Intermediate, input, pos return Qt.QValidator.Acceptable, input, pos - - def _validate_oldQt(self, input, pos): - """Old Qt (v4.4.) -compatible implementation of validate""" - state, _, pos = self._validate(input, pos) - return state, pos - - # select the appropriate implementation of validate. See: - # https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg26344.html - validate = Qt.PYQT_QSTRING_API_1 and _validate_oldQt or _validate diff --git a/lib/taurus/tauruscustomsettings.py b/lib/taurus/tauruscustomsettings.py index d1a3243da..17e9d0932 100755 --- a/lib/taurus/tauruscustomsettings.py +++ b/lib/taurus/tauruscustomsettings.py @@ -93,8 +93,8 @@ pass #: Default serialization mode **for the tango scheme**. Possible values are: -#: 'Serial' (default), 'Concurrent', or 'TangoSerial' (deprecated) -TANGO_SERIALIZATION_MODE = 'Serial' +#: 'Serial', 'Concurrent', or 'TangoSerial' (default) +TANGO_SERIALIZATION_MODE = 'TangoSerial' #: PLY (lex/yacc) optimization: 1=Active (default) , 0=disabled. #: Set PLY_OPTIMIZE = 0 if you are getting yacc exceptions while loading @@ -108,7 +108,7 @@ # Qt configuration # ---------------------------------------------------------------------------- -#: Set preffered API if not is already loaded +#: Set preferred API (if one is not already loaded) DEFAULT_QT_API = 'pyqt' #: Auto initialize Qt logging to python logging @@ -117,6 +117,13 @@ #: Remove input hook (only valid for PyQt4) QT_AUTO_REMOVE_INPUTHOOK = True +#: Avoid application abort on unhandled python exceptions +#: (which happens since PyQt 5.5). +#: http://pyqt.sf.net/Docs/PyQt5/incompatibilities.html#unhandled-python-exceptions +#: If True (or commented out) an except hook is added to force the old +# behaviour (exception is just printed) on pyqt5 +QT_AVOID_ABORT_ON_EXCEPTION = True + #: Select the theme to be used: set the theme dir and the theme name. #: The path can be absolute or relative to the dir of taurus.qt.qtgui.icon #: If not set, the dir of taurus.qt.qtgui.icon will be used diff --git a/lib/taurus/test/fuzzytest.py b/lib/taurus/test/fuzzytest.py index b2a356287..ca1949039 100644 --- a/lib/taurus/test/fuzzytest.py +++ b/lib/taurus/test/fuzzytest.py @@ -24,6 +24,8 @@ ############################################################################# '''Utility functions to deal with non-ideal (fuzzy) tests''' +from __future__ import print_function +from future.utils import string_types def loopTest(testname, maxtries=100, maxfails=10): @@ -97,22 +99,22 @@ def calculateTestFuzziness(test, maxtries=100, maxfails=10, **kwargs): times that the test should be passed to have a confidence>99%% that the bug is fixed' ''' - print ("Running the test %i times (or until it fails %i times)" + - "to estimate the failure rate") % (maxtries, maxfails) + print(("Running the test %i times (or until it fails %i times)" + + "to estimate the failure rate") % (maxtries, maxfails)) import numpy - if isinstance(test, str): + if isinstance(test, string_types): tries, fails = loopTest(test, maxtries=maxtries, maxfails=maxfails) else: tries, fails = loopSubprocess(test, maxtries=maxtries, maxfails=maxfails, **kwargs) r = float(fails) / tries dr = numpy.sqrt(fails) / tries - print 'Failure rate = %g +/- %g (%i/%i)' % (r, dr, fails, tries) + print('Failure rate = %g +/- %g (%i/%i)' % (r, dr, fails, tries)) # calculating n using p-value=1% and failure rate with -1 sigma n = numpy.ceil(numpy.log(.01) / numpy.log(1 - (r - dr))) - print ('Number of consecutive times that the test should be passed ' + - 'to have a confidence>99%% that the bug is fixed: %g') % n + print(('Number of consecutive times that the test should be passed ' + + 'to have a confidence>99%% that the bug is fixed: %g') % n) return r, dr, n @@ -127,7 +129,7 @@ def kk(): exit(1) return - print calculateTestFuzziness(kk) + print(calculateTestFuzziness(kk)) # print calculateTestFuzziness('test_pytango_bug659.TestPyTango_Bug659') # diff --git a/lib/taurus/test/moduleexplorer.py b/lib/taurus/test/moduleexplorer.py index dddd64d7c..c14742821 100644 --- a/lib/taurus/test/moduleexplorer.py +++ b/lib/taurus/test/moduleexplorer.py @@ -25,8 +25,9 @@ ########################################################################### '''Utility code for returning info about a module''' +from __future__ import print_function -import sys +from builtins import object import os import inspect import glob @@ -50,7 +51,7 @@ def _matchesAnyPattern(self, name, paterns): for p in paterns: if re.match(p, name) is not None: if self.verbose: - print 'excluding "%s" (matches %s)' % (name, p.pattern) + print('excluding "%s" (matches %s)' % (name, p.pattern)) return True return False @@ -90,7 +91,7 @@ def exploreModule(self, modulename): externalmembernames, submodules, warnings ''' if self.verbose: - print "Exploring %s..." % modulename + print("Exploring %s..." % modulename) warnings = [] try: module = __import__(modulename, fromlist=['']) @@ -99,7 +100,7 @@ def exploreModule(self, modulename): modulename, repr(e)) warnings.append(msg) if self.verbose: - print msg + print(msg) return dict(modulename=modulename, basemodulename=modulename.split('.')[-1], modulepath=None, @@ -174,7 +175,7 @@ def getAll(info, key): ret = [(mname, el) for el in info[key]] except KeyError: return [] - for sminfo in info['submodules'].itervalues(): + for sminfo in info['submodules'].values(): ret += ModuleExplorer.getAll(sminfo, key) return ret @@ -213,12 +214,13 @@ def main(modulename='taurus', exclude_patterns=( ): moduleinfo, allw = ModuleExplorer.explore( modulename, exclude_patterns=exclude_patterns, verbose=True) - print '\n\n' + '*' * 50 - print "Exploration finished with %i warnings:" % (len(allw)) + print('\n\n' + '*' * 50) + print("Exploration finished with %i warnings:" % (len(allw))) for m, w in allw: - print w - print '*' * 50 + '\n' - print + print(w) + print('*' * 50 + '\n') + print() + print(len(allw)) assert len(allw) == 0 # import pprint diff --git a/lib/taurus/test/resource.py b/lib/taurus/test/resource.py index c756bf83e..2ad8fbb5c 100644 --- a/lib/taurus/test/resource.py +++ b/lib/taurus/test/resource.py @@ -24,6 +24,7 @@ ########################################################################### '''Utility code for working with test resources''' +from __future__ import print_function import os import sys @@ -62,8 +63,8 @@ def getResourcePath(resmodule, fname=''): if __name__ == "__main__": - print getResourcePath('taurus.test') - print getResourcePath('taurus.test', 'resource.py') + print(getResourcePath('taurus.test')) + print(getResourcePath('taurus.test', 'resource.py')) # print getResourcePath('taurus.qt.qtgui.plot', 'taurusplot.py') # print getResourcePath('taurus.test', 'kk.py') # print getResourcePath('taurus.kk', 'resource.py') diff --git a/lib/taurus/test/test_import.py b/lib/taurus/test/test_import.py index fbd01d35a..2c92951fe 100644 --- a/lib/taurus/test/test_import.py +++ b/lib/taurus/test/test_import.py @@ -24,7 +24,9 @@ """Taurus import tests""" +from __future__ import absolute_import +import sys import unittest @@ -36,7 +38,7 @@ class TaurusImportTestCase(unittest.TestCase): def setUp(self): """Preconditions: moduleexplorer utility has to be available """ - from moduleexplorer import ModuleExplorer + from .moduleexplorer import ModuleExplorer self.explore = ModuleExplorer.explore def testImportSubmodules(self): @@ -46,7 +48,8 @@ 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_.*', + r'taurus\.qt\.qtgui\.qwt5'] try: import PyTango @@ -57,7 +60,6 @@ def testImportSubmodules(self): except ImportError: exclude_patterns.append(r'taurus\.core\.epics') - moduleinfo, wrn = self.explore('taurus', verbose=False, exclude_patterns=exclude_patterns) msg = None diff --git a/lib/taurus/test/testsuite.py b/lib/taurus/test/testsuite.py index f847d903f..488bf259a 100644 --- a/lib/taurus/test/testsuite.py +++ b/lib/taurus/test/testsuite.py @@ -31,14 +31,27 @@ testsuite.run() """ - -__docformat__ = 'restructuredtext' +from __future__ import print_function import os +import sys import re import unittest import taurus +from taurus.external.qt import PYQT4 + +__docformat__ = 'restructuredtext' + +PY3_EXCLUDED = ( + 'unittest.loader._FailedTest.taurus.qt.qtgui.qwt5', + 'unittest.loader._FailedTest.taurus.qt.qtgui.extra_sardana', + 'unittest.loader._FailedTest.taurus.qt.qtgui.extra_pool', + 'unittest.loader._FailedTest.taurus.qt.qtgui.extra_macroexecutor' +) +ONLY_PYQT4 = ( + 'unittest.loader._FailedTest.taurus.qt.qtgui.qwt5', +) def _filter_suite(suite, exclude_pattern, ret=None): """removes TestCases from a suite based on regexp matching on the Test id""" @@ -46,8 +59,20 @@ def _filter_suite(suite, exclude_pattern, ret=None): ret = unittest.TestSuite() for e in suite: if isinstance(e, unittest.TestCase): + + if e.__module__ == 'unittest.case': + continue + + if sys.version_info.major > 2 and e.id() in PY3_EXCLUDED: + print("Excluded %s" % e.id()) + continue + + if not PYQT4 and e.id() in ONLY_PYQT4: + print("Excluded %s" % e.id()) + continue + if re.match(exclude_pattern, e.id()): - print "Excluded %s" % e.id() + print("Excluded %s" % e.id()) continue ret.addTest(e) else: @@ -77,7 +102,6 @@ def run(disableLogger=True, exclude_pattern='(?!)'): def main(): - import sys import taurus.test.skip import argparse from taurus import Release @@ -99,7 +123,7 @@ def main(): args = parser.parse_args() if args.version: - print Release.version + print(Release.version) sys.exit(0) if args.skip_gui: @@ -121,4 +145,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/setup.py b/setup.py index 8dd4e561d..65be1f17a 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,9 @@ import os import imp -from setuptools import setup, find_packages +import sys +from distutils.version import LooseVersion +from setuptools import setup, find_packages, __version__ def get_release_info(): @@ -57,13 +59,21 @@ def get_release_info(): install_requires = [ 'numpy>=1.1', - 'enum34', 'pint>=0.8', + 'future', ] +#Workaround for old setuptools + +if LooseVersion(__version__) < LooseVersion('20.2'): + if sys.version_info < (3, 4): + install_requires.append('enum34') +else: + install_requires.append('enum34;python_version<"3.4"') + + extras_require = { - 'taurus-qt': ['qtpy >=1.2.1', - # 'PyQt4 >=4.8', + 'taurus-qt': [# 'PyQt4 >=4.8', # 'PyQt4.Qwt5 >=5.2.0', # [Taurus-Qt-Plot] 'ply >=2.3', # [Taurus-Qt-Synoptic] 'lxml >=2.1', # [Taurus-Qt-TaurusGUI] @@ -84,8 +94,8 @@ def get_release_info(): console_scripts = [ 'taurustestsuite = taurus.test.testsuite:main', 'taurusconfigbrowser = taurus.qt.qtgui.panel.taurusconfigeditor:main', - 'taurusplot = taurus.qt.qtgui.plot.taurusplot:main', - 'taurustrend = taurus.qt.qtgui.plot.taurustrend:main', + 'taurusplot = taurus.qt.qtgui.qwt5.taurusplot:main', + 'taurustrend = taurus.qt.qtgui.qwt5.taurustrend:main', 'taurusform = taurus.qt.qtgui.panel.taurusform:taurusFormMain', 'tauruspanel = taurus.qt.qtgui.panel.taurusdevicepanel:TaurusPanelMain', 'taurusdevicepanel = taurus.qt.qtgui.panel.taurusdevicepanel:TaurusDevicePanelMain', @@ -104,13 +114,14 @@ def get_release_info(): } classifiers = [ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Environment :: X11 Applications :: Qt', 'Environment :: Win32 (MS Windows)', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', + 'Natural Language :: English', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', @@ -118,6 +129,7 @@ def get_release_info(): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.5', 'Topic :: Scientific/Engineering', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: User Interfaces',