Wednesday, January 25, 2012

Qt 4.8 Address Book Tutorial - Part 5

This code is based on the Qt 4.8 Address Book Tutorial  Part 5, Adding a Find Function.




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

'''
    PyQt4 conversion of Qt Tutorial 'Address Book' - Part 5
    
    Here we look at ways to locate contacts and addresses in the address book.
    
    NOTES:
    =====
    In the original C++ code, the FindDialog class is written
    in a separate file (finddialog.cpp); here it's defined
    in the same module as the AddressBook class.
        
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-part5.html
'''
import sys
from PyQt4.QtGui import (QApplication, QWidget, QLabel, QTextEdit, QLineEdit,
                         QGridLayout, QPushButton, QVBoxLayout, QMessageBox,
                         QHBoxLayout, QDialog)
from PyQt4.QtCore import (Qt, pyqtSlot)

class FindDialog(QDialog):
    def __init__(self, parent=None):
        super(FindDialog, self).__init__(parent)

        findLabel = QLabel(self.tr("Enter the name of a contact:"))
        self.lineEdit = QLineEdit()

        findBtn = QPushButton(self.tr("&Find"))
        self.findText = ''

        layout = QHBoxLayout(self)
        layout.addWidget(findLabel)
        layout.addWidget(self.lineEdit)
        layout.addWidget(findBtn)

        self.setLayout(layout)
        self.setWindowTitle(self.tr("Find a Contact"))
        findBtn.clicked.connect(self.findClicked)
        findBtn.clicked.connect(self.accept)

    def findClicked(self):
        text = self.lineEdit.text()
        if text:
            self.findText = text
            self.lineEdit.clear()
            self.hide()
        else:
            QMessageBox.information(
                self,
                self.tr("Empty Field"),
                self.tr("Please enter a name."))

    def getFindText(self):
        return self.findText

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 = ''          # key (name) at mode change
        self._oldAddress = ''       # key value (address) 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)

        # create find dialog
        self._dialog = FindDialog()

        # 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"))
        self._findBtn = QPushButton(self.tr("&Find"))

        # set button visibility
        self._addBtn.show()
        self._submitBtn.hide()
        self._cancelBtn.hide()
        self._editBtn.setEnabled(False)
        self._removeBtn.setEnabled(False)
        self._findBtn.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)
        self._findBtn.clicked.connect(self.findContact)

        # 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.addWidget(self._findBtn)
        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])

    @pyqtSlot()
    def findContact(self):
        self._dialog.show()

        if self._dialog.exec() == QDialog.Accepted:
            contactName = self._dialog.getFindText()

            if contactName in self._contacts:
                self._nameLine.setText(contactName)
                self._addrText.setText(self._contacts[contactName])
            else:
                QMessageBox.information(
                    self,
                    self.tr("Contact Not Found"),
                    self.tr("{0} is not in your Address Book").format(contactName))
                return

        self.updateInterface(self.NavMode)

    # 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.hide()
            self._editBtn.hide()
            self._removeBtn.hide()
            self._findBtn.hide()

            self._submitBtn.show()
            self._cancelBtn.show()

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

        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._findBtn.setEnabled(num > 2)
            self._nextBtn.setEnabled(num > 1)
            self._prevBtn.setEnabled(num > 1)

            self._addBtn.show()
            self._editBtn.show()
            self._removeBtn.show()
            self._findBtn.show()

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