Completed
Pull Request — master (#107)
by
unknown
01:11
created

header2module()   B

Complexity

Conditions 5

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
c 1
b 0
f 0
dl 0
loc 45
rs 8.0894
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