pyqtgraph/widgets/TreeWidget.py

156 lines
5.7 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtGui, QtCore
from weakref import *
__all__ = ['TreeWidget']
class TreeWidget(QtGui.QTreeWidget):
"""Extends QTreeWidget to allow internal drag/drop with widgets in the tree.
Also maintains the expanded state of subtrees as they are moved.
This class demonstrates the absurd lengths one must go to to make drag/drop work."""
sigItemMoved = QtCore.Signal(object, object, object) # (item, parent, index)
def __init__(self, parent=None):
QtGui.QTreeWidget.__init__(self, parent)
#self.itemWidgets = WeakKeyDictionary()
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed|QtGui.QAbstractItemView.SelectedClicked)
self.placeholders = []
self.childNestingLimit = None
def setItemWidget(self, item, col, wid):
w = QtGui.QWidget() ## foster parent / surrogate child widget
l = QtGui.QVBoxLayout()
l.setContentsMargins(0,0,0,0)
w.setLayout(l)
w.setSizePolicy(wid.sizePolicy())
w.setMinimumHeight(wid.minimumHeight())
w.setMinimumWidth(wid.minimumWidth())
l.addWidget(wid)
w.realChild = wid
self.placeholders.append(w)
QtGui.QTreeWidget.setItemWidget(self, item, col, w)
def itemWidget(self, item, col):
w = QtGui.QTreeWidget.itemWidget(self, item, col)
if w is not None:
w = w.realChild
return w
def dropMimeData(self, parent, index, data, action):
item = self.currentItem()
p = parent
#print "drop", item, "->", parent, index
while True:
if p is None:
break
if p is item:
return False
#raise Exception("Can not move item into itself.")
p = p.parent()
if not self.itemMoving(item, parent, index):
return False
currentParent = item.parent()
if currentParent is None:
currentParent = self.invisibleRootItem()
if parent is None:
parent = self.invisibleRootItem()
if currentParent is parent and index > parent.indexOfChild(item):
index -= 1
self.prepareMove(item)
currentParent.removeChild(item)
#print " insert child to index", index
parent.insertChild(index, item) ## index will not be correct
self.setCurrentItem(item)
self.recoverMove(item)
#self.emit(QtCore.SIGNAL('itemMoved'), item, parent, index)
self.sigItemMoved.emit(item, parent, index)
return True
def itemMoving(self, item, parent, index):
"""Called when item has been dropped elsewhere in the tree.
Return True to accept the move, False to reject."""
return True
def prepareMove(self, item):
item.__widgets = []
item.__expanded = item.isExpanded()
for i in range(self.columnCount()):
w = self.itemWidget(item, i)
item.__widgets.append(w)
if w is None:
continue
w.setParent(None)
for i in range(item.childCount()):
self.prepareMove(item.child(i))
def recoverMove(self, item):
for i in range(self.columnCount()):
w = item.__widgets[i]
if w is None:
continue
self.setItemWidget(item, i, w)
for i in range(item.childCount()):
self.recoverMove(item.child(i))
item.setExpanded(False) ## Items do not re-expand correctly unless they are collapsed first.
QtGui.QApplication.instance().processEvents()
item.setExpanded(item.__expanded)
def collapseTree(self, item):
item.setExpanded(False)
for i in range(item.childCount()):
self.collapseTree(item.child(i))
def removeTopLevelItem(self, item):
for i in range(self.topLevelItemCount()):
if self.topLevelItem(i) is item:
self.takeTopLevelItem(i)
return
raise Exception("Item '%s' not in top-level items." % str(item))
def listAllItems(self, item=None):
items = []
if item != None:
items.append(item)
else:
item = self.invisibleRootItem()
for cindex in range(item.childCount()):
foundItems = self.listAllItems(item=item.child(cindex))
for f in foundItems:
items.append(f)
return items
def dropEvent(self, ev):
QtGui.QTreeWidget.dropEvent(self, ev)
self.updateDropFlags()
def updateDropFlags(self):
### intended to put a limit on how deep nests of children can go.
### self.childNestingLimit is upheld when moving items without children, but if the item being moved has children/grandchildren, the children/grandchildren
### can end up over the childNestingLimit.
if self.childNestingLimit == None:
pass # enable drops in all items (but only if there are drops that aren't enabled? for performance...)
else:
items = self.listAllItems()
for item in items:
parentCount = 0
p = item.parent()
while p is not None:
parentCount += 1
p = p.parent()
if parentCount >= self.childNestingLimit:
item.setFlags(item.flags() & (~QtCore.Qt.ItemIsDropEnabled))
else:
item.setFlags(item.flags() | QtCore.Qt.ItemIsDropEnabled)