PDA

View Full Version : QTreeWidget selectedItems ordered parent-child



Alundra
19th November 2014, 22:49
Hi,
I use the selectedItems() function to get the actual selected items to remove them.
The problem is if you have a tree like that :

A
-B
--C

You select A then B then C and then delete, since a for loop is used that delete A then B and C of the for loop are not valid and I got a crash.


void CMainEditorWindow::Delete()
{
// Get the selected items.
QList< QTreeWidgetItem* > SelectedItems = SceneOutlinerWidget->GetSelectedItems();

// Delete each actor.
foreach( QTreeWidgetItem* Item, SelectedItems )
{
CSceneOutlinerItem* SceneOutlinerItem = static_cast< CSceneOutlinerItem* >( Item );
MainEditorUndoStack->push( new CUndoCommandDeleteActor( SceneOutlinerItem->GetUniqueID() ) );
}
}

Is it possible to have selected items but remove item found in children of other or that needs to be implemented in a recursive function manually ?
Thanks for the help

StrikeByte
21st November 2014, 10:37
You can use a reverse for loop, start with deleting the last item first

Alundra
21st November 2014, 14:47
That's not safe because if you select C then B then A delete will crash because reverse loop will start to delete A then B then C.

d_stranz
22nd November 2014, 20:19
Make a separate method to delete each item. The method should take the item to be deleted, along with a reference to a QList of deleted items. The first time through the foreach() loop, this list will be empty. Inside the method, check to see if the current item is on the list. If it is, do nothing and return. If it isn't, check to see if it has children. For each child, call the deletion method again (recursively), passing the child item and list. After you run out of children, push the current item onto the list and delete the item.



// Pseudocode

void CMainEditorWindow::DeleteItem( CSceneOutlinerItem * item, QList< CSceneOutlinerItem * > & deletedItems )
{
if ( deletedItems contains item )
return;

if ( item has children )
{
foreach ( CSceneOutlinerItem * child, children of item )
{
DeleteItem( child, deletedItems );
}
}

// Now, delete the current item
deletedItems.push_back( item );
MainEditorUndoStack->push( new CUndoCommandDeleteActor( item->GetUniqueID() ) );
}

void CMainEditorWindow::Delete()
{
// Get the selected items.
QList< QTreeWidgetItem* > SelectedItems = SceneOutlinerWidget->GetSelectedItems();

QList< CSceneOutlinerItem * > deletedItems;

// Delete each actor.
foreach( QTreeWidgetItem* Item, SelectedItems )
{
CSceneOutlinerItem* SceneOutlinerItem = static_cast< CSceneOutlinerItem* >( Item );
DeleteItem( SceneOutlinerItem, deletedItems );

}
}


By keeping the list of deleted items and checking it before you try to do anything, you guarantee that no matter what order the items appear on the selection list, you will delete each item's children first, and you won't try to delete anything twice.

There are probably other ways to do this, but this is pretty straightforward. You can figure out which QTreeWidget / QTreeWidgetItem methods to use to travel down the tree to find the children of the current item.

Alundra
23rd November 2014, 00:37
Ok yea, that must be made yourself, looks like the only way.
Since CUndoCommandDeleteActor stores the children of the deleted actor so the best way is to find recursively to keep only the parent if child and parent selected.

Alundra
23rd November 2014, 14:59
Here the solution I wrote :


bool RecursiveFindItem( QTreeWidgetItem* Item, QTreeWidgetItem* TestItem )
{
// Check if the item is the same.
if( Item == TestItem )
return true;

// For each child of the item.
for( int i = 0; i < Item->childCount(); ++i )
{
// Check if we found the item.
if( RecursiveFindItem( Item->child( i ), TestItem ) )
return true;
}

// Return not found.
return false;
}

void CSceneOutlinerWidget::DeleteSelectedActors()
{
// Find unique items.
QList< QTreeWidgetItem* > UniqueItems;
foreach( QTreeWidgetItem* Item, m_SceneOutlinerTree->selectedItems() )
{
// Find the selected item in the list.
bool ItemFound = false;
foreach( QTreeWidgetItem* UniqueItem, UniqueItems )
{
if( RecursiveFindItem( UniqueItem, Item ) )
{
ItemFound = true;
break;
}
}

// Remove the item if found or add in the list.
if( ItemFound )
UniqueItems.removeOne( Item );
else
UniqueItems.append( Item );
}

// Delete each unique item.
foreach( QTreeWidgetItem* Item, UniqueItems )
{
CSceneOutlinerItem* SceneOutlinerItem = static_cast< CSceneOutlinerItem* >( Item );
CMainEditorWindow::GetUndoStack()->push( new CUndoCommandDeleteActor( SceneOutlinerItem->GetUniqueID() ) );
}
}

That works but the I surely missed something because if I select A then B then C, only A is removed, if I select C then B then A I got 3 item to delete, normally only A is needed.

EDIT : The final solution working :


bool RecursiveFindItem( QTreeWidgetItem* Item, QTreeWidgetItem* TestItem )
{
// Check if the item is the same.
if( Item == TestItem )
return true;

// For each child of the item.
for( int i = 0; i < Item->childCount(); ++i )
{
// Check if we found the item.
if( RecursiveFindItem( Item->child( i ), TestItem ) )
return true;
}

// Return not found.
return false;
}

void RecursiveRemoveUniqueItems( QTreeWidgetItem* Item, QList< QTreeWidgetItem* >* UniqueItems )
{
// Remove the item if found in the unique list.
foreach( QTreeWidgetItem* UniqueItem, *UniqueItems )
{
if( UniqueItem == Item )
{
UniqueItems->removeOne( UniqueItem );
break;
}
}

// For each child of the item.
for( int i = 0; i < Item->childCount(); ++i )
RecursiveRemoveUniqueItems( Item->child( i ), UniqueItems );
}

void CSceneOutlinerWidget::DeleteSelectedActors()
{
// Find unique items.
QList< QTreeWidgetItem* > UniqueItems;
foreach( QTreeWidgetItem* Item, m_SceneOutlinerTree->selectedItems() )
{
// Remove item and children if already in unique list.
RecursiveRemoveUniqueItems( Item, &UniqueItems );

// Search the item.
bool ItemFound = false;
foreach( QTreeWidgetItem* UniqueItem, UniqueItems )
{
if( RecursiveFindItem( UniqueItem, Item ) )
{
ItemFound = true;
break;
}
}

// Check if the item is found.
if( ItemFound )
continue;

// Add the item in the list.
UniqueItems.append( Item );
}

// Delete each unique item.
foreach( QTreeWidgetItem* Item, UniqueItems )
{
CSceneOutlinerItem* SceneOutlinerItem = static_cast< CSceneOutlinerItem* >( Item );
CMainEditorWindow::GetUndoStack()->push( new CUndoCommandDeleteActor( SceneOutlinerItem->GetUniqueID() ) );
}
}