user26472
user26472

Reputation: 97

QCompleter select data from multiple columns

I'm trying to have a completer complete on a line edit where one puts in some string like "firstname, lastname", which each correspond to one of two columns from a table model (the ultimate goal is for the order to be flexible, but for the moment i just want this running). The current ways i'm trying to go about it is either:

a) merge the results from the two columns into a proxy w a third column that just has firstname,lastname as a discrete string (which is inconvenient if the user puts it in as lastname,firstname. Current implementation doesn't work bc when i try to do setCompletionColumn on the "fake" column it doesn't activate my reimplemented data method. columnCount includes the fake column tho)

b) have a Completer which doesn't filter, with a backend proxymodel whose acceptsrow() filter filters on both first and last (don't know how to do the non-filtering completer - otherwise it's just looking through one column and the popup at the end shows a bunch of first names without the last names)

c) spoof a Tree model and make it so when i put a comma, the model then looks through a "branch" consisting of all people whose first OR last names are (begin with?) the pre-comma string. (i'm just like not sure how to even start on this, it uses the same sort of acceptsrow as b) but idk how to do like separate ~branches~)

this is giving me a headache, and none of these implementations are particularly pleasant. Am I making this too hard on myself and like using the wrong tool, or is it just something i've gotta buckle down into? I can paste some code, but atm i'm wondering if what im doing is even worthwhile.


update: code snippets and pix

![enter image description here]1

the red circle is what i'm talking about with multiple firstnames, they correspond to different entries in the table but the completer is limited to one column. In terms of clicking and getting the name of the user i've got that mostly figured out, it's just the format and handling commas.

Here's part of my completer class:

 class CustomQCompleter(QCompleter): #dropdown box of suggestions    
    def __init__(self, parent=None,*args):#parent=None):
        self.columnind=0
        super(CustomQCompleter, self).__init__(*args)
        self.parent=parent
        self.setCompletionColumn(self.columnind)
    def pathFromIndex(self,index,role=QtCore.Qt.DisplayRole): #decides what gets put into selection box
        #print(index)
        model=self.source_model
        return model.data(model.index(index.row(),self.columnind),role=QtCore.Qt.DisplayRole)
    def splitPath(self, path):
        self.local_completion_prefix = path #???
        print(path)
        sp=self.local_completion_prefix.split(',')
        if len(sp)>1: #
            print('path split')
            return sp

        return [path]

and here's the acceptsrow on my reimplemented proxymodel:

def filterAcceptsRow(self, row_num, parent): #input matches if this returns true       
    self.filterString=self.parent().inp.text()
    self.filterList=[i.strip() for i in self.filterString.split(',')]
    l=len(self.filterList)
    while l<2:#easiest way to get thru this atm, clean up later
        self.filterList.append('')
        l=len(self.filterList)
    if self.baseModel is not None:
        model=self.baseModel
    else:
        model = self.sourceModel()  # the underlying model, 

                      # implmented as a python array

    row = [model.data(model.index(row_num,0)),model.data(model.index(row_num,1))] #gets the data for this row from sql model in list format



    #(row[0] starts with fname and row[1] starts w lname) OR vice versa
    tests=[row[0].startswith(self.filterList[i]) and row[1].startswith(self.filterList[1-i]) for i in [0,1]]
    #tests = len(self.filterSet.intersection(set([row[col] for col in self.filterColumns])))==2
   # print(tests)
    return True in tests   

and ideally it would look something like this:

enter image description here

Upvotes: 0

Views: 1704

Answers (1)

eyllanesc
eyllanesc

Reputation: 243945

The strategy is to use a proxy where a new role is created that will return the concatenated texts, so that the popup with the concatenated texts is seen, we will establish a custom delegate to the popup() of QCompleter.

import sys

from PyQt5.QtCore import QIdentityProxyModel, Qt
from PyQt5.QtWidgets import QStyledItemDelegate, QCompleter, QApplication, QWidget, \
    QVBoxLayout, QLineEdit, QTableView, QStyleOptionViewItem, QStyle
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel

JoinRole = Qt.UserRole +1

class JoinProxyModel(QIdentityProxyModel):
    def __init__(self, columns, *args, **kwargs):
        QIdentityProxyModel.__init__(self, *args, **kwargs)
        self._columns = columns

    def data(self, index, role):
        if role == JoinRole:
            texts = []
            for c in self._columns:
                texts.append(self.sibling(index.row(), c, index.parent()).data())
            return ", ".join(texts)
        return QIdentityProxyModel.data(self, index, role)

class JoinDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        opt = QStyleOptionViewItem(option)
        self.initStyleOption(opt, index)
        opt.text = index.data(JoinRole)
        widget = option.widget
        style = widget.style() if widget else QApplication.style()
        style.drawControl(QStyle.CE_ItemViewItem, opt, painter, widget)


class JoinCompleter(QCompleter):
    def __init__(self, model, columns, parent=None):
        QCompleter.__init__(self, parent)
        # columns: are the columns that are going to concatenate
        proxy = JoinProxyModel(columns)
        proxy.setSourceModel(model)
        self.setModel(proxy)

        self.setCompletionRole(JoinRole)
        self.setFilterMode(Qt.MatchContains)
        self.popup().setItemDelegate(JoinDelegate(self))


def createConnection():
    db = QSqlDatabase.addDatabase("QSQLITE");
    db.setDatabaseName(":memory:")
    if not db.open():
        QMessageBox.critical(nullptr, QObject.tr("Cannot open database"),
            QObject.tr("Unable to establish a database connection.\n"
                        "This example needs SQLite support. Please read "
                        "the Qt SQL driver documentation for information how "
                        "to build it.\n\n"
                        "Click Cancel to exit."), QMessageBox.Cancel)
        return False

    query = QSqlQuery()
    query.exec_("create table person (id int primary key, "
               "firstname varchar(20), lastname varchar(20))")
    query.exec_("insert into person values(101, 'Danny', 'Young')")
    query.exec_("insert into person values(102, 'Christine', 'Holand')")
    query.exec_("insert into person values(103, 'Lars', 'Gordon')")
    query.exec_("insert into person values(104, 'Roberto', 'Robitaille')")
    query.exec_("insert into person values(105, 'Maria', 'Papadopoulos')")

    return True

if __name__ == '__main__':
    app = QApplication(sys.argv)

    if not createConnection():
        sys.exit(-1)

    w = QWidget()

    lay = QVBoxLayout(w)
    le = QLineEdit()
    view = QTableView()
    model = QSqlTableModel()
    model.setTable("person")
    model.select()

    completer = JoinCompleter(model, [1, 2])
    le.setCompleter(completer)
    view.setModel(model)

    lay.addWidget(le)
    lay.addWidget(view)

    w.show()
    sys.exit(app.exec_())

Upvotes: 1

Related Questions