Qt AbstractItemModel removeRows and delete causes core - c++

I have the following to insert nodes:
layoutAboutToBeChanged();
beginInsertRows(createIndex(p_parent->row(), 0, p_parent), start, end);
TreeNode* p_node = new TreeNode(p_parent, p_data);
p_parent->appendChild(start, p_node);
endInsertRows();
layoutChanged();
And to remove rows:
layoutAboutToBeChanged();
beginRemoveRows(createIndex(p_parent->row(), 0, p_parent), row, row);
p_parent->removeChildren(row, row+1, this);
endRemoveRows();
layoutChanged();
When removeChildren is called, for each node that is removed the following is done:
changePersistentIndex(createIndex(p_node->row(), 0, p_node), QModelIndex());
delete p_node;
It works. I can add nodes and remove nodes.
Terminology NOTE: I'm using nodes and rows interchangeably. Sorry for any confusion.
What doesn't work:
If a new row is inserted in front of a selected node. The newly
inserted node becomes selected. [This is not what I expect of want.]
If a row is selected and then later deleted, immediate core dump.
If mouse over a row that is deleted, immediate core dump.
If I don't delete p_node. Everything runs fine. But obviously that creates a memory leak.
What am I doing wrong?
For reference I'm using QT 5.0.2 on 64 bit Linux.

Do append and remove children methods update the rowCount?
It seems to be the problem.
Check how is done in QStandardItemModel
Updating rowcount should solve the 3 points without having to update the persistent indexes:
If rowcount is not updated, is normal that selected item changes to the inserted before, its where the index is pointing.
& 3. Indexes are pointing to a deleted item.

To fix crashes you should use deleteLater instead of delete, so your view won't die trying to access invalid objects.
Selected item seems a index problem. Looks like it's missing a notification to view.

to point 1: maybe
model->blockSignals(true);
...
model->blockSignals(false);
will fix that selection behaviour.
i guess selected indexes/rows will have some functions called from the framework. so if they are deleted, they cause the crash. if you want to delete them, set the selection to another row/index and it should run fine ... '
if you mouse over a deleted row' ... if the row should be just empty, why not set the text empty?

Related

Qt 5.5 using C++, removing and item from ComboBox is removing all items before the item as well

Working on a program where 6 combobox's have relative data, once an index is selected, the others shouldn't be able to select it, hence I'm just removing it from the index on the others, however in practice it is removing the index plus every index before it for some reason. Any idea why?
void AssignStatsWindow::on_comboBox_currentIndexChanged()
{
ui->comboBox_2->removeItem(ui->comboBox->currentIndex()); //these should remove 1 index but removes many
ui->comboBox_3->removeItem(ui->comboBox->currentIndex());
ui->comboBox_4->removeItem(ui->comboBox->currentIndex());
ui->comboBox_5->removeItem(ui->comboBox->currentIndex());
ui->comboBox_6->removeItem(ui->comboBox->currentIndex());
for (int i = ui->comboBox->count(); i >= 0; --i) //removes all but newly selected index, seems to be working fine
{
if (i != ui->comboBox->currentIndex()) {
ui->comboBox->removeItem(i);
}
}
}
comboBox is the one having the indexChanged and triggering the code, comboBox_2 through 6 are the others that need adjust and are 'over-removing' indexes. Once I get this first one working correctly it should be easy to build the rest of the indexChanged for the rest of the comboBoxes. Any help would be greatly appreciated.
From the Qt 5.5 documentation:
Removes the item at the given index from the combobox. This will update the current index if the index is removed.
in practice it is removing the index plus every index before it for some reason. Any idea why?
It seems that the work to remove an item is being performed in a slot that responds to the currentIndexChanged signal. The above documentation states removing an item will change the current index of the combo box which will result in the slot being triggered many times, thereby removing many items.

Qt C++ : removing next-to-last item from QListWidget makes program crash

in this program, items (markers) are added to a QListWidget calles ui->lwMarkers. These items can also be removed again by pressing the "Remove button" which calls the following function
void Form::on_pbRemoveMarker_clicked()
{
if (ui->lwMarkers->currentRow() < 0) return;
delete ui->lwMarkers->takeItem(ui->lwMarkers->currentRow());
}
Inside the function, the first line is to make sure an item (marker) is actually selected.
The second line is (at least, I hope) to delete the selected item.
Adding and removing: all goes well, unless when you want to remove next-to-last item. Then it crashes, unfortunately. I do not see why.
Can anyone shed a light on this issue?
If it can help: the full code is from the qt-google-maps project: https://code.google.com/p/qt-google-maps/ . This project uses the Google Maps API v2, I altered the code to use v3.
The question that I ask, is a particular behaviour of their code, and I simply don't see the reason of the crash. Any help?
The crash always happens just before the delete, I believe it is because of the takeItem and the error I get is as follows:
ASSERT failure in QList::operator[]: "index out of range", file ../../QtSDK/Desktop/Qt/473/gcc/include/QtCore/qlist.h, line 464
Could it be that takeItem is the evildoer? According to the class reference , it removes the item from the QListWidget and returns it. It also mentions:
Items removed from a list widget will not be managed by Qt, and will need to be deleted manually.
This is why, to me, the original code seems correct, you remove the item from the list widget and delete it afterwards. Still, it gives the above mentioned error when trying to remove the next-to-last item.
I suggest to use the item() function instead, and delete it accordingly:
void Form::on_pbRemoveMarker_clicked()
{
if (ui->lwMarkers->currentRow() < 0) return;
delete ui->lwMarkers->item(ui->lwMarkers->currentRow());
}
The frontend of the program is now working as expected, but do I leave memory leaks this way?
Any other problem you might see?

QSortFilterProxyModel crashing app

I have a model subclassed from QAbstractListModel which has a QList to maintain the data which contains QDateTime which is used to maintain this list. I have to maintain this data for an hour i.e., older data will be removed from the list. This basically is FIFO list. I have a proxy model (subclass of QSortFilterProxyModel) to sort the data. Whenever the data changes, proxy model is loosing the index and displaying data unfiltered. Following is the code snippet to do this.
emit layoutAboutToBeChanged();
beginInsertRows(QModelIndex(), 0, 1); //we are prepending
m_entries.prepend(e);
endInsertRows();
emit layoutChanged();
This seems to have solved the problem. But, if something is selected on the view (QTreeView), then the application is crashing after sometime with lot of these error messages.
QSortFilterProxyModel: index from wrong model passed to mapFromSource
QSortFilterProxyModel: index from wrong model passed to mapFromSource
QSortFilterProxyModel: index from wrong model passed to mapFromSource
Stack trace on the debugger shows the mouseSelectEvent and other functions which needs QModelIndex.
Sorry for the long question. Could someone please help in solving this problem?
Thanks.
The documentation of beginInsertRows says void QAbstractItemModel::beginInsertRows(const QModelIndex & parent, int first, int last) which means that when you insert only one item parameters first = last = 0. In your snippet you insert one item with m_entries.prepend(e) but you delcare that you are going to insert two: beginInsertRows(QModelIndex(), 0, 1); The view receives signal that two rows have been inserted and when it asks for the second one - boom! access violation. What you need is beginInsertRows(QModelIndex(), 0, 0);. Also I don't think you need to emit layoutAboutToBeChanged() an emit layoutChanged(); but I am not sure about that.

QTreeView::scrollTo not working

Qt 4.8
I have a QTreeView based class with an asociated QAbstractItemModel based class. If I reload the model with new information I want to expand/scroll the tree to a previous selected item.
Both clases, tree view and model are correctly created and connected using QTreeView::setSelectionModel(...) working everything properly.
After reloading the model, I get a valid index to the previous selected item and I scrollTo it:
myTreeView->scrollTo(index);
but the tree is not expanded. However, if I expand the tree manually, the item is really selected.
Tree view is initialized in contruct with:
header()->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
header()->setStretchLastSection(false);
header()->setResizeMode(0, QHeaderView::ResizeToContents);
Any idea about expanding the tree to the selection?
Even QTreeView::scrollTo documentation says:
Scroll the contents of the tree view until the given model item index is
visible. The hint parameter specifies more precisely where the item should
be located after the operation. If any of the parents of the model item
are collapsed, they will be expanded to ensure that the model item is visible.
That is not really true (I think)
If solved the problem expanding all previous tree levels manually:
// This slot is invoqued from model using last selected item
void MyTreeWidget::ItemSelectedManually(const QModelIndex & ar_index)
{
std::vector<std::pair<int, int> > indexes;
// first of all, I save all item "offsets" relative to its parent
QModelIndex indexAbobe = ar_index.parent();
while (indexAbobe.isValid())
{
indexes.push_back(std::make_pair(indexAbobe.row(), indexAbobe.column()));
indexAbobe = indexAbobe.parent();
}
// now, select actual selection model
auto model = _viewer.selectionModel()->model();
// get root item
QModelIndex index = model->index(0, 0, QModelIndex());
if (index.isValid())
{
// now, expand all items below
for (auto it = indexes.rbegin(); it != indexes.rend() && index.isValid(); ++it)
{
auto row = (*it).first;
auto colum = (*it).second;
_viewer.setExpanded(index, true);
// and get a new item relative to parent
index = model->index(row, colum, index);
}
}
// finally, scroll to real item, after expanding everything above.
_viewer.scrollTo(ar_index);
}
I just dealt with similar situation, setting model index via setModelIndex (which internally ends up with scrollTo) worked OK for one of my models, but badly for the other one.
When I forcefully expanded all level 1 items, the scrollTo worked just as described above (calling expandAll suffices).
The reason was a bug in my model class in:
QModelIndex MyBadModel::parent(const QModelIndex& index) const
and as I fixed that, things got normal there too. The bug was such that internalId of parent model index was not the same as when this same model index (for parent) is calculated "from other direction", therefore in this model index (returned by parent method) could not be found in the list of visual indices.
Everything was simple.
Just change autoExpandDelay property from -1 to 0(for example).
ui->treeView->setAutoExpandDelay(0);
QTreeView::scrollTo should expand the hierarchy appropriately.
It's likely that your QModelIndex object is being invalidated when the model is updated (and perhaps still selecting the correct row because the row information is still valid though the parentage is not, don't ask me how those internals work). From the QModelIndex documentation:
Note: Model indexes should be used immediately and then discarded. You should not rely on indexes to remain valid after calling model functions that change the structure of the model or delete items. If you need to keep a model index over time use a QPersistentModelIndex.
You can certainly look into the QPersistentModelIndex object, but like it says in its documentation:
It is good practice to check that persistent model indexes are valid before using them.
Otherwise, you can always query for that item again after the model refresh.
Recently I struggled with same problem.
It's most likely a bug in your model class implementation.
in my case the row() method (that supposed to return index of the index under its parent) was not implemented correctly.
Sadly QT doesnt complain about that, and even selects the index (if you expand manually you will notice).
So, just go though the model code and hunt for bugs in row() and parent() methods etc.
You may be calling scrollTo before the tree view has finished reacting to the changes in current index and which indices are expanded/collapsed. A possible solution may be to delay the call to scrollTo by connecting it to a single-shot timer like this:
QTimer::singleShot(0, [this]{scrollTo(index);});
Using the timer will delay the call until control is passed back to the event queue.

How do I delete a top level QTreeWidgetItem from a QTreeWidget?

I'm attempting to remove a top level tree widget item if there are no child nodes within the top level item. What is the correct way to do this? I can't seem to find the API call within Qt's documentation. Is it safe to just call delete on the top level tree widget item? I haven't run into any issues yet, but I'd like to know if that's safe practice. Thanks much.
if(topLevelTreeWidgetItem->childCount() > 1) {
topLevelTreeWidgetItem->removeChild(childItem);
}
else
{
delete topLevelTreeWidgetItem;
}
deleteing a QTreeWidgetItem directly is perfectly safe.
According to the documentation for ~QTreeWidgetItem():
Destroys this tree widget item. The item will be removed from
QTreeWidgets to which it has been added. This makes it safe to delete
an item at any time.
I've used delete on many QTreeWidgetItems in practice and it works quite well.
To delete a top level item call QTreeWidget::takeTopLevelItem method and then delete the returned item:
delete treeWidget->takeTopLevelItem(index);
Where index is index of the item to be removed.
Function takeChild only works with QTreeWidgetItem. With QtreeWidget, you can use QtreeWidget::takeTopLevelItem(int index)