Sunday, September 1, 2013

PyQt4: Widget that holds an image and a quit button

I made an extension to the WidgetWithQuit class I introduced recently in this post.
It now can hold an image (input should be a 2D or 3D numpy.ndarray). In WidgetWithQuit the quit button would keep its position relative to the bottom right corner. In WidgetWithPictureAndQuit the image displayed is resized likewise: It always keeps the distance to the borders and the quit button while keeping the aspect ratio. If the image, after resizing, is not centered (it happens, when it is not wide enough), it will be centered, such that the distance to the left and right border will be increased.
In addition to the usual imports, you now also need qimage2ndarray which can be installed via pip.
Please note that in order to make the example shown below work you will need the code introduced in my recent post.

from qimage2ndarray import array2qimage

class WidgetWithPictureAndQuit(WidgetWithQuit):
    def __init__(self, parent=None, buttontext="Quit", margin=10, image=None):
        WidgetWithQuit.__init__(self, parent, buttontext, margin)
        self.image = array2qimage(image)
        self.label = QtGui.QLabel(self)
        if self.image is not None:
            self.resize_image()

    def resize_image(self):
        widget_geometry = self.geometry()
        button_geometry = self.quit_button.geometry()
        width = widget_geometry.width() - 2*self.margin
        height = widget_geometry.height() - (button_geometry.height() + 3*self.margin)
        width_offset = self.margin
        pixmap = QtGui.QPixmap.fromImage(self.image).scaled(width, height, QtCore.Qt.KeepAspectRatio)
        if pixmap.width() < width:
            width_offset = (widget_geometry.width() - pixmap.width())/2
        self.label.setGeometry(width_offset, self.margin, width, height)
        self.label.setPixmap(pixmap)

    def resizeEvent(self, event):
        WidgetWithQuit.resizeEvent(self, event)
        self.resize_image()

PyQt: Qt Widget with button with a fixed margin from bottom right corner

PyQt4 provides an easy to use interface for the creation of GUI Applications. In order to get some practice in PyQt4 I decided to create a subclass WidgetWithQuit of QWidget that has a QuitButton (derived form QPushButton) as member variable that sends a quit signal to the qapp on a left click.
Thanks to the high level interface of PyQt4 this requires only a small modification of the resizeEvent function of QWidget (see code below).


from PyQt4 import QtGui, QtCore
import sys


class QuitButton(QtGui.QPushButton):
    def __init__(self, parent, text="Quit"):
        QtGui.QPushButton.__init__(self, text, parent)
        parent.connect(self, QtCore.SIGNAL('clicked()'),
                       QtGui.qApp, QtCore.SLOT('quit()'))
        self.resize(self.sizeHint().width(), 
                    self.sizeHint().height())


class WidgetWithQuit(QtGui.QWidget):
    """QWidget that has a QuitButton at the bottom right;
    margin specified in the constructor"""
    def __init__(self, parent=None, buttontext="Quit", margin=10):
        self.margin = margin
        QtGui.QWidget.__init__(self, parent)
        self.quit_button = QuitButton(self, text=buttontext)
        self.align_button()

    def align_button(self):
        widget_geometry = self.geometry()
        button_geometry = self.quit_button.geometry()
        width_offset = widget_geometry.width() - (button_geometry.width() + self.margin)
        height_offset = widget_geometry.height() - (button_geometry.height() + self.margin)
        self.quit_button.move(width_offset, height_offset)

    def resizeEvent(self, event):
        QtGui.QWidget.resizeEvent(self, event)
        self.align_button()


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    widget = WidgetWithQuit()
    widget.setGeometry(300, 300, 100, 100)
    widget.quit_button.setToolTip('Quit on left mouse click')
    widget.show()
    sys.exit(app.exec_())

Of course this is just a very simple, "academic" example; still it should be clear that PyQt4 offers an easy to use interface to build user-friendly GUI applications.
Thanks to C/C++ binding generators such as boost python you can still rely on fast C/C++ for the underlying algorithms which gives you the best of both worlds: easy implementation of the GUI using a high level language and fast algorithms working on (big) data in the background.
For the documentation of the classes used above, see: