Tuesday, January 31, 2012

Qt 4.8 Basic Sort/Filter Model Example - Part 1

The following code is based on the Qt 4.8  Basic Sort/Filter Model Example; listed under Qt Item Views Examples. The original example has one class, Window, and is split over two files: main.cpp and window.cpp with associated header files. Here they've been combined in one module file built up in three stages.

The example demonstrates the use of QSortProxyModel, QStandardItemModel and QTreeView. One standard item model is used to create two views containing the same data. The second view uses a proxy model to allow sorting and filtering. Changes in the second view are not reflected in the first view even though the underlying models are the same.

Part 1 creates the basic GUI.



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

'''
    PyQt4 conversion of Qt Basic Sort/Filter Example
    
        The Basic Sort/Filter Model example illustrates how to use 
        QSortFilterProxyModel to perform basic sorting and filtering.
          
    BEHAVIOUR:
    =========
    GUI is laid out. The initial conditions for each widget
    are in place. Both the case sentivity checkboxes are checked.
    The Filter Column combobox is set to 'Sender'.
    
    NOTES:
    =====
    Create Window class and implement methods to build the GUI
    (refactored the original C++ to split the creation of the
     various screen areas up for easier reading.)
     
    Create signal/slot connections, adding stub methods for the slots.
    
last modified: 2012-01-30 jg
ref:
    http://developer.qt.nokia.com/doc/qt-4.8/itemviews-basicsortfiltermodel.html
    
'''
from PyQt4.QtGui import (QApplication, QWidget, QGroupBox, QHBoxLayout, QLabel,
                         QVBoxLayout, QGridLayout, QTreeView, QSortFilterProxyModel,
                         QCheckBox, QLineEdit, QComboBox, QStandardItemModel)
from PyQt4.QtCore import (Qt, pyqtSlot, QRegExp)

class Window(QWidget):
    def __init__(self, parent=None):    # initialise base class
        super(Window, self).__init__(parent)

        layout = QVBoxLayout()
        layout.addWidget(self._createSourcePanel())
        layout.addWidget(self._createProxyPanel())
        self.setLayout(layout)

    # private methods ---------------------------------------------------------
    def _createSourcePanel(self):
        sourceGroupBox = QGroupBox(self.tr("Original Model"))
        sourceView = QTreeView()
        sourceView.setRootIsDecorated(False)
        sourceView.setAlternatingRowColors(True)

        sourceLayout = QHBoxLayout()
        sourceLayout.addWidget(sourceView)
        sourceGroupBox.setLayout(sourceLayout)

        return sourceGroupBox

    def _createProxyPanel(self):
        proxyGroupBox = QGroupBox(self.tr("Sorted/Filter Model"))
        proxyView = QTreeView()
        proxyView.setRootIsDecorated(False)
        proxyView.setAlternatingRowColors(True)
        proxyView.sortByColumn(1, Qt.AscendingOrder)

        proxyModel = QSortFilterProxyModel()
        proxyModel.setDynamicSortFilter(True)
        proxyView.setModel(proxyModel)
        proxyView.setSortingEnabled(True)   # click col hdr to sort

        proxyLayout = QVBoxLayout()
        proxyLayout.addWidget(proxyView)
        proxyLayout.addLayout(self._createProxyFilterPanel())
        proxyGroupBox.setLayout(proxyLayout)

        return proxyGroupBox

    def _createProxyFilterPanel(self):
        sortCaseSensitivityCheckBox = QCheckBox(self.tr("Case sensitive sorting"));
        sortCaseSensitivityCheckBox.setChecked(True)
        filterCaseSensitivityCheckBox = QCheckBox(self.tr("Case sensitive filter"));
        filterCaseSensitivityCheckBox.setChecked(True)

        filterPatternLineEdit = QLineEdit();
        filterPatternLabel = QLabel(self.tr("&Filter pattern:"));
        filterPatternLabel.setBuddy(filterPatternLineEdit);

        filterSyntaxComboBox = QComboBox();
        filterSyntaxComboBox.addItem(self.tr("Regular expression"), QRegExp.RegExp);
        filterSyntaxComboBox.addItem(self.tr("Wildcard"), QRegExp.Wildcard);
        filterSyntaxComboBox.addItem(self.tr("Fixed string"), QRegExp.FixedString);
        filterSyntaxLabel = QLabel(self.tr("Filter &syntax:"));
        filterSyntaxLabel.setBuddy(filterSyntaxComboBox);

        filterColumnComboBox = QComboBox();
        filterColumnComboBox.addItem(self.tr("Subject"));
        filterColumnComboBox.addItem(self.tr("Sender"));
        filterColumnComboBox.addItem(self.tr("Date"));
        filterColumnComboBox.setCurrentIndex(1)
        filterColumnLabel = QLabel(self.tr("Filter &column:"));
        filterColumnLabel.setBuddy(filterColumnComboBox);

        # connect signals/slots for event handling
        filterPatternLineEdit.textChanged.connect(self._filterRegExpChanged)
        filterSyntaxComboBox.currentIndexChanged.connect(self._filterRegExpChanged)
        filterColumnComboBox.currentIndexChanged.connect(self._filterColumnChanged)
        filterCaseSensitivityCheckBox.toggled.connect(self._filterRegExpChanged)
        sortCaseSensitivityCheckBox.toggled.connect(self._sortChanged)

        grid = QGridLayout()
        grid.addWidget(filterPatternLabel, 0, 0)
        grid.addWidget(filterPatternLineEdit, 0, 1)
        grid.addWidget(filterSyntaxLabel, 1, 0)
        grid.addWidget(filterSyntaxComboBox, 1, 1)
        grid.addWidget(filterColumnLabel, 2, 0)
        grid.addWidget(filterColumnComboBox, 2, 1)
        grid.addWidget(filterCaseSensitivityCheckBox, 3, 0)
        grid.addWidget(sortCaseSensitivityCheckBox, 3, 1, Qt.AlignRight)

        return grid

    # private slots -----------------------------------------------------------
    @pyqtSlot()
    def _filterRegExpChanged(self):
        pass

    @pyqtSlot()
    def _filterColumnChanged(self):
        pass

    @pyqtSlot()
    def _sortChanged(self):
        pass


# main ========================================================================
def main():
    import sys

    app = QApplication(sys.argv)
    mw = Window()
    mw.setWindowTitle("Basic Sort/Filter Model - Part 1")
    mw.resize(500, 450)
    mw.show()

    sys.exit(app.exec_())

if __name__ == '__main__':
    main()