PyQt5 setLayout function causing program to crash? - pyqt

I am using Mark Summerfield's Rapid GUI programming book, which was written for PyQt4, and I am using PyQt5. Some things must be different.
Can anybody see why this is failing on my Linux machine that runs Ubuntu 13.04? It runs on Mint 15 but sometimes ends with a segmentation fault. I think it has to do with the difference between PyQt4 and PyQt5, and I have been looking into the C++ implementation at the qt-project.org website. So far I can tell that QVBoxLayout does inherit from QDialog, and it does have a setLayout function. However, commenting out the last line in the _init_ function will allow the program to run without crashing, but also without any widgets added to the QDialog box.
import sys
import PyQt5.QtCore
import PyQt5.QtGui
import PyQt5.QtWidgets
class Form(PyQt5.QtWidgets.QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.browser = PyQt5.QtWidgets.QTextBrowser()
self.lineEdit = PyQt5.QtWidgets.QLineEdit("default statement here")
self.lineEdit.selectAll()
layout = PyQt5.QtWidgets.QVBoxLayout()
layout.addWidget(self.browser)
layout.addWidget(self.lineEdit)
self.setLayout(layout) # <--- program seems to crash here
app = PyQt5.QtWidgets.QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
I get an initial error message like this, repeated about 10 times:
(python3:9896): Gtk-CRITICAL **: IA__gtk_widget_style_get: assertion `GTK_IS_WIDGET (widget)' failed
Then it is followed by the following block, which repeats until I kill the program:
QXcbShmImage: shmget() failed (22) for size -524284 (65535x65535)
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::translate: Painter not active
QPainter::save: Painter not active
QPainter::setClipRect: Painter not active
QPainter::pen: Painter not active
QPainter::setPen: Painter not active
QPainter::pen: Painter not active
QPainter::setPen: Painter not active
QPainter::restore: Unbalanced save/restore
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setClipRect: Painter not active
[etc, etc, etc...]

The problem is the size of the QTextBrowser.
See this bug:
https://bugreports.qt-project.org/browse/QTBUG-32527

Related

Python3 + Pillow + QT5: Crash when I resize a label containing an image

I'm getting a message box: "Python has stopped working" when I load an image into a QLabel in a window that is already visible. Selecting debug shows: an unhandled Win32 exception occurred in Python.exe.
If I load the image into the label before showing the window, it displays correctly.
Here's the stripped down code:
#!/usr/bin/etc python
import sys
import os
import stat
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import *
from PIL.ImageQt import *
def update(label):
filename = r"C:\Users\me\Pictures\images\00000229.jpg"
im1 = Image.open(filename)
print ("Read ({},{})".format(im1.width, im1.height))
im2 = im1.rotate(90, expand=True)
print("Rotate ({},{})".format(im2.width, im2.height))
im2.thumbnail((1200,1200))
print("Thumbnail({},{})".format(im2.width, im2.height))
qimage = ImageQt(im2)
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
app = QApplication(sys.argv)
desktop = QDesktopWidget()
deskGeometry = desktop.availableGeometry()
print("desktop ({},{})".format(deskGeometry.width(), deskGeometry.height()))
window = QFrame()
# If you let QT pick the sizes itself, it picks dumb ones, then complains
# 'cause they are dumb
window.setMinimumSize(300, 200)
window.setMaximumSize(deskGeometry.width(), deskGeometry.height())
label = QLabel()
#call update here: no crash
caption = QLabel()
caption.setText("Hello world")
box = QVBoxLayout()
box.addWidget(label)
box.addWidget(caption)
#call update here, no crash
window.setLayout(box)
#call update here, no crash
window.show()
#this call to update results in a crash
update(label)
#window.updateGeometry()
print("App: exec")
app.exec_()
Output:
desktop (3623,2160)
Read (1515,1051)
Rotate (1051,1515)
Thumbnail(832,1200)
App: exec
Do I need to do anything special to tell QT that the window size will be changing? Any suggestions for diagnosing the problem from here...
Update:
If I copy the body of the update function and paste it in place of the call to update, it no long crashes -- it works as expected.
From this I conclude that there is an object-lifetime issue. Somewhere behind the scenes QT and/or Pillow is keeping a pointer to an internal buffer rather than making a copy or "stealing" the buffer. When the object containing the buffer is deleted the pointer becomes invalid and "Bad Things Happen[TM]"
Now to determine who's being lazy...
I found a solution based on the observation mentioned in the Update that this appeared to be an object lifetime issue.
Changing the line in the update function from
pixmap = QPixmap.fromImage(qimage)
to
pixmap = QPixmap.fromImage(qimage).copy()
forces a copy of the pixmap. This copy apparently has its own data buffer rather than borrowing the buffer from the Image.
The Label then keeps a reference to the pixmap -- ensuring the lifetime of the buffer. The 'bug' seems to be that QPixmap.fromImage captures a pointer to the data in the Image, but does not keep a reference to the Image so if the Image gets garbage collected (which is likely 'cause it's a big object, the Label (and the pixmap) have a pointer to unallocated memory.
[This 'pointer to the buffer' stuff is sheer speculation on my part, but the bottom line is the program no longer crashes.]
In case anybody else stumbles upon this:
I struggled with a very similar issue and was able to solve it using slots and signals (based on this great article) since the .copy() solution did not work for me. My solution was to separate the creation of the QPixmap and the QLabel.setPixmap into different functions. When the function that creates the QPixmap finishes, it emits a signal that triggers the function that sets the pixmap to the label.
For instance, if you wanted to use threading:
class WorkerSignals(QObject):
ready = pyqtSignal()
class Worker(QRunnable):
def __init__(self, some_function, *args, **kwargs):
super(Worker, self).__init__()
self.some_function = some_function
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['ready'] = self.signals.ready
#pyqtSlot()
def run(self):
self.some_function(*self.args, **self.kwargs)
def make_pixmap(qimage, ready, **kwargs):
pixmap = QPixmap.fromImage(qimage) # qimage is some QImage
ready.emit()
def set_pixmap(pixmap):
label.setPixmap(pixmap) # 'label' is some QLabel
threadpool = QThreadPool
worker = Worker(make_pixmap, image)
worker.signals.ready.connect(set_pixmap)
threadpool.start(worker)
Obviously using threading is not necessary in this scenario, but this is just to show that it's possible in case you want to process and show images without hanging the rest of the GUI.
Edit: The crashes came back, disregard the above. Working on another fix.

Access violation after exit using QSystemTrayIcon from PyQt4

I wrote a simple class for notifying user using tray balloon message. Here is the code:
# -*- coding: utf8 -*-
import sys
import time
from PyQt4 import QtGui
class TrayInformer():
def __init__(self, icon_file):
self.app = QtGui.QApplication(sys.argv)
self.app.setQuitOnLastWindowClosed(False)
self.tray_icon = QtGui.QSystemTrayIcon(
QtGui.QIcon(icon_file)
)
def notify(self, title, message, wait_time=1000):
self.tray_icon.show()
self.tray_icon.showMessage(title, message)
time.sleep(wait_time / 1000)
self.tray_icon.hide()
inf = TrayInformer('timeico.ico')
inf.notify('Error', 'Connection refused', 5000)
inf.notify('test', 'test', 500)
After running it I sometimes get: Process finished with exit code -1073741819 (0xC0000005). What could be the problem?
Try to instantiate QSystemTrayIcon with parent specified. I've had resembling random crashes on exit, and surprisingly tray icon appeared to cause the problem
<...> the problem is probably the random order in which C++
instances get destroyed. Explicitly del'ing a object, or giving it an appropriate parent is the best way to deal with it.
QSystemTrayIcon still crashed app (PyQt4 4.9.1)

QDockWidget hide title bar

I can't find how to hide the title bar of my Qdockwidgets :
I have found this on the references :
QDockWidget.setTitleBarWidget (self, QWidget widget)
The widget argument has it's ownership transferred to Qt.
Sets an arbitrary widget as the dock widget's title bar. If widget is 0, any custom title bar widget previously set on the dock widget is removed, but not deleted, and the default title bar will be used instead.
And this in quite a few places :
dockWidget->setTitleBarWidget(new QWidget());
but how to actually use them ? I simply can't find any simple example of code showing those in action not even in the references and all of my tries ended up with various errors.
In the whole page : http://pyqt.sourceforge.net/Docs/PyQt4/qdockwidget.html#setTitleBarWidget there is simply nothing which tell me how I can use this :
QDockWidget.setTitleBarWidget (self, QWidget widget)
If I read the documentation I should replace the "widget" one by a 0 so I get no title bar displayed
self.dockWdg1.setTitleBarWidget(self, QtGui.QWidget(0))# If I don't add QtGui in front of QWidget it don't know what is QWidget
give me this error :
TypeError: QWidget(QWidget parent=None, Qt.WindowFlags flags=0): argument 1 has unexpected type 'int'
Another question, is there actually people using this references : ? http://pyqt.sourceforge.net/Docs/PyQt4/qdockwidget.html#setTitleBarWidget
My question can seem weird but I find thoses references so incomplete (no code example) and the explanations so obscures I really wonder if I am looking on the right one, especially when on the other side we have this : http://srinikom.github.io/pyside-docs/PySide/QtGui/QDockWidget.html#PySide.QtGui.QDockWidget with pySide which seems so so so so so much better. PyQT references succeed in being even worst than MaxScript references which is not a little thing to do.
My complete code :
import sys, random
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QPalette, QBrush, QPixmap
class MainWin(QtGui.QMainWindow):
def __init__(self):
super(MainWin, self).__init__()
self.initUI()
def initUI(self):
#central widget
self.theboard = Board(self)
self.setCentralWidget(self.theboard)
#dock1 Left Dock
self.dockWdg1 = LeftDock(self)
#Use SizeQWidget() to define the initial size of QDockWidget
self.content1 = SizeQWidget()
self.dockWdg1.setWidget(self.content1)
self.dockWdg1.setFeatures(self.dockWdg1.NoDockWidgetFeatures)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.dockWdg1)
self.dockWdg1.setTitleBarWidget(self, QtGui.QWidget(0))# If I don't add QtGui in front of QWidget it don't know what is QWidget
self.resize(360, 760)
self.setWindowTitle('Test')
self.show()
class LeftDock(QtGui.QDockWidget):
def __init__(self, parent):
super(LeftDock, self).__init__(parent)
self.initLeftDock()
def initLeftDock(self):
self.setGeometry(300, 300, 200, 120)
#self.setWindowTitle('LeftDock')
class Board(QtGui.QFrame):
def __init__(self, parent):
super(Board, self).__init__(parent)
self.initBoard()
def initBoard(self):
print("ddd")
#Dummy QWidget used by QDockWidget for defining initial size.
class SizeQWidget(QtGui.QWidget):
def sizeHint(self):
return QtCore.QSize(100, 75)
def main():
app = QtGui.QApplication([])
mw = MainWin()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The PyQt docs are automatically created from the C++ docs. I always just use the C++ docs.
The docs are thus not of very good overall quality.
Now to your main issue at hand.
Note that QtGui.QWidget takes a QObject* as first parameter (the parent of the newly created object). Also note that a null pointer is represented in Python by None, not 0.
Then we need to know that self is usually implicitly passed by calling the method on an instance. (self in the context of PyQt is just like every other self you will ever encounter in Python).
Thus, this works:
self.dockWdg1.setTitleBarWidget(QtGui.QWidget(None))
Personally I would do this a little bit different:
self.dockWdg1.setTitleBarWidget(QtGui.QWidget(self.dockWdg1))
That is, setting the parent of the title bar widget to the dock widget itself, preserving a nice object hierarchy.
I might also remark that "removing" the title bar widget of a dock widget by replacing it with an invisible (size is zero) widget it becomes impossible to float the dock widget.
i use this to hide them all in c++
for (auto &dock:findChildren<QDockWidget*>())
dock->setTitleBarWidget(new QWidget);

PySide/PyQt: Is it possible to make strings that you attach to the QTextBrowser separate clickable units

This might be a silly question but:
When you append a given string to a QTextBrowser object, can you make it a link to a signal to a function that takes its text and does something with it? All I need is for it to save the text to a variable actually.
As in, can a link lead to a function instead of to a website.
It certainly is possible.
Here is a code example:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
main_layout = QtGui.QVBoxLayout()
self.browser = QtGui.QTextBrowser()
self.browser.setHtml('''<html><body>some text<br/>click me to call a function<br/>
Click me to scroll down<br>foo<br>foo<br>foo<br>foo<br>foo<br>foo<br>
foo<a id="my_anchor"></a><br>bar<br>bar<br>bar<br>bar<br>bar<br>bar<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!</body></html''')
self.browser.anchorClicked.connect(self.on_anchor_clicked)
main_layout.addWidget(self.browser)
self.setLayout(main_layout)
def on_anchor_clicked(self,url):
text = str(url.toString())
if text.startswith('some_special_identifier://'):
self.browser.setSource(QtCore.QUrl()) #stops the page from changing
function = text.replace('some_special_identifier://','')
if hasattr(self,function):
getattr(self,function)()
def a_function(self):
print 'you called?'
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Any link that has a url that begins with "some_special_identifier://" will be picked up and the text after that will be used to look up and call a function of the same name. Please note that this could be a little risky as there is the potential for all sorts of functions to be called that perhaps you don't intend, if the user has any control over what is displayed in the TextBrowser. It might be better to only allow certain functions to be run, and perhaps only at certain times. That is of course up to you to enforce!
P.S. my code is written for Python 2.7 (I see you are using Python 3). So I think you will need to change print 'text' to print('text') at the very least!

Separate user interaction from programmical change: PyQt, QComboBox

I have several QComboBoxes in my PyQt4/Python3 GUI and they are filled with some entries from a database during the initialisation. Initial CurrentIndex is set to 0. There is also a tick box which changes the language of the items in my combo boxes. To preserve current user selection I backup index of the current item and setCurrentIndex to this number after I fill in ComboBox with translated items. All those actions emit currentIndexChanged signal.
Based on the items selected in QComboBoxes some plot is displayed. The idea is to redraw the plot online - as soon as the user changes any of ComboBox current item. And here I have a problem since if I redraw the plot every time signal currentIndexChanged is emited, I redraw it also several times during initialization and if the translation tick box selection was changed.
What is the best way to separate these cases? In principle I need to separate programmical current Index Change from the user, and update the plot only in the later case (during GUI initialisation I can programically call update plot function once). Should I write/rewrite any signal? If so, I never did that before and would welcome any hint or a good example. Use another signal? Or maybe there is a way to temporary block all signals?
There are a few different things you can try.
Firstly, you can make sure you do all your initialization before you connect up the signals.
Secondly, you could use the activated signal, which is only sent whenever the user selects an item. (But note that, unlike currentIndexChanged, this signal is sent even if the index hasn't changed).
Thirdly, you could use blockSignals to temporarily stop any signals being sent while the current index is being changed programmatically.
Here's a script that demonstrates these possibilities:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.combo = QtGui.QComboBox()
self.combo.setEditable(True)
self.combo.addItems('One Two Three Four Five'.split())
self.buttonOne = QtGui.QPushButton('Change (Default)', self)
self.buttonOne.clicked.connect(self.handleButtonOne)
self.buttonTwo = QtGui.QPushButton('Change (Blocked)', self)
self.buttonTwo.clicked.connect(self.handleButtonTwo)
layout.addWidget(self.combo)
layout.addWidget(self.buttonOne)
layout.addWidget(self.buttonTwo)
self.changeIndex()
self.combo.activated['QString'].connect(self.handleActivated)
self.combo.currentIndexChanged['QString'].connect(self.handleChanged)
self.changeIndex()
def handleButtonOne(self):
self.changeIndex()
def handleButtonTwo(self):
self.combo.blockSignals(True)
self.changeIndex()
self.combo.blockSignals(False)
def changeIndex(self):
index = self.combo.currentIndex()
if index < self.combo.count() - 1:
self.combo.setCurrentIndex(index + 1)
else:
self.combo.setCurrentIndex(0)
def handleActivated(self, text):
print('handleActivated: %s' % text)
def handleChanged(self, text):
print('handleChanged: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Resources