Dave
Dave

Reputation: 87

Sorting case insensitively in QAbstractItemModel

I have trouble with trying create my own sorting function with QAbstractItemModel. It works but not case insensitive. I have tried to use QSortFilterProxyModel, but any success. My sort function:

def sort(self, col, order):
    self.emit(SIGNAL("layoutAboutToBeChanged()"))
    self.tableData = sorted(self.tableData, key=operator.itemgetter(col))       
    if order == Qt.AscendingOrder:
        self.tableData.reverse()
    self.emit(SIGNAL("layoutChanged()"))

I am using QTableView. How I could make it case insensitive?

Full example:

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import operator
import sys

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()

        header = ["one", "two"]
        tableDict = [["abcdef", "tuvwx"], ["aBcde", "TUvWx"], ["acdef","tUvWX"], ["Acdef", "TUVwx"], ["ACdef", "TUVwx"]]
        self.myTable = newTableModel(header, tableDict)

        mainLayout = QHBoxLayout()
        mainLayout.addWidget(self.myTable.tableView)
        self.setLayout(mainLayout)
        self.setWindowTitle("Test table")

class newTableModel(QAbstractTableModel): 
    def __init__(self, header, data, parent=None, *args):
        super(newTableModel, self).__init__(parent)
        self.tableView = QTableView()
        self.tableData = data
        self.header = header

        self.tableView.setShowGrid(True)
        self.tableView.setFrameStyle( QFrame.NoFrame )
        self.tableView.setFocusPolicy( Qt.NoFocus )
        self.tableView.setSelectionMode( QAbstractItemView.NoSelection )

        vHeader = self.tableView.verticalHeader()
        vHeader.setVisible(False)
        vHeader.setStretchLastSection(False)
        hHeader = self.tableView.horizontalHeader()
        hHeader.setVisible(True)
        hHeader.setStretchLastSection(False)
        self.tableView.setSortingEnabled(True)

        self.tableView.setModel(self)
        self.tableView.resizeRowsToContents()
        self.tableView.resizeColumnsToContents()
        vHeader.setResizeMode(QHeaderView.ResizeToContents)

        self.tableView.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.tableView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def rowCount(self, parent): 
        return len(self.tableData) 

    def columnCount(self, parent): 
        return len(self.tableData[0]) 

    def data(self, index, role=Qt.DisplayRole):
        row = index.row()
        col = index.column()
        if role == Qt.DisplayRole:
            return "{0}".format(self.tableData[row][col])
        return None

    def setData(self, index, value, role):
        if index.isValid():
             return True
        return False

    def flags(self, index):
        fl = QAbstractTableModel.flags(self, index)
        if index.column() == 0:
            fl |= Qt.ItemIsUserCheckable
        return fl

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self.header[col]

    def sort(self, col, order):
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
        self.tableData = sorted(self.tableData, key=operator.itemgetter(col))      
        if order == Qt.AscendingOrder:
            self.tableData.reverse()
        self.emit(SIGNAL("layoutChanged()"))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

This table data in example shows sorting - non case sensitive.

Upvotes: 2

Views: 1347

Answers (1)

ekhumoro
ekhumoro

Reputation: 120608

You simply need to convert the values to lower (or upper) case in the sort-key that is passed to the sorted function. For improved efficiency, you can also use the reverse argument to avoid doing that in a separate step:

def sort(self, col, order):
    self.layoutAboutToBeChanged.emit()
    self.tableData = sorted(
        self.tableData,
        key=lambda row: row[col].lower(),
        reverse=(order != Qt.AscendingOrder),
        )
    self.layoutChanged.emit()

Note that sorted does a stable sort, so equal values (after the key has been applied), will keep their original places. Thus, the second column in your example won't show any changes when it's sorted, since the values are all the "same" (if you ignore case).

UPDATE:

Here's a solution that will work for strings and numbers. It assumes the columns aren't a mixture of the two types:

def sort(self, col, order):
    if self.tableData and self.tableData[0]:
        self.layoutAboutToBeChanged.emit()
        if isinstance(self.tableData[0][col], str):
            sortkey = lambda row: row[col].lower()
        else:
            sortkey = operator.itemgetter(col)
        self.tableData = sorted(
            self.tableData, key=sortkey,
            reverse=(order != Qt.AscendingOrder))
        self.layoutChanged.emit()

Upvotes: 3

Related Questions