Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use latest version of qt-helpers #671

Merged
merged 3 commits into from
Jun 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 213 additions & 54 deletions glue/external/qt.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
""" A Qt API selector that can be used to switch between PyQt and PySide.
# 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.
#
# This file includes code adapted from:
#
# * IPython, which is released under the modified BSD license
# (https://github.com/ipython/ipython/blob/master/COPYING.rst)
#
# * python_qt_binding, which is released under the BSD license
# (https://pypi.python.org/pypi/python_qt_binding)
#
# See also this discussion
#
# http://qt-project.org/wiki/Differences_Between_PySide_and_PyQt

This file lovingly borrows from the IPython and python_qt_binding project

https://github.com/ipython/ipython/blob/master/IPython/external/qt.py
https://github.com/ros-visualization/python_qt_binding/


See also this discussion
http://qt-project.org/wiki/Differences_Between_PySide_and_PyQt
"""
This module provides a way to import from Python Qt wrappers in a uniform
way, regardless of whether PySide or PyQt is used.

Do not use this if you need PyQt with the old QString/QVariant API.
"""
Expand All @@ -17,28 +54,48 @@
import os
import sys

__all__ = ['QtCore', 'QtGui', 'is_pyside', 'is_pyqt4', 'is_pyqt5', 'load_ui',
'QT_API_PYQT4', 'QT_API_PYQT5', 'QT_API_PYSIDE']

# Available APIs.
QT_API_PYQT = 'pyqt'
QT_API_PYQT4 = 'pyqt'
QT_API_PYSIDE = 'pyside'
QT_API_PYQT5 = 'pyqt5'
QT_API = None

# import hook to protect importing of both PySide and PyQt4

def is_pyside():
return QT_API == QT_API_PYSIDE

class ImportDenier(object):
__forbidden = set()

def __init__(self):
self.__forbidden = None
def is_pyqt4():
return QT_API == QT_API_PYQT4


def is_pyqt5():
return QT_API == QT_API_PYQT5


def forbid(self, module_name):
self.__forbidden = module_name
# Backward-compatibility
is_pyqt = is_pyqt4
QT_API_PYQT = QT_API_PYQT4

_forbidden = set()


def deny_module(module):
_forbidden.add(module)


class ImportDenier(object):
"""
Import hook to protect importing of both PySide and PyQt.
"""

def find_module(self, mod_name, pth):
if pth:
if pth or not mod_name in _forbidden:
return
if mod_name == self.__forbidden:
else:
return self

def load_module(self, mod_name):
Expand All @@ -51,30 +108,34 @@ def load_module(self, mod_name):


def prepare_pyqt4():
# For PySide compatibility, use the new-style string API that automatically
# converts QStrings to Unicode Python strings. Also, automatically unpack
# QVariants to their underlying objects.
# For PySide compatibility, use the new-style string API that
# automatically converts QStrings to Unicode Python strings. Also,
# automatically unpack QVariants to their underlying objects.
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)

prepare_pyqt5 = prepare_pyqt4


def register_module(module, modlabel):
"""Register an imported module into a
submodule of glue.external.qt. Enables syntax like
from glue.qt.QtGui import QMessageBox
"""
sys.modules[__name__ + '.' + modlabel] = module
Register an imported module into a submodule of qt_helpers.

This enables syntax such as:

def deny_module(mod_name):
_import_hook.forbid(mod_name)
>>> from qt_helpers.QtGui import QMessageBox
"""
sys.modules[__name__ + '.' + modlabel] = module


def _load_pyqt4():

prepare_pyqt4()

from PyQt4 import QtCore, QtGui, QtTest
from distutils.version import LooseVersion

if LooseVersion(QtCore.PYQT_VERSION_STR) < LooseVersion('4.8'):
raise ImportError("Glue Requires PyQt4 >= 4.8")

Expand All @@ -91,13 +152,45 @@ def _load_pyqt4():
register_module(QtTest, 'QtTest')

global QT_API
QT_API = QT_API_PYQT
QT_API = QT_API_PYQT4

deny_module('PySide')
deny_module('PyQt5')


def _load_pyqt5():

prepare_pyqt5()

from PyQt5 import QtCore, QtGui, QtTest, QtWidgets
from distutils.version import LooseVersion

QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
QtCore.Property = QtCore.pyqtProperty

# In PyQt5, some widgets such as QMessageBox have moved from QtGui to
# QWidgets so we add backward-compatibility hooks here for now
for widget in dir(QtWidgets):
if widget.startswith('Q'):
setattr(QtGui, widget, getattr(QtWidgets, widget))
QtGui.QItemSelectionModel = QtCore.QItemSelectionModel

register_module(QtCore, 'QtCore')
register_module(QtGui, 'QtGui')
register_module(QtTest, 'QtTest')

global QT_API
QT_API = QT_API_PYQT5

deny_module('PySide')
deny_module('PyQt4')


def _load_pyside():

from PySide import QtCore, QtGui, __version__, QtTest

if __version__ < '1.0.3':
# old PySide, fallback on PyQt
raise ImportError("Glue requires PySide >= 1.0.3")
Expand All @@ -114,34 +207,100 @@ def setMargin(self, x):
QT_API = QT_API_PYSIDE

deny_module('PyQt4')
deny_module('PyQt5')

loaders = [_load_pyqt4, _load_pyside]
if os.environ.get('QT_API') == QT_API_PYSIDE:
loaders = loaders[::-1]

msgs = []

# acutally do the loading
for loader in loaders:
try:
loader()
# we set this env var, since IPython also looks for it
os.environ['QT_API'] = QT_API
QtCore = sys.modules[__name__ + '.QtCore']
QtGui = sys.modules[__name__ + '.QtGui']
break
except ImportError as e:
msgs.append(str(e))
pass
else:
raise ImportError("Could not find a suitable QT installation."
" Encountered the following errors: %s" %
'\n'.join(msgs))

QtCore = None
QtGui = None

def is_pyside():
return QT_API == QT_API_PYSIDE

def reload_qt():
"""
Reload the Qt bindings.

If the QT_API environment variable has been updated, this will load the
new Qt bindings given by this variable. This should be used instead of
the build-in ``reload`` function because the latter can in some cases
cause issues with the ImportDenier (which prevents users from importing
e.g. PySide if PyQt4 is loaded).
"""

_forbidden.clear()

global QtCore
global QtGui

if os.environ.get('QT_API') == QT_API_PYQT5:
loaders = [_load_pyqt5]
elif os.environ.get('QT_API') == QT_API_PYSIDE:
loaders = [_load_pyside, _load_pyqt4]
else:
loaders = [_load_pyqt4, _load_pyside]

msgs = []

# acutally do the loading
for loader in loaders:
try:
loader()
# we set this env var, since IPython also looks for it
os.environ['QT_API'] = QT_API
QtCore = sys.modules[__name__ + '.QtCore']
QtGui = sys.modules[__name__ + '.QtGui']
break
except ImportError as e:
msgs.append(str(e))
pass
else:
raise ImportError("Could not find a suitable QT installation."
" Encountered the following errors: %s" %
'\n'.join(msgs))


def load_ui(path, parent=None, custom_widgets=None):
if is_pyside():
return _load_ui_pyside(path, parent, custom_widgets=custom_widgets)
elif is_pyqt5():
return _load_ui_pyqt5(path, parent)
else:
return _load_ui_pyqt4(path, parent)


def _load_ui_pyside(path, parent, custom_widgets=None):

from PySide.QtUiTools import QUiLoader

loader = QUiLoader()

# must register custom widgets referenced in .ui files
if custom_widgets is not None:
for w in custom_widgets:
loader.registerCustomWidget(w)

widget = loader.load(path, parent)

return widget


def _load_ui_pyqt4(path, parent):
from PyQt4.uic import loadUi
return loadUi(path, parent)


def _load_ui_pyqt5(path, parent):
from PyQt5.uic import loadUi
return loadUi(path, parent)


def get_qapp(icon_path=None):
qapp = QtGui.QApplication.instance()
if qapp is None:
qapp = QtGui.QApplication([''])
qapp.setQuitOnLastWindowClosed(True)
if icon_path is not None:
qapp.setWindowIcon(QIcon(icon_path))
return qapp


def is_pyqt():
return QT_API == QT_API_PYQT
# Now load default Qt
reload_qt()
Loading