Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Remove Move-Back Behavior On Recurve Tree (QAbstractItemModel)
Forum Updated to NodeBB v4.3 + New Features

Remove Move-Back Behavior On Recurve Tree (QAbstractItemModel)

Scheduled Pinned Locked Moved General and Desktop
7 Posts 2 Posters 2.2k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    TrueTrav
    wrote on last edited by SGaist
    #1

    I've setup my own derivative class of QAbstractItemModel for my engine's node hierarchy. It seems to perform inserts and deletes properly. On top of that, I can get my elements to my via drag and drop. I've overridden moveRows in my QAbstractItemModel subclass, and I call moveRows right before dropMimeData() returns. It appears that dropMimeData() needs to return false upon success after moveRows() is called for the QTreeView to update correctly. If it returns true, then my items will disappear in the view. I'll print out my scene's node hierarchy to the console, and everything looks correct. It just seems that the model doesn't update. Here's the implementation of my SceneModel class (subclassed from QAbstractItemModel):

    const QString SceneModelMIMEType = "application/node.bin";
    
    #include "SceneModel.h"
    #include "Scene.h"
    #include "Node.h"
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <QKeyEvent>
    #include <QMimeData>
    #include <QByteArray>
    #include <QDebug>
    
    
    Qt::ItemFlags SceneModel::flags(const QModelIndex &index) const
    {
    	Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
    	if(index.isValid())
    		return defaultFlags | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsEditable;
    	else
    		return defaultFlags | Qt::ItemIsDropEnabled;
    
    	return QAbstractItemModel::flags(index);
    }
    
    Qt::DropActions SceneModel::supportedDropActions() const
    {
    	return QAbstractItemModel::supportedDropActions() | Qt::MoveAction;
    }
    
    int SceneModel::rowCount(const QModelIndex &parent) const
    {
    	// make sure the scene's root node is valid and the parent's column is zero
    	if(!Scene::GetInstance()->GetRoot() || parent.column() > 0)
    		return 0;
    
    	// return the parent's children, if valid
    	if(parent.isValid())
    		return static_cast<Node*>(parent.internalPointer())->GetNumChildren();
    
    	// otherwise, return the children
    	return Scene::GetInstance()->GetRoot()->GetNumChildren();
    }
    
    int SceneModel::columnCount(const QModelIndex &parent) const
    {
    	return 1;
    }
    
    QModelIndex SceneModel::index(int row, int column, const QModelIndex &parent) const
    {
    	// make sure the root, row and column data is valid
    	if(!Scene::GetInstance()->GetRoot() || !hasIndex(row, column, parent))
    		return QModelIndex();
    
    	// get the parent Node from the index
    	Node *parentItem = Scene::GetInstance()->GetRoot();
    	if(parent.isValid())
    		parentItem = static_cast<Node*>(parent.internalPointer());
    
    	Node *childItem = parentItem->GetChild(row);
    	if(childItem)
    		return createIndex(row, column, childItem);
    
    	return QModelIndex();
    }
    
    QModelIndex SceneModel::parent(const QModelIndex &index) const
    {
    	// make sure the index is valid
    	if(!index.isValid())
    		return QModelIndex();
    
    	// get the child item to get the parent item
    	Node *childItem = static_cast<Node*>(index.internalPointer());
    	if(!childItem) return QModelIndex();
    	Node *parentItem = childItem->GetParent();
    
    	// return nothing if the parent item is the root
    	if(!parentItem || parentItem == Scene::GetInstance()->GetRoot())
    		return QModelIndex();
    
    	return createIndex(parentItem->GetChildIndex(), 0, parentItem);
    }
    
    QVariant SceneModel::data(const QModelIndex &index, int role) const
    {
    	if(!index.isValid())
    		return QVariant();
    
    	// handle displaying the data
    	if(role == Qt::DisplayRole || role == Qt::EditRole)
    	{
    		// get the child and parent Node pointers
    		Node *childItem = static_cast<Node*>(index.internalPointer());
    		return QString(childItem->GetName().c_str());
    	}
    
    	return QVariant();
    }
    
    bool SceneModel::submit()
    {
    	// check if the selected node is valid
    	Node *selectedNode = Scene::GetInstance()->GetSelectedNode();
    	if(selectedNode)
    	{
    		// get the selected node's index, and update its display data
    		QModelIndex selectedIndex = createIndex(selectedNode->GetChildIndex(), 0, selectedNode);
    		setData(selectedIndex, QString(selectedNode->GetName().c_str()), Qt::DisplayRole);
    	}
    	return true;
    }
    
    bool SceneModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
    	// check if the index is valid, and either in EditRole or DisplayRole mode
    	if(index.isValid() && (role == Qt::EditRole || role == Qt::DisplayRole))
    	{
    		//
    		Node *node = static_cast<Node*>(index.internalPointer());
    		if(!node) return false;
    
    		// set the node's new name, and signal that data has changed
    		node->SetName(std::string(value.toString().toUtf8()));
    		emit dataChanged(index, index);
    		return true;
    	}
    	return setData(index, value, role);
    }
    
    bool SceneModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
    {
    	// make sure this is aciton shouldn't be ignored
    	if(!data || action == Qt::IgnoreAction)
    		return false;
    
    	// get rowCount if row is invalid (this might be an old Qt bug)
    	if(row == -1)
    		row = rowCount() - 1;
    
    	// get the parent node
    	Node *parentNode = static_cast<Node*>(parent.internalPointer());
    	if(!parentNode) return false;
    
    	// get the encoded data of our Node pointer
    	QByteArray encodedData = data->data(SceneModelMIMEType);
    	Node *node = (Node*)encodedData.toULongLong();
    	if(!node) return false;
    
    	Node *sourceParentNode = node->GetParent();
    	QModelIndex sourceParent = createIndex(sourceParentNode->GetChildIndex(), 0, sourceParentNode);
    
    	//qDebug() << "dropMimeData() - movedNode:" << node->GetName().c_str() << "parentNode:" << sourceParentNode->GetName().c_str();
    	/*qDebug()	<< "sourceParent:" << sourceParentNode->GetName().c_str()
    				<< "destinationParent:" << parentNode->GetName().c_str()
    				<< "movingNode:" << node->GetName().c_str()
    				<< "sourceRow:" << node->GetChildIndex()
    				<< "destinationRow:" << row;*/
    
    	// move the row, but don't return true as that'll trigger the drop event
    	moveRow(sourceParent, node->GetChildIndex(), parent, row);
    	return false; // returning true causes weird behavior
    }
    
    QStringList SceneModel::mimeTypes() const
    {
    	QStringList types;
    	types << SceneModelMIMEType;
    	return types;
    }
    
    QMimeData *SceneModel::mimeData(const QModelIndexList &indexes) const
    {
    	// make sure there's exactly 1 index and it's valid
    	if(indexes.size() != 1 || !indexes[0].isValid())
    		return nullptr;
    
    	// get the Node pointer from the index's internal pointer, convert it to quintptr, then to a UTF string
    	quintptr nodeAddress = (quintptr)indexes[0].internalPointer();
    	QByteArray encodedData(QString::number(nodeAddress).toUtf8());
    
    	// allocate mimeData, provide it with encodedData, and return it
    	QMimeData *mimeData = new QMimeData();
    	mimeData->setData(SceneModelMIMEType, encodedData);
    	return mimeData;
    }
    
    bool SceneModel::insertRows(int row, int count, const QModelIndex &parent)
    {
    	Node *parentNode = static_cast<Node*>(parent.internalPointer());
    	if(!parentNode)
    		parentNode = Scene::GetInstance()->GetRoot();
    
    	// make sure a parent node was found before continuing
    	if(parentNode)
    	{
    		// begin inserting rows, and add begin adding uniquely-named fields
    		beginInsertRows(parent, row, row+count-1);
    
    		// insert nodes
    		for(int i=0;i<count;++i)
    			parentNode->AddChild("New Node");
    		endInsertRows();
    		return true;
    	}
    	return QAbstractItemModel::insertRows(row, count, parent);
    }
    
    bool SceneModel::removeRows(int row, int count, const QModelIndex &parent)
    {
    	Node *parentItem = static_cast<Node*>(parent.internalPointer());
    	if(!parentItem)
    		parentItem = Scene::GetInstance()->GetRoot();
    
    	// make sure a parent node was found before continuing
    	if(parentItem)
    	{
    		beginRemoveRows(parent, row, row+count-1);
    		parentItem->RemoveChildren(row, count);
    		endRemoveRows();
    		return true;
    	}
    	return QAbstractItemModel::removeRows(row, count, parent);
    }
    
    bool SceneModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
    {
    	Node *sourceParentNode = static_cast<Node*>(sourceParent.internalPointer());
    	Node *destinationParentNode = static_cast<Node*>(destinationParent.internalPointer());
    	Node *childNode = sourceParentNode->GetChild(sourceRow);
    
    	// if source and destination parents are the same, move elements locally
    	if(sourceParentNode == destinationParentNode)
    	{
    		// only process if a local move is possible
    		if(sourceParentNode->IsMovePossible(sourceRow, count, destinationChild))
    		{
    			beginMoveRows(sourceParent, sourceRow, sourceRow+count-1, destinationParent, destinationChild);
    			sourceParentNode->MoveChildren(sourceRow, count, destinationChild);
    			endMoveRows();
    		}
    	} else {
    		// otherwise, move the node under the parent
    		beginMoveRows(sourceParent, sourceRow, sourceRow+count-1, destinationParent, destinationChild);
    		childNode->SetParent(destinationParentNode);
    		endMoveRows();
    	}
    	Scene::GetInstance()->GetRoot()->PrintChildren();
    	return true;
    }
    

    I didn't provide the interface since it doesn't have any member fields. It just has a constructor and destructor, but the constructor that initializes the base class with the passed-in QObject *parent parameter. I couldn't figure out how to embed the code above into code tags so that it's much easier to read. Sorry if something didn't make sense in my description above. I'm tired today.

    [edit: Added missing coding tags ``` before and after code SGaist]

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      One thing I can see in your code (but not directly related) is that you call setData in setData rather than the base class implementation.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      0
      • T Offline
        T Offline
        TrueTrav
        wrote on last edited by
        #3

        Thanks for fixing my code. I saw checked out your edit below, and I'll post my code properly going forward. Also, good point about setData. This could have been bad as it could have created an infinite loop if I didn't have any conditions that resulted in another return statement. When I have dropMimeData() returning true, it looks like my changes stick for a split-second. Then, the node that was moved will disappear from the view. The output of my Node's hierarchy to the console shows that all nodes are correct.

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          Is it really row move ? Aren't you changing the layout of your data structure ?

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          0
          • T Offline
            T Offline
            TrueTrav
            wrote on last edited by TrueTrav
            #5

            ^That is a good question that I should have asked originally. I am changing the layout of my data structure. So, when I drop the node I'm dragging onto my destination node in the hierarchy, I use my node's SetParent() method to change the hierarchy relationship in my Node tree. It's essentially a "move", at least as far as my nodes are concerned, but I'm not sure if this would be considered a "move" as far as my model is concerned. If this isn't a legitimate move, then should I be adding/removing rows?

            EDIT: As far as software design goes: my SceneModel subclass of QAbstractItemModel is supposed to make a display model for my Scene class' internal structure. Every tutorial I've seen that has a Node hierarchy assumes that the Nodes structure is maintained within the SceneModel itself. The model as a reference to the root node that's never drawn. In my example, my root node is in my separate Scene class. My implementation of SceneModel takes a required parameter in its constructor for a Scene object. Then, whenever I insert/remove rows, my overrides add/remove the respective nodes using their AddChild()/RemoveChild() methods. The problem is, now I can't update my Scene object independently of SceneModel as it'll cause a runtime exception due to the internalPointer references in SceneModel's QModelIndex list are invalid. So, I'm forced to have to use SceneModel to insert/remove rows. This means that whenever I create a Scene object, I need to have a SceneModel object pointing to that Scene object.

            My solution is to have my Scene class contain an allocated pointer to my SceneModel. Then, whenever I call AddChild()/RemoveChild() on a node, it'll signal the scene that owns it to call its SceneModel's insertRows()/removeRows() methods that simply call beginInsertRows()/endInsertRows() and beginRemoveRows()/endRemoveRows() on the correct QModelIndex targets after modifying the node's hierarchy. SceneModel will obviously have a pointer to the Scene object that owns it. when dropMimeEventData() is called, instead of calling moveRows as I am now, I will just call SetParent() or MoveChildren() on the affected nodes. Then, SetParent() and MoveChildren() which will signal my Scene object to call insertRows()/removeRows()/moveRows() as necessary. Does this sound like a decent solution? The only caveat I can think of is that I'm not modifying my Scene's structure between the begin/end methods in the model.

            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #6

              Sounds fishy, your Scene node shouldn't have to know about the model that contains it.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              0
              • T Offline
                T Offline
                TrueTrav
                wrote on last edited by
                #7

                That's what I originally thought, considering QAbstractItemModel's index() and parent() methods get spammed every frame. It looks like model indexes are constantly being recreated before the QTreeView gets repainted. No luck though.

                1 Reply Last reply
                0

                • Login

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved