PDA

View Full Version : Help with Concept: Sorting/filtering TreeView & QAbstractItemModel and filtering



bpkelly
24th April 2017, 17:54
Can someone help me present a list filtered by the column 1 of the TreeView and the Hierarchical AbstractItemModel?

I am trying to present a list in the lowerleft of only items that are of Type VARIABLE or STRING BUILDER. I have attempted to use sortFilterProxyModel and am missing something. I guess it only works on items that match when their parent is rootnode.

I have been beating my head against the wall on how to do this without making two seperate AbstractItem models and attempting to maintain the QmodelIndexes.

12445

Santosh Reddy
25th April 2017, 14:04
I guess it only works on items that match when their parent is rootnode

No, the filter is applied recursively to all children. Please re-check the filter expression.

bpkelly
25th April 2017, 16:38
Santosh,
Thanks for your answer. But I think I am still stuck here, per the class reference QSortFilterProxyModel

For hierarchical models, the filter is applied recursively to all children. If a parent item doesn't match the filter, none of its children will be shown.

For instance if I change my RegExp to 'CAMERA' and Add a bunch of TYPE: CAMERA it works just fine. I get only a list of cameras in the lower left, when the RegExp filter is applied.

I think I understand why collapsing a Tree to a Table is not trivial, if you want to maintain all the modelIndexes in the model and the local modelIndexes in the proxymodel, especially if you want to be able to remove or add items in both views. I think that is why the Filter is so constrained. In my case the second non-tree view, I only intend the items to be read only, but select-able. When the item is selected I want to move the reference to a List owned the Variable Aggregate tool (TYPE: STRING BUILDER).

When the tree is properly populated, my main program will iterate over the tree and create a bunch of TIFF images.

I am not proficient in C++(so for me, looking at the source code is like a 50yd dash in a 40yd gym), and I am only a rank amateur in python. I do this type of stuff only when needed to eliminate a lot of repetitive stuff for work, not for a living. so forgive me if I am way off base. I feel like a guy who can build Ikea furniture let loose in a master cabinetmakers workshop.

There are some things that I personally cannot figure out from the documentation:
1) does removing objects/references from the proxy always remove items from the source?
2) Is it possible to change all the parents to rootNode in the proxy yet maintain the hierarchy in the source model?
3) Since my second and third ListViews are read only(in the sense you cannot edit the objects, you can only move them from one listview to the other), can make all the Variable nodes in my source QPersistantModelIndexes then append them to a QList, then MapFromSource in an AbstractProxyList?

PS. I should add the total number of items in my tree would probably never exceed 100 and mostly hover around 25-30 items, so the additional processor overhead shouldn't be a concern, which people brought up in other topics relating to PersistantIndexes.

Added after 7 minutes:

I have been able to figure out child location in the tree for only Variables, can I some how use this info to map to a proxy?
12446

d_stranz
26th April 2017, 18:49
As you have discovered, you can't use a simple regexp to filter your tree. If I understand your requirement correctly, you want to build a second view of the model that contains only the parts of the original model that have leaves (bottom-most children) that match a certain phrase like "VARIABLE". It also looks like your tree model only contains two columns in each row.

If that is correct, then you will need to derive from QSortFilterProxyModel to create your own class and override (re-implement) the protected method QSortFilterProxyModel::filterAcceptsRow(). Your override must do this:

- if the given "row" of the QModelIndex source_parent is a leaf (i.e. QAbstractItemModel::hasChildren() returns false), and the value in column 2 matches your filter string, then you return true, otherwise you return false.

- if the given row is not a leaf (hasChildren() returns true), then you need to recursively call filterAcceptsRow() for each child of "row" and do the same test as in the first case. You can't simply return true here, you have to go to the leaves of the node to see if any one of them matches. If any leaf node matches, then you want the parents of that node to be included. If no leave node matches, then you don't want any of the parent nodes displayed (except the root node).

I'm sorry, but I can't make up code off the top of my head and ensure it will work without writing a test program, and I don't have the time for that now.

I -think- that filterAcceptsRow() is the only method you'll need to override, since the other methods in the proxy model likely use its return value when computing row counts, etc. (If filterAcceptsRow() returns false, then that item and all of its children are ignored, so if an item has children some of which don't pass the filter, then row count will be reduced, and so forth). Be sure you refer to the source model when calling methods like hasChildren(), etc. on the QModelIndex "source_parent" that is passed to this method, otherwise you could get yourself into an infinitely recursive loop.

You don't need to mess with persistent model indexes or any of that.

bpkelly
26th April 2017, 19:26
d_stranz,
Thank you for your answer. I think I can pound that one out, but is it also true that all the parent items of TYPES other than VARIABLE will be shown in that resultant filter schema if at the end its a leaf of type VARIABLE?

If a tree looks like the one below:

Root
-Camera
-Roll
--Var0
--job0
---Var1
---Var2
---StringBuilder0
--job1
---Var3

What I really need is something that I can make a listview or abstractview that returns the following after filtering:
Var0
Var1
Var2
StringBuilder0
Var3

If it still populated with parents which end with leaves of type VARIABLE, very little filtering would have been achieved.

The idea is the user can aggregate any number of these Variables or String builders into another String Builder class.
so the user would select one item from the left and use ">" to push it into the String Builder or use "<" to remove it, where item order is respected and builds the resultant string to fill the contents of a Barcode or Datamatrix, or a text in an image.

Each of these variables have their own types:
Static
RandFromSet
RandIntOfLength
IncrementorDecrementor

They can be set to update when their owner gets traversed, or upon being called if the variable is used twice in the same parent.

And I have successfully built that aspect of the traversing engine with a static set of pre-built nodes. Now I am trying to open it up so the user can create his own schema.

But the GUI aspect of what I thought the simple task of filtering this tree to only show the leaves of a specific type has really pushed the bounds of my self-educated problem solving abilities.

I thought of trying to create a hashmap in my basemodel that collects the VARIABLE's as QPersistantModelIndexes that regenerates itself anytime a name or structural change occurs
Then create an empty proxyModel and fill it with said QPersistantModelIndexes where I locally change the parent to root, and connecting some datachanged-emits. But I cannot figure out how to force indexes into an empty proxymodel. I cannot figure out what the base container in a model is or what it holds, I am guessing its a list of QmodelIndexes. It's probably not virtual anyway... So this is probably a dead end.

The flailing around is driving me bonkers. It seems like such an easy task, yet not.

I wonder if I am just over complicating the hell out of it.

Am I fundamentally breaking the MVC concept here somehow? How would you all do it? Should I forget about ModelView and just bruteforce it with widgets?

Thanks ahead of time!

bpkelly
26th April 2017, 21:39
just to confirm, the iteration method returned the parents.... Ugh.

d_stranz
27th April 2017, 01:45
Maybe I misunderstood what you are trying to achieve. The screenshot in your original post showed a tree in both views, so I assumed you wanted a tree where only the branches that had leaves matching your keyword were shown, so yes, what I suggested would show the parents too.

If all you want is a list of the leaves, then have a look at KDescendentsProxyModel (https://inqlude.org/libraries/kitemmodels.html). There is also a KDE FlatProxyModel (https://api.kde.org/bundled-apps-api/calligra-apidocs/calligra/plan/html/classKPlato_1_1FlatProxyModel.html) which converts a tree model into a table model. You might be able to use two layers of proxy models, first to convert your tree into a table, then to filter only rows from the table that contain your keyword.

bpkelly
27th April 2017, 17:19
d_stranz,
Thanks yes it was probably due to ignorance or poor explanation on my part.

So I was able to find something very similar done in Python on stackoverflow (link (http://stackoverflow.com/questions/21564976/how-to-create-a-proxy-model-that-would-flatten-nodes-of-a-qabstractitemmodel-int))

It changes names when I change a node Name in either the listview or the TreeView. Which was what I was gunning for, but is not adding items when I add items to the sourceModel thru myGUI , nothing happens, any hints?


from PyQt4 import QtCore, QtGui


class FlatProxyModel(QtGui.QAbstractProxyModel):
def __init__(self, parent = None):
super(FlatProxyModel, self).__init__(parent)

@QtCore.pyqtSlot(QtCore.QModelIndex, QtCore.QModelIndex)
def sourceDataChanged(self, topLeft, bottomRight):
self.dataChanged.emit(self.mapFromSource(topLeft), self.mapFromSource(bottomRight))
def buildMap(self, model, parent = QtCore.QModelIndex(), row = 0):
if row == 0:
self.m_rowMap = {}
self.m_indexMap = {}
rows = model.rowCount(parent)
for r in range(rows):
index = model.index(r, 0, parent)
print('row', row, 'item', model.data(index,0))
self.m_rowMap[index] = row
self.m_indexMap[row] = index
row = row + 1
if model.hasChildren(index):
row = self.buildMap(model, index, row)
return row
def setSourceModel(self, model):
self.model = model
QtGui.QAbstractProxyModel.setSourceModel(self, model)
self.buildMap(model)
model.dataChanged.connect(self.sourceDataChanged)
def mapFromSource(self, index):
if index not in self.m_rowMap: return QtCore.QModelIndex()
return self.createIndex(self.m_rowMap[index], index.column())
def mapToSource(self, index):
if not index.isValid() or index.row() not in self.m_indexMap:
return QtCore.QModelIndex()
return self.m_indexMap[index.row()]
def columnCount(self, parent):
return QtGui.QAbstractProxyModel.sourceModel(self)\
.columnCount(self.mapToSource(parent))
def rowCount(self, parent):
return len(self.m_rowMap) if not parent.isValid() else 0
def index(self, row, column, parent):
if parent.isValid(): return QtCore.QModelIndex()
return self.createIndex(row, column)
def parent(self, index):
return QtCore.QModelIndex()




Thanks So much for even bothering to read it!

I finally Figured it out!!!
Added the following to the FlatProxyModel Class:

def update(self):
self.model = self.sourceModel()
self.m_indexMap.clear()
self.m_rowMap.clear()
self.buildMap(self.model)

to the flatten proxy

then added a Signal/Slots to controller

QtCore.QObject.connect(self._model,QtCore.SIGNAL(' dataChanged(QModelIndex,QModelIndex)'),self.flatte nedProxyModel.update)
QtCore.QObject.connect(self._model,QtCore.SIGNAL(' dataChanged(QModelIndex,QModelIndex)'),self.filter edProxyModel.modelReset)