#!/usr/bin/env python3 # -*- coding: utf-8 -*- ''' PyQt4 conversion of Qt Tutorial 'Address Book' - Part 6 This part covers the Qt file handling features we use to write loading and saving routines for the address book. NOTES: ===== Python's 'pickle' is used to save and load the file. The 'Save' button is disabled if there are no contacts. The QFileDialog handles normal errors e.g. asks if you want to overwrite an existing file when saving or if you want to select another file when trying to load a non-existant file. 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-part6.html ''' import sys, pickle from PyQt4.QtGui import (QApplication, QWidget, QLabel, QTextEdit, QLineEdit, QGridLayout, QPushButton, QVBoxLayout, QMessageBox, QHBoxLayout, QDialog, QFileDialog) 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")) self._loadBtn = QPushButton(self.tr("&Load...")) self._loadBtn.setToolTip(self.tr("Load contacts from a file.")) self._saveBtn = QPushButton(self.tr("Sa&ve...")) self._saveBtn.setToolTip(self.tr("Save contacts to a file.")) # set button visibility self._addBtn.show() self._submitBtn.hide() self._cancelBtn.hide() self._editBtn.setEnabled(False) self._removeBtn.setEnabled(False) self._findBtn.setEnabled(False) self._saveBtn.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) self._loadBtn.clicked.connect(self.loadFromFile) self._saveBtn.clicked.connect(self.saveToFile) # 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.addWidget(self._loadBtn) layout.addWidget(self._saveBtn) 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) @pyqtSlot() def loadFromFile(self): fname = QFileDialog.getOpenFileName( self, self.tr("Open Address Book"), "", self.tr("Address Book (*.abk);;All Files (*)")) if not fname: return with open(fname, 'rb') as f: self._contacts = pickle.load(f) # sort the contacts and display the first name sKeys = sorted(self._contacts.keys()) name = sKeys[0] self._nameLine.setText(name) self._addrText.setText(self._contacts[name]) self.updateInterface(self.NavMode) @pyqtSlot() def saveToFile(self): fname = QFileDialog.getSaveFileName( self, self.tr("Save Address Book"), "", self.tr("Address Book (*.abk);;All Files (*)")) if not fname: return with open(fname, 'wb') as f: pickle.dump(self._contacts, f, pickle.HIGHEST_PROTOCOL) 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._loadBtn.hide() self._saveBtn.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._loadBtn.setEnabled(True) self._saveBtn.setEnabled(num >= 1) self._addBtn.show() self._editBtn.show() self._removeBtn.show() self._findBtn.show() self._loadBtn.show() self._saveBtn.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()
Page List
▼
Wednesday, January 25, 2012
Qt 4.8 Address Book Tutorial - Part 6
This code is based on the Qt 4.8 Address Book Tutorial Part 6, Loading and Saving.