PDA

View Full Version : Problem inserting child items into a QAbstractItemModel



Valheru
13th October 2006, 16:27
This one is a bit complex, for me at least :p

I have a QAbstractItemModel. This is because a large amount of items are added at one go, and using a normal QTreeWidget is horribly slow.
The model uses a self-made ModelItem that is more or less copied from the Qt documentation for each row in it. This works fine. When I want to add children to the existing rows however, something goes horribly wrong.

What I'm doing is displaying newsgroup posts in a threaded view, ie. replies to posts show up under that parent rows in the QTreeView and can be expanded with a "+". Using qDebug() I can see that my code does exactly as expected...except that Qt somehow messes up what it displays in the QTreeView. I suspect that this is because the internal index of the children I am adding is being set wrong, but I can't tell what is going wrong. If anyone can help with this I'd be very grateful, I've been staring at this for the last hour trying everything I can think of, but my understanding of how to implement this just isn't on par with what I'm trying to do :(

The code of interest is in the constructor of the QAbstractItemModel. Basically what happens is that none of the children actually show up. What is shown in their place are multiple instances of more-or-less random various parent rows.


/************************************************** *************************
* Copyright (C) 2006 by Lawrence Lee *
* valheru@facticius.net *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
************************************************** *************************/
#include "modelItem.h"
#include "articleModel.h"
#include <QtCore/QDebug>
#include <QtCore/QStringList>

ArticleModel::ArticleModel( const QStringList &data, QObject *parent ) : QAbstractItemModel( parent )
{
QList<QString> rootData;
rootData << "From" << "Subject" << "Date" << "Message-ID" << "References" << "Lines";
rootItem = new ModelItem( rootData );
for ( int i = 0; i < data.count(); i += 6 ){
if( data[ i + 4 ] == "" || data[ i + 4 ].isEmpty() ){
///It has no reference, so we simply add it to the root of the model
ModelItem *item = new ModelItem( QStringList() << data[ i ] << data[ i + 1 ]
<< data[ i + 2 ] << data[ i + 3 ]
<< data[ i + 4 ] << data[ i + 5 ], rootItem );
rootItem->appendChild( item );
///Remove it from the list
// for( int x = i; x < i + 6; ++x ){
// const_cast< QStringList& >( data ).removeAt( x );
// }
}
}
int maxReferences = 0;
for ( int i = 0; i < data.count(); i += 6 ){
if( !data[ i + 4 ].isEmpty() ){
QStringList references = data[ 4 ].split( " ", QString::SkipEmptyParts );
if( references.count() > maxReferences ){
maxReferences = references.count();
}
}
}
int iteration = 1;
while( iteration <= maxReferences ){
for( int i = 0; i < data.count(); i += 6 ){
if( !data[ i + 4 ].isEmpty() ){
QStringList references = data[ i + 4 ].split( " ", QString::SkipEmptyParts );
if( references.count() == iteration ){
for( int p = 0; p < rowCount(); ++p ){
if( references[ 0 ] == rootItem->child( p )->data( 3 ).toString() ){
ModelItem *item = new ModelItem( QStringList() << data[ i ] << data[ i + 1 ]
<< data[ i + 2 ] << data[ i + 3 ]
<< data[ i + 4 ] << data[ i + 5 ], rootItem );
rootItem->child( p )->appendChild( item );
for( int t = 0; t < item->columnCount(); ++t ){
qDebug() << item->data( t ).toString();
}
qDebug() << data[ i + 1 ] << "is the child of" << rootItem->child( p )->data( 1 ).toString();
qDebug() << "because reference" << references[ 0 ] << "in child refers to parents Message-ID"
<< rootItem->child( p )->data( 3 ).toString() << "\r\n";
}
}
}
}
}
++iteration;
}
}

ArticleModel::~ArticleModel()
{
delete rootItem;
}

QModelIndex ArticleModel::index( int row, int column, const QModelIndex &parent ) const
{
ModelItem * parentItem;

if ( !parent.isValid() )
parentItem = rootItem;
else
parentItem = static_cast<ModelItem*>( parent.internalPointer() );

ModelItem *childItem = parentItem->child( row );
if ( childItem )
return createIndex( row, column, childItem );
else
return QModelIndex();
}

QModelIndex ArticleModel::parent( const QModelIndex &index ) const
{
if ( !index.isValid() )
return QModelIndex();

ModelItem *childItem = static_cast<ModelItem*>( index.internalPointer() );
ModelItem *parentItem = childItem->parent();

if ( parentItem == rootItem )
return QModelIndex();

return createIndex( parentItem->row(), 0, parentItem );
}

int ArticleModel::rowCount( const QModelIndex &parent ) const
{
ModelItem * parentItem;

if ( !parent.isValid() )
parentItem = rootItem;
else
parentItem = static_cast<ModelItem*>( parent.internalPointer() );

return parentItem->childCount();
}

int ArticleModel::columnCount( const QModelIndex &parent ) const
{
if ( parent.isValid() )
return static_cast<ModelItem*>( parent.internalPointer() ) ->columnCount();
else
return rootItem->columnCount();
}

QVariant ArticleModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() )
return QVariant();

if ( role != Qt::DisplayRole )
return QVariant();

ModelItem *item = static_cast<ModelItem*>( index.internalPointer() );

return item->data( index.column() );
}

Qt::ItemFlags ArticleModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() )
return Qt::ItemIsEnabled;

return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant ArticleModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
return rootItem->data( section );

return QVariant();
}

/************************************************** *************************
* Copyright (C) 2006 by Lawrence Lee *
* valheru@facticius.net *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
************************************************** *************************/
#include "modelItem.h"

ModelItem::ModelItem( const QList<QString> &data, ModelItem* parent )
{
parentItem = parent;
itemData = data;
}

ModelItem::~ ModelItem()
{
qDeleteAll( childItems );
}

void ModelItem::appendChild( ModelItem *item )
{
childItems.append( item );
}

ModelItem *ModelItem::child( int row )
{
return childItems.value( row );
}

int ModelItem::childCount() const
{
return childItems.count();
}

int ModelItem::row() const
{
if ( parentItem != 0 ) {
return parentItem->childItems.indexOf( const_cast<ModelItem*>( this ) );
}
return 0;
}

int ModelItem::columnCount() const
{
return itemData.count();
}

QVariant ModelItem::data( int column ) const
{
return itemData.value( column );
}

ModelItem *ModelItem::parent()
{
return parentItem;
}

Valheru
13th October 2006, 16:40
Ok, that's just plain weird. I just changed the code that controls the info that is passed the the child item when it is constructed, like so :


ModelItem *item = new ModelItem( QStringList() << QString::number( i ) << QString::number( i + 1)
<< QString::number( i + 2 ) << QString::number( i + 3 )
<< QString::number( i + 4 ) << QString::number( i + 5) );
rootItem->child( p )->appendChild( item );It runs fine, but the moment I click on a "+" to expand an entry, the program segfaults. GDB point to line 50 of modelItem.cpp ( the custom model item class ) as the cause of the segfault :


int ModelItem::row() const
{
if ( parentItem != 0 ) { ///<--- This is where it segfaults according to GDB
return parentItem->childItems.indexOf( const_cast<ModelItem*>( this ) );
}
return 0;
}


Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread -1217018192 (LWP 13634)]
0x08078c3d in ModelItem::row (this=0x0) at modelItem.cpp:50
50 if ( parentItem != 0 ) {
(gdb) bt
#0 0x08078c3d in ModelItem::row (this=0x0) at modelItem.cpp:50
#1 0x080769d5 in ArticleModel::parent (this=0x8143a50, index=@0x81cd660) at articleModel.cpp:114
#2 0xb7e75646 in QTreeView::indexRowSizeHint () from /opt/qt4/lib/libQtGui.so.4
#3 0xb7e75a47 in QTreeView::indexRowSizeHint () from /opt/qt4/lib/libQtGui.so.4
#4 0xb7e787c6 in QTreeView::drawTree () from /opt/qt4/lib/libQtGui.so.4
#5 0xb7e7abcc in QTreeView::paintEvent () from /opt/qt4/lib/libQtGui.so.4
#6 0xb7b3b90e in QWidget::event () from /opt/qt4/lib/libQtGui.so.4
#7 0xb7d8e914 in QFrame::event () from /opt/qt4/lib/libQtGui.so.4
#8 0xb7df729c in QAbstractScrollArea::viewportEvent () from /opt/qt4/lib/libQtGui.so.4
#9 0xb7e4a092 in QAbstractItemView::viewportEvent () from /opt/qt4/lib/libQtGui.so.4
#10 0xb7df8785 in QAbstractScrollArea::setViewport () from /opt/qt4/lib/libQtGui.so.4
#11 0xb7af9561 in QApplicationPrivate::notify_helper () from /opt/qt4/lib/libQtGui.so.4
#12 0xb7aff167 in QApplication::notify () from /opt/qt4/lib/libQtGui.so.4
#13 0xb7b4356e in QApplicationPrivate::createEventDispatcher () from /opt/qt4/lib/libQtGui.so.4
#14 0xb7c159b1 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#15 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#16 0xb7c1529d in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#17 0xb7c1529d in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#18 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#19 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#20 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#21 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#22 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#23 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#24 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#25 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#26 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#27 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#28 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#29 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#30 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#31 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#32 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#33 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#34 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#35 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#36 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#37 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#38 0xb7c1529d in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#39 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#40 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#41 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#42 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#43 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4
#44 0xb7c153df in QWidget::update () from /opt/qt4/lib/libQtGui.so.4
#45 0xb7c155e7 in QWidgetPrivate::drawWidget () from /opt/qt4/lib/libQtGui.so.4

Valheru
13th October 2006, 23:48
Just noticed something else : when selecting an root-level item, all other instances of that item which have been erroniously added as children to other root-items also become selected. This is definitely a problem then that some sort of index is not being set for the children items, and the modelview is becoming confused and assigning parent items to as children when the view asks for them to render. What index and how to set it remains the problem, however. I would guess that the internal pointer is not being maintained for them, but I don't know if I'm right.

/edit : clicking on one of the children does not select the child, instead it selects the root-item(s) with the same data as that child item. This is driving me nuts.

wysota
14th October 2006, 08:34
Why don't you just use QStandardItemModel as your model? From what I've seen you are simply keeping strings in your model. The standard model can do that for you as well...


QStandardItemModel model(0, 6);
model.insertRows(model.rowCount(), 1, QModelIndex()); // adds an item at level "0"
QModelIndex ind = model.index(model.rowCount()-1, 0);
model.setData(ind, "From field data", Qt::DisplayRole);
model.insertRows(0, 1, ind); // adds a child to the previous item
model.insertColumns(0, 4, ind); // adds a child to the previous item
QModelIndex chldind = model.index(0, 0, ind);
model.setData(chldind, "From field data in child item", Qt::DisplayRole);

and so on...

As for your code I guess you have something in the parent-child traversal/relationship messed up. I'd suggest using the standard model if you can (and I think you can) - there is no need reinventing the wheel.

Valheru
14th October 2006, 10:52
I think I may have found the problem already. It lies with creating the index before appending it to a child. If you look at the Simple Tree Model then you see that they buffer the data into a QList< QVariant > before creating the child :


rootItem->addChild( new ItemModel( QList< QVariant >, rootItem )I think that that was the whole problem, by creating the item beforehand it messes up the unique index that the model gives the item.

Wysota, I would use the QStandardItemModel, but the calls of setData bring a large amount of overhead. Some newsgroups, such as alt.binaries.boneless, have an obscene amount of articles in them. If you were to list it ( the download if the headers takes about 30 minutes on my 20 MBit connection, although that depends on how stressed the server is ) I think it would take an inordinate amount of time to add them to the view.

/edit : I feel so stupid. Really. The problem was, as it normally is, a really dumb mistake. On line 63 of the ArticleModel.cpp I was passing the header of the TreeView as the parent, while it should have been
rootItem->child( p ) Now everything is working fine. And to think I spend about 6 hours on this >:(

wysota
14th October 2006, 18:35
Wysota, I would use the QStandardItemModel, but the calls of setData bring a large amount of overhead. Some newsgroups, such as alt.binaries.boneless, have an obscene amount of articles in them. If you were to list it ( the download if the headers takes about 30 minutes on my 20 MBit connection, although that depends on how stressed the server is ) I think it would take an inordinate amount of time to add them to the view.

Who says you have to add headers all at the time with single calls to insertRows/setData? You can subclass the model and provide your own methods for adding items.