doc: add lcjh to the credits
[git-cola.git] / qtpy / uic.py
blobd26a25a15d46191bb7dfbe4ec78ee420e319223a
1 import os
3 from . import PYSIDE, PYSIDE2, PYQT4, PYQT5
4 from .QtWidgets import QComboBox
7 if PYQT5:
9 from PyQt5.uic import *
11 elif PYQT4:
13 from PyQt4.uic import *
15 else:
17 __all__ = ['loadUi', 'loadUiType']
19 # In PySide, loadUi does not exist, so we define it using QUiLoader, and
20 # then make sure we expose that function. This is adapted from qt-helpers
21 # which was released under a 3-clause BSD license:
22 # qt-helpers - a common front-end to various Qt modules
24 # Copyright (c) 2015, Chris Beaumont and Thomas Robitaille
26 # All rights reserved.
28 # Redistribution and use in source and binary forms, with or without
29 # modification, are permitted provided that the following conditions are
30 # met:
32 # * Redistributions of source code must retain the above copyright
33 # notice, this list of conditions and the following disclaimer.
34 # * Redistributions in binary form must reproduce the above copyright
35 # notice, this list of conditions and the following disclaimer in the
36 # documentation and/or other materials provided with the
37 # distribution.
38 # * Neither the name of the Glue project nor the names of its contributors
39 # may be used to endorse or promote products derived from this software
40 # without specific prior written permission.
42 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
43 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
44 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
45 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
46 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
47 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
48 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
49 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
50 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
51 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
52 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54 # Which itself was based on the solution at
56 # https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8
58 # which was released under the MIT license:
60 # Copyright (c) 2011 Sebastian Wiesner <lunaryorn@gmail.com>
61 # Modifications by Charl Botha <cpbotha@vxlabs.com>
63 # Permission is hereby granted, free of charge, to any person obtaining a
64 # copy of this software and associated documentation files (the "Software"),
65 # to deal in the Software without restriction, including without limitation
66 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
67 # and/or sell copies of the Software, and to permit persons to whom the
68 # Software is furnished to do so, subject to the following conditions:
70 # The above copyright notice and this permission notice shall be included in
71 # all copies or substantial portions of the Software.
73 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
74 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
75 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
76 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
77 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
78 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
79 # DEALINGS IN THE SOFTWARE.
81 if PYSIDE:
82 from PySide.QtCore import QMetaObject
83 from PySide.QtUiTools import QUiLoader
84 try:
85 from pysideuic import compileUi
86 except ImportError:
87 pass
88 elif PYSIDE2:
89 from PySide2.QtCore import QMetaObject
90 from PySide2.QtUiTools import QUiLoader
91 try:
92 from pyside2uic import compileUi
93 except ImportError:
94 pass
96 class UiLoader(QUiLoader):
97 """
98 Subclass of :class:`~PySide.QtUiTools.QUiLoader` to create the user
99 interface in a base instance.
101 Unlike :class:`~PySide.QtUiTools.QUiLoader` itself this class does not
102 create a new instance of the top-level widget, but creates the user
103 interface in an existing instance of the top-level class if needed.
105 This mimics the behaviour of :func:`PyQt4.uic.loadUi`.
108 def __init__(self, baseinstance, customWidgets=None):
110 Create a loader for the given ``baseinstance``.
112 The user interface is created in ``baseinstance``, which must be an
113 instance of the top-level class in the user interface to load, or a
114 subclass thereof.
116 ``customWidgets`` is a dictionary mapping from class name to class
117 object for custom widgets. Usually, this should be done by calling
118 registerCustomWidget on the QUiLoader, but with PySide 1.1.2 on
119 Ubuntu 12.04 x86_64 this causes a segfault.
121 ``parent`` is the parent object of this loader.
124 QUiLoader.__init__(self, baseinstance)
126 self.baseinstance = baseinstance
128 if customWidgets is None:
129 self.customWidgets = {}
130 else:
131 self.customWidgets = customWidgets
133 def createWidget(self, class_name, parent=None, name=''):
135 Function that is called for each widget defined in ui file,
136 overridden here to populate baseinstance instead.
139 if parent is None and self.baseinstance:
140 # supposed to create the top-level widget, return the base
141 # instance instead
142 return self.baseinstance
144 else:
146 # For some reason, Line is not in the list of available
147 # widgets, but works fine, so we have to special case it here.
148 if class_name in self.availableWidgets() or class_name == 'Line':
149 # create a new widget for child widgets
150 widget = QUiLoader.createWidget(self, class_name, parent, name)
152 else:
153 # If not in the list of availableWidgets, must be a custom
154 # widget. This will raise KeyError if the user has not
155 # supplied the relevant class_name in the dictionary or if
156 # customWidgets is empty.
157 try:
158 widget = self.customWidgets[class_name](parent)
159 except KeyError:
160 raise Exception('No custom widget ' + class_name + ' '
161 'found in customWidgets')
163 if self.baseinstance:
164 # set an attribute for the new child widget on the base
165 # instance, just like PyQt4.uic.loadUi does.
166 setattr(self.baseinstance, name, widget)
168 return widget
170 def _get_custom_widgets(ui_file):
172 This function is used to parse a ui file and look for the <customwidgets>
173 section, then automatically load all the custom widget classes.
176 import sys
177 import importlib
178 from xml.etree.ElementTree import ElementTree
180 # Parse the UI file
181 etree = ElementTree()
182 ui = etree.parse(ui_file)
184 # Get the customwidgets section
185 custom_widgets = ui.find('customwidgets')
187 if custom_widgets is None:
188 return {}
190 custom_widget_classes = {}
192 for custom_widget in list(custom_widgets):
194 cw_class = custom_widget.find('class').text
195 cw_header = custom_widget.find('header').text
197 module = importlib.import_module(cw_header)
199 custom_widget_classes[cw_class] = getattr(module, cw_class)
201 return custom_widget_classes
203 def loadUi(uifile, baseinstance=None, workingDirectory=None):
205 Dynamically load a user interface from the given ``uifile``.
207 ``uifile`` is a string containing a file name of the UI file to load.
209 If ``baseinstance`` is ``None``, the a new instance of the top-level
210 widget will be created. Otherwise, the user interface is created within
211 the given ``baseinstance``. In this case ``baseinstance`` must be an
212 instance of the top-level widget class in the UI file to load, or a
213 subclass thereof. In other words, if you've created a ``QMainWindow``
214 interface in the designer, ``baseinstance`` must be a ``QMainWindow``
215 or a subclass thereof, too. You cannot load a ``QMainWindow`` UI file
216 with a plain :class:`~PySide.QtGui.QWidget` as ``baseinstance``.
218 :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on
219 the created user interface, so you can implemented your slots according
220 to its conventions in your widget class.
222 Return ``baseinstance``, if ``baseinstance`` is not ``None``. Otherwise
223 return the newly created instance of the user interface.
226 # We parse the UI file and import any required custom widgets
227 customWidgets = _get_custom_widgets(uifile)
229 loader = UiLoader(baseinstance, customWidgets)
231 if workingDirectory is not None:
232 loader.setWorkingDirectory(workingDirectory)
234 widget = loader.load(uifile)
235 QMetaObject.connectSlotsByName(widget)
236 return widget
238 def loadUiType(uifile, from_imports=False):
239 """Load a .ui file and return the generated form class and
240 the Qt base class.
242 The "loadUiType" command convert the ui file to py code
243 in-memory first and then execute it in a special frame to
244 retrieve the form_class.
246 Credit: https://stackoverflow.com/a/14195313/15954282
249 import sys
250 if sys.version_info >= (3, 0):
251 from io import StringIO
252 else:
253 from io import BytesIO as StringIO
254 from xml.etree.ElementTree import ElementTree
255 from . import QtWidgets
257 # Parse the UI file
258 etree = ElementTree()
259 ui = etree.parse(uifile)
261 widget_class = ui.find('widget').get('class')
262 form_class = ui.find('class').text
264 with open(uifile, 'r') as fd:
265 code_stream = StringIO()
266 frame = {}
268 compileUi(fd, code_stream, indent=0, from_imports=from_imports)
269 pyc = compile(code_stream.getvalue(), '<string>', 'exec')
270 exec(pyc, frame)
272 # Fetch the base_class and form class based on their type in the
273 # xml from designer
274 form_class = frame['Ui_%s' % form_class]
275 base_class = getattr(QtWidgets, widget_class)
277 return form_class, base_class