1# -*- coding: utf-8 -*-
2#
3# Copyright © 2009- The Spyder Development Team
4# Copyright © 2014-2015 Colin Duquesnoy
5#
6# Licensed under the terms of the MIT License
7# (see LICENSE.txt for details)
8
9"""
10**QtPy** is a shim over the various Python Qt bindings. It is used to write
11Qt binding indenpendent libraries or applications.
12
13If one of the APIs has already been imported, then it will be used.
14
15Otherwise, the shim will automatically select the first available API (PyQt5,
16PySide2, PyQt4 and finally PySide); in that case, you can force the use of one
17specific bindings (e.g. if your application is using one specific bindings and
18you need to use library that use QtPy) by setting up the ``QT_API`` environment
19variable.
20
21PyQt5
22=====
23
24For PyQt5, you don't have to set anything as it will be used automatically::
25
26    >>> from qtpy import QtGui, QtWidgets, QtCore
27    >>> print(QtWidgets.QWidget)
28
29
30PySide2
31======
32
33Set the QT_API environment variable to 'pyside2' before importing other
34packages::
35
36    >>> import os
37    >>> os.environ['QT_API'] = 'pyside2'
38    >>> from qtpy import QtGui, QtWidgets, QtCore
39    >>> print(QtWidgets.QWidget)
40
41PyQt4
42=====
43
44Set the ``QT_API`` environment variable to 'pyqt' before importing any python
45package::
46
47    >>> import os
48    >>> os.environ['QT_API'] = 'pyqt'
49    >>> from qtpy import QtGui, QtWidgets, QtCore
50    >>> print(QtWidgets.QWidget)
51
52PySide
53======
54
55Set the QT_API environment variable to 'pyside' before importing other
56packages::
57
58    >>> import os
59    >>> os.environ['QT_API'] = 'pyside'
60    >>> from qtpy import QtGui, QtWidgets, QtCore
61    >>> print(QtWidgets.QWidget)
62
63"""
64
65from distutils.version import LooseVersion
66import os
67import platform
68import sys
69import warnings
70
71# Version of QtPy
72from ._version import __version__
73
74
75class PythonQtError(RuntimeError):
76    """Error raise if no bindings could be selected."""
77    pass
78
79
80class PythonQtWarning(Warning):
81    """Warning if some features are not implemented in a binding."""
82    pass
83
84
85# Qt API environment variable name
86QT_API = 'QT_API'
87
88# Names of the expected PyQt5 api
89PYQT5_API = ['pyqt5']
90
91# Names of the expected PyQt4 api
92PYQT4_API = [
93    'pyqt',  # name used in IPython.qt
94    'pyqt4'  # pyqode.qt original name
95]
96
97# Names of the expected PySide api
98PYSIDE_API = ['pyside']
99
100# Names of the expected PySide2 api
101PYSIDE2_API = ['pyside2']
102
103# Detecting if a binding was specified by the user
104binding_specified = QT_API in os.environ
105
106# Setting a default value for QT_API
107os.environ.setdefault(QT_API, 'pyqt5')
108
109API = os.environ[QT_API].lower()
110initial_api = API
111assert API in (PYQT5_API + PYQT4_API + PYSIDE_API + PYSIDE2_API)
112
113is_old_pyqt = is_pyqt46 = False
114PYQT5 = True
115PYQT4 = PYSIDE = PYSIDE2 = False
116
117# When `FORCE_QT_API` is set, we disregard
118# any previously imported python bindings.
119if os.environ.get('FORCE_QT_API') is not None:
120    if 'PyQt5' in sys.modules:
121        API = initial_api if initial_api in PYQT5_API else 'pyqt5'
122    elif 'PySide2' in sys.modules:
123        API = initial_api if initial_api in PYSIDE2_API else 'pyside2'
124    elif 'PyQt4' in sys.modules:
125        API = initial_api if initial_api in PYQT4_API else 'pyqt4'
126    elif 'PySide' in sys.modules:
127        API = initial_api if initial_api in PYSIDE_API else 'pyside'
128
129
130if API in PYQT5_API:
131    try:
132        from PyQt5.QtCore import PYQT_VERSION_STR as PYQT_VERSION  # analysis:ignore
133        from PyQt5.QtCore import QT_VERSION_STR as QT_VERSION  # analysis:ignore
134        PYSIDE_VERSION = None
135
136        if sys.platform == 'darwin':
137            macos_version = LooseVersion(platform.mac_ver()[0])
138            if macos_version < LooseVersion('10.10'):
139                if LooseVersion(QT_VERSION) >= LooseVersion('5.9'):
140                    raise PythonQtError("Qt 5.9 or higher only works in "
141                                        "macOS 10.10 or higher. Your "
142                                        "program will fail in this "
143                                        "system.")
144            elif macos_version < LooseVersion('10.11'):
145                if LooseVersion(QT_VERSION) >= LooseVersion('5.11'):
146                    raise PythonQtError("Qt 5.11 or higher only works in "
147                                        "macOS 10.11 or higher. Your "
148                                        "program will fail in this "
149                                        "system.")
150
151            del macos_version
152    except ImportError:
153        API = os.environ['QT_API'] = 'pyside2'
154
155if API in PYSIDE2_API:
156    try:
157        from PySide2 import __version__ as PYSIDE_VERSION  # analysis:ignore
158        from PySide2.QtCore import __version__ as QT_VERSION  # analysis:ignore
159
160        PYQT_VERSION = None
161        PYQT5 = False
162        PYSIDE2 = True
163
164        if sys.platform == 'darwin':
165            macos_version = LooseVersion(platform.mac_ver()[0])
166            if macos_version < LooseVersion('10.11'):
167                if LooseVersion(QT_VERSION) >= LooseVersion('5.11'):
168                    raise PythonQtError("Qt 5.11 or higher only works in "
169                                        "macOS 10.11 or higher. Your "
170                                        "program will fail in this "
171                                        "system.")
172
173            del macos_version
174    except ImportError:
175        API = os.environ['QT_API'] = 'pyqt'
176
177if API in PYQT4_API:
178    try:
179        import sip
180        try:
181            sip.setapi('QString', 2)
182            sip.setapi('QVariant', 2)
183            sip.setapi('QDate', 2)
184            sip.setapi('QDateTime', 2)
185            sip.setapi('QTextStream', 2)
186            sip.setapi('QTime', 2)
187            sip.setapi('QUrl', 2)
188        except (AttributeError, ValueError):
189            # PyQt < v4.6
190            pass
191        from PyQt4.Qt import PYQT_VERSION_STR as PYQT_VERSION  # analysis:ignore
192        from PyQt4.Qt import QT_VERSION_STR as QT_VERSION  # analysis:ignore
193        PYSIDE_VERSION = None
194        PYQT5 = False
195        PYQT4 = True
196    except ImportError:
197        API = os.environ['QT_API'] = 'pyside'
198    else:
199        is_old_pyqt = PYQT_VERSION.startswith(('4.4', '4.5', '4.6', '4.7'))
200        is_pyqt46 = PYQT_VERSION.startswith('4.6')
201
202if API in PYSIDE_API:
203    try:
204        from PySide import __version__ as PYSIDE_VERSION  # analysis:ignore
205        from PySide.QtCore import __version__ as QT_VERSION  # analysis:ignore
206        PYQT_VERSION = None
207        PYQT5 = PYSIDE2 = False
208        PYSIDE = True
209    except ImportError:
210        raise PythonQtError('No Qt bindings could be found')
211
212# If a correct API name is passed to QT_API and it could not be found,
213# switches to another and informs through the warning
214if API != initial_api and binding_specified:
215    warnings.warn('Selected binding "{}" could not be found, '
216                  'using "{}"'.format(initial_api, API), RuntimeWarning)
217
218API_NAME = {'pyqt5': 'PyQt5', 'pyqt': 'PyQt4', 'pyqt4': 'PyQt4',
219            'pyside': 'PySide', 'pyside2':'PySide2'}[API]
220
221if PYQT4:
222    import sip
223    try:
224        API_NAME += (" (API v{0})".format(sip.getapi('QString')))
225    except AttributeError:
226        pass
227