#!/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()
Page List
▼
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.