PQty 4: Deeper unstanding of signals definition - pyqt4

I have a question concerning the Signal & Slot example on: http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html
Kindly look at the slight modification:
from PyQt4.QtCore import QObject, pyqtSignal
class Foo(QObject):
# Define a new signal called 'trigger' that has no arguments.
trigger = pyqtSignal() # Line (1) this is working
def __init__(self):
super(Foo, self).__init__()
#self.trigger = pyqtSignal() # Line (2) If i would activate this instead of Line (1) I would get an error
def connect_and_emit_trigger(self):
# Connect the trigger signal to a slot.
self.trigger.connect(self.handle_trigger)
# Emit the signal.
self.trigger.emit()
def handle_trigger(self):
# Show that the slot has been called.
print "trigger signal received"
if __name__=="__main__":
a=Foo()
a.connect_and_emit_trigger()
This is still working. However, If I would comment Line (1) and activate Line (2) instead, I get the error message:
Python 2.7.8 (default, Jul 2 2014, 19:48:49) [MSC v.1500 64 bit (AMD64)] on xxxx, Standard
>>> Exception "unhandled AttributeError"
'PyQt4.QtCore.pyqtSignal' object has no attribute 'connect'
I don't understand this, because both formulations look similar for me. Who can explain me what is going on here?

Related

PyGtk Segmentation with signal "activate link" in a markup-text label

The MWE below simply creates a label with a hyper-text in it. Most of the time (but not every time) it causes a Segmentation fault when I click on that link.
I assume that I missunderstand something about PyGtk and I use it the wrong way?
That is the error output:
Window._on_activate_link()
Fatal Python error: Segmentation fault
Current thread 0x00007fa2718a7740 (most recent call first):
File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 1641 in main
File "./window.py", line 45 in <module>
Speicherzugriffsfehler
Here are informations about my system and versions
Linux-4.19.0-14-amd64-x86_64-with-debian-10.8
Python 3.7.3 CPython
Gtk 3.0
GIO 3.30.4
Cairo 1.16.2
This is the MWE
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import faulthandler; faulthandler.enable()
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class FeedybusWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
hbox = Gtk.Box(spacing=5)
self.add(hbox)
self._feed_label = Gtk.Label(label='')
self._feed_label.set_use_markup(True)
self._feed_label.set_markup('Click me')
self._feed_label.connect('activate-link', self._on_activate_link)
hbox.pack_start(self._feed_label, True, True, 20)
# EVENT: destroy
self.connect('delete-event', self._on_delete_event)
self.connect('destroy', self._on_destroy)
def _on_activate_link(self, label, uri):
print('Window._on_activate_link()')
if uri == 'renamelabel':
self._feed_label.set_markup('Click me AGAIN')
return True
return False
def _on_delete_event(self, window, event=None):
self.destroy()
def _on_destroy(self, caller):
Gtk.main_quit()
if __name__ == '__main__':
window = FeedybusWindow()
window.show_all()
Gtk.main()
EDIT: Of course I can use other GUI elements instead of a Gtk.Label. But this would be a workaround not a solution. The focus of my question is if I am using the Gtk package the wrong way or that there maybe is a bug in Gtk that I should report.
This must be a bug (maybe: https://gitlab.gnome.org/GNOME/gtk/-/issues/1498). I can't see any problems with your code, and I can reproduce the crash on my Ubuntu 20.04 system as well.
It seems like setting the label in the activate-link handler is the problem.
FWIW: A workaround is to set it outside the handler like this:
def _on_activate_link(self, label, uri):
print('Window._on_activate_link()')
if uri == 'renamelabel':
Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self._rename)
return False
def _rename(self):
self._feed_label.set_markup('Click me AGAIN')

PyQt 5.6: connecting to a DBus signal hangs

I'm trying to connect a slot to a signal emitted through DBus in PyQt 5.6 with Python 3.5.
When I run my script like this QDBUS_DEBUG=1 python3 qtdbustest.py it never reaches the call to print('Connected') but instead just hangs at the bus.connect(...) call. The signal is seen on the bus as evident in the debug output:
QDBusConnectionPrivate(0x7f3e60002b00) : connected successfully
QDBusConnectionPrivate(0x7f3e60002b00) got message (signal):
QDBusMessage(type=Signal, service="org.freedesktop.DBus",
path="/org/freedesktop/DBus", interface="org.freedesktop.DBus",
member="NameAcquired", signature="s", contents=(":1.137") )
QDBusConnectionPrivate(0x7f3e60002b00) delivery is suspended
Here is my minimal working example:
#!/usr/bin/python3
import sys
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QApplication
from PyQt5.QtDBus import QDBusConnection, QDBusMessage
class DbusTest(QObject):
def __init__(self):
super(DbusTest, self).__init__()
bus = QDBusConnection.systemBus()
bus.connect(
'org.freedesktop.DBus',
'/org/freedesktop/DBus',
'org.freedesktop.DBus',
'NameAcquired',
self.testMessage
)
print('Connected')
#pyqtSlot(QDBusMessage)
def testMessage(self, msg):
print(msg)
if __name__ == '__main__':
app = QApplication(sys.argv)
discoverer = DbusTest()
sys.exit(app.exec_())
What am I doing wrong? There must be something I overlooked so that the call to bus.connect(...) actually returns.
I was able to fix your example like this:
bus = QDBusConnection.systemBus()
bus.registerObject('/', self)
bus.connect( ...
However, I have to admit I don't exactly understand why it works (which is to say, I couldn't find any corroborating documentation). It does seem to make sense that you'd need to register the receiver object before attempting to make the connection, though.

pyside signal not fired with pyinstaller when communicating between threads

I am trying to build an .app for my pyside gui and I'm having a problem when I use signals to communicate between the main thread and another one. When I run the python code, everything works fine. The problem only occurs when I use the .app built from pyinstaller.
The project is quite big, so it's hard to include any code, so i'll try to explain what I'm doing.
My second thread is used to call some functions from a dylib which controls our devices. The main thread uses a callback to tell the dylib to stop what it's doing. The dylib calls the callback function in a loop, and stops if the value returned from the main thread is different than 0.
As I mentioned, when I run the Python code (under Windows, Ubuntu, Mac), it works perfectly. But when using the .app built with pyinstaller, it looks like the signal is not sent from the main thread. While debugging, I printed stuff and eventually saw that the dylib doesn't receive the returned value from the callback. The dylib does use the callback, and I can see the expected data in the main thread.
So why would the signal not be fired with frozen python code?
Has anybody ever encounter a similar problem?
If you could give me some debugging advices, it would be really helpful?
EDIT
I managed to write a !little code which reproduces the problem. When I run the python code, pressing "ok" followed by "Cancel" sets the message "DLL got the value" in the widget. When freezing the code with Pyinstaller, under mac, the message never gets set on the widget. On Windows 7, everything is fine.
EDIT
Actually, no need for the shared library to cause the problem. The problem occurs only with the Python code.
Here is the Python code:
import sys
from PySide import QtGui
from PySide.QtCore import QThread, Signal, QObject
def functionInDLL(callback):
return_value = 0
while (return_value == 0):
return_value = callback(2)
class MyThread(QThread):
str_signal = Signal(str)
def __init__(self):
QThread.__init__(self)
self.return_value = 0
def returnValueToDll(self, data=0):
return self.return_value
def run(self):
if functionInDLL(self.returnValueToDll) == 42:
print"DLL got the value."
self.str_signal.emit("DLL got the value.")
else:
print"DLL did not get the value."
self.str_signal.emit("DLL did not get the value.")
self.exec_()
def setValueSentToDll(self, data):
self.return_value = data
class MainThreadClass(QObject):
int_signal = Signal(int)
def __init__(self):
super(MainThreadClass, self).__init__()
self.test_thread = MyThread()
self.int_signal.connect(self.test_thread.setValueSentToDll)
def startMyThread(self):
self.test_thread.start()
def sendStopValueToDLL(self):
self.int_signal.emit(42)
class MyGUI(QtGui.QWidget):
def __init__(self):
super(MyGUI, self).__init__()
self.text_information = QtGui.QLabel(text="No started yet...")
self.dll_information = QtGui.QLabel(text="")
self.ok_button = QtGui.QPushButton("OK")
self.cancel_button = QtGui.QPushButton("Cancel")
self.close_button = QtGui.QPushButton("Close")
label_hbox = QtGui.QHBoxLayout()
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.ok_button)
hbox.addWidget(self.cancel_button)
hbox.addWidget(self.close_button)
vbox = QtGui.QVBoxLayout()
label_hbox.addWidget(self.text_information)
label_hbox.addWidget(self.dll_information)
vbox.addLayout(label_hbox)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.ok_button.clicked.connect(self.onOk)
self.cancel_button.clicked.connect(self.onCancel)
self.close_button.clicked.connect(self.onClose)
self.show()
self.main_thread = MainThreadClass()
self.main_thread.test_thread.str_signal.connect(lambda data: self.setDllStatusOnWidget(data))
def onOk(self):
self.main_thread.startMyThread()
self.text_information.setText("Started")
self.dll_information.setText("")
def onCancel(self):
self.main_thread.sendStopValueToDLL()
self.text_information.setText("Canceled")
def onClose(self):
self.main_thread.test_thread.exit()
self.close()
def setDllStatusOnWidget(self, text=""):
self.dll_information.setText(text)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = MyGUI()
sys.exit(app.exec_())
Thanks
Using Python2.7, Mac Pro Yosemite
Thanks to codewarrior, my bug was solved.
When I replaced the line:
self.main_thread.test_thread.str_signal.connect(lambda data: self.setDllStatusOnWidget(data))
by this line, removing the lambda:
self.main_thread.test_thread.str_signal.connect(self.setDllStatusOnWidget)
See the link for more details. I hope this can help other people.
pyinstaller_issue

Cairo introspection errors in Eclipse with PyDev `TypeError: Couldn't find conversion for foreign struct 'cairo.Context'`

I am working on adding a printer interface to some home-brewed Python3 code with a Gtk3 UI, using (mostly) Eclipse Indigo with the PyDev plugin.
While developing the PrintOperation callbacks I found a problem where apparently the gi-introspection fails to find the right underlying library struct for the Cairo Context. The error reported in the console is:
Traceback (most recent call last):
File "/home/bob/Projects/MovieList/src/MovieList/MovieListIO.py", line 203, in on_printDialog_draw_page
cr = context.get_cairo_context()
File "/usr/lib/python3/dist-packages/gi/types.py", line 43, in function
return info.invoke(*args, **kwargs)
TypeError: Couldn't find conversion for foreign struct 'cairo.Context'
At first I thought this was something to do with Eclipse and/or PyDev, because I could run the program within Idle without any error messages. But then I found that when the program was packaged for deployment with the built-in command-line Python tools, the installed version also gave the error. So, I wrote a couple of test scripts abstracting the printer functionality to try to isolate what was going on. In both cases, the key line is in the on_printOperation_draw_page() callback (marked with comments).
Here is the first test script (Script 1, printTestPdf.py), which loads a pdf file using Poppler, and prints it using the system print dialog:
#!/usr/bin/env python3
import os
from gi.repository import Gtk, Poppler
testFile = 'file://' + os.path.join(os.getcwd(), 'printTestPdf.pdf')
pdfDocument = Poppler.Document.new_from_file(testFile, None)
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Pdf Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
"""
Handler for the button click.
"""
printOperation = Gtk.PrintOperation()
printOperation.connect('draw-page', self.on_printOperation_draw_page)
printOperation.set_job_name('Print Pdf Test')
printOperation.set_n_pages(pdfDocument.get_n_pages())
printOperation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
parent=self)
def on_printOperation_draw_page(self, printOperation, context, pageNo):
"""
Handler for the draw-page signal from the printOperation.
"""
cr = context.get_cairo_context() # <-- THIS IS THE LINE
page = pdfDocument.get_page(pageNo)
page.render_for_printing(cr)
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
This is the second script (Script 2, printTestHtml.py), which is almost identical, except it loads an HTML file for printing using weasyprint:
#!/usr/bin/env python3
import os
from gi.repository import Gtk
from weasyprint import HTML
testFile = os.path.join(os.getcwd(), 'printTestHtml.html')
pdfDocument = HTML(filename=testFile).render()
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Html Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
"""
Handler for the button click.
"""
printOperation = Gtk.PrintOperation()
printOperation.connect('begin-print', self.on_printOperation_begin_print)
printOperation.connect('draw-page', self.on_printOperation_draw_page)
printOperation.set_job_name('Print HTML Test')
printOperation.set_n_pages(len(pdfDocument.pages))
printOperation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
parent=self)
def on_printOperation_draw_page(self, printOperation, context, pageNo):
"""
Handler for the draw-page signal from the printOperation.
"""
cr = context.get_cairo_context() # <-- THIS IS THE LINE
page = pdfDocument.pages[pageNo]
page.paint(cr) # <-- there is a separate issue here
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
Both scripts generate an internal pdf document, which is used to render each page on request via the PrintOperation draw_page callback.
Now, whether and how the scripts succeed or fail depends on the context in which they are run. Script 1 always works, except if it is run after a failure of Script 2 in Idle. Script 2 always generates the error message as reported above when run in Eclipse. In Idle, Script 2's behaviour is complex. Sometimes it fails due to a second problem (marked), and does not exhibit the first failure. However, for reasons I have yet to establish, every so often it generates the original error, and when it does, it keeps doing it and Script 1 show the error too, until Idle is re-booted. Running directly from the command line matches the behaviour in Eclipse. I have tried to summarise this behaviour below:
* Eclipse
- Script 1: Always OK
- Script 2: Always Fails
* Command line
- Script 1: Always OK
- Script 2: Always Fails
* Idle
- Script 1: OK, except after failure of Script 2
- Script 2: Intermittent Fail. Knock-on to future runs (up to next error)
This pattern of failure may help determine what the root problem is, but it is beyond me to understand it.
Ignoring the bizarre behaviour in Idle, it is possible the difference between Script 1 and Script 2 holds a clue to my original problem. Why does Script 1 run successfully, while Script 2 generates the introspection error?
If you can offer any suggestions as to what is going wrong I would be most grateful. If you can come up with a solution I will be delighted!
In view of the lack of response I have come up with the following workaround, which uses WebKit instead of weasyprint to do the parsing, rendering and administration of the printing from html:
#!/usr/bin/env python3
import os
from gi.repository import Gtk, WebKit
testFile = 'file://' + os.path.join(os.getcwd(), 'printTestHtml.html')
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Html WebKit Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
webView = WebKit.WebView()
webView.load_uri(testFile)
webFrame = webView.get_main_frame()
webFrame.print_full(Gtk.PrintOperation(),
Gtk.PrintOperationAction.PRINT_DIALOG)
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
What I think is going on is that somehow weasyprint interferes with the introspection process. I have raised this as a bug on the weasyprint home page on github.
Just in case it helps I was looking for a solution to the cairo error.
Had this cairo error happen on my RPi3 with Pandas and Matplotlib. The plot window was showing up blank.
I had to run sudo apt-get install python-gobject-cairo
Based on this: https://github.com/rbgirshick/py-faster-rcnn/issues/221

The difference between PyQt and Qt when handling user defined signal/slot

Well, I am familiar with Qt, but when using PyQt, the syntax of signal/slot really confused me.
When using C++/Qt, the compiler will give you a hint where you are wrong about the signal/slot, but the PyQt default configuration doesn't give a hint about error. Is there a ways or such as debug trigger mode to enable PyQt to display more information?
the Code is as following:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class workThread(QThread):
def __init__(self,parent = None):
super(workThread,self).__init__(parent)
self.mWorkDoneSignal = pyqtSignal() ## some people say this should be defined as clas member, however, I defined it as class member and still fails.
def run(self):
print "workThread start"
time.sleep(1)
print "workThread stop"
print self.emit(SIGNAL("mWorkDoneSignal"))
class MainWidget(QWidget):
def __init__(self , parent = None):
super(MainWidget,self).__init__(parent)
#pyqtSlot()
def display(self):
print "dispaly"
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
c = workThread()
d = MainWidget()
##In Qt, when using QObject::connect or such things, the return value will show the
## signal/slot binding is success or failed
print QObject.connect(c,SIGNAL('mWorkDoneSignal()'),d,SLOT('display()'))
c.start()
d.show()
app.exec_()
In C++,the QObject::connect return value will show the signal/slot binding is success or not. In PyQt, the return value is True, but it doesn't trigger the slot.
My Question:
1) Is the signal shoud be a class member or instance member?
2) If QObject.connect 's return value can't give the hint of the binding is success or not, is there other ways to detect it?
I want to bind the signal/slot outside the signal sender and slot receiver, so I perfer to use the QObject.connect ways. But how can I write this correct, I tried the following ways,both fail.
QObject.connect(c,SIGNAL('mWorkDoneSignal'),d,SLOT('display'))
QObject.connect(c,SIGNAL('mWorkDoneSignal()'),d,SLOT('display()'))
First, you should really use new style signals with pyqt. In fact, QObject.connect and QObject.emit will not even be there anymore in PyQt5.
def __init__(self,parent = None):
super(workThread,self).__init__(parent)
self.mWorkDoneSignal = pyqtSignal()
This creates an unbound signal and assigns it to a instance variable mWorkDoneSignal, wich dosn't really have an effect. If you want to create an signal, then you really have to declare it on the class.
So if you didn't really create a signal here, then why did this call succeed:
QObject.connect(c,SIGNAL('mWorkDoneSignal()'),d,SLOT('display()'))
The answer lies in the handling of old style signals by PyQt4:
The act of emitting a PyQt4 signal implicitly defines it.
For that reason when you connect a signal to a slot, only the existence of the slot is checked. The signal itself doesn't really need to exist at that point, so the call will always succeed unless the slot doesn't exist.
I tried the following ways,both fail.
QObject.connect(c,SIGNAL('mWorkDoneSignal'),d,SLOT('display'))
QObject.connect(c,SIGNAL('mWorkDoneSignal()'),d,SLOT('display()'))
The first fails because display (without parenthesis) isn't a valid slot.
The second succeeds. The reason it doesn't work is because you emit mWorkDoneSignal, but what you actually need to emit is:
self.emit(SIGNAL("mWorkDoneSignal()"))
Using new style signals, there's no way to mess things like this up:
from utils import sigint
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class workThread(QThread):
mWorkDoneSignal = pyqtSignal()
def __init__(self,parent = None):
super(workThread,self).__init__(parent)
def run(self):
print "workThread start"
time.sleep(1)
print "workThread stop"
self.mWorkDoneSignal.emit()
class MainWidget(QWidget):
def __init__(self , parent = None):
super(MainWidget,self).__init__(parent)
#pyqtSlot()
def display(self):
print "dispaly"
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
c = workThread()
d = MainWidget()
c.mWorkDoneSignal.connect(d.display)
c.start()
d.show()
app.exec_()

Resources