Skip to content

Commit a6feba3

Browse files
authored
Merge pull request #132 from abstractfactory/master
Implement #131
2 parents 41102c9 + e281372 commit a6feba3

File tree

3 files changed

+219
-9
lines changed

3 files changed

+219
-9
lines changed

Qt.py

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,20 @@
1919
- PyQt4
2020
2121
Usage:
22-
>>> import sys
23-
>>> from Qt import QtWidgets
24-
>>> app = QtWidgets.QApplication(sys.argv)
25-
>>> button = QtWidgets.QPushButton("Hello World")
26-
>>> button.show()
27-
>>> app.exec_()
22+
>> import sys
23+
>> from Qt import QtWidgets
24+
>> app = QtWidgets.QApplication(sys.argv)
25+
>> button = QtWidgets.QPushButton("Hello World")
26+
>> button.show()
27+
>> app.exec_()
2828
2929
"""
3030

3131
import os
3232
import sys
33+
import shutil
3334

34-
__version__ = "0.4.3"
35+
__version__ = "0.5.0"
3536

3637
# All unique members of Qt.py
3738
__added__ = list()
@@ -84,6 +85,31 @@ def add(object, name, value, safe=True):
8485
remap(object, name, value, safe)
8586

8687

88+
def convert(lines):
89+
"""Convert compiled .ui file from PySide2 to Qt.py
90+
91+
Arguments:
92+
lines (list): Each line of of .ui file
93+
94+
Usage:
95+
>> with open("myui.py") as f:
96+
.. lines = convert(f.readlines())
97+
98+
"""
99+
100+
def parse(line):
101+
line = line.replace("from PySide2 import", "from Qt import")
102+
line = line.replace("QtWidgets.QApplication.translate", "Qt.translate")
103+
return line
104+
105+
parsed = list()
106+
for line in lines:
107+
line = parse(line)
108+
parsed.append(line)
109+
110+
return parsed
111+
112+
87113
def pyqt5():
88114
import PyQt5.Qt
89115
from PyQt5 import QtCore, uic
@@ -100,6 +126,10 @@ def pyqt5():
100126
add(PyQt5, "__remapped__", __remapped__)
101127
add(PyQt5, "__modified__", __modified__)
102128
add(PyQt5, "load_ui", lambda fname: uic.loadUi(fname))
129+
add(PyQt5, "convert", convert)
130+
add(PyQt5, "translate", lambda
131+
context, sourceText, disambiguation, n: QtCore.QCoreApplication(
132+
context, sourceText, disambiguation, n))
103133

104134
return PyQt5
105135

@@ -150,6 +180,10 @@ def pyqt4():
150180
add(PyQt4, "__remapped__", __remapped__)
151181
add(PyQt4, "__modified__", __modified__)
152182
add(PyQt4, "load_ui", lambda fname: uic.loadUi(fname))
183+
add(PyQt4, "convert", convert)
184+
add(PyQt4, "translate", lambda
185+
context, sourceText, disambiguation, n: QtCore.QCoreApplication(
186+
context, sourceText, disambiguation, None, n))
153187

154188
return PyQt4
155189

@@ -168,6 +202,10 @@ def pyside2():
168202
add(PySide2, "__remapped__", __remapped__)
169203
add(PySide2, "__modified__", __modified__)
170204
add(PySide2, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
205+
add(PySide2, "convert", convert)
206+
add(PySide2, "translate", lambda
207+
context, sourceText, disambiguation, n: QtCore.QCoreApplication(
208+
context, sourceText, disambiguation, n))
171209

172210
return PySide2
173211

@@ -198,6 +236,10 @@ def pyside():
198236
add(PySide, "__remapped__", __remapped__)
199237
add(PySide, "__modified__", __modified__)
200238
add(PySide, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
239+
add(PySide, "convert", convert)
240+
add(PySide, "translate", lambda
241+
context, sourceText, disambiguation, n: QtCore.QCoreApplication(
242+
context, sourceText, disambiguation, None, n))
201243

202244
return PySide
203245

@@ -207,6 +249,60 @@ def log(text, verbose):
207249
sys.stdout.write(text)
208250

209251

252+
def cli(args):
253+
"""Qt.py command-line interface"""
254+
import argparse
255+
256+
parser = argparse.ArgumentParser()
257+
parser.add_argument("--convert",
258+
help="Path to compiled Python module, e.g. my_ui.py")
259+
parser.add_argument("--compile",
260+
help="Accept raw .ui file and compile with native "
261+
"PySide2 compiler.")
262+
parser.add_argument("--stdout",
263+
help="Write to stdout instead of file",
264+
action="store_true")
265+
parser.add_argument("--stdin",
266+
help="Read from stdin instead of file",
267+
action="store_true")
268+
269+
args = parser.parse_args(args)
270+
271+
if args.stdout:
272+
raise NotImplementedError("--stdout")
273+
274+
if args.stdin:
275+
raise NotImplementedError("--stdin")
276+
277+
if args.compile:
278+
raise NotImplementedError("--compile")
279+
280+
if args.convert:
281+
sys.stdout.write("#\n"
282+
"# WARNING: --convert is an ALPHA feature.\n#\n"
283+
"# See https://github.com/mottosso/Qt.py/pull/132\n"
284+
"# for details.\n"
285+
"#\n")
286+
287+
#
288+
# ------> Read
289+
#
290+
with open(args.convert) as f:
291+
lines = convert(f.readlines())
292+
293+
backup = "%s_backup%s" % os.path.splitext(args.convert)
294+
sys.stdout.write("Creating \"%s\"..\n" % backup)
295+
shutil.copy(args.convert, backup)
296+
297+
#
298+
# <------ Write
299+
#
300+
with open(args.convert, "w") as f:
301+
f.write("".join(lines))
302+
303+
sys.stdout.write("Successfully converted \"%s\"\n" % args.convert)
304+
305+
210306
def init():
211307
"""Try loading each binding in turn
212308
@@ -216,6 +312,9 @@ def init():
216312
This means no functions or variables can be called after
217313
this has executed.
218314
315+
For debugging and testing, this module may be accessed
316+
through `Qt.__shim__`.
317+
219318
"""
220319

221320
preferred = os.getenv("QT_PREFERRED_BINDING")
@@ -272,4 +371,4 @@ def init():
272371
raise ImportError("No Qt binding were found.")
273372

274373

275-
init()
374+
cli(sys.argv[1:]) if __name__ == "__main__" else init()

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,27 @@ Using the OS path separator (`os.pathsep`) which is `:` on Unix systems and `;`
135135

136136
<br>
137137

138-
##### Load Qt Designer .ui files
138+
##### Compile Qt Designer files
139+
140+
> WARNING - ALPHA FUNCTIONALITY<br>
141+
> See [#132](https://github.com/mottosso/Qt.py/pull/132) for details.
142+
143+
`.ui` files compiled via `pyside2-uic` inherently contain traces of PySide2 - e.g. the line `from PySide2 import QtGui`.
144+
145+
In order to use these with Qt.py, or any other binding, one must first erase such traces and replace them with cross-compatible code.
146+
147+
```bash
148+
$ pyside2-uic my_ui.ui -o my_ui.py
149+
$ python -m Qt --convert my_ui.py
150+
# Creating "my_ui_backup.py"..
151+
# Successfully converted "my_ui.py"
152+
```
153+
154+
Now you may use the file as you normally would, with Qt.py
155+
156+
<br>
157+
158+
##### Load Qt Designer files
139159

140160
The `uic.loadUi` function of PyQt4 and PyQt5 as well as the `QtUiTools.QUiLoader().load` function of PySide/PySide2 are mapped to a convenience function `load_ui`.
141161

tests.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,97 @@ def test_vendoring():
159159
) == 0
160160

161161

162+
def test_convert_simple():
163+
"""python -m Qt --convert works in general"""
164+
before = """\
165+
from PySide2 import QtCore, QtGui, QtWidgets
166+
167+
class Ui_uic(object):
168+
def setupUi(self, uic):
169+
self.retranslateUi(uic)
170+
171+
def retranslateUi(self, uic):
172+
self.pushButton_2.setText(
173+
QtWidgets.QApplication.translate("uic", "NOT Ok", None, -1))
174+
""".split("\n")
175+
176+
after = """\
177+
from Qt import QtCore, QtGui, QtWidgets
178+
179+
class Ui_uic(object):
180+
def setupUi(self, uic):
181+
self.retranslateUi(uic)
182+
183+
def retranslateUi(self, uic):
184+
self.pushButton_2.setText(
185+
Qt.translate("uic", "NOT Ok", None, -1))
186+
""".split("\n")
187+
188+
import Qt
189+
assert Qt.convert(before) == after, after
190+
191+
192+
def test_convert_idempotency():
193+
"""Converting a converted file produces an identical file"""
194+
before = """\
195+
from PySide2 import QtCore, QtGui, QtWidgets
196+
197+
class Ui_uic(object):
198+
def setupUi(self, uic):
199+
self.retranslateUi(uic)
200+
201+
def retranslateUi(self, uic):
202+
self.pushButton_2.setText(
203+
QtWidgets.QApplication.translate("uic", "NOT Ok", None, -1))
204+
"""
205+
206+
after = """\
207+
from Qt import QtCore, QtGui, QtWidgets
208+
209+
class Ui_uic(object):
210+
def setupUi(self, uic):
211+
self.retranslateUi(uic)
212+
213+
def retranslateUi(self, uic):
214+
self.pushButton_2.setText(
215+
Qt.translate("uic", "NOT Ok", None, -1))
216+
"""
217+
218+
fname = os.path.join(self.tempdir, "idempotency.py")
219+
with open(fname, "w") as f:
220+
f.write(before)
221+
222+
import Qt
223+
224+
os.chdir(self.tempdir)
225+
Qt.__shim__.cli(args=["--convert", "idempotency.py"])
226+
227+
with open(fname) as f:
228+
assert f.read() == after
229+
230+
Qt.__shim__.cli(args=["--convert", "idempotency.py"])
231+
232+
with open(fname) as f:
233+
assert f.read() == after
234+
235+
236+
def test_convert_backup():
237+
"""Converting produces a backup"""
238+
239+
fname = os.path.join(self.tempdir, "idempotency.py")
240+
with open(fname, "w") as f:
241+
f.write("")
242+
243+
import Qt
244+
245+
os.chdir(self.tempdir)
246+
Qt.__shim__.cli(args=["--convert", "idempotency.py"])
247+
248+
assert os.path.exists(
249+
os.path.join(self.tempdir, "%s_backup%s" % os.path.splitext(fname))
250+
)
251+
252+
162253
def test_meta_add():
163254
"""Qt.add() appends to __added__"""
164255
import types

0 commit comments

Comments
 (0)