1

There are two ListWidgets with drag-N-drop functionality enabled (example was taken from previous post). When a drop event occurs droppedOnA(arg) and droppedOnA(arg) methods are called. Dragging and dropping from "outside" of dialog window (from a file-browser for example) onto the list widgets works well. But dragging and dropping from-to one listWidget onto another doesn't. Because there is no way to track WHAT items were dropped since the droppedOnA(arg) and droppedOnA(arg) methods receiving no arguments (the way it happens when the dropped items are dragged from "outside" of dialog window. Before I start using a messy approach to get around it I would like to make sure there is no other way to do it. Is there?

from PyQt4 import QtGui, QtCore
import sys, os

class MyClassItem(QtGui.QListWidgetItem):
    def __init__(self, parent=None):
        super(QtGui.QListWidgetItem, self).__init__(parent)       


class ThumbListWidget(QtGui.QListWidget):
    def __init__(self, type, parent=None):
        super(ThumbListWidget, self).__init__(parent)
        self.setIconSize(QtCore.QSize(124, 124))
        self.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            super(ThumbListWidget, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.setDropAction(QtCore.Qt.CopyAction)
            event.accept()
        else:
            super(ThumbListWidget, self).dragMoveEvent(event)

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            event.setDropAction(QtCore.Qt.CopyAction)
            event.accept()
            links = []
            for url in event.mimeData().urls():
                links.append(str(url.toLocalFile()))
            self.emit(QtCore.SIGNAL("dropped"), links)
        else:
            event.setDropAction(QtCore.Qt.MoveAction)
            super(ThumbListWidget, self).dropEvent(event)
            self.emit(QtCore.SIGNAL("dropped"))


class Dialog_01(QtGui.QMainWindow):
    def __init__(self):
        super(QtGui.QMainWindow,self).__init__()
        self.listItems={}

        myQWidget = QtGui.QWidget()
        myBoxLayout = QtGui.QVBoxLayout()
        myQWidget.setLayout(myBoxLayout)
        self.setCentralWidget(myQWidget)

        self.listWidgetA = ThumbListWidget(self)
        self.listWidgetB = ThumbListWidget(self)

        for i in range(3):
            listItemAInstance=MyClassItem()
            name='A'+'%04d'%i
            listItemAInstance.setText(name)
            listItemAInstance.setBackgroundColor(QtCore.Qt.darkGray)   
            if i%2: listItemAInstance.setBackgroundColor(QtCore.Qt.gray)

            icon=self.getIcon(name)
            listItemAInstance.setIcon( icon ) 

            self.listWidgetA.addItem(listItemAInstance) 

            listItemBInstance=MyClassItem()
            name='B'+'%04d'%i
            listItemBInstance.setText(name)
            icon=self.getIcon(name)
            listItemBInstance.setIcon( icon )

            if i%2: listItemBInstance.setBackgroundColor(QtCore.Qt.lightGray)
            self.listWidgetB.addItem(listItemBInstance) 

        myBoxLayout.addWidget(self.listWidgetA)      

        myBoxLayout.addWidget(self.listWidgetB)   
        self.connect(self.listWidgetA, QtCore.SIGNAL("dropped"), self.droppedOnA)
        self.connect(self.listWidgetB, QtCore.SIGNAL("dropped"), self.droppedOnB)

        Button_01 = QtGui.QPushButton("Print Dropped Items")
        Button_01.clicked.connect(self.printWhatItemsWereDropped)
        myBoxLayout.addWidget(Button_01)


    def printWhatItemsWereDropped(self):
        print "List of items dropped: "


    def getThumbPath(self, name):
        thumb_dirpath=os.path.expanduser("~")+'/thumbs/' +name+'/'+name+'.jpg'
        return thumb_dirpath

    def getIcon(self, name):
        thumbpath=self.getThumbPath(name)

        if not thumbpath: return
        color='black'
        if os.path.exists( os.path.dirname( thumbpath ) ) == False: os.makedirs( os.path.dirname( thumbpath ) )

        img = QtGui.QImage(64, 64, QtGui.QImage.Format_RGB32)
        img.fill(QtGui.QColor(96,96,96))     

        painter = QtGui.QPainter(img)
        font = painter.font()
        font.setBold(True)
        font.setPointSize(48)

        filename, fileExtension = os.path.splitext( os.path.basename( thumbpath ) )  

        text=filename.upper()
        font.setPointSize(18)        

        painter.setPen(QtGui.QColor(color))
        painter.setFont(font)
        painter.drawText(img.rect(), QtCore.Qt.AlignCenter, text)
        painter.end()
        img.save(thumbpath, 'JPG')

        icon = QtGui.QIcon( thumbpath )
        pixmap = icon.pixmap(64, 64)
        icon = QtGui.QIcon(pixmap)
        return icon


    def droppedOnA(self, arg=None):
        print '\n\t droppedOnA', arg

    def droppedOnB(self, arg=None):
        print '\n\t droppedOnB', arg


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    dialog_1 = Dialog_01()
    dialog_1.show()
    dialog_1.resize(720,480)
    sys.exit(app.exec_())
Cœur
  • 37,241
  • 25
  • 195
  • 267
alphanumeric
  • 17,967
  • 64
  • 244
  • 392

1 Answers1

2

I already suggested to how to do this in a comment to a previous answer. But anyway, here's a slightly crude implementation which should print a list of the source list-widget name, followed by the dropped indexes.

class ThumbListWidget(QtGui.QListWidget):
    _drag_info = []

    def __init__(self, type, name, parent=None):
        super(ThumbListWidget, self).__init__(parent)
        self.setObjectName(name)
        ...

    def startDrag(self, actions):
        self._drag_info[:] = [str(self.objectName())]
        for item in self.selectedItems():
            self._drag_info.append(self.row(item))
        super(ThumbListWidget, self).startDrag(actions)

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            ...
        elif self._drag_info:
            event.setDropAction(QtCore.Qt.MoveAction)
            super(ThumbListWidget, self).dropEvent(event)
            self.emit(QtCore.SIGNAL("dropped"), list(self._drag_info))

    ...

    self.listWidgetA = ThumbListWidget(self, 'A')
    self.listWidgetB = ThumbListWidget(self, 'B')
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Amazing! Thanks Ekhumoro! – alphanumeric Mar 27 '14 at 23:10
  • @Sputnix. In answer to your comments elsewhere: Why do you care about the `drag_info` for items that don't get dropped? The `drag_info` list is just internal temporary storage that will be reset with every new drag - it's not intended to be accessed externally. – ekhumoro Mar 28 '14 at 03:32
  • I thought _drag_info list variable is being used on every startDrag event to store the items that were "start-dragged". Later this variable is used to retrieve stored items to be sent down to a function linked via "dropped" Signal. That function receives a name of the listWidget (A or B) and an item's index number. It works great. And once again, thanks for sharing this technique! The only thing I wish I would be able to do is: instead of recording "startDrag()ed" items into _drag_info variable capture only those items that were actually dropped. – alphanumeric Mar 28 '14 at 22:24
  • The reason why I would like to be able to capture/"record" the items that were actually dropped (and not startDraged) is to avoid a situation when a user clicks on an item, starts dragging it... changes their mind and release the drag (practically duplicating an item in a ListWidget from where the item was originally dragged) or dropping the item on its own ListWidget. If _drag_info variable is updated on startDrag event then the item index is still being sent down to the function. That could result to a confusion down the code since the item was never "dropped" outside own listwidget. – alphanumeric Mar 28 '14 at 22:29
  • @Sputnix. This makes no sense. Your original example _already allows_ items to be drag-dropped both between widgets and on themselves (i.e. so the items can be re-arranged). And in fact you specifically asked for these features in [this question](http://stackoverflow.com/q/22489018/984421) and [this question](http://stackoverflow.com/q/22543644/984421). There is no way to "get the items that were dropped". That is what the base-class `dropEvent` method does. You'd have to write your own custom implementation of it you wanted different behaviour. – ekhumoro Mar 28 '14 at 22:46
  • Ekhumoro, thanks! I just re-run your code again. Now I realized the name of the listWidget supplied by dropEvent() method via self._drag_info variable can be used to determine on what listWidget a drop occurred (along with item's index). That's practically all info needed to build the list of all the items droped onto a "destination" listWidget (even if the drop occurred multiple times). I didn't realize the importance of the supplied ListWidget name ("A" or "B") first. Now it is all clear. Thanks! – alphanumeric Mar 28 '14 at 23:01
  • @Sputnix. Okay, glad you got it working. I made a slight refinement to my example. The `dropped` signal should really send a copy of the draginfo, as it is a class-level attribute that is shared between all instances of `ThumbListWidget`. I probably should have explained all this better in my original answer. – ekhumoro Mar 28 '14 at 23:26