Page List

Wednesday, January 25, 2012

Qt 4.8 Address Book Tutorial - Part 3

This code is based on the Qt 4.8 Address Book Tutorial  Part 3, Navigating Between Entries.




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

'''
    PyQt4 conversion of Qt Tutorial 'Address Book' - Part 3
    
    Add the ability to navigate backwards and forwards through
    the  contacts.

    NOTES:
    =====
    The method name for 'next()' has been changed to 'next_()' as
    'next' is a reserved word in Python.
    
    The 'next_()' and 'prev()' methods are implemented using sorted
    lists of the contacts names (keys). As lists in Python
    use 'pointers' (memory addresses) to objects and not the actual objects
    themselves the methods should scale reasonably well.
        
last modified: 2012-01-24 jg
ref: 
    http://developer.qt.nokia.com/doc/qt-4.8/tutorials-addressbook.html
    http://developer.qt.nokia.com/doc/qt-4.8/tutorials-addressbook-part3.html
'''
import sys
from PyQt4.QtGui import (QApplication, QWidget, QLabel, QTextEdit, QLineEdit,
                         QGridLayout, QPushButton, QVBoxLayout, QMessageBox,
                         QHBoxLayout)
from PyQt4.QtCore import (Qt, pyqtSlot)

class AddressBook(QWidget):

    def __init__(self, parent=None):
        super(AddressBook, self).__init__(parent)

        # create a dictonary to hold contacts
        self._contacts = {}

        # create input labels and fields
        nameLabel = QLabel(self.tr("Name:"))
        self._nameLine = QLineEdit(self)
        self._nameLine.setReadOnly(True)

        addrLabel = QLabel(self.tr("Address:"))
        self._addrText = QTextEdit(self)
        self._addrText.setReadOnly(True)

        # create and populate layout
        mainLayout = QGridLayout()
        mainLayout.addWidget(nameLabel, 0, 0)
        mainLayout.addWidget(self._nameLine, 0, 1)
        mainLayout.addWidget(addrLabel, 1, 0, Qt.AlignTop)
        mainLayout.addWidget(self._addrText, 1, 1)
        mainLayout.addLayout(self._initButtonPanel(), 1, 2)
        mainLayout.addLayout(self._initNavPanel(), 2, 1)

        # set this objects layout and window title
        self.setLayout(mainLayout)
        self.setWindowTitle(self.tr("Simple Address Book"))

    def _initNavPanel(self):
        self._nextBtn = QPushButton(self.tr("&Next"))
        self._prevBtn = QPushButton(self.tr("&Previous"))

        self._nextBtn.setEnabled(False)
        self._prevBtn.setEnabled(False)

        self._nextBtn.clicked.connect(self.next_)
        self._prevBtn.clicked.connect(self.previous)

        layout = QHBoxLayout()
        layout.addWidget(self._prevBtn)
        layout.addWidget(self._nextBtn)

        return layout

    def _initButtonPanel(self):
        # create buttons
        self._addBtn = QPushButton(self.tr("&Add"))
        self._submitBtn = QPushButton(self.tr("&Submit"))
        self._cancelBtn = QPushButton(self.tr("&Cancel"))

        # set button visibility
        self._addBtn.show()
        self._submitBtn.hide()
        self._cancelBtn.hide()

        # define signal/slot connections for event handling
        self._addBtn.clicked.connect(self._addContact)
        self._submitBtn.clicked.connect(self._submitContact)
        self._cancelBtn.clicked.connect(self._cancel)

        # layout buttons
        layout = QVBoxLayout()
        layout.addWidget(self._addBtn, Qt.AlignTop)
        layout.addWidget(self._submitBtn)
        layout.addWidget(self._cancelBtn)
        layout.addStretch()             # force additional space to bottom

        return layout

    @pyqtSlot()
    def _addContact(self):
        self._oldName = self._nameLine.text()
        self._oldAddress = self._addrText.toPlainText()

        self._nameLine.clear()
        self._addrText.clear()

        self._nameLine.setReadOnly(False)
        self._nameLine.setFocus(Qt.OtherFocusReason)
        self._addrText.setReadOnly(False)

        self._addBtn.setEnabled(False)
        self._submitBtn.show()
        self._cancelBtn.show()

        self._nextBtn.setEnabled(False)   # disable navigationd during 'Add'
        self._prevBtn.setEnabled(False)

    @pyqtSlot()
    def _submitContact(self):
        name = self._nameLine.text()
        addr = self._addrText.toPlainText()

        if not (name or addr):
            QMessageBox.information(self,
                                    self.tr("Empty Field"),
                                    self.tr("Please enter a name and address."))
            return

        if name not in self._contacts:
            self._contacts[name] = addr
            QMessageBox.information(self,
                                    self.tr("Add Successful"),
             self.tr("{0} has been added to your address book.").format(name))
        else:
            QMessageBox.information(self,
                                     self.tr("Add Unsuccessful"),
             self.tr("Sorry, {0} is already in your address book.").format(name))

        # restore buttons
        num = len(self._contacts)
        if num == 0:
            self._nameLine.clear()
            self._addrText.clear()

        self._nameLine.setReadOnly(True)
        self._addrText.setReadOnly(True)
        self._addBtn.setEnabled(True)
        self._submitBtn.hide()
        self._cancelBtn.hide()

        # enable navigation if more than one contact
        self._nextBtn.setEnabled(num > 1)
        self._prevBtn.setEnabled(num > 1)

    @pyqtSlot()
    def _cancel(self):
        self._nameLine.setText(self._oldName)
        self._nameLine.setReadOnly(True)

        self._addrText.setText(self._oldAddress)
        self._addrText.setReadOnly(True)
        self._addBtn.setEnabled(True)
        self._submitBtn.hide()
        self._cancelBtn.hide()

        # enable navigation if more than one contact
        num = len(self._contacts)
        self._nextBtn.setEnabled(num > 1)
        self._prevBtn.setEnabled(num > 1)

    @pyqtSlot()
    def next_(self):
        name = self._nameLine.text()            # current contact's name

        sKeys = sorted(self._contacts.keys())   # sort names
        i = sKeys.index(name) + 1               # get index for next name in sorted list
        if i >= len(sKeys): i = 0               # if we're past the end, go to the beginning
        nextKey = sKeys[i]                      # name at new index is key we want

        self._nameLine.setText(nextKey)
        self._addrText.setText(self._contacts[nextKey])

    @pyqtSlot()
    def previous(self):
        name = self._nameLine.text()            # current contact's name

        sKeys = sorted(self._contacts.keys())   # sort names
        i = sKeys.index(name) - 1               # index for previous name in sorted list
        if i < 0: i = len(sKeys) - 1            # if below the beginning, go to the end
        prevKey = sKeys[i]                      # name at the new index is the key we want

        self._nameLine.setText(prevKey)
        self._addrText.setText(self._contacts[prevKey])


def main():
    app = QApplication(sys.argv)    # required for all GUI applications

    ab = AddressBook()
    ab.show()                       # make me visible

    sys.exit(app.exec_())           # start main event thread

if __name__ == '__main__':
    main()