#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
PyQt4 conversion of Qt Address Book (Tabbed) Example
The address book example shows how to use proxy models to display
different views onto data from a single model.
This example provides an address book that allows contacts to be
grouped alphabetically into 9 groups:
ABC, DEF, GHI, ... , VW, ..., XYZ.
This is achieved by using multiple views on the same model, each of
which is filtered using an instance of the QSortFilterProxyModel class.
BEHAVIOUR:
=========
New entries can be added by clicking the 'Add' button or
selecting 'Tools->Add'.
When the first entry has been added, the 'Address Book'
tab is removed.
NOTES:
=====
Implemented last custom class, AddDialog.
Implemented addEntry() in AddressWidget and NewAddressTab
last modified: 2012-01-29 jg
ref:
http://developer.qt.nokia.com/doc/qt-4.8/itemviews-addressbook.html
'''
from PyQt4.QtGui import (QApplication, QMainWindow, QWidget, QAction, QFileDialog,
QLabel, QPushButton, QVBoxLayout, QSortFilterProxyModel,
QTableView, QAbstractItemView, QTabWidget, QItemSelection,
QDialog, QLineEdit, QTextEdit, QGridLayout, QHBoxLayout,
QMessageBox)
from PyQt4.QtCore import (pyqtSlot, pyqtSignal, Qt, QAbstractTableModel, QModelIndex,
QRegExp)
class AddDialog(QDialog):
def __init__(self, parent=None): # initialise base class
super(AddDialog, self).__init__(parent)
nameLabel = QLabel(self.tr("Name"))
addrLabel = QLabel(self.tr("Address"))
okBtn = QPushButton(self.tr("OK"))
cancelBtn = QPushButton(self.tr("Cancel"))
self.nameText = QLineEdit()
self.addrText = QTextEdit()
grid = QGridLayout()
grid.setColumnStretch(1, 2)
grid.addWidget(nameLabel, 0, 0)
grid.addWidget(self.nameText, 0, 1)
grid.addWidget(addrLabel, 1, 0, Qt.AlignLeft | Qt.AlignTop)
grid.addWidget(self.addrText, 1, 1, Qt.AlignLeft)
btnLayout = QHBoxLayout()
btnLayout.addWidget(okBtn)
btnLayout.addWidget(cancelBtn)
grid.addLayout(btnLayout, 2, 1, Qt.AlignRight)
mainLayout = QVBoxLayout()
mainLayout.addLayout(grid)
self.setLayout(mainLayout)
# connect signals/slots for event handling
okBtn.clicked.connect(self.accept)
cancelBtn.clicked.connect(self.reject)
self.setWindowTitle(self.tr("Add a Contact"))
class NewAddressTab(QWidget):
def __init__(self, parent=None): # initialise base class
super(NewAddressTab, self).__init__(parent)
descripLabel = QLabel(self.tr(
"There are currently no contacts in your Address Book"
"\nClick Add to add new contacts."))
addBtn = QPushButton(self.tr("Add"))
addBtn.clicked.connect(self._addEntry)
layout = QVBoxLayout()
layout.addWidget(descripLabel)
layout.addWidget(addBtn)
self.setLayout(layout)
# signal ------------------------------------------------------------------
sendDetails = pyqtSignal(str, str, name="sendDetails")
# private methods ---------------------------------------------------------
def _addEntry(self):
aDialog = AddDialog()
if aDialog.exec():
name = aDialog.nameText.text()
addr = aDialog.addrText.toPlainText()
self.sendDetails.emit(name, addr)
class TableModel(QAbstractTableModel):
def __init__(self, parent=None, pairs=[['', '']]): # initialise base class
super(TableModel, self).__init__(parent)
self._listOfPairs = pairs
# overridden methods ------------------------------------------------------
def rowCount(self, parent=QModelIndex()):
return len(self._listOfPairs)
def columnCount(self, parent=QModelIndex()):
return 2
def data(self, index, role=Qt.DisplayRole):
# displays the row contents
if not index.isValid():
return None
if (index.row() >= len(self._listOfPairs) or
index.row() < 0):
return None
if role == Qt.DisplayRole:
pair = self._listOfPairs[index.row()]
if index.column() == 0:
return pair[0]
elif index.column() == 1:
return pair[1]
return None
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole:
return None
if orientation == Qt.Horizontal:
if section == 0: return self.tr("Name")
elif section == 1: return self.tr("Address")
else: return None
return None
def insertRows(self, position, rows, index=QModelIndex()):
self.beginInsertRows(QModelIndex(), position, position + rows - 1)
for r in range(rows):
pair = [" ", " "]
self._listOfPairs.insert(position, pair)
self.endInsertRows()
return True
def removeRows(self, position, rows, index=QModelIndex()):
self.beginRemoveRows(QModelIndex(), position, position + rows - 1)
for r in range(rows):
# use list slicing to remove rows
self._listOfPairs = (self._listOfPairs[:position] +
self._listOfPairs[position + rows:])
self.endRemoveRows()
return True
def setData(self, index, value, role=Qt.EditRole):
# updates the table model and emits a signal
# to notify views that data has changed
if index.isValid() and role == Qt.EditRole:
row = index.row()
pair = self._listOfPairs[row]
if index.column() == 0:
pair[0] = value
elif index.column() == 1:
pair[1] = value
else:
return False
self._listOfPairs[row] = pair
# this signal is defined in the QAbstractTableModel
# parent class
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
return Qt.ItemFlags(QAbstractTableModel.flags(self, index) |
Qt.ItemIsEditable)
# public methods ----------------------------------------------------------
def getList(self):
return self._listOfPairs
class AddressWidget(QTabWidget):
def __init__(self, parent=None): # initialise base class
super(AddressWidget, self).__init__(parent)
self._table = TableModel(self)
self._newAddressTab = NewAddressTab(self)
self._newAddressTab.sendDetails.connect(self.addEntry)
self.addTab(self._newAddressTab, "Address Book")
self._setUpTabs()
# signals --------------------------------------------------------------
selectionChanged = pyqtSignal(QItemSelection, name="selectionChanged")
# public slots ---------------------------------------------------------
@pyqtSlot(str, str)
def addEntry(self, name=None, address=None):
if not name:
aDialog = AddDialog()
if aDialog.exec():
name = aDialog.nameText.text()
address = aDialog.addrText.toPlainText()
# recursive call
self.addEntry(name, address)
else:
entries = self._table.getList()
pair = [name, address]
if not pair in entries:
self._table.insertRows(0, 1, QModelIndex())
index = self._table.index(0, 0, QModelIndex())
self._table.setData(index, name, Qt.EditRole)
index = self._table.index(0, 1, QModelIndex())
self._table.setData(index, address, Qt.EditRole)
self.removeTab(self.indexOf(self._newAddressTab))
else:
QMessageBox.information(
self,
self.tr("Duplicate Name"),
self.tr("The name {0} already exists.".format(name)))
@pyqtSlot()
def editEntry(self):
pass
@pyqtSlot()
def removeEntry(self):
pass
# public methods ---------------------------------------------------------
def readFromFile(self, fname):
pass
def writeToFile(self, fname):
pass
# private methods ---------------------------------------------------------
def _setUpTabs(self):
groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"]
for i in range(len(groups)):
tabName = groups[i] # 'str' is a reserved word in Python
proxyModel = QSortFilterProxyModel(self)
proxyModel.setSourceModel(self._table)
proxyModel.setDynamicSortFilter(True)
tableView = QTableView()
tableView.setModel(proxyModel)
tableView.setSortingEnabled(True)
tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
tableView.horizontalHeader().setStretchLastSection(True)
tableView.verticalHeader().hide()
tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
tableView.setSelectionMode(QAbstractItemView.SingleSelection)
# use a QRegExp to limit names displayed on each tab
# to their appropriate tab group
newStr = "^[{0}].*".format(tabName)
proxyModel.setFilterRegExp(QRegExp(newStr, Qt.CaseInsensitive))
proxyModel.setFilterKeyColumn(0)
proxyModel.sort(0, Qt.AscendingOrder)
tableView.selectionModel().selectionChanged.connect(self.selectionChanged)
self.addTab(tableView, tabName)
class MainWindow(QMainWindow):
def __init__(self, parent=None): # initialise base class
super(MainWindow, self).__init__(parent)
self._addrWidget = AddressWidget()
self.setCentralWidget(self._addrWidget)
self._createFileMenu() # extracted from createMenus()
self._createToolsMenu() # extracted from createMenus()
self._addrWidget.selectionChanged.connect(self._updateActions)
self.setWindowTitle(self.tr("Address Book - Part 3"))
# private methods ---------------------------------------------------------
def _createFileMenu(self):
# create actions
openAct = QAction(self.tr("&Open..."), self)
saveAct = QAction(self.tr("&Save..."), self)
exitAct = QAction(self.tr("E&xit"), self)
# connect signals/slots for event handling
openAct.triggered.connect(self._openFile)
saveAct.triggered.connect(self._saveFile)
exitAct.triggered.connect(QApplication.instance().exit)
# create menu
fileMenu = self.menuBar().addMenu(self.tr("&File"))
fileMenu.addAction(openAct)
fileMenu.addAction(saveAct)
fileMenu.addSeparator()
fileMenu.addAction(exitAct)
def _createToolsMenu(self):
# create actions
addAct = QAction(self.tr("&Add Entry..."), self)
self._editAct = QAction(self.tr("&EditEntry..."), self)
self._removeAct = QAction(self.tr("&Remove Entry"), self)
# connect signals/slots for event handling
addAct.triggered.connect(self._addrWidget.addEntry)
self._editAct.triggered.connect(self._addrWidget.editEntry)
self._removeAct.triggered.connect(self._addrWidget.removeEntry)
# create menu
toolMenu = self.menuBar().addMenu(self.tr("&Tools"))
toolMenu.addAction(addAct)
toolMenu.addAction(self._editAct)
toolMenu.addSeparator()
toolMenu.addAction(self._removeAct)
# set menu item visibility
self._editAct.setEnabled(False)
self._removeAct.setEnabled(False)
# private slots ---------------------------------------------------------
@pyqtSlot()
def _openFile(self):
fname = QFileDialog.getOpenFileName(self)
if fname:
self._addrWidget.readFromFile(fname)
@pyqtSlot()
def _saveFile(self):
fname = QFileDialog.getSaveFileName(self)
if fname:
self._addrWidget.writeToFile(fname)
@pyqtSlot(QItemSelection)
def _updateActions(self, sel):
indexes = sel.indexes()
if indexes:
self._removeAct.setEnabled(True)
self._editAct.setEnabled(True)
else:
self._removeAct.setEnabled(False)
self._editAct.setEnabled(False)
# main ========================================================================
def main():
import sys
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Monday, January 30, 2012
Qt 4.8 Address Book Example - Part 3
In Part 3 the AddressWidget.addEntry() method is implemented along with the last class, AddDialog.
Labels:
Qt Address Book Example