1import os 2 3from . import PYSIDE, PYSIDE2, PYQT4, PYQT5 4from .QtWidgets import QComboBox 5 6 7if PYQT5: 8 9 from PyQt5.uic import * 10 11elif PYQT4: 12 13 from PyQt4.uic import * 14 15else: 16 17 __all__ = ['loadUi'] 18 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 23 # 24 # Copyright (c) 2015, Chris Beaumont and Thomas Robitaille 25 # 26 # All rights reserved. 27 # 28 # Redistribution and use in source and binary forms, with or without 29 # modification, are permitted provided that the following conditions are 30 # met: 31 # 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. 41 # 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. 53 # 54 # Which itself was based on the solution at 55 # 56 # https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8 57 # 58 # which was released under the MIT license: 59 # 60 # Copyright (c) 2011 Sebastian Wiesner <lunaryorn@gmail.com> 61 # Modifications by Charl Botha <cpbotha@vxlabs.com> 62 # 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: 69 # 70 # The above copyright notice and this permission notice shall be included in 71 # all copies or substantial portions of the Software. 72 # 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. 80 81 if PYSIDE: 82 from PySide.QtCore import QMetaObject 83 from PySide.QtUiTools import QUiLoader 84 elif PYSIDE2: 85 from PySide2.QtCore import QMetaObject 86 from PySide2.QtUiTools import QUiLoader 87 88 class UiLoader(QUiLoader): 89 """ 90 Subclass of :class:`~PySide.QtUiTools.QUiLoader` to create the user 91 interface in a base instance. 92 93 Unlike :class:`~PySide.QtUiTools.QUiLoader` itself this class does not 94 create a new instance of the top-level widget, but creates the user 95 interface in an existing instance of the top-level class if needed. 96 97 This mimics the behaviour of :func:`PyQt4.uic.loadUi`. 98 """ 99 100 def __init__(self, baseinstance, customWidgets=None): 101 """ 102 Create a loader for the given ``baseinstance``. 103 104 The user interface is created in ``baseinstance``, which must be an 105 instance of the top-level class in the user interface to load, or a 106 subclass thereof. 107 108 ``customWidgets`` is a dictionary mapping from class name to class 109 object for custom widgets. Usually, this should be done by calling 110 registerCustomWidget on the QUiLoader, but with PySide 1.1.2 on 111 Ubuntu 12.04 x86_64 this causes a segfault. 112 113 ``parent`` is the parent object of this loader. 114 """ 115 116 QUiLoader.__init__(self, baseinstance) 117 118 self.baseinstance = baseinstance 119 120 if customWidgets is None: 121 self.customWidgets = {} 122 else: 123 self.customWidgets = customWidgets 124 125 def createWidget(self, class_name, parent=None, name=''): 126 """ 127 Function that is called for each widget defined in ui file, 128 overridden here to populate baseinstance instead. 129 """ 130 131 if parent is None and self.baseinstance: 132 # supposed to create the top-level widget, return the base 133 # instance instead 134 return self.baseinstance 135 136 else: 137 138 # For some reason, Line is not in the list of available 139 # widgets, but works fine, so we have to special case it here. 140 if class_name in self.availableWidgets() or class_name == 'Line': 141 # create a new widget for child widgets 142 widget = QUiLoader.createWidget(self, class_name, parent, name) 143 144 else: 145 # If not in the list of availableWidgets, must be a custom 146 # widget. This will raise KeyError if the user has not 147 # supplied the relevant class_name in the dictionary or if 148 # customWidgets is empty. 149 try: 150 widget = self.customWidgets[class_name](parent) 151 except KeyError: 152 raise Exception('No custom widget ' + class_name + ' ' 153 'found in customWidgets') 154 155 if self.baseinstance: 156 # set an attribute for the new child widget on the base 157 # instance, just like PyQt4.uic.loadUi does. 158 setattr(self.baseinstance, name, widget) 159 160 return widget 161 162 def _get_custom_widgets(ui_file): 163 """ 164 This function is used to parse a ui file and look for the <customwidgets> 165 section, then automatically load all the custom widget classes. 166 """ 167 168 import sys 169 import importlib 170 from xml.etree.ElementTree import ElementTree 171 172 # Parse the UI file 173 etree = ElementTree() 174 ui = etree.parse(ui_file) 175 176 # Get the customwidgets section 177 custom_widgets = ui.find('customwidgets') 178 179 if custom_widgets is None: 180 return {} 181 182 custom_widget_classes = {} 183 184 for custom_widget in custom_widgets.getchildren(): 185 186 cw_class = custom_widget.find('class').text 187 cw_header = custom_widget.find('header').text 188 189 module = importlib.import_module(cw_header) 190 191 custom_widget_classes[cw_class] = getattr(module, cw_class) 192 193 return custom_widget_classes 194 195 def loadUi(uifile, baseinstance=None, workingDirectory=None): 196 """ 197 Dynamically load a user interface from the given ``uifile``. 198 199 ``uifile`` is a string containing a file name of the UI file to load. 200 201 If ``baseinstance`` is ``None``, the a new instance of the top-level 202 widget will be created. Otherwise, the user interface is created within 203 the given ``baseinstance``. In this case ``baseinstance`` must be an 204 instance of the top-level widget class in the UI file to load, or a 205 subclass thereof. In other words, if you've created a ``QMainWindow`` 206 interface in the designer, ``baseinstance`` must be a ``QMainWindow`` 207 or a subclass thereof, too. You cannot load a ``QMainWindow`` UI file 208 with a plain :class:`~PySide.QtGui.QWidget` as ``baseinstance``. 209 210 :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on 211 the created user interface, so you can implemented your slots according 212 to its conventions in your widget class. 213 214 Return ``baseinstance``, if ``baseinstance`` is not ``None``. Otherwise 215 return the newly created instance of the user interface. 216 """ 217 218 # We parse the UI file and import any required custom widgets 219 customWidgets = _get_custom_widgets(uifile) 220 221 loader = UiLoader(baseinstance, customWidgets) 222 223 if workingDirectory is not None: 224 loader.setWorkingDirectory(workingDirectory) 225 226 widget = loader.load(uifile) 227 QMetaObject.connectSlotsByName(widget) 228 return widget 229