Page List

Wednesday, January 25, 2012

Qt 4.8 Address Book Tutorial - Part 4

This code is based on the Qt 4.8 Address Book Tutorial  Part 4, Editing and Removing Addresses.




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

'''
    PyQt4 conversion of Qt Tutorial 'Address Book' - Part 4
    
    We now have an address book that not only holds contacts in an organized manner, 
    but also allows navigation. It would be convenient to include edit and remove 
    functions so that a contact's details can be changed when needed. 
    However, this requires a little improvement, added through implementing:
    
        enum: AddMode, EditMode, NavMode
        updateInteface(mode)
        editBtn, removeBtn
        editContact()
        removeContact()

    NOTE:
    ====
    The code to handle the enabling/disabling, hiding/showing of the
    various buttons has been extracted to the single method, 'updateInterface()'
  
        
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-part4.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):

    # application operation 'modes'
    # assigns values such that NavMode = 0, AddMode = 1 and EditMode = 2
    NavMode, AddMode, EditMode = range(3)

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

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

        # create attributes to represent object state
        self._currentMode = ''      # user actions
        self._oldName = ''          # entry value at mode change
        self._oldAddress = ''       # entry value at mode change

        # 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"))
        self._editBtn = QPushButton(self.tr("&Edit"))
        self._removeBtn = QPushButton(self.tr("&Remove"))

        # set button visibility
        self._addBtn.show()
        self._submitBtn.hide()
        self._cancelBtn.hide()
        self._editBtn.setEnabled(False)
        self._removeBtn.setEnabled(False)

        # 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)
        self._editBtn.clicked.connect(self._editContact)
        self._removeBtn.clicked.connect(self._removeContact)

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

        return layout

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

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

        self.updateInterface(self.AddMode)

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

        if not (name or address):
            QMessageBox.information(self,
                                    self.tr("Empty Field"),
                                    self.tr("Please enter a name and address."))
            return
        if self._currentMode == self.AddMode:
            if name not in self._contacts:
                self._contacts[name] = address
                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))

        elif self._currentMode == self.EditMode:
            if self._oldName != name:
                if name not in self._contacts:
                    QMessageBox.information(self,
                                            self.tr("Edit Successful"),
                     self.tr("{0} has been edited in your address book.").format(name))
                    del self._contacts[self._oldName]
                    self._contacts[name] = address
                else:
                    QMessageBox.information(self,
                                             self.tr("Edit Unsuccessful"),
                     self.tr("Sorry, {0} is already in your address book.").format(name))
            elif self._oldAddress != address:
                    QMessageBox.information(self,
                                            self.tr("Edit Successful"),
                     self.tr("{0} has been edited in your address book.").format(name))
                    self._contacts[name] = address

        self.updateInterface(self.NavMode)

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

        self._addrText.setText(self._oldAddress)
        self.updateInterface(self.NavMode)

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

        self.updateInterface(self.EditMode)

    @pyqtSlot()
    def _removeContact(self):
        name = self._nameLine.text()

        if name in self._contacts:
            ans = QMessageBox.question(
                        self,
                        self.tr("Confirm Remove"),
                        self.tr("Are you sure you want to remove {0}?").format(name),
                        QMessageBox.Yes | QMessageBox.No,
                        QMessageBox.No)
            if ans == QMessageBox.Yes:
                self.previous()
                del self._contacts[name]
                QMessageBox.information(
                        self,
                        self.tr("Remove successful"),
                        self.tr("{0} has been removed from your address book.").format(name),
                        QMessageBox.Ok)
        self.updateInterface(self.NavMode)

    # public slots ------------------------------------------------------------
    @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])

    # public methods ---------------------------------------------------------
    def updateInterface(self, mode):
        '''
            Enable/disable buttons based on whether the user is adding,
            editing or navigating Address Book entries.
        '''
        self._currentMode = mode

        if mode != self.NavMode:
            self._nameLine.setReadOnly(False)
            self._nameLine.setFocus(Qt.OtherFocusReason)
            self._addrText.setReadOnly(False)

            self._addBtn.setEnabled(False)
            self._editBtn.setEnabled(False)
            self._removeBtn.setEnabled(False)
            self._nextBtn.setEnabled(False)
            self._prevBtn.setEnabled(False)

            self._submitBtn.show()
            self._cancelBtn.show()
        else:
            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._editBtn.setEnabled(num >= 1)
            self._removeBtn.setEnabled(num >= 1)
            self._nextBtn.setEnabled(num > 1)
            self._prevBtn.setEnabled(num > 1)

            self._submitBtn.hide()
            self._cancelBtn.hide()


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()