Sunday, January 22, 2012

Qt Tutorial #1-7 One Thing Leads to Another

This is based on Qt Tutorial #1-7 One Thing Leads to Another



#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
    PyQt4 conversion of Qt Tutorial 7
        
    The example demonstrates chaining signals so they are propagated
    from one widget to another. 
        
    BEHAVIOUR:
    =========
    The application displays 16 LCDRange widgets all set to zero
    as in 6_buildingblocks.pyw; however, they no longer act
    independently.
    
    Move the slider on the bottom-right widget and see the values
    of all the widgets change.  Move the slider on the 8th
    widget and the values of widgets 1 thru 8 change.
    
    NOTES:
    =====
    The original example uses two files: lcdrange.cpp and main.cpp
    Here they are combined into one module.

    The original C++ code connects the slider valueChanged 
    signal to itself; this is not necessary in PyQt4 and will produce 
    a "cannot connect a signal to itself" compile error if attempted.
    
    The original code also included a 'value()' method; it was not
    being called and so has not been included in this example.
    
    Demonstrates the use of the 'pyqtSlot' decorator which, according to PyQt 
    documentation, reduces memory usage.
    
last modified: 2012-01-19 jg
ref: 
    http://doc.trolltech.com/3.3/tutorial1-07.html
    http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/new_style_signals_slots.html
'''
import sys
from PyQt4.QtGui import (QApplication, QWidget, QPushButton, QFont,
                         QVBoxLayout, QGridLayout, QLCDNumber, QSlider)
from PyQt4.QtCore import (Qt, pyqtSlot)

class LCDRange(QWidget):
    '''
        A two digit QLCDNumber and QSlider widget.
    '''
    def __init__(self, parent=None):
        super(LCDRange, self).__init__(parent)

        # local variable, not directly called by
        # external methods
        lcd = QLCDNumber(2, self);

        # 'slider' defined as an 'attribute'
        # as it must be accessed by the 'setValue()' method
        self.slider = QSlider(Qt.Horizontal, self);
        self.slider.setRange(0, 99);
        self.slider.setValue(0);

        self.slider.valueChanged.connect(lcd.display)

        layout = QVBoxLayout()
        layout.addWidget(lcd)
        layout.addWidget(self.slider)
        self.setLayout(layout)

    # a PyQt defined 'slot' 
    #   sets the value of the slider which automatically
    #   results in a 'valueChanged' signal being sent
    @pyqtSlot(int)
    def setValue(self, value):
        self.slider.setValue(value)


class MyWidget(QWidget):
    def __init__(self, parent=None, name=''):
        super(MyWidget, self).__init__(parent)
        if name:
            self.setObjectName(name)

        quitBtn = QPushButton('Quit', self)
        quitBtn.setFont(QFont("Times", 18, QFont.Bold))
        quitBtn.clicked.connect(QApplication.instance().quit)

        grid = QGridLayout()
        previous = None
        for r in range(4):
            for c in range(4):
                lr = LCDRange(self)
                grid.addWidget(lr, r, c)
                if previous:
                    # connect to the 'setValue()' method of the
                    # previous LCDRange widget; triggering a
                    # 'valueChanged' signal that is then propagated
                    # to all 'previously' created LCDRange widgets
                    lr.slider.valueChanged.connect(previous.setValue)
                previous = lr


        # nesting layouts
        vbox = QVBoxLayout()
        vbox.addLayout(grid)
        vbox.addWidget(quitBtn)

        self.setLayout(vbox)

def main():
    app = QApplication(sys.argv)    # required

    w = MyWidget(name='signalChain')
    w.show()
    sys.exit(app.exec_())   # start main event loop, exit when app closed

if __name__ == '__main__':
    main()