PDA

View Full Version : reordering QListWidgetItems of a QListWidget



OriginalCopy
2nd December 2007, 23:04
void MyListWidget::dropEvent(QDropEvent* event) {
QList<QListWidgetItem *> before = getListWidgets();
QListWidget::dropEvent(event);
//TODO remove this one
//TODO do not use the qtest lib, remove it from linker
//QTest::qWait(5000);
QList<QListWidgetItem *> after = getListWidgets();
dbg() << "before" << before;
dbg() << "after" << after;
//TODO: compare before and after, if not equal, emit a signal
}

QList<QListWidgetItem *> MyListWidget::getListWidgets(void) {
QList<QListWidgetItem *> r(findItems("*",Qt::MatchWildcard));
return r;
}

MyListWidget is a QListWidget. What I'm trying to do is emitting a signal when the widgets are really reordered, as dropEvent sometimes occurs even when the order is the same. The problem is that the list "after" contains one more element, as QListWidget::dropEvent() duplicates the QListWidgetItem before moving it to the new position (QTest::qWait() proves it).

My question is: how, where or when to attempt to get the state of the list widgets after the copy has been removed and everything is in place, so that a simple


if(after != before) emit widgetsReordered;

will do the job ?

wysota
3rd December 2007, 00:46
Why do you reimplement dropEvent for the list widget? You should reimplement dropMimeData() instead. You'd immediately know what is being dropped, where and why and you can use that information to manipulate the list. But in general I think you should just connect to the layoutChanged() (or some other) signal of the listwidget model...

OriginalCopy
3rd December 2007, 17:34
it behaves the same within dropMimeData(). There is no method layoutChange(), and I don't find anything similar.

wysota
3rd December 2007, 17:51
There is no method layoutChange(), and I don't find anything similar.
Launch Qt Assistant and type in "layoutChanged" in the index tab.

BTW. Of course it behaves the same within dropMimeData, because the base implementation of dropEvent calls dropMimeData to do its job. The point is to implement what you want inside dropMimeData based on arguments you are given. Testing whether the drop changes the order or not based on those arguments is trivial.

OriginalCopy
3rd December 2007, 18:19
thank you, I'll follow that path. However, it seems that Qt duplicates the list item, it drops the copy at the new position, and afterwards it removes the old one. This is not what one would normally describe as "moving object O from position A to position B", thus it messes pointer adresses (as a new instance is created at every drop). A simple dump:


Debug: virtual bool MyListWidget::dropMimeData(int, const QMimeData*, Qt::DropAction) 41 src\MyListWidget.cpp index: 1 data QMimeData(0x4386548)
Debug: virtual bool MyListWidget::dropMimeData(int, const QMimeData*, Qt::DropAction) 42 src\MyListWidget.cpp before (0x43e4d78, 0x4386368, 0x43fe8e0, 0x441f3c0)
Debug: virtual bool MyListWidget::dropMimeData(int, const QMimeData*, Qt::DropAction) 43 src\MyListWidget.cpp after (0x43e4d78, 0x4386568, 0x4386368, 0x43fe8e0, 0x441f3c0)
Debug: virtual bool MyListWidget::dropMimeData(int, const QMimeData*, Qt::DropAction) 44 src\MyListWidget.cpp return true
Here, 0x4386568 is a duplicate of 0x441f3c0. Practically I moved the item at the address 0x441f3c0 to the index 1. But how could I decide which of the elements is duplicated ? (I know it in this given scenario because I've got the output while debugging it).

As you can see, Qt screws with pointers and that hinders me to implement the fastest and the most elegant solution. Do you have any idea on how to avoid this problem ?


I prefer to work with pointers, as it would be faster to compare if object A is the same as object B (ie they point to the same address) rather than doing things like "string comparison". In my particular example, string comparison is luckily possible, as there are not allowed items with the same ->text(). However, if there was no such constraint, it would be harder to identify the object which was moved.

I've tried doing it like this:


bool MyListWidget::dropMimeData(int index,const QMimeData* data,Qt::DropAction action) {
QStringList before = getListWidgets();
bool r = QListWidget::dropMimeData(index,data,action);
QStringList after = getListWidgets();
dbg() << connect(model(),SIGNAL(layoutChanged()),this,SLOT( layoutChanged()));
dbg() << "index:" << index << "data" << data;
dbg() << "before" << before;
dbg() << "after" << after;
dbg() << "return" << r;
return r;
}

void MyListWidget::layoutChanged(void) {
dbg() << "//TODO: reordered";
}
but it doesn't get connected

wysota
3rd December 2007, 18:34
However, it seems that Qt duplicates the list item, it drops the copy at the new position, and afterwards it removes the old one.
What is wrong with that?

This is not what one would normally describe as "moving object O from position A to position B"
Hmm... I would :)

thus it messes pointer adresses (as a new instance is created at every drop). A simple dump:


Here, 0x4386568 is a duplicate of 0x441f3c0. Practically I moved the item at the address 0x441f3c0 to the index 1. But how could I decide which of the elements is duplicated ? (I know it in this given scenario because I've got the output while debugging it).
What are these pointers?


As you can see, Qt screws with pointers and that hinders me to implement the fastest and the most elegant solution. Do you have any idea on how to avoid this problem ?
Yes, don't rely on pointers. Use the mime data to store the data of the original (you can store its position as well if you have to) and then compare it to the data obtained while dropping.


I prefer to work with pointers, as it would be faster to compare if object A is the same as object B (ie they point to the same address) rather than doing things like "string comparison".
Depends what you mean by "the same". You are using the simplest possible case here - dragging and dropping within the same widget. But you could be dropping on a different application than you are dragging from. Hard to compare pointers then...


In my particular example, string comparison is luckily possible, as there are not allowed items with the same ->text(). However, if there was no such constraint, it would be harder to identify the object which was moved.
Who said you should compare strings? Compare whatever you want. If you want to compare pointers, do it - Qt doesn't care what you store within the mime data object.



bool MyListWidget::dropMimeData(int index,const QMimeData* data,Qt::DropAction action) {
QStringList before = getListWidgets();
bool r = QListWidget::dropMimeData(index,data,action);
QStringList after = getListWidgets();
dbg() << connect(model(),SIGNAL(layoutChanged()),this,SLOT( layoutChanged()));
dbg() << "index:" << index << "data" << data;
dbg() << "before" << before;
dbg() << "after" << after;
dbg() << "return" << r;
return r;
}

void MyListWidget::layoutChanged(void) {
dbg() << "//TODO: reordered";
}
but it doesn't get connected
Most probably the layout doesn't change :) I told you the signal could be different. It's best to just check which signals are emitted in the drop within the same widget. Certainly dataChanged() might be one of them.

BTW. It doesn't get connected (connect returns false) or the slot is not called? These are two completely different things.

If you reimplement dropMimeData() in most cases you should also reimplement mimeData().

OriginalCopy
3rd December 2007, 19:46
It doesn't get connected (connect returns false) or the slot is not called?
connect returns true, yet slot not called.

I'm getting nervous when I think how elegant would be the implementation where I'd work with only lists of pointers (here: before and after).


Who said you should compare strings?
I was referring to my specific problem in this project, where I'm actually interested in the ->text() values of the list items


Depends what you mean by "the same". You are using the simplest possible case here - dragging and dropping within the same widget. But you could be dropping on a different application than you are dragging from. Hard to compare pointers then...

same means in this context the same instance situated at the same memory address. In this program, I'm only handling drags and drops from/to the same list widget


What are these pointers?

they are at &before and &after, and their elements point to the addresses listed in the output:


before (0x43e4d78, 0x4386368, 0x43fe8e0, 0x441f3c0)
after (0x43e4d78, 0x4386568, 0x4386368, 0x43fe8e0, 0x441f3c0)

They are the addresses of the QWidgetListItem's of the list widget

wysota
3rd December 2007, 20:45
I was referring to my specific problem in this project, where I'm actually interested in the ->text() values of the list items
I don't know what your project is, so it's hard to suggest anything. If you are interested only in texts why do you keep referring to pointers to items? I admit I'm having a hard time understanding what are you trying to do.


they are at &before and &after, and their elements point to the addresses listed in the output:
Hmm... How is GetListWidgets() implemented? I'm not really sure why you try to compare addresses here...

Here is some code. It should work - for me it doesn't because my implementation of dropMimeData doesn't get called and I don't know why. If you manage to overcome it, it should be working just fine.


#include <QApplication>
#include <QListWidget>
#include <QMimeData>
#include <QMessageBox>
#include <QtDebug>

class LW : public QListWidget {
public:
LW() : QListWidget(){
setDragEnabled(true);
setAcceptDrops(true);
setDragDropMode(DragDrop);
}
protected:
QStringList mimeTypes () const{
return QStringList() << "application/x-internal";
}
QMimeData * mimeData ( const QList<QListWidgetItem *> items ) const{
qDebug("MD: %d", items.count()); QMimeData *md = new
QMimeData; int r = row(items.at(0));
md->setData("application/x-internal", QString::number(r).toAscii());
return md;
}
bool dropMimeData( int index, const QMimeData * data, Qt::DropAction action ){
qDebug("DMD");
QByteArray ba = data->data("application/x-internal");
QString rstr(ba);
int r = rstr.toInt();
// move from r to index
QMessageBox::information(this, "DRAG&DROP", QString("Taken from %1 and dropped to %2").arg(r).arg(index));
if(r==index)
return false;
QListWidgetItem *item = takeItem(r);
if(r<index){
insertItem(index-1, item);
} else {
insertItem(index, item);
}
return true;
}

};
int main(int argc, char **argv){
QApplication app(argc, argv);
LW lw;
for(int i=0; i<10; i++){
new QListWidgetItem(QString("ITEM %1").arg(i+1), &lw);
}
lw.show();
return app.exec();
}

OriginalCopy
3rd December 2007, 20:59
getListWidgets() is in the first post: http://www.qtcentre.org/forum/p-reordering-qlistwidgetitems-of-a-qlistwidget-post55967/postcount1.html

what I want is MyListWidget to emit a signal when the order of the items changes. I keep referring to QListWidgetItem*'s because they are holding the ->text(). Another property of MyListWidget is that it doesn't accept "duplicate ->text()s", ie: it's not possible to have two items with the same text values.

I feel that if I manage to solve this problem in an elegant way (or understand a solution proposed by you) I will learn a lot about Qt :-)

The only thing that disturbs me is the garbage left by Qt while in dropMimeData. Yeah, it is cleaned up at a later point before returning to the calling event loop, but where ? Knowing that would light the way

I will make a simplified version of the program, bbiaf

wysota
3rd December 2007, 21:08
getListWidgets() is in the first post: http://www.qtcentre.org/forum/p-reordering-qlistwidgetitems-of-a-qlistwidget-post55967/postcount1.html
Something is wrong then because it returns a list of widget items and you assign it to a list of strings...


what I want is MyListWidget to emit a signal when the order of the items changes.
Have you tried QListView::indexesMoved ( const QModelIndexList & indexes )?

And what is the purpose of knowing that the order has changed?


The only thing that disturbs me is the garbage left by Qt while in dropMimeData.
What garbage?

OriginalCopy
3rd December 2007, 21:37
Something is wrong then because it returns a list of widget items and you assign it to a list of strings...
yeah, I copy/pasted wrong. Here's a scheleton:

//implementation:

#include <QtGui>
#include "mylistwidget.h"


int main(int argc,char* argv[]) {
QApplication::setStyle("plastique");
QApplication app(argc,argv);
MyListWidget *mainwindow = new MyListWidget;
mainwindow->addItem("foo");
mainwindow->addItem("bar");
mainwindow->addItem("hello");
mainwindow->addItem("world");
Q_CHECK_PTR(mainwindow);
mainwindow->show();
return app.exec();
}

MyListWidget::MyListWidget(QWidget* parent) : QListWidget(parent) {
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
setLineWidth(1);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOf f);
setTabKeyNavigation(true);
setDragEnabled(true);
setDragDropMode(QAbstractItemView::InternalMove);
setSelectionMode(QAbstractItemView::ExtendedSelect ion);
setTextElideMode(Qt::ElideLeft);
setMovement(QListView::Free);
setResizeMode(QListView::Adjust);
setViewMode(QListView::ListMode);
setUniformItemSizes(true);
setSelectionRectVisible(false);
}
MyListWidget::~MyListWidget() {

}

bool MyListWidget::addItem(const QString& label) {
//TODO do not add already existing items
QList<QListWidgetItem *> items = findItems(label,Qt::MatchFixedString);
if(items.isEmpty()) {
QListWidget::addItem(label);
return true;
}
return false;
}

QList<QListWidgetItem *> MyListWidget::getListWidgets(void) {
QList<QListWidgetItem *> r(findItems("*",Qt::MatchWildcard));
return r;
}

QStringList MyListWidget::getListLabels(void) {
QList<QListWidgetItem *> t(findItems("*",Qt::MatchWildcard));
QStringList r;
QList<QListWidgetItem *>::const_iterator iter;
for(iter = t.begin();iter != t.end();++iter) {
r.append((*iter)->text());
}
return r;
}

mylistwidget.h:


#ifndef __MYLISTWIDGET_H__
#define __MYLISTWIDGET_H__
#include <QListWidget>

class MyListWidget : public QListWidget {
Q_OBJECT
public:
MyListWidget(QWidget* parent=0);
~MyListWidget();
public:
bool addItem(const QString& label);
QList<QListWidgetItem *> getListWidgets(void);
QStringList getListLabels(void);
};

#endif // __MYLISTWIDGET_H__



Have you tried QListView::indexesMoved ( const QModelIndexList & indexes )?
yes, useless/not working


And what is the purpose of knowing that the order has changed?
the purpose lays deep enough in the things needed by the app that for simplicity's sake we should not even bother about "WHY?" (simple said: a kind of priority queue)

MyListWidget should be as self-contained as possible, as I may need to use it in other places. A signal emitted when the order changes would bring the ideal modularity and reusability of the code.

wysota
3rd December 2007, 21:59
I'd like to know an answer to the question I asked. Why? Because maybe there are better ways to achieve the same effect (for instance by implementing a simple model over your of widgets or whatever). There is no point in reinventing the wheel. Assuming that you have some widgets you want to reorder, a simple model wrapped around a vertical layout holding the widgets is enough to provide reordering and all other stuff you might think of without even touching the view.

OriginalCopy
3rd December 2007, 22:04
think of mylistwidget as a priority queue. for now, I use it only for displaying settings to the user, but it is possible that I will use it for let's say, displaying trusted friends, or users of the system, or or or ...

wysota
3rd December 2007, 22:26
So I suggest you implement a model for each of these and display them using QListView or other model-based widget. Right now you are really reinventing the wheel.

Here is a simple example of a widget model (implemented just for fun). Of course it's not complete :)


#include <QAbstractListModel>
#include <QApplication>
#include <QListView>
#include <QPushButton>
#include <QLayout>

class WidgetModel : public QAbstractListModel {
public:
WidgetModel(QBoxLayout *l, QObject *parent=0) : QAbstractListModel(parent){
m_layout = l;
}

QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const{
QLayoutItem *item = m_layout->itemAt(index.row());
QWidget *widget = item->widget();
if(!widget) return QVariant();
if(role == Qt::DisplayRole){ return widget->objectName(); }
if(role == Qt::ToolTipRole){ return widget->toolTip(); }
return QVariant();
}

int rowCount ( const QModelIndex & parent = QModelIndex() ) const{
return m_layout->count();
}

QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const{
return createIndex(row, column, m_layout->itemAt(row));
}


protected:
QBoxLayout *m_layout;
};

int main(int argc, char **argv){
QApplication app(argc, argv);
QWidget w;
QVBoxLayout *l = new QVBoxLayout(&w);
for(int i=0;i<10;i++){
QPushButton *b = new QPushButton;
b->setObjectName(QString("PushButton_%1").arg(i+1));
b->setToolTip(QString("Tool tip #%1").arg(i+1));
l->addWidget(b);
}
QListView lv;
WidgetModel model(l);
lv.setModel(&model);
lv.show();
w.show();
return app.exec();
}

OriginalCopy
4th December 2007, 16:49
Right now I'm learning about MVC, interesting stuff in the manual. Thank you for your patience, help, information and everything.