3 # Federico Pellegrin, 2016-2022 (fedepell) adapted for Python
6 This tool helps with finding Python Qt5 tools and libraries,
7 and provides translation from QT5 files to Python code.
9 The following snippet illustrates the tool usage::
19 features = 'py pyqt5',
20 source = 'main.py textures.qrc aboutDialog.ui',
23 Here, the UI description and resource files will be processed
29 Load the "pyqt5" tool.
31 Add into the sources list also the qrc resources files or ui5
32 definition files and they will be translated into python code
33 with the system tools (PyQt5, PySide2, PyQt4 are searched in this
34 order) and then compiled
38 from xml
.sax
import make_parser
39 from xml
.sax
.handler
import ContentHandler
42 ContentHandler
= object
47 from waflib
.Tools
import python
48 from waflib
import Task
, Options
49 from waflib
.TaskGen
import feature
, extension
50 from waflib
.Configure
import conf
51 from waflib
import Logs
55 File extension for the resource (.qrc) files
60 File extension for the user interface (.ui) files
64 class XMLHandler(ContentHandler
):
71 def startElement(self
, name
, attrs
):
74 def endElement(self
, name
):
76 self
.files
.append(str(''.join(self
.buf
)))
77 def characters(self
, cars
):
81 def create_pyrcc_task(self
, node
):
82 "Creates rcc and py task for ``.qrc`` files"
83 rcnode
= node
.change_ext('.py')
84 self
.create_task('pyrcc', node
, rcnode
)
85 if getattr(self
, 'install_from', None):
86 self
.install_from
= self
.install_from
.get_bld()
88 self
.install_from
= self
.path
.get_bld()
89 self
.install_path
= getattr(self
, 'install_path', '${PYTHONDIR}')
90 self
.process_py(rcnode
)
93 def create_pyuic_task(self
, node
):
94 "Create uic tasks and py for user interface ``.ui`` definition files"
95 uinode
= node
.change_ext('.py')
96 self
.create_task('ui5py', node
, uinode
)
97 if getattr(self
, 'install_from', None):
98 self
.install_from
= self
.install_from
.get_bld()
100 self
.install_from
= self
.path
.get_bld()
101 self
.install_path
= getattr(self
, 'install_path', '${PYTHONDIR}')
102 self
.process_py(uinode
)
105 def add_pylang(self
, node
):
106 """Adds all the .ts file into ``self.lang``"""
107 self
.lang
= self
.to_list(getattr(self
, 'lang', [])) + [node
]
110 def apply_pyqt5(self
):
112 The additional parameters are:
114 :param lang: list of translation files (\\*.ts) to process
115 :type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
116 :param langname: if given, transform the \\*.ts files into a .qrc files to include in the binary file
117 :type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
119 if getattr(self
, 'lang', None):
121 for x
in self
.to_list(self
.lang
):
122 if isinstance(x
, str):
123 x
= self
.path
.find_resource(x
+ '.ts')
124 qmtasks
.append(self
.create_task('ts2qm', x
, x
.change_ext('.qm')))
127 if getattr(self
, 'langname', None):
128 qmnodes
= [k
.outputs
[0] for k
in qmtasks
]
129 rcnode
= self
.langname
130 if isinstance(rcnode
, str):
131 rcnode
= self
.path
.find_or_declare(rcnode
+ '.qrc')
132 t
= self
.create_task('qm2rcc', qmnodes
, rcnode
)
133 create_pyrcc_task(self
, t
.outputs
[0])
135 class pyrcc(Task
.Task
):
137 Processes ``.qrc`` files
140 run_str
= '${QT_PYRCC} ${QT_PYRCC_FLAGS} ${SRC} -o ${TGT}'
144 return os
.path
.splitext(self
.inputs
[0].name
)[0]
147 """Parse the *.qrc* files"""
149 Logs
.error('No xml.sax support was found, rcc dependencies will be incomplete!')
152 parser
= make_parser()
153 curHandler
= XMLHandler()
154 parser
.setContentHandler(curHandler
)
155 fi
= open(self
.inputs
[0].abspath(), 'r')
163 root
= self
.inputs
[0].parent
164 for x
in curHandler
.files
:
165 nd
= root
.find_resource(x
)
170 return (nodes
, names
)
173 class ui5py(Task
.Task
):
175 Processes ``.ui`` files for python
178 run_str
= '${QT_PYUIC} ${QT_PYUIC_FLAGS} ${SRC} -o ${TGT}'
181 class ts2qm(Task
.Task
):
183 Generates ``.qm`` files from ``.ts`` files
186 run_str
= '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
188 class qm2rcc(Task
.Task
):
190 Generates ``.qrc`` files from ``.qm`` files
195 """Create a qrc file including the inputs"""
196 txt
= '\n'.join(['<file>%s</file>' % k
.path_from(self
.outputs
[0].parent
) for k
in self
.inputs
])
197 code
= '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
198 self
.outputs
[0].write(code
)
201 self
.find_pyqt5_binaries()
203 # warn about this during the configuration too
205 Logs
.error('No xml.sax support was found, rcc dependencies will be incomplete!')
208 def find_pyqt5_binaries(self
):
210 Detects PyQt5 or PySide2 programs such as pyuic5/pyside2-uic, pyrcc5/pyside2-rcc
214 if getattr(Options
.options
, 'want_pyqt5', True):
215 self
.find_program(['pyuic5'], var
='QT_PYUIC')
216 self
.find_program(['pyrcc5'], var
='QT_PYRCC')
217 self
.find_program(['pylupdate5'], var
='QT_PYLUPDATE')
218 elif getattr(Options
.options
, 'want_pyside2', True):
219 self
.find_program(['pyside2-uic','uic-qt5'], var
='QT_PYUIC')
220 self
.find_program(['pyside2-rcc','rcc-qt5'], var
='QT_PYRCC')
221 self
.find_program(['pyside2-lupdate','lupdate-qt5'], var
='QT_PYLUPDATE')
222 elif getattr(Options
.options
, 'want_pyqt4', True):
223 self
.find_program(['pyuic4'], var
='QT_PYUIC')
224 self
.find_program(['pyrcc4'], var
='QT_PYRCC')
225 self
.find_program(['pylupdate4'], var
='QT_PYLUPDATE')
227 self
.find_program(['pyuic5','pyside2-uic','pyuic4','uic-qt5'], var
='QT_PYUIC')
228 self
.find_program(['pyrcc5','pyside2-rcc','pyrcc4','rcc-qt5'], var
='QT_PYRCC')
229 self
.find_program(['pylupdate5', 'pyside2-lupdate','pylupdate4','lupdate-qt5'], var
='QT_PYLUPDATE')
232 self
.fatal('cannot find the uic compiler for python for qt5')
235 self
.fatal('cannot find the rcc compiler for python for qt5')
237 self
.find_program(['lrelease-qt5', 'lrelease'], var
='QT_LRELEASE')
243 pyqt5opt
=opt
.add_option_group("Python QT5 Options")
244 pyqt5opt
.add_option('--pyqt5-pyqt5', action
='store_true', default
=False, dest
='want_pyqt5', help='use PyQt5 bindings as python QT5 bindings (default PyQt5 is searched first, PySide2 after, PyQt4 last)')
245 pyqt5opt
.add_option('--pyqt5-pyside2', action
='store_true', default
=False, dest
='want_pyside2', help='use PySide2 bindings as python QT5 bindings (default PyQt5 is searched first, PySide2 after, PyQt4 last)')
246 pyqt5opt
.add_option('--pyqt5-pyqt4', action
='store_true', default
=False, dest
='want_pyqt4', help='use PyQt4 bindings as python QT5 bindings (default PyQt5 is searched first, PySide2 after, PyQt4 last)')