PDA

View Full Version : Tree Model Item Highlighter



sajis997
2nd January 2011, 19:35
Hello forum,

Hello forum,

I have subclassed the QAbstractItemModel to generate a customized tree model and since the tree model represent a large database, it is hard for the user to browse down all the items in the tree and find a particular item. For a better usability, i would like to have a textfield where the user type a item name and the item with the name matched will be highlighted in the tree.

Any hint on implementing this feature. I think i need to work with regular expression here and i am not sure how to search down the tree and highlight it.

If there is already any example implementing this feature, please forward me to this.


I am not sure if a have to subclass the QAbstractProxyModel or the QSortFilterProxyModel


My analysis is that i am filtering the item of the tree using the regular expression entered in the field, but i am not changing the contents of the tree view , except highlighting the item that matches the nearest expression. The item , may it be the parent item or the child item will be matched and highlighted. In that case QAbstract ProxyModel sounds like a good choice.


Any suggestion on implementing this feature would be very helpful.


Happy New Year

Sajjad

d_stranz
2nd January 2011, 22:54
I think that to do the searching, you need to create a separate QSortFilterProxyModel just for that, and use the regexp methods to filter the actual model. The proxy model's match() method will return a list of the model indexes that match. I think you will then have to map these back to model indexes in the source model (mapToSource()) and use setItemData() with Qt::BackgroundRole to set the correct brush for each highlighted item.

sajis997
3rd January 2011, 00:40
Thanks for the hint,


i have looking through the example about sort filter proxy model and the Qt doc says that


"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. "


I want to over-ride the feature in the bold text and i want to apply filter both to the parent and child . But the parent item does not match i want to go down to the child as well.

Which class and specifically which function i should look into to change that behavior?


Regards
Sajjad

d_stranz
3rd January 2011, 04:22
"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. "



Hmmm, that's a problem. But if you read further in that same section, it says you can reimplement the filterAcceptsRow() and/or filterAcceptsColumn() virtual methods to override the default behavior. The problem you will then have is that if you accept a parent item that doesn't actually contain the search string, you don't want it to also be highlighted. You only want to highlight the specific child item that matches.

So I guess what you have to do is something like this. From the documentation, where "index0" is the QModelIndex() for the row or column:



if ( sourceModel()->data(index0).toString().contains(filterRegExp()) )
sourceModel()->setData( index0, QVariant( highlightBrush ), Qt::BackgroundRole );
else
sourceModel()->setData( index0, QVariant( normalBrush ), Qt::BackgroundRole );
return true;


Basically, your QSortFilterProxyModel will end up accepting everything, but in doing so will set the background color for anly those items that match the regexp.

I'm pretty sure you want to use "index0" in the setData() call, since that is the index that was used in the comparison. It probably doesn't matter anyway since all the proxy model is doing is setting colors, not rearranging the tree.

It probably isn't necessary to reimplement any other methods in your proxy model, since the default behavior will traverse the entire tree for you. The filterAccepts...() methods are what will do the work for you.

sajis997
3rd January 2011, 04:58
Thanks again,


I need to elaborate a little bit more. if the entered search string matches the parent item , then it will highlight the parent item. And if the search string does not match the parent item , ONLY then the query pattern will go down more to the hierarchy and check if there is any child item that matches the search query string.


And i hope that the attached code snippet meant it as well.


Regards
Sajjad

sajis997
3rd January 2011, 10:58
Hello


To highlight a particular tree item with query pattern i have done the following, please provide me hint if i have missed something on the process , because i am not getting the output i am looking for.


1. I have subclassed the QSortFilterProxyModel and overloaded the following function as follows:


bool H3DHighlighterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
QColor highLighter = Qt::cyan;

if(sourceModel()->data(source_parent).toString().contains(filterRegE xp()))
{
sourceModel()->setData(source_parent,QVariant(highLighter),Qt::Ba ckgroundColorRole);
return true;
}

return false;
}



2. Then i created an object of the type i have declared above as folows:



m_highlightModel = new H3DHighlighterProxyModel(this);
m_highlightModel->setSourceModel(m_h3dTreeModel);
m_highlightModel->setDynamicSortFilter(true);

connect(m_nodeHighlightingEditor,SIGNAL(textChange d(QString)),this,SLOT(highlightRegExpChanged()));


3. Ad i define the slot declared above as follows:




QRegExp regExp(m_nodeHighlightingEditor->text(),Qt::CaseInsensitive);

m_highlightModel->setFilterRegExp(regExp);



4. At last i have made the following changes in the function data() at the original model class:




..................................

.................................
else if(role == Qt::BackgroundColorRole)
{
return item->data(index.column());
}
else if(role == Qt::ForegroundRole)
{
return item->data(index.column());
}
else
return QVariant();
.................................................. .....
.................................................. ......




Please let me know if i have missed anything.


Regards
Sajjad

d_stranz
3rd January 2011, 16:00
Obviously, I can't test your code, but I think this will be a problem:



bool H3DHighlighterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
QColor highLighter = Qt::cyan;

if(sourceModel()->data(source_parent).toString().contains(filterRegE xp()))
{
sourceModel()->setData(source_parent,QVariant(highLighter),Qt::Ba ckgroundColorRole);
return true;
}

return false;
}


If I understand the docs correctly, returning false for any row will stop the recursion into the children of that row. So you have to accept all rows, but only set the highlight color for the rows that match. Returning false says "this row doesn't match, so remove it (and all its children) from the proxy model". That isn't what you want, if I understand correctly. You want your proxy model to contain everything that is in your tree, just some of them should be highlighted.

I am not sure step 4 is necessary; setting the background color in the filter will do it all, because the default behavior of the QSortFilterProxyModel will retrieve the background color. In any case, you have not implemented it correctly. I do not know what "item" is supposed to be. If it is some parent index for the model index that is passed into the data() method, then you need to be returning this:



return item->data(index.column(), role );


otherwise you get the data for the default role (Qt::DisplayRole), not the QBrush for the background or foreground roles.

The data() method is basically a lookup into a hash map by role. For every role, there is a default value; by overriding the data() method for specific roles, you are telling the model-view mechanism to use the data you specify for that role, not the default.

So, if what you are trying to do is to highlight the parent and all of the children for a node that matches the search string, I think you need to do something like this in your filterAcceptsRow() method:



bool H3DHighlighterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
QColor highLighter = Qt::cyan;
QColor noHighlighter = Qt::white;

// If the source data (retrieved using the default Qt::DisplayRole) matches the filter,
// then set its background to the highlight color
if( sourceModel()->data(source_parent).toString().contains(filterRegE xp()) )
{
sourceModel()->setData(source_parent,QVariant( highLighter ),Qt::BackgroundColorRole);
}
else
{
// If you want all children of a matching parent to be highlighted, then do this:
// Set the background color to the same color as its parent
const QModelIndex & parent = source_parent.parent();
if ( parent.isValid() )
{
QVariant bkgnd = sourceModel()->data( parent, Qt::BackgroundRole );
sourceModel()->setData(source_parent, bkgnd,Qt::BackgroundColorRole);
}
else
sourceModel()->setData(source_parent,QVariant( noHighLighter ),Qt::BackgroundColorRole);
}

return true;
}


The last step (if parent is not valid) will have the result of setting all non-matching items to the white background color - the first time though the method, the model index will be the root item (0,0). If it doesn't match, then it will be set to the white background color since it has no parent . As the recursion proceeds, any of its children that don't match will also be set to white background, and so on.

I hope I have understood what you are trying to do :D

And by the way, the docs say to use Qt::BackgroundRole instead of Qt::BackgroundColorRole. Both will work for now, but BackgroundColorRole has been declared as obsolete and might be removed in a later version of Qt.

sajis997
3rd January 2011, 19:37
Tanks for so many useful hints. I believe that i am missing the basic concept of model-view framework. I never realized that the filtering process is done in the recursive manner. Could you provide me with more references so that i can find more about it?


By the way, i have tried as follows, but no response so far. Lets elaborate:

1. The over-ridden function filterAcceptRow(....) has gone through the following amendments:



bool H3DHighlighterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
std::cout << "In the filtration" << std::endl;

QColor highLighter = Qt::cyan;
QColor noHighLighter = Qt::white;

//if the entered string matches any of the contents of the tree view, then highlight
if(sourceModel()->data(source_parent).toString().contains(filterRegE xp()))
{
std::cout << "Will be highlighted" << std::endl;
sourceModel()->setData(source_parent,QVariant(highLighter),Qt::Ba ckgroundRole);
}
else
{
//otherwise there will be no highlight
std::cout << "Will not be highlighted" << std::endl;
sourceModel()->setData(source_parent,QVariant(noHighLighter),Qt:: BackgroundRole);
}

return true;
}




As you notice i have some stream out put statements, and when i run the application, with key press event on the line edit i do not even get the output of the very first cout statement. I think that there is something wrong with the regular expression i am definiing. And i am doing it as follows:



void H3DNodeListWidget::highlightRegExpChanged()
{
QRegExp regExp(m_nodeHighlightingEditor->text(),Qt::CaseInsensitive);

m_highlightModel->setFilterRegExp(regExp);
}



Any more hints to resolve this issue?


Thanks
Sajjad

d_stranz
5th January 2011, 02:10
I am not sure if the problem is the regexp at all. The filterAccepts...() methods will be called no matter what the regexp is. If the regexp is no good, then the if test will just fail to match anything.

Maybe in the highlightRegExpChanged() method you need to invalidate the model? From the docs for QSortFilterProxyModel:


void QSortFilterProxyModel::invalidateFilter () [protected]
Invalidates the current filtering.

This function should be called if you are implementing custom filtering (e.g. filterAcceptsRow()), and your filter parameters have changed.

This function was introduced in Qt 4.3.

See also invalidate().


Try adding invalidateFilter() at the end of your highlightRegExpChanged() method.

d_stranz
9th January 2011, 17:58
Sajjad - did you ever get this working?