klayout/testdata/python/qtbinding.py

734 lines
21 KiB
Python

# KLayout Layout Viewer
# Copyright (C) 2006-2025 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import pya
import unittest
import os
import sys
import gc
testlog = ""
# an event filter class
class EventFilter(pya.QObject):
_log = []
def log(self):
return self._log
def eventFilter(self, obj, event):
self._log.append(type(event).__name__ + ": " + repr(event.type()))
pya.QObject.eventFilter(self, obj, event)
# QAction implementation
class MyAction(pya.QAction):
ce = None
def __init__(self, p, n):
pya.QAction.__init__(self, p)
self.objectName = n
ce = None
def childEvent(self, ev):
if self.ce:
self.ce(ev)
def on_child_event(self, _ce):
self.ce = _ce
class MyStandardItemModel(pya.QStandardItemModel):
# make this method public
def srn(self, rn):
if hasattr(self.__class__, "setRoleNames"):
self.setRoleNames(rn)
else:
self.setItemRoleNames(rn)
# Another event filter
class MyObject(pya.QObject):
ef = None
def eventFilter(self, watched, event):
if self.ef(watched, event):
return True
return pya.QObject.eventFilter(self, watched, event)
def on_event_filter(self, ef):
self.ef = ef
def map2str(dict):
# A helper function to product a "canonical" (i.e. sorted-keys) string
# representation of a dict
keys = list(dict)
for k in keys:
if type(k) is str:
strKeys = []
strDict = {}
for x in keys:
strKeys.append(str(x))
strDict[str(x)] = dict[x]
strings = []
for x in sorted(strKeys):
strings.append(str(x) + ": " + str(strDict[x]))
return "{" + ", ".join(strings) + "}"
strings = []
for x in sorted(keys):
strings.append(str(x) + ": " + str(dict[x]))
return "{" + ", ".join(strings) + "}"
# The Qt binding tests
class QtBindingTest(unittest.TestCase):
def test_00(self):
# all references of PA are released now:
pass
# ...
def test_10(self):
a = MyAction(None, "a")
a.text = "mytext"
a.checkable = True
self.assertEqual(a.isChecked(), False)
a.checked = True
self.assertEqual(a.text, "mytext")
self.assertEqual(a.objectName, "a")
a.text += "."
self.assertEqual(a.text, "mytext.")
self.assertEqual(a.checked, True)
global testlog
testlog = ""
def f(checked):
global testlog
testlog += "[" + str(checked) + "]"
a.triggered(f)
self.assertEqual(testlog, "")
a.trigger() # also toggles checked state
self.assertEqual(testlog, "[False]")
testlog = ""
a.trigger() # also toggles checked state
self.assertEqual(testlog, "[True]")
def test_11(self):
a = pya.QAction(None)
aa = MyAction(a, "aa")
self.assertEqual(aa.objectName, "aa")
# destroying a will also destroy aa
a.destroy()
self.assertEqual(a._destroyed(), True)
self.assertEqual(aa._destroyed(), True)
def test_12(self):
a = pya.QAction(None)
aa = pya.QAction(a)
aa.objectName = "aa"
# destroying a will also destroy aa
a = None
self.assertEqual(aa._destroyed(), True)
def test_13(self):
a = pya.QAction(None)
aa = pya.QAction(a)
aa.objectName = "aa"
aa.text = "aatext"
cc = []
for c in a.children():
cc.append(c.objectName)
self.assertEqual(",".join(cc), "aa")
# aa now is kept by a
aa = None
# fetch aa again
for c in a.children():
if c.objectName == "aa":
aa = c
self.assertEqual(aa != None, True)
self.assertEqual(type(aa), pya.QAction)
self.assertEqual(aa.text, "aatext")
self.assertEqual(aa._destroyed(), False)
def test_20(self):
global no_event
global ce_log
no_event = False
def event_filter(watched, event):
global no_event
return no_event
ef = MyObject()
ef.on_event_filter(event_filter)
def child_event(ce):
global ce_log
ce_log.append(str(ce.added()) + ":" + ce.child().objectName)
ce_log = []
a = MyAction(None, "a")
a.on_child_event(child_event)
a.installEventFilter(ef)
aa = MyAction(None, "aa")
self.assertEqual(",".join(ce_log), "")
aa.setParent(a)
self.assertEqual(",".join(ce_log), "True:aa")
ce_log = []
# destroy aa
aa.destroy()
aa = None
self.assertEqual(",".join(ce_log), "False:aa")
ce_log = []
no_event = True
aa = MyAction(None, "aa")
aa.setParent(a)
self.assertEqual(",".join(ce_log), "")
ce_log = []
no_event = False
aa.destroy()
aa = None
self.assertEqual(",".join(ce_log), "False:aa")
ce_log = []
def test_30(self):
# dialog construction, cleanup, object dependency ...
mw = None
dialog = pya.QDialog(mw)
label = pya.QLabel(dialog)
layout = pya.QHBoxLayout(dialog)
layout.addWidget(label)
dialog = pya.QDialog(mw)
label = pya.QLabel(dialog)
layout = pya.QHBoxLayout(dialog)
layout.addWidget(label)
label.destroy()
dialog = pya.QDialog(mw)
label = pya.QLabel(dialog)
layout = pya.QHBoxLayout(dialog)
layout.addWidget(label)
layout.destroy()
dialog = pya.QDialog(mw)
label = pya.QLabel(dialog)
layout = pya.QHBoxLayout(dialog)
layout.addWidget(label)
dialog.destroy()
dialog = pya.QDialog(mw)
label = pya.QLabel(dialog)
layout = pya.QHBoxLayout(dialog)
layout.addWidget(label)
dialog = None
label = None
layout = None
def test_31(self):
# Optional arguments, enums, QFlag's
mw = None
mb = pya.QMessageBox(pya.QMessageBox.Critical, "title", "text")
self.assertEqual(mb.icon.to_i() != pya.QMessageBox.Warning.to_i(), True)
self.assertEqual(mb.icon.to_i() == pya.QMessageBox.Critical.to_i(), True)
self.assertEqual(mb.standardButtons.to_i() == pya.QMessageBox.NoButton.to_i(), True)
mb = pya.QMessageBox(pya.QMessageBox.Critical, "title", "text", pya.QMessageBox.Ok)
self.assertEqual(mb.standardButtons.to_i() == pya.QMessageBox.Ok.to_i(), True)
mb = pya.QMessageBox(pya.QMessageBox.Critical, "title", "text", pya.QMessageBox.Ok | pya.QMessageBox.Cancel)
self.assertEqual(mb.standardButtons.to_i() == pya.QMessageBox.Ok.to_i() + pya.QMessageBox.Cancel.to_i(), True)
def test_40(self):
# Lifetime management of objects/methods not using QObject.parent
# QTreeWidget (parent)/QTreeWidgetItem (child)
# constructor with parent-like argument:
tw = pya.QTreeWidget()
ti = pya.QTreeWidgetItem(tw)
# strange, but true:
self.assertEqual(ti.parent(), None)
self.assertEqual(tw.topLevelItemCount, 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.topLevelItemCount, 1)
# the tree item belongs to the widget, hence it's destroyed with
# the widget
ti = tw.topLevelItem(0)
tw._destroy()
# gives true, because tw owns ti too.
self.assertEqual(ti._destroyed(), True)
# The same works for insert too
tw = pya.QTreeWidget()
ti = pya.QTreeWidgetItem()
tw.insertTopLevelItem(0, ti)
self.assertEqual(tw.topLevelItemCount, 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.topLevelItemCount, 1)
# the tree item belongs to the widget, hence it's destroyed with
# the widget
ti = tw.topLevelItem(0)
tw._destroy()
# gives true, because tw owns ti
self.assertEqual(ti._destroyed(), True)
# And add:
tw = pya.QTreeWidget()
ti = pya.QTreeWidgetItem()
tw.addTopLevelItem(ti)
self.assertEqual(tw.topLevelItemCount, 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.topLevelItemCount, 1)
# the tree item belongs to the widget, hence it's destroyed with
# the widget
ti = tw.topLevelItem(0)
tw._destroy()
# gives true, because tw owns ti
self.assertEqual(ti._destroyed(), True)
# But the item is released when we take it and add:
tw = pya.QTreeWidget()
ti = pya.QTreeWidgetItem()
tw.addTopLevelItem(ti)
self.assertEqual(tw.topLevelItemCount, 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.topLevelItemCount, 1)
ti = tw.takeTopLevelItem(0)
tw._destroy()
# gives false, because we took ti and tw no longer owns it
self.assertEqual(ti._destroyed(), False)
# And we can destroy a child too
tw = pya.QTreeWidget()
ti = pya.QTreeWidgetItem()
tw.addTopLevelItem(ti)
self.assertEqual(tw.topLevelItemCount, 1)
ti._destroy()
self.assertEqual(tw.topLevelItemCount, 0)
def test_41(self):
# Lifetime management of objects/methods not using QObject.parent
# QTreeWidgetItem (parent)/QTreeWidgetItem (child)
# constructor with parent-like argument (supported by QObject parent/child relationship):
tw = pya.QTreeWidgetItem()
ti = pya.QTreeWidgetItem(tw)
# that's not QObject.parent - this one still is 0 (not seen by RBA)
self.assertEqual(ti.parent(), tw)
self.assertEqual(tw.childCount(), 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.childCount(), 1)
# the tree item belongs to the widget, hence it's destroyed with
# the widget
ti = tw.child(0)
tw._destroy()
# gives true, because tw owns ti too.
self.assertEqual(ti._destroyed(), True)
# The same works for insert too
tw = pya.QTreeWidgetItem()
ti = pya.QTreeWidgetItem()
tw.insertChild(0, ti)
self.assertEqual(tw.childCount(), 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.childCount(), 1)
# the tree item belongs to the widget, hence it's destroyed with
# the widget
ti = tw.child(0)
tw._destroy()
# gives true, because tw owns ti
self.assertEqual(ti._destroyed(), True)
# And add:
tw = pya.QTreeWidgetItem()
ti = pya.QTreeWidgetItem()
tw.addChild(ti)
self.assertEqual(tw.childCount(), 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.childCount(), 1)
# the tree item belongs to the widget, hence it's destroyed with
# the widget
ti = tw.child(0)
tw._destroy()
# gives true, because tw owns ti
self.assertEqual(ti._destroyed(), True)
# But the item is released when we take it and add:
tw = pya.QTreeWidgetItem()
ti = pya.QTreeWidgetItem()
tw.addChild(ti)
self.assertEqual(tw.childCount(), 1)
ti = None
# gives 1, because the tree widget item is kept by
# the tree widget:
self.assertEqual(tw.childCount(), 1)
ti = tw.takeChild(0)
tw._destroy()
# gives false, because we took ti and tw no longer owns it
self.assertEqual(ti._destroyed(), False)
# And we can destroy a child too
tw = pya.QTreeWidgetItem()
ti = pya.QTreeWidgetItem()
tw.addChild(ti)
self.assertEqual(tw.childCount(), 1)
ti._destroy()
self.assertEqual(tw.childCount(), 0)
def test_42(self):
# QKeyEvent and related issues
ef = EventFilter()
widget = pya.QLineEdit()
widget.setText("ABC")
pya.QApplication.processEvents()
widget.installEventFilter(ef)
ke = pya.QKeyEvent(pya.QEvent.KeyPress, pya.Qt.Key_O.to_i(), pya.Qt.ShiftModifier, "O")
pya.QCoreApplication.postEvent(widget, ke)
ke = pya.QKeyEvent(pya.QEvent.KeyPress, pya.Qt.Key_Left.to_i(), pya.Qt.NoModifier)
pya.QCoreApplication.postEvent(widget, ke)
ke = pya.QKeyEvent(pya.QEvent.KeyPress, pya.Qt.Key_P.to_i(), pya.Qt.NoModifier, "p")
pya.QCoreApplication.postEvent(widget, ke)
pya.QApplication.processEvents()
s1 = "QKeyEvent: ShortcutOverride (51)\nQKeyEvent: KeyPress (6)\nQKeyEvent: ShortcutOverride (51)\nQKeyEvent: KeyPress (6)\nQKeyEvent: ShortcutOverride (51)\nQKeyEvent: KeyPress (6)"
s2 = "QKeyEvent: KeyPress (6)\nQKeyEvent: KeyPress (6)\nQKeyEvent: KeyPress (6)"
s3 = "QKeyEvent_Native: ShortcutOverride (51)\nQKeyEvent: KeyPress (6)\nQKeyEvent_Native: ShortcutOverride (51)\nQKeyEvent: KeyPress (6)\nQKeyEvent_Native: ShortcutOverride (51)\nQKeyEvent: KeyPress (6)"
self.assertIn("\n".join(ef.log()), (s1, s2, s3))
ef = None
self.assertEqual(widget.text, "ABCpO")
widget = None
def test_43(self):
# QHash bindings
slm = MyStandardItemModel()
rn = slm.roleNames()
if sys.version_info < (3, 0):
self.assertEqual(map2str(rn), "{0: display, 1: decoration, 2: edit, 3: toolTip, 4: statusTip, 5: whatsThis}")
else:
self.assertEqual(map2str(rn), "{0: b'display', 1: b'decoration', 2: b'edit', 3: b'toolTip', 4: b'statusTip', 5: b'whatsThis'}")
rnNew = slm.roleNames()
rnNew[7] = "blabla"
slm.srn(rnNew)
rn = slm.roleNames()
if sys.version_info < (3, 0):
self.assertEqual(map2str(rn), "{0: display, 1: decoration, 2: edit, 3: toolTip, 4: statusTip, 5: whatsThis, 7: blabla}")
else:
self.assertEqual(map2str(rn), "{0: b'display', 1: b'decoration', 2: b'edit', 3: b'toolTip', 4: b'statusTip', 5: b'whatsThis', 7: b'blabla'}")
def test_44(self):
# Ability to monitor native child objects
parent = pya.QScrollArea()
parent.show() # this makes resize actually change the widget's size
child = parent.viewport
# ensure parent and child are working
self.assertEqual(child._destroyed(), False)
parent.resize(200, 200)
self.assertEqual(child.width > 100, True)
self.assertEqual(child.height > 100, True)
parent.resize(100, 100)
self.assertEqual(child.width <= 100, True)
self.assertEqual(child.height <= 100, True)
# now if we delete the parent, the child needs to become disconnected
parent._destroy()
self.assertEqual(parent._destroyed(), True)
self.assertEqual(child._destroyed(), True)
def test_45(self):
triggered = ""
class TriggerLog:
triggered = ""
def triggered1(self, b):
if b:
self.triggered += "1"
else:
self.triggered += "0"
def triggered0(self):
self.triggered += "*"
# Ability to connect to signals while ignoring arguments and
# to emit signals
b = pya.QPushButton()
log = TriggerLog()
b.clicked(log.triggered1)
self.assertEqual(log.triggered, "")
b.emit_clicked(True)
self.assertEqual(log.triggered, "1")
b.emit_clicked(False)
self.assertEqual(log.triggered, "10")
b.clicked(log.triggered0)
b.emit_clicked(True)
self.assertEqual(log.triggered, "10*")
b.emit_clicked(False)
self.assertEqual(log.triggered, "10**")
# We do the same with free functions since they behave differently in Python:
global trigger_log
trigger_log = ""
def triggered_f0():
global trigger_log
trigger_log += "x"
def triggered_f1(b):
global trigger_log
if b:
trigger_log += "+"
else:
trigger_log += "-"
b.clicked(triggered_f1)
self.assertEqual(trigger_log, "")
b.emit_clicked(True)
self.assertEqual(trigger_log, "+")
b.emit_clicked(False)
self.assertEqual(trigger_log, "+-")
b.clicked(triggered_f0)
b.emit_clicked(True)
self.assertEqual(trigger_log, "+-x")
b.emit_clicked(False)
self.assertEqual(trigger_log, "+-xx")
def test_51(self):
# issue #707 (QJsonValue constructor ambiguous)
if "QJsonValue" in pya.__dict__:
v = pya.QJsonValue("hello")
self.assertEqual(v.toString(), "hello")
self.assertEqual(v.toVariant(), "hello")
self.assertEqual(v.toInt(), 0)
v = pya.QJsonValue(17)
self.assertEqual(v.toString(), "")
self.assertEqual(v.toVariant(), 17)
self.assertEqual(v.toInt(), 17)
v = pya.QJsonValue(2.5)
self.assertEqual(v.toString(), "")
self.assertEqual(v.toVariant(), 2.5)
self.assertEqual(v.toDouble(), 2.5)
v = pya.QJsonValue(True)
self.assertEqual(v.toString(), "")
self.assertEqual(v.toVariant(), True)
self.assertEqual(v.toBool(), True)
def test_52(self):
# issue #708 (Image serialization to QByteArray)
img = pya.QImage(10, 10, pya.QImage.Format_Mono)
img.fill(0)
buf = pya.QBuffer()
img.save(buf, "PNG")
self.assertEqual(len(buf.data) > 100, True)
self.assertEqual(buf.data[0:8], b'\x89PNG\r\n\x1a\n')
def test_53(self):
# issue #771 (QMimeData not working)
mimeData = pya.QMimeData()
mimeData.setData("application/json",'{"test":"test"}')
jsonData = mimeData.data("application/json");
if sys.version_info < (3, 0):
self.assertEqual(str(jsonData), '{"test":"test"}')
else:
self.assertEqual(str(jsonData), 'b\'{"test":"test"}\'')
def test_54(self):
# issue #1029 (Crash for QBrush passed to setData)
item = pya.QTreeWidgetItem()
item.setBackground(0, pya.QBrush(pya.QColor(0xFF, 0xFF, 0x00)))
self.assertEqual(item.background(0).color.red, 255)
self.assertEqual(item.background(0).color.green, 255)
self.assertEqual(item.background(0).color.blue, 0)
def test_55(self):
# addWidget to QHBoxLayout keeps object managed
window = pya.QDialog()
layout = pya.QHBoxLayout(window)
w = pya.QPushButton()
oid = str(w)
layout.addWidget(w)
self.assertEqual(str(layout.itemAt(0).widget()), oid)
# try to kill the object
w = None
# still there
w = layout.itemAt(0).widget()
self.assertEqual(w._destroyed(), False)
self.assertEqual(str(w), oid)
# killing the window kills the layout kills the widget
window._destroy()
self.assertEqual(window._destroyed(), True)
self.assertEqual(layout._destroyed(), True)
self.assertEqual(w._destroyed(), True)
def test_56(self):
# Creating QImage from binary data
bstr = b'\x01\x02\x03\x04\x11\x12\x13\x14\x21\x22\x33\x34' + b'\x31\x32\x33\x34\x41\x42\x43\x44\x51\x52\x53\x54' + b'\x61\x62\x63\x64\x71\x72\x73\x74\x81\x82\x83\x84' + b'\x91\x92\x93\x94\xa1\xa2\xa3\xa4\xb1\xb2\xb3\xb4'
image = pya.QImage(bstr, 3, 4, pya.QImage.Format_ARGB32)
self.assertEqual("%08x" % image.pixel(0, 0), "04030201")
self.assertEqual("%08x" % image.pixel(1, 0), "14131211")
self.assertEqual("%08x" % image.pixel(0, 2), "64636261")
def test_57(self):
# QColor with string parameter (suppressing QLatin1String)
color = pya.QColor("blue")
self.assertEqual(color.name(), "#0000ff")
def test_58(self):
# The various ways to refer to enums
self.assertEqual(pya.Qt.MouseButton(4).to_i(), 4)
self.assertEqual(pya.Qt_MouseButton(4).to_i(), 4)
self.assertEqual(pya.Qt_MouseButton(4).__int__(), 4)
self.assertEqual(pya.Qt_MouseButton(4).__hash__(), 4)
self.assertEqual(int(pya.Qt_MouseButton(4)), 4)
self.assertEqual(str(pya.Qt_MouseButton(1)), "LeftButton")
self.assertEqual(pya.Qt.MouseButton.LeftButton.to_i(), 1)
self.assertEqual(pya.Qt_MouseButton.LeftButton.to_i(), 1)
self.assertEqual(pya.Qt.LeftButton.to_i(), 1)
self.assertEqual((pya.Qt_MouseButton.LeftButton | pya.Qt_MouseButton.RightButton).to_i(), 3)
self.assertEqual(type(pya.Qt_MouseButton.LeftButton | pya.Qt_MouseButton.RightButton).__name__, "Qt_QFlags_MouseButton")
self.assertEqual((pya.Qt.MouseButton.LeftButton | pya.Qt.MouseButton.RightButton).to_i(), 3)
self.assertEqual(type(pya.Qt.MouseButton.LeftButton | pya.Qt.MouseButton.RightButton).__name__, "Qt_QFlags_MouseButton")
self.assertEqual((pya.Qt.LeftButton | pya.Qt.RightButton).to_i(), 3)
self.assertEqual(type(pya.Qt.LeftButton | pya.Qt.RightButton).__name__, "Qt_QFlags_MouseButton")
def test_59(self):
# Enums can act as hash keys
h = {}
h[pya.Qt.MouseButton.LeftButton] = "left"
h[pya.Qt.MouseButton.RightButton] = "right"
self.assertEqual(pya.Qt.MouseButton.LeftButton in h, True)
self.assertEqual(h[pya.Qt.MouseButton.LeftButton], "left")
self.assertEqual(h[pya.Qt.MouseButton.RightButton], "right")
self.assertEqual(pya.Qt.MouseButton.NoButton in h, False)
# run unit tests
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(QtBindingTest)
if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful():
sys.exit(1)