|
1
|
|
|
import os |
|
2
|
|
|
|
|
3
|
|
|
from . import PYSIDE, PYSIDE2, PYQT4, PYQT5 |
|
4
|
|
|
from .QtWidgets import QComboBox |
|
5
|
|
|
|
|
6
|
|
|
|
|
7
|
|
|
if PYQT5: |
|
8
|
|
|
|
|
9
|
|
|
from PyQt5.uic import * |
|
10
|
|
|
|
|
11
|
|
|
elif PYQT4: |
|
12
|
|
|
|
|
13
|
|
|
from PyQt4.uic import * |
|
14
|
|
|
|
|
15
|
|
|
else: |
|
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 <[email protected]> |
|
61
|
|
|
# Modifications by Charl Botha <[email protected]> |
|
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 header2module(header): |
|
163
|
|
|
# file pysideuic/uiparser.py |
|
164
|
|
|
# This function is part of the PySide project. |
|
165
|
|
|
# |
|
166
|
|
|
# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). |
|
167
|
|
|
# Copyright (C) 2010 Riverbank Computing Limited. |
|
168
|
|
|
# Copyright (C) 2009 Torsten Marek |
|
169
|
|
|
# |
|
170
|
|
|
# Contact: PySide team <[email protected]> |
|
171
|
|
|
# |
|
172
|
|
|
# This program is free software; you can redistribute it and/or |
|
173
|
|
|
# modify it under the terms of the GNU General Public License |
|
174
|
|
|
# version 2 as published by the Free Software Foundation. |
|
175
|
|
|
# |
|
176
|
|
|
# This program is distributed in the hope that it will be useful, but |
|
177
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
178
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
179
|
|
|
# General Public License for more details. |
|
180
|
|
|
# |
|
181
|
|
|
# You should have received a copy of the GNU General Public License |
|
182
|
|
|
# along with this program; if not, write to the Free Software |
|
183
|
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
|
184
|
|
|
# 02110-1301 USA |
|
185
|
|
|
|
|
186
|
|
|
"""header2module(header) -> string |
|
187
|
|
|
Convert paths to C++ header files to according Python modules |
|
188
|
|
|
>>> header2module("foo/bar/baz.h") |
|
189
|
|
|
'foo.bar.baz' |
|
190
|
|
|
""" |
|
191
|
|
|
if header.endswith(".h"): |
|
192
|
|
|
header = header[:-2] |
|
193
|
|
|
|
|
194
|
|
|
mpath = [] |
|
195
|
|
|
for part in header.split('/'): |
|
196
|
|
|
# Ignore any empty parts or those that refer to the current |
|
197
|
|
|
# directory. |
|
198
|
|
|
if part not in ('', '.'): |
|
199
|
|
|
if part == '..': |
|
200
|
|
|
# We should allow this for Python3. |
|
201
|
|
|
raise SyntaxError("custom widget header file name may not contain '..'.") |
|
202
|
|
|
|
|
203
|
|
|
mpath.append(part) |
|
204
|
|
|
|
|
205
|
|
|
|
|
206
|
|
|
return '.'.join(mpath) |
|
207
|
|
|
|
|
208
|
|
|
def _get_custom_widgets(ui_file): |
|
209
|
|
|
""" |
|
210
|
|
|
This function is used to parse a ui file and look for the <customwidgets> |
|
211
|
|
|
section, then automatically load all the custom widget classes. |
|
212
|
|
|
""" |
|
213
|
|
|
|
|
214
|
|
|
import sys |
|
215
|
|
|
import importlib |
|
216
|
|
|
from xml.etree.ElementTree import ElementTree |
|
217
|
|
|
|
|
218
|
|
|
# Parse the UI file |
|
219
|
|
|
etree = ElementTree() |
|
220
|
|
|
ui = etree.parse(ui_file) |
|
221
|
|
|
|
|
222
|
|
|
# Get the customwidgets section |
|
223
|
|
|
custom_widgets = ui.find('customwidgets') |
|
224
|
|
|
|
|
225
|
|
|
if custom_widgets is None: |
|
226
|
|
|
return {} |
|
227
|
|
|
|
|
228
|
|
|
custom_widget_classes = {} |
|
229
|
|
|
|
|
230
|
|
|
for custom_widget in custom_widgets.getchildren(): |
|
231
|
|
|
|
|
232
|
|
|
cw_class = custom_widget.find('class').text |
|
233
|
|
|
cw_header = custom_widget.find('header').text |
|
234
|
|
|
module = importlib.import_module(header2module(cw_header)) |
|
235
|
|
|
|
|
236
|
|
|
custom_widget_classes[cw_class] = getattr(module, cw_class) |
|
237
|
|
|
|
|
238
|
|
|
return custom_widget_classes |
|
239
|
|
|
|
|
240
|
|
|
def loadUi(uifile, baseinstance=None, workingDirectory=None): |
|
241
|
|
|
""" |
|
242
|
|
|
Dynamically load a user interface from the given ``uifile``. |
|
243
|
|
|
|
|
244
|
|
|
``uifile`` is a string containing a file name of the UI file to load. |
|
245
|
|
|
|
|
246
|
|
|
If ``baseinstance`` is ``None``, the a new instance of the top-level |
|
247
|
|
|
widget will be created. Otherwise, the user interface is created within |
|
248
|
|
|
the given ``baseinstance``. In this case ``baseinstance`` must be an |
|
249
|
|
|
instance of the top-level widget class in the UI file to load, or a |
|
250
|
|
|
subclass thereof. In other words, if you've created a ``QMainWindow`` |
|
251
|
|
|
interface in the designer, ``baseinstance`` must be a ``QMainWindow`` |
|
252
|
|
|
or a subclass thereof, too. You cannot load a ``QMainWindow`` UI file |
|
253
|
|
|
with a plain :class:`~PySide.QtGui.QWidget` as ``baseinstance``. |
|
254
|
|
|
|
|
255
|
|
|
:method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on |
|
256
|
|
|
the created user interface, so you can implemented your slots according |
|
257
|
|
|
to its conventions in your widget class. |
|
258
|
|
|
|
|
259
|
|
|
Return ``baseinstance``, if ``baseinstance`` is not ``None``. Otherwise |
|
260
|
|
|
return the newly created instance of the user interface. |
|
261
|
|
|
""" |
|
262
|
|
|
|
|
263
|
|
|
# We parse the UI file and import any required custom widgets |
|
264
|
|
|
customWidgets = _get_custom_widgets(uifile) |
|
265
|
|
|
|
|
266
|
|
|
loader = UiLoader(baseinstance, customWidgets) |
|
267
|
|
|
|
|
268
|
|
|
if workingDirectory is not None: |
|
269
|
|
|
loader.setWorkingDirectory(workingDirectory) |
|
270
|
|
|
|
|
271
|
|
|
widget = loader.load(uifile) |
|
272
|
|
|
QMetaObject.connectSlotsByName(widget) |
|
273
|
|
|
return widget |
|
274
|
|
|
|