You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
9.2 KiB

4 years ago
  1. import os
  2. from . import PYSIDE, PYSIDE2, PYQT4, PYQT5
  3. from .QtWidgets import QComboBox
  4. if PYQT5:
  5. from PyQt5.uic import *
  6. elif PYQT4:
  7. from PyQt4.uic import *
  8. else:
  9. __all__ = ['loadUi']
  10. # In PySide, loadUi does not exist, so we define it using QUiLoader, and
  11. # then make sure we expose that function. This is adapted from qt-helpers
  12. # which was released under a 3-clause BSD license:
  13. # qt-helpers - a common front-end to various Qt modules
  14. #
  15. # Copyright (c) 2015, Chris Beaumont and Thomas Robitaille
  16. #
  17. # All rights reserved.
  18. #
  19. # Redistribution and use in source and binary forms, with or without
  20. # modification, are permitted provided that the following conditions are
  21. # met:
  22. #
  23. # * Redistributions of source code must retain the above copyright
  24. # notice, this list of conditions and the following disclaimer.
  25. # * Redistributions in binary form must reproduce the above copyright
  26. # notice, this list of conditions and the following disclaimer in the
  27. # documentation and/or other materials provided with the
  28. # distribution.
  29. # * Neither the name of the Glue project nor the names of its contributors
  30. # may be used to endorse or promote products derived from this software
  31. # without specific prior written permission.
  32. #
  33. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  34. # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  35. # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  36. # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  37. # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  38. # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  39. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  40. # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  41. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  42. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  43. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. #
  45. # Which itself was based on the solution at
  46. #
  47. # https://gist.github.com/cpbotha/1b42a20c8f3eb9bb7cb8
  48. #
  49. # which was released under the MIT license:
  50. #
  51. # Copyright (c) 2011 Sebastian Wiesner <lunaryorn@gmail.com>
  52. # Modifications by Charl Botha <cpbotha@vxlabs.com>
  53. #
  54. # Permission is hereby granted, free of charge, to any person obtaining a
  55. # copy of this software and associated documentation files (the "Software"),
  56. # to deal in the Software without restriction, including without limitation
  57. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  58. # and/or sell copies of the Software, and to permit persons to whom the
  59. # Software is furnished to do so, subject to the following conditions:
  60. #
  61. # The above copyright notice and this permission notice shall be included in
  62. # all copies or substantial portions of the Software.
  63. #
  64. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  65. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  66. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  67. # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  68. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  69. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  70. # DEALINGS IN THE SOFTWARE.
  71. if PYSIDE:
  72. from PySide.QtCore import QMetaObject
  73. from PySide.QtUiTools import QUiLoader
  74. elif PYSIDE2:
  75. from PySide2.QtCore import QMetaObject
  76. from PySide2.QtUiTools import QUiLoader
  77. class UiLoader(QUiLoader):
  78. """
  79. Subclass of :class:`~PySide.QtUiTools.QUiLoader` to create the user
  80. interface in a base instance.
  81. Unlike :class:`~PySide.QtUiTools.QUiLoader` itself this class does not
  82. create a new instance of the top-level widget, but creates the user
  83. interface in an existing instance of the top-level class if needed.
  84. This mimics the behaviour of :func:`PyQt4.uic.loadUi`.
  85. """
  86. def __init__(self, baseinstance, customWidgets=None):
  87. """
  88. Create a loader for the given ``baseinstance``.
  89. The user interface is created in ``baseinstance``, which must be an
  90. instance of the top-level class in the user interface to load, or a
  91. subclass thereof.
  92. ``customWidgets`` is a dictionary mapping from class name to class
  93. object for custom widgets. Usually, this should be done by calling
  94. registerCustomWidget on the QUiLoader, but with PySide 1.1.2 on
  95. Ubuntu 12.04 x86_64 this causes a segfault.
  96. ``parent`` is the parent object of this loader.
  97. """
  98. QUiLoader.__init__(self, baseinstance)
  99. self.baseinstance = baseinstance
  100. if customWidgets is None:
  101. self.customWidgets = {}
  102. else:
  103. self.customWidgets = customWidgets
  104. def createWidget(self, class_name, parent=None, name=''):
  105. """
  106. Function that is called for each widget defined in ui file,
  107. overridden here to populate baseinstance instead.
  108. """
  109. if parent is None and self.baseinstance:
  110. # supposed to create the top-level widget, return the base
  111. # instance instead
  112. return self.baseinstance
  113. else:
  114. # For some reason, Line is not in the list of available
  115. # widgets, but works fine, so we have to special case it here.
  116. if class_name in self.availableWidgets() or class_name == 'Line':
  117. # create a new widget for child widgets
  118. widget = QUiLoader.createWidget(self, class_name, parent, name)
  119. else:
  120. # If not in the list of availableWidgets, must be a custom
  121. # widget. This will raise KeyError if the user has not
  122. # supplied the relevant class_name in the dictionary or if
  123. # customWidgets is empty.
  124. try:
  125. widget = self.customWidgets[class_name](parent)
  126. except KeyError:
  127. raise Exception('No custom widget ' + class_name + ' '
  128. 'found in customWidgets')
  129. if self.baseinstance:
  130. # set an attribute for the new child widget on the base
  131. # instance, just like PyQt4.uic.loadUi does.
  132. setattr(self.baseinstance, name, widget)
  133. return widget
  134. def _get_custom_widgets(ui_file):
  135. """
  136. This function is used to parse a ui file and look for the <customwidgets>
  137. section, then automatically load all the custom widget classes.
  138. """
  139. import sys
  140. import importlib
  141. from xml.etree.ElementTree import ElementTree
  142. # Parse the UI file
  143. etree = ElementTree()
  144. ui = etree.parse(ui_file)
  145. # Get the customwidgets section
  146. custom_widgets = ui.find('customwidgets')
  147. if custom_widgets is None:
  148. return {}
  149. custom_widget_classes = {}
  150. for custom_widget in custom_widgets.getchildren():
  151. cw_class = custom_widget.find('class').text
  152. cw_header = custom_widget.find('header').text
  153. module = importlib.import_module(cw_header)
  154. custom_widget_classes[cw_class] = getattr(module, cw_class)
  155. return custom_widget_classes
  156. def loadUi(uifile, baseinstance=None, workingDirectory=None):
  157. """
  158. Dynamically load a user interface from the given ``uifile``.
  159. ``uifile`` is a string containing a file name of the UI file to load.
  160. If ``baseinstance`` is ``None``, the a new instance of the top-level
  161. widget will be created. Otherwise, the user interface is created within
  162. the given ``baseinstance``. In this case ``baseinstance`` must be an
  163. instance of the top-level widget class in the UI file to load, or a
  164. subclass thereof. In other words, if you've created a ``QMainWindow``
  165. interface in the designer, ``baseinstance`` must be a ``QMainWindow``
  166. or a subclass thereof, too. You cannot load a ``QMainWindow`` UI file
  167. with a plain :class:`~PySide.QtGui.QWidget` as ``baseinstance``.
  168. :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on
  169. the created user interface, so you can implemented your slots according
  170. to its conventions in your widget class.
  171. Return ``baseinstance``, if ``baseinstance`` is not ``None``. Otherwise
  172. return the newly created instance of the user interface.
  173. """
  174. # We parse the UI file and import any required custom widgets
  175. customWidgets = _get_custom_widgets(uifile)
  176. loader = UiLoader(baseinstance, customWidgets)
  177. if workingDirectory is not None:
  178. loader.setWorkingDirectory(workingDirectory)
  179. widget = loader.load(uifile)
  180. QMetaObject.connectSlotsByName(widget)
  181. return widget