hotkeys: use Alt-m for amend on macOS
[git-cola.git] / qtpy / uic.py
blob6f940530a4225ec5bd0bacac26b2e1d99ac22292
1 from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6
3 if PYQT6:
4 from PyQt6.uic import *
6 elif PYQT5:
7 from PyQt5.uic import *
9 else:
10 __all__ = ["loadUi", "loadUiType"]
12 # In PySide, loadUi does not exist, so we define it using QUiLoader, and
13 # then make sure we expose that function. This is adapted from qt-helpers
14 # which was released under a 3-clause BSD license:
15 # qt-helpers - a common front-end to various Qt modules
17 # Copyright (c) 2015, Chris Beaumont and Thomas Robitaille
19 # All rights reserved.
21 # Redistribution and use in source and binary forms, with or without
22 # modification, are permitted provided that the following conditions are
23 # met:
25 # * Redistributions of source code must retain the above copyright
26 # notice, this list of conditions and the following disclaimer.
27 # * Redistributions in binary form must reproduce the above copyright
28 # notice, this list of conditions and the following disclaimer in the
29 # documentation and/or other materials provided with the
30 # distribution.
31 # * Neither the name of the Glue project nor the names of its contributors
32 # may be used to endorse or promote products derived from this software
33 # without specific prior written permission.
35 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
36 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
37 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
39 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
40 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
41 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
42 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
43 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
44 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
45 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 # Which itself was based on the solution at
49 # https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8
51 # which was released under the MIT license:
53 # Copyright (c) 2011 Sebastian Wiesner <lunaryorn@gmail.com>
54 # Modifications by Charl Botha <cpbotha@vxlabs.com>
56 # Permission is hereby granted, free of charge, to any person obtaining a
57 # copy of this software and associated documentation files (the "Software"),
58 # to deal in the Software without restriction, including without limitation
59 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
60 # and/or sell copies of the Software, and to permit persons to whom the
61 # Software is furnished to do so, subject to the following conditions:
63 # The above copyright notice and this permission notice shall be included in
64 # all copies or substantial portions of the Software.
66 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
67 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
68 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
69 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
70 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
71 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
72 # DEALINGS IN THE SOFTWARE.
74 if PYSIDE6:
75 from PySide6.QtCore import QMetaObject
76 from PySide6.QtUiTools import QUiLoader, loadUiType
77 elif PYSIDE2:
78 from PySide2.QtCore import QMetaObject
79 from PySide2.QtUiTools import QUiLoader
81 try:
82 from xml.etree.ElementTree import Element
84 from pyside2uic import compileUi
86 # Patch UIParser as xml.etree.Elementree.Element.getiterator
87 # was deprecated since Python 3.2 and removed in Python 3.9
88 # https://docs.python.org/3.9/whatsnew/3.9.html#removed
89 from pyside2uic.uiparser import UIParser
91 class ElemPatched(Element):
92 def getiterator(self, *args, **kwargs):
93 return self.iter(*args, **kwargs)
95 def readResources(self, elem):
96 return self._readResources(ElemPatched(elem))
98 UIParser._readResources = UIParser.readResources
99 UIParser.readResources = readResources
100 except ImportError:
101 pass
103 class UiLoader(QUiLoader):
105 Subclass of :class:`~PySide.QtUiTools.QUiLoader` to create the user
106 interface in a base instance.
108 Unlike :class:`~PySide.QtUiTools.QUiLoader` itself this class does not
109 create a new instance of the top-level widget, but creates the user
110 interface in an existing instance of the top-level class if needed.
112 This mimics the behaviour of :func:`PyQt4.uic.loadUi`.
115 def __init__(self, baseinstance, customWidgets=None):
117 Create a loader for the given ``baseinstance``.
119 The user interface is created in ``baseinstance``, which must be an
120 instance of the top-level class in the user interface to load, or a
121 subclass thereof.
123 ``customWidgets`` is a dictionary mapping from class name to class
124 object for custom widgets. Usually, this should be done by calling
125 registerCustomWidget on the QUiLoader, but with PySide 1.1.2 on
126 Ubuntu 12.04 x86_64 this causes a segfault.
128 ``parent`` is the parent object of this loader.
131 QUiLoader.__init__(self, baseinstance)
133 self.baseinstance = baseinstance
135 if customWidgets is None:
136 self.customWidgets = {}
137 else:
138 self.customWidgets = customWidgets
140 def createWidget(self, class_name, parent=None, name=""):
142 Function that is called for each widget defined in ui file,
143 overridden here to populate baseinstance instead.
146 if parent is None and self.baseinstance:
147 # supposed to create the top-level widget, return the base
148 # instance instead
149 return self.baseinstance
151 # For some reason, Line is not in the list of available
152 # widgets, but works fine, so we have to special case it here.
153 if class_name in self.availableWidgets() or class_name == "Line":
154 # create a new widget for child widgets
155 widget = QUiLoader.createWidget(
156 self,
157 class_name,
158 parent,
159 name,
162 else:
163 # If not in the list of availableWidgets, must be a custom
164 # widget. This will raise KeyError if the user has not
165 # supplied the relevant class_name in the dictionary or if
166 # customWidgets is empty.
167 try:
168 widget = self.customWidgets[class_name](parent)
169 except KeyError as error:
170 raise NoCustomWidget(
171 f"No custom widget {class_name} "
172 "found in customWidgets",
173 ) from error
175 if self.baseinstance:
176 # set an attribute for the new child widget on the base
177 # instance, just like PyQt4.uic.loadUi does.
178 setattr(self.baseinstance, name, widget)
180 return widget
182 def _get_custom_widgets(ui_file):
184 This function is used to parse a ui file and look for the <customwidgets>
185 section, then automatically load all the custom widget classes.
188 import importlib
189 from xml.etree.ElementTree import ElementTree
191 # Parse the UI file
192 etree = ElementTree()
193 ui = etree.parse(ui_file)
195 # Get the customwidgets section
196 custom_widgets = ui.find("customwidgets")
198 if custom_widgets is None:
199 return {}
201 custom_widget_classes = {}
203 for custom_widget in list(custom_widgets):
204 cw_class = custom_widget.find("class").text
205 cw_header = custom_widget.find("header").text
207 module = importlib.import_module(cw_header)
209 custom_widget_classes[cw_class] = getattr(module, cw_class)
211 return custom_widget_classes
213 def loadUi(uifile, baseinstance=None, workingDirectory=None):
215 Dynamically load a user interface from the given ``uifile``.
217 ``uifile`` is a string containing a file name of the UI file to load.
219 If ``baseinstance`` is ``None``, the a new instance of the top-level
220 widget will be created. Otherwise, the user interface is created within
221 the given ``baseinstance``. In this case ``baseinstance`` must be an
222 instance of the top-level widget class in the UI file to load, or a
223 subclass thereof. In other words, if you've created a ``QMainWindow``
224 interface in the designer, ``baseinstance`` must be a ``QMainWindow``
225 or a subclass thereof, too. You cannot load a ``QMainWindow`` UI file
226 with a plain :class:`~PySide.QtGui.QWidget` as ``baseinstance``.
228 :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on
229 the created user interface, so you can implemented your slots according
230 to its conventions in your widget class.
232 Return ``baseinstance``, if ``baseinstance`` is not ``None``. Otherwise
233 return the newly created instance of the user interface.
236 # We parse the UI file and import any required custom widgets
237 customWidgets = _get_custom_widgets(uifile)
239 loader = UiLoader(baseinstance, customWidgets)
241 if workingDirectory is not None:
242 loader.setWorkingDirectory(workingDirectory)
244 widget = loader.load(uifile)
245 QMetaObject.connectSlotsByName(widget)
246 return widget
248 if PYSIDE2:
250 def loadUiType(uifile, from_imports=False):
251 """Load a .ui file and return the generated form class and
252 the Qt base class.
254 The "loadUiType" command convert the ui file to py code
255 in-memory first and then execute it in a special frame to
256 retrieve the form_class.
258 Credit: https://stackoverflow.com/a/14195313/15954282
261 import sys
262 from io import StringIO
263 from xml.etree.ElementTree import ElementTree
265 from . import QtWidgets
267 # Parse the UI file
268 etree = ElementTree()
269 ui = etree.parse(uifile)
271 widget_class = ui.find("widget").get("class")
272 form_class = ui.find("class").text
274 with open(uifile, encoding="utf-8") as fd:
275 code_stream = StringIO()
276 frame = {}
278 compileUi(fd, code_stream, indent=0, from_imports=from_imports)
279 pyc = compile(code_stream.getvalue(), "<string>", "exec")
280 exec(pyc, frame)
282 # Fetch the base_class and form class based on their type in the
283 # xml from designer
284 form_class = frame["Ui_%s" % form_class]
285 base_class = getattr(QtWidgets, widget_class)
287 return form_class, base_class