PDA

View Full Version : QtTreeWidget: issue with indicator display



gjaegy
24th May 2012, 09:56
Hi,

It seems part of this question has already been asked a few time, but I haven't been able to find any matching answer until now, so I decided to create a new post.

I have a tree containing my scene graph, using a custom derived QtTreeWidget class directly (no model/view):

7745

Now, I have two issues, which are linked together I guess, related to moving items within the tree:



first of all, no drop indicator is being shown, although I think I have called the right code:

setAcceptDrops(imTRUE);
setDragEnabled(true);
setDragDropMode(DragDrop);
setDragDropOverwriteMode(imFALSE);
setDropIndicatorShown(imTRUE);

setSelectionMode(QAbstractItemView::SingleSelectio n);
setSelectionBehavior(QAbstractItemView::SelectRows );
I have tried to derive my own style and experiment with QStyle::PE_IndicatorItemViewItemDrop, but without any success.
I have also tried to draw a custom line by myself, but I can't figure out where it should be drawn exactly (see point 2 below).


it is very hard to precisely define where the node will be dropped. More precisely, I can't figure out why the node gets sometimes added as child of the target, and sometimes it gets added before or after the target item. I know the position of the cursor defines this behaviour, however, without any visual indicator, this is really hard to use.


Any help would be appreciated, I have had this issue for month now :(
Thanks guys !

Spitfire
25th May 2012, 16:05
IMHO you should drop point 2 and fix point 1 (which solves both issues).

As to why it happens, can't tell.
Would be easier if your example be compilable :)
Only thing that comes to my mind is that you turn off the indicator somewhere.

Do you reimplement paint event of that widget?

ugluk
25th May 2012, 16:30
I'd take a look at your:

Qt::ItemFlags QTreeWidgetItem::flags () const

and check if you return Qt::ItemIsDragEnabled, but this might just as well not be the case, as you can drag&drop, but don't see the indicator. Can you try on some other OS, such as linux? I'd install arch linux in a virtual machine (it always has the latest Qt) and see how things go in there?

gjaegy
29th May 2012, 07:48
Thanks guys for your answer, I really appreciate.

@Spitfire: you are right, that's basically what I am trying to achieve. I have checked my code, I don't disable the indicator anywhere. Unfortunately it's not easy to make a compilable example, some work is involved, but I will do it if I can't find any solution soon.
I have overriden the paint event, however currently all it does is calling the base class (QtTreeWidget) paint method. So that's not the problem.

@ugluk: the flags seems to be fine, I added the piece of code below for each created item, the flag returned by flags() is 61 (decimal) and its value is not modified by the line just below:


QTreeWidgetItem* pItem = new QTreeWidgetItem();
Qt::ItemFlags flag = pItem->flags();
flag |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
pItem->setFlags(flag);

Below the full relevant code of my class:


//////////////////////////////////////////////////////////////////////////

class imQMimeData : public QMimeData
{
public :

imQMimeData () : QMimeData ()
{
m_eDropAction = Qt::MoveAction;
}

virtual ~imQMimeData () {}

imNode* GetNode(void) const { return m_pNode; }
void SetNode(imNode* _pNode) { m_pNode = _pNode; }

QTreeWidgetItem* GetItem(void) const { return m_pItem; }
void SetItem(QTreeWidgetItem* _pItem) { m_pItem = _pItem; }

imU32 GetOldIndex() const { return m_nOldIndex; }
void SetOldIndex(imU32 _nIndex) { m_nOldIndex = _nIndex; }

public :

imNode* m_pNode;
QTreeWidgetItem* m_pItem;
imU32 m_nOldIndex;
Qt::DropAction m_eDropAction;
};

//////////////////////////////////////////////////////////////////////////

imGuiSceneExplorer::imGuiSceneExplorer(QWidget * parent) : QTreeWidget(parent), imIStreamingCallback()
{
m_pScene = imNULL;
m_bBuildingTree = imFALSE;
m_pUI = imNULL;
}


//////////////////////////////////////////////////////////////////////////

imGuiSceneExplorer::~imGuiSceneExplorer()
{
clear();
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::SetUI(Ui::imGuiMainWindowClass * _pUI)
{
m_pUI = _pUI;

setColumnCount(2);

headerItem()->setText(0, tr("Name"));
headerItem()->setText(1, tr("Type"));

header()->setResizeMode(0, QHeaderView::ResizeToContents);
header()->setResizeMode(1, QHeaderView::ResizeToContents);

// drag & drop
setAcceptDrops(imTRUE);
setDragEnabled(true);
setDragDropMode(DragDrop);
setDragDropOverwriteMode(imFALSE);
setDropIndicatorShown(imTRUE);

setSelectionMode(QAbstractItemView::SingleSelectio n);
setSelectionBehavior(QAbstractItemView::SelectRows );

// context menu
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowTreeContextMenu(const QPoint&)));
}

//////////////////////////////////////////////////////////////////////////

QSize imGuiSceneExplorer::sizeHint() const
{
QSize theSize(256, 768);
return theSize;
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::SelectNode(imNode* _pNode)
{
QTreeWidgetItemIterator it(this);
while (*it)
{
imNode* pNode = GetNodeFromItem(*it);
if (pNode == _pNode)
{
setCurrentItem(*it);

if (m_pScene)
m_pScene->SetSelectedNode(_pNode);

return;
}
++it;
}
}

//////////////////////////////////////////////////////////////////////////

imNode* imGuiSceneExplorer::GetNodeFromItem(QTreeWidgetIte m* _pItem)
{
if (!_pItem)
return imNULL;
return *(imNode**)_pItem->data(0, Qt::UserRole).toByteArray().constData();
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::BuildContent( imScene* _pScene )
{
clear();

m_pScene = _pScene;
if (!_pScene)
return;

// create top level item
m_bBuildingTree = imTRUE;
imNode* pRoot = _pScene->GetRootNode();
AddNodeItem(pRoot, imNULL, -1);
m_bBuildingTree = imFALSE;
}

//////////////////////////////////////////////////////////////////////////

QTreeWidgetItem* imGuiSceneExplorer::CreateItemFromNode(imNode* _pNode)
{
imAssert(_pNode);
if (_pNode)
{
QTreeWidgetItem* pItem = imNew QTreeWidgetItem();
Qt::ItemFlags flag = pItem->flags();
flag |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
pItem->setFlags(flag);

// node name
imStringBase sName(_pNode->GetName().GetLength() ? _pNode->GetName() : _pNode->RTTIGetClass()->getName());
QString sQName = QString::fromWCharArray((const wchar_t*)sName);
pItem->setText(0, sQName);

// node type
imStringBase sType(_pNode->RTTIGetClass()->getName());
QString sQType = QString::fromWCharArray((const wchar_t*)sType);
pItem->setText(1, sQType);

// set data
QByteArray data((const char *)&_pNode, sizeof(_pNode));
QVariant oVariant(data);
pItem->setData( 0, Qt::UserRole, oVariant);

return pItem;
}
return imNULL;
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::rowsInserted ( const QModelIndex & parent, int start, int end )
{
if (!m_bBuildingTree)
{
QTreeWidgetItem* pItemParent = itemFromIndex(parent);
if (pItemParent)
{
imNode* pParent = GetNodeFromItem(pItemParent);
if (pParent)
{
for (imInt i = start; i <= end; ++i)
{
QTreeWidgetItem* pItemChild = pItemParent->child(i);
imNode* pChild = GetNodeFromItem(pItemChild);
pParent->InsertChild(pChild, i);
}
}
}
}

QTreeWidget::rowsInserted(parent,start,end);
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::rowsAboutToBeRemoved ( const QModelIndex & parent, int start, int end )
{
if (!m_bBuildingTree)
{
QTreeWidgetItem* pItemParent = itemFromIndex(parent);
imNode* pParent = GetNodeFromItem(pItemParent);

for (imInt i = start; i <= end; ++i)
{
QTreeWidgetItem* pItemChild = pItemParent->child(i);
imNode* pChild = GetNodeFromItem(pItemChild);
pParent->RemoveChild(i, imFALSE);
}
}

QTreeWidget::rowsAboutToBeRemoved(parent,start,end );
}

//////////////////////////////////////////////////////////////////////////

QMimeData *imGuiSceneExplorer::mimeData(const QList<QTreeWidgetItem *> items) const
{
imAssert(items.size());
if (items.size())
{
imQMimeData *mimeData = imNew imQMimeData();
imNode* pNode = *(imNode**)items[0]->data(0, Qt::UserRole).toByteArray().constData();
mimeData->SetNode(pNode);
mimeData->SetItem(items[0]);
mimeData->SetOldIndex(items[0]->parent() ? items[0]->parent()->indexOfChild(items[0]) : 0);

return mimeData;
}
return imNULL;
}

//////////////////////////////////////////////////////////////////////////

bool imGuiSceneExplorer::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
{
if (action == Qt::IgnoreAction)
return true;

if (parent)
{
const imQMimeData* myData = static_cast<const imQMimeData *>(data);
if (myData && parent != myData->GetItem())
{
imNode* pNode = myData->GetNode();
AddNodeItem(pNode, parent, index);
}
}
return true;
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::dragEnterEvent ( QDragEnterEvent * event )
{
QTreeWidget::dragEnterEvent(event);

event->accept();
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::dragMoveEvent ( QDragMoveEvent * event )
{
QTreeWidget::dragMoveEvent(event);

event->accept();
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::paintEvent( QPaintEvent* event )
{
QTreeWidget::paintEvent (event);
}

//////////////////////////////////////////////////////////////////////////

void imGuiSceneExplorer::OnItemSelected()
{
// do something
}

//////////////////////////////////////////////////////////////////////////

Spitfire
29th May 2012, 09:27
Ok, now it's obvious :)

Your imGuiSceneExplorer::mimeData() doesn't do its job.
It should "Return an object that contains a serialized description of the specified items" but in your case it returns basically empty QMimeData object.

As a test, call base implementation of mimeData() and return whatever you get from there, you'll see your problem fixed.
Now you just need to figure out how to copy data from that object or serialize everything yourself.

gjaegy
29th May 2012, 09:46
@Spitfire: that was it !! Many many thanks for that ! a shame i can't pay you a beer for that ;)

thanks once again, this issue has been here for a while. Happy day :)

Spitfire
29th May 2012, 11:00
I'm glad I could help.
And if you ever visit Manchester, I'm happy to go for a beer :D

gjaegy
30th May 2012, 07:48
not very likely, but I will do ;)

I have it now all working.

As I am currently fixing all the annoying stuff I have for ages (I am really not a Qt specialist), may I ask you one additional question ?

Basically, when using the DragDrop mode
setDragDropMode(DragDrop); the default behaviour is a copy operation. One has to press "shift" to swtich to a move operation.

Now, I want to have a move operation by default, and press "Ctrl" to switch to copy operation.

I know I can use
setDragDropMode(InternalMove);, but in that case, pressing Ctrl shows a "+" indicator, however, when mouse is being release, dropMimeData() doesn't get called.

Is there a way to solve this easily ?

Spitfire
30th May 2012, 09:34
QTreeWidget* tree = new QTreeWidget( this );
tree->setDragEnabled( true );
tree->setAcceptDrops( true );
tree->setDropIndicatorShown( true );
tree->setDefaultDropAction( Qt::MoveAction ); // that's what you need

This should do it. No need to set setDragDropMode() as setDragEnabled() and setAcceptDrops() do it for you.

gjaegy
30th May 2012, 11:49
Hi, thanks for your answer.

The problem is that calling
setDefaultDropAction( Qt::MoveAction ); leads to the following behaviour: I can't insert my items between two existing nodes anymore (indicator=line), all I can do is to move the item as a child (indicator=rectangle). Does that make sense ?

Moving/copying by inserting the item between two existing items was possible before.

Any idea ?

Spitfire
30th May 2012, 14:32
What you mean you can't insert the items?
Does the line indicator appear but nothing happens?

This works fine for me:


MainWindow::MainWindow( QWidget* p )
:
QMainWindow( p )
{
QTreeWidget* tree = new QTreeWidget( this );
tree->setDragEnabled( true );
tree->setAcceptDrops( true );
tree->setDropIndicatorShown( true );
tree->setDefaultDropAction( Qt::MoveAction );

for( int i = 0; i < 5; i++ )
{
QTreeWidgetItem* root = new QTreeWidgetItem( tree->invisibleRootItem() );
root->setText( 0, "root" + QString::number( i ) );

for( int j = 0; j < 5; ++j )
{
QTreeWidgetItem* leaf = new QTreeWidgetItem( root );
leaf->setText( 0, "Leaf " + QString::number( j ) );
}
}

this->setCentralWidget( tree );
}

See if this example has the same issue you're experiencing.
You may need to upgrade it to use mime types, if you do and are able to replicate the issue - post the code here.

gjaegy
30th May 2012, 14:52
OK, your example worked for me.
I have managed to fix my issue: actually the widget was being defined using Qt Designer, where some properties were set for that widget.
So, basically, to avoid confusion I've cleared all the properties defined in Qt Designer, and this seems to work now.

I think I now rather owe you a barrel of beer :)

Thanks a lot for your help, that was really appreciated.

Spitfire
30th May 2012, 15:18
I'm glad I could help.

Take care! :)