Python 如何通过QSortFilterProxyModel将行数据插入QAbstractItemModel?
这是我昨天问的一个问题的后续: 在写这篇文章时,我认为添加一些搜索功能会很酷,所以我尝试合并一个Python 如何通过QSortFilterProxyModel将行数据插入QAbstractItemModel?,python,pyqt,pyqt4,qabstractitemmodel,qsortfilterproxymodel,Python,Pyqt,Pyqt4,Qabstractitemmodel,Qsortfilterproxymodel,这是我昨天问的一个问题的后续: 在写这篇文章时,我认为添加一些搜索功能会很酷,所以我尝试合并一个QSortFiltertProxyModel。通过这样做,我现在意识到事情完全复杂了,但我真的很喜欢它的发展方向,所以我想跟上它 现在,我最想要的很多功能似乎都能正常工作,但特别是在尝试添加新行数据时,它似乎会崩溃。在不同的时候,我都会收到这样的错误,它们说,QSortFilterProxyModel:index从错误的模型传递到mapFromSource(我真的不知道这是从哪里来的) 此文件的当前
QSortFiltertProxyModel
。通过这样做,我现在意识到事情完全复杂了,但我真的很喜欢它的发展方向,所以我想跟上它
现在,我最想要的很多功能似乎都能正常工作,但特别是在尝试添加新行数据时,它似乎会崩溃。在不同的时候,我都会收到这样的错误,它们说,QSortFilterProxyModel:index从错误的模型传递到mapFromSource
(我真的不知道这是从哪里来的)
此文件的当前代码:
import sys
from functools import partial
from PyQt4 import QtGui, QtCore
HORIZONTAL_HEADERS = ("Asset Name", "Date Added")
class AssetClass(object):
'''
a trivial custom data object
'''
def __init__(self, **kwargs):
if not kwargs.get('name') or not kwargs.get('type'):
return
self.name = kwargs.get('name')
self.date_added = kwargs.get('date_added')
self.type = kwargs.get('type')
def __repr__(self):
return "%s - %s %s" % (self.type, self.name, self.date_added)
class TreeItem(object):
'''
a python object used to return row/column data, and keep note of
it's parents and/or children
'''
def __init__(self, asset, header, parent_item):
self.asset = asset
self.parent_item = parent_item
self.header = header
self.child_items = []
def appendChild(self, item):
self.child_items.append(item)
def removeChild(self, row):
print 'removeChild: item is %s' % self.child_items[row]
del self.child_items[row]
def child(self, row):
return self.child_items[row]
def childCount(self):
return len(self.child_items)
def columnCount(self):
return 2
def data(self, column):
if self.asset == None:
if column == 0:
return QtCore.QVariant(self.header)
if column == 1:
return QtCore.QVariant("")
else:
if column == 0:
return QtCore.QVariant(self.asset.name)
if column == 1:
return QtCore.QVariant(self.asset.date_added)
return QtCore.QVariant()
def parent(self):
return self.parent_item
def row(self):
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
'''
a model to display a few names, ordered by sex
'''
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.assets = []
model_data = (("VEHICLE", "Truck", 'May 27th, 2020'),
("VEHICLE", "Car", 'May 25th, 2020'),
("CHARACTER", "Peter", 'May 27th, 2020'),
("CHARACTER", "Rachel", 'May 29th, 2020'),
("PROP", "Chair", 'May 27th, 2020'),
("PROP", "Axe", 'May 17th, 2020'))
for asset_type, name, date in model_data:
asset = AssetClass(type=asset_type, name=name, date_added=date)
self.assets.append(asset)
self.rootItem = TreeItem(None, "ALL", None)
self.parents = {0: self.rootItem}
self.setupModelData()
def columnCount(self, parent=None):
if parent and parent.isValid():
return parent.internalPointer().columnCount()
else:
return len(HORIZONTAL_HEADERS)
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
if item:
return item.asset
return QtCore.QVariant()
def headerData(self, column, orientation, role):
if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
try:
return QtCore.QVariant(HORIZONTAL_HEADERS[column])
except IndexError:
pass
return QtCore.QVariant()
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parent_item = self.rootItem
else:
parent_item = parent.internalPointer()
childItem = parent_item.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
if not childItem:
return QtCore.QModelIndex()
parent_item = childItem.parent()
if parent_item == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parent_item.row(), 0, parent_item)
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
p_Item = self.rootItem
else:
p_Item = parent.internalPointer()
return p_Item.childCount()
def setupModelData(self):
for asset in self.assets:
asset_type = asset.type
if not self.parents.has_key(asset_type):
new_parent = TreeItem(None, asset_type, self.rootItem)
self.rootItem.appendChild(new_parent)
self.parents[asset_type] = new_parent
parent_item = self.parents[asset_type]
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def addSubRow(self, new_asset):
asset_type, name, date = new_asset
asset = AssetClass(type=asset_type, name=name, date_added=date)
parent_item = self.parents[asset_type]
already_exists = False
for child in parent_item.child_items:
if child.asset.name == name and child.asset.type == asset_type:
already_exists = True
if already_exists:
print 'this asset already exists'
return
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def removeRows(self, row, rows, parent=QtCore.QModelIndex()):
# Determine parent
if parent.internalPointer() is None:
parent_node = self.root
else:
parent_node = parent.internalPointer()
# # Delete it from the parent
self.beginRemoveRows(parent, row, row + rows - 1)
parent_node.removeChild(row)
self.endRemoveRows()
return True
def searchModel(self, asset):
'''
get the modelIndex for a given appointment
'''
def searchNode(node):
'''
a function called recursively, looking at all nodes beneath node
'''
for child in node.child_items:
print child.childCount()
if asset == child.asset:
index = self.createIndex(child.row(), 0, child)
return index
if child.childCount() > 0:
result = searchNode(child)
if result:
return result
retarg = searchNode(self.parents[0])
print retarg
return retarg
def findAssetName(self, name):
app = None
for asset in self.assets:
if asset.name == name:
app = asset
break
if app != None:
index = self.searchModel(app)
return (True, index)
return (False, None)
class TreeView(QtGui.QTreeView):
right_button_clicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(TreeView, self).__init__(parent)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.openMenu)
def selectedRows(self):
rows = []
for index in self.selectedIndexes():
if index.column() == 0:
rows.append(index.row())
print type(rows)
return rows
def openMenu(self, position):
indexes = self.selectedIndexes()
if len(indexes) > 0:
level = 0
index = indexes[0]
while index.parent().isValid():
index = index.parent()
level += 1
menu = QtGui.QMenu()
editMenu = None
if level == 0:
editMenu = QtGui.QAction("Edit person", self)
menu.addAction(editMenu)
elif level == 1:
editMenu = QtGui.QAction("Delete", self)
menu.addAction(editMenu)
elif level == 2:
editMenu = QtGui.QAction("Edit object", self)
menu.addAction(editMenu)
if editMenu:
editMenu.triggered.connect(self._on_right_click)
menu.exec_(self.viewport().mapToGlobal(position))
def _on_right_click(self):
self.right_button_clicked.emit()
class Proxy01(QtGui.QSortFilterProxyModel):
def __init__(self):
super(Proxy01, self).__init__()
self.keyword = None
self.searched_parents = None
def setFilterRegExp(self, pattern):
if isinstance(pattern, str):
pattern = QtCore.QRegExp(pattern, QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.FixedString)
super(Proxy01, self).setFilterRegExp(pattern)
def setData(self, index, value, role=QtCore.Qt.EditRole):
# Call source model's setData()
super(Proxy01, self).setData(index, value, role)
self.invalidate()
def _accept_index(self, idx):
if idx.isValid():
text = idx.data(QtCore.Qt.DisplayRole).toString()
if self.filterRegExp().indexIn(text) >= 0:
return True
for row in range(idx.model().rowCount(idx)):
if self._accept_index(idx.model().index(row, 0, idx)):
return True
return False
def filterAcceptsRow(self, source_row, source_parent):
idx = self.sourceModel().index(source_row, 0, source_parent)
return self._accept_index(idx)
class Dialog(QtGui.QDialog):
add_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setMinimumSize(300, 150)
self.model = TreeModel()
self.proxy1 = Proxy01()
self.proxy1.setSourceModel(self.model)
self.proxy1.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
layout = QtGui.QVBoxLayout(self)
self.tree_view = TreeView(self)
self.tree_view.setModel(self.proxy1)
self.tree_view.setAlternatingRowColors(True)
self.tree_view.right_button_clicked.connect(self.deleteMenuButtonClicked)
layout.addWidget(self.tree_view)
label = QtGui.QLabel("Search for the following person")
layout.addWidget(label)
search_layout = QtGui.QHBoxLayout(self)
self.search_line_edit = QtGui.QLineEdit()
self.search_line_edit.textChanged.connect(self.updateFilter)
search_layout.addWidget(self.search_line_edit)
search_line_button = QtGui.QPushButton('Search')
search_line_button.setMaximumWidth(75)
search_layout.addWidget(search_line_button)
layout.addLayout(search_layout)
self.add_button = QtGui.QPushButton("Add \"Character - Smith\"")
layout.addWidget(self.add_button)
QtCore.QObject.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.addButtonClicked)
self.delete_button = QtGui.QPushButton("Delete Selected")
layout.addWidget(self.delete_button)
QtCore.QObject.connect(self.delete_button, QtCore.SIGNAL("clicked()"), self.deleteButtonClicked)
self.but = QtGui.QPushButton("Clear Selection")
layout.addWidget(self.but)
QtCore.QObject.connect(self.but, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)
QtCore.QObject.connect(self.tree_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.row_clicked)
def row_clicked(self, index):
'''
when a row is clicked... show the name
'''
real_index = self.tree_view.model().mapToSource(index)
print self.tree_view.model().sourceModel().data(real_index, QtCore.Qt.UserRole)
def but_clicked(self):
'''
when a name button is clicked, I iterate over the model,
find the person with this name, and set the treeviews current item
'''
name = self.sender().text()
print "BUTTON CLICKED:", name
result, index = self.model.findAssetName(name)
if result:
if index:
self.tree_view.setCurrentIndex(index)
return
self.tree_view.clearSelection()
def addButtonClicked(self):
new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')
self.tree_view.model().sourceModel().addSubRow(new_asset)
self.tree_view.model().sourceModel().layoutChanged.emit()
@QtCore.pyqtSlot()
def deleteMenuButtonClicked(self):
self.deleteButtonClicked()
def deleteButtonClicked(self):
model = self.tree_view.model()
current = self.tree_view.currentIndex()
model.removeRows(current.row(), 1, model.parent(current))
def customFilter(self):
self.proxy1.setKeyword(self.search_line_edit.text())
def updateFilter(self, text):
self.proxy1.setFilterRegExp(text)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
dialog = Dialog()
dialog.show()
sys.exit(app.exec_())
根据我的发现,我确信我需要插入新行,类似于我删除行的方式,但是我可以找到的示例我只能找到声明要添加的新行的场景,但是数据只是在之后通过edit()
进行“输入”。但是我宁愿直接传递AssetClass
对象
如何在实际传递自定义对象时使用
insertRows(self、row、rows、parent=QtCore.QModelIndex())
方法格式?经过一些检查,我能够在没有任何可见错误的情况下工作。通过直接调整源模型,并在QSortFilterProxyModel
上调用invalidate()
,我基本上可以插入项,这似乎只是重新绘制/刷新它(但不确定这是否正确)。我做了类似于删除现有行的操作
完整代码:
# http://rowinggolfer.blogspot.com/2010/05/qtreeview-and-qabractitemmodel-example.html
import sys
from functools import partial
from PyQt4 import QtGui, QtCore
HORIZONTAL_HEADERS = ("Asset Name", "Date Added")
class AssetClass(object):
'''
a trivial custom data object
'''
def __init__(self, **kwargs):
if not kwargs.get('name') or not kwargs.get('type'):
return
self.name = kwargs.get('name')
self.date_added = kwargs.get('date_added')
self.type = kwargs.get('type')
def __repr__(self):
return "%s - %s %s" % (self.type, self.name, self.date_added)
class TreeItem(object):
'''
a python object used to return row/column data, and keep note of
it's parents and/or children
'''
def __init__(self, asset, header, parent_item):
self.asset = asset
self.parent_item = parent_item
self.header = header
self.child_items = []
def appendChild(self, item):
self.child_items.append(item)
def removeChild(self, item):
self.child_items.remove(item)
def child(self, row):
return self.child_items[row]
def childCount(self):
return len(self.child_items)
def columnCount(self):
return 2
def data(self, column):
if self.asset == None:
if column == 0:
return QtCore.QVariant(self.header)
if column == 1:
return QtCore.QVariant("")
else:
if column == 0:
return QtCore.QVariant(self.asset.name)
if column == 1:
return QtCore.QVariant(self.asset.date_added)
return QtCore.QVariant()
def parent(self):
return self.parent_item
def row(self):
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
'''
a model to display a few names, ordered by sex
'''
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.assets = []
model_data = (("VEHICLE", "Truck", 'May 27th, 2020'),
("VEHICLE", "Car", 'May 25th, 2020'),
("CHARACTER", "Peter", 'May 27th, 2020'),
("CHARACTER", "Rachel", 'May 29th, 2020'),
("PROP", "Chair", 'May 27th, 2020'),
("PROP", "Axe", 'May 17th, 2020'))
for asset_type, name, date in model_data:
asset = AssetClass(type=asset_type, name=name, date_added=date)
self.assets.append(asset)
self.rootItem = TreeItem(None, "ALL", None)
self.parents = {0: self.rootItem}
self.setupModelData()
def columnCount(self, parent=None):
if parent and parent.isValid():
return parent.internalPointer().columnCount()
else:
return len(HORIZONTAL_HEADERS)
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
if item:
return item.asset
return QtCore.QVariant()
def headerData(self, column, orientation, role):
if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
try:
return QtCore.QVariant(HORIZONTAL_HEADERS[column])
except IndexError:
pass
return QtCore.QVariant()
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parent_item = self.rootItem
else:
parent_item = parent.internalPointer()
childItem = parent_item.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
if not childItem:
return QtCore.QModelIndex()
parent_item = childItem.parent()
if parent_item == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parent_item.row(), 0, parent_item)
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
p_Item = self.rootItem
else:
p_Item = parent.internalPointer()
return p_Item.childCount()
def setupModelData(self):
for asset in self.assets:
asset_type = asset.type
if not self.parents.has_key(asset_type):
new_parent = TreeItem(None, asset_type, self.rootItem)
self.rootItem.appendChild(new_parent)
self.parents[asset_type] = new_parent
parent_item = self.parents[asset_type]
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def addSubRow(self, new_asset):
asset_type, name, date = new_asset
asset = AssetClass(type=asset_type, name=name, date_added=date)
parent_item = self.parents[asset_type]
already_exists = False
for child in parent_item.child_items:
if child.asset.name == name and child.asset.type == asset_type:
already_exists = True
if already_exists:
print 'this asset already exists'
return
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def customRemoveRow(self, rowIndex):
child_tree_item = rowIndex.internalPointer()
asset_type = rowIndex.parent().data().toString()
parent_item = self.parents[str(asset_type)]
self.beginRemoveRows(rowIndex.parent(), rowIndex.row(), rowIndex.row() + 1)
parent_item.removeChild(child_tree_item)
self.endRemoveRows()
def searchModel(self, asset):
'''
get the modelIndex for a given appointment
'''
def searchNode(node):
'''
a function called recursively, looking at all nodes beneath node
'''
for child in node.child_items:
print child.childCount()
if asset == child.asset:
index = self.createIndex(child.row(), 0, child)
return index
if child.childCount() > 0:
result = searchNode(child)
if result:
return result
retarg = searchNode(self.parents[0])
print retarg
return retarg
def findAssetName(self, name):
app = None
for asset in self.assets:
if asset.name == name:
app = asset
break
if app != None:
index = self.searchModel(app)
return (True, index)
return (False, None)
class TreeView(QtGui.QTreeView):
right_button_clicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(TreeView, self).__init__(parent)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.openMenu)
def selectedRows(self):
rows = []
for index in self.selectedIndexes():
if index.column() == 0:
rows.append(index.row())
print type(rows)
return rows
def openMenu(self, position):
indexes = self.selectedIndexes()
if len(indexes) > 0:
level = 0
index = indexes[0]
while index.parent().isValid():
index = index.parent()
level += 1
menu = QtGui.QMenu()
editMenu = None
if level == 0:
editMenu = QtGui.QAction("Edit person", self)
menu.addAction(editMenu)
elif level == 1:
editMenu = QtGui.QAction("Delete", self)
menu.addAction(editMenu)
elif level == 2:
editMenu = QtGui.QAction("Edit object", self)
menu.addAction(editMenu)
if editMenu:
editMenu.triggered.connect(self._on_right_click)
menu.exec_(self.viewport().mapToGlobal(position))
def _on_right_click(self):
self.right_button_clicked.emit()
class Proxy01(QtGui.QSortFilterProxyModel):
def __init__(self):
super(Proxy01, self).__init__()
self.keyword = None
self.searched_parents = None
def setFilterRegExp(self, pattern):
if isinstance(pattern, str):
pattern = QtCore.QRegExp(pattern, QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.FixedString)
super(Proxy01, self).setFilterRegExp(pattern)
def _accept_index(self, idx):
if idx.isValid():
text = idx.data(QtCore.Qt.DisplayRole).toString()
if self.filterRegExp().indexIn(text) >= 0:
return True
for row in range(idx.model().rowCount(idx)):
if self._accept_index(idx.model().index(row, 0, idx)):
return True
return False
def filterAcceptsRow(self, source_row, source_parent):
idx = self.sourceModel().index(source_row, 0, source_parent)
return self._accept_index(idx)
class Dialog(QtGui.QDialog):
add_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setMinimumSize(300, 150)
self.model = TreeModel()
self.proxy1 = Proxy01()
self.proxy1.setSourceModel(self.model)
self.proxy1.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
layout = QtGui.QVBoxLayout(self)
self.tree_view = TreeView(self)
self.tree_view.setModel(self.proxy1)
self.tree_view.setAlternatingRowColors(True)
self.tree_view.right_button_clicked.connect(self.deleteMenuButtonClicked)
layout.addWidget(self.tree_view)
label = QtGui.QLabel("Search for the following person")
layout.addWidget(label)
search_layout = QtGui.QHBoxLayout(self)
self.search_line_edit = QtGui.QLineEdit()
self.search_line_edit.textChanged.connect(self.updateFilter)
search_layout.addWidget(self.search_line_edit)
search_line_button = QtGui.QPushButton('Search')
search_line_button.setMaximumWidth(75)
search_layout.addWidget(search_line_button)
layout.addLayout(search_layout)
self.add_button = QtGui.QPushButton("Add \"Character - Smith\"")
layout.addWidget(self.add_button)
QtCore.QObject.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.addButtonClicked)
self.delete_button = QtGui.QPushButton("Delete Selected")
layout.addWidget(self.delete_button)
QtCore.QObject.connect(self.delete_button, QtCore.SIGNAL("clicked()"), self.deleteButtonClicked)
self.but = QtGui.QPushButton("Clear Selection")
layout.addWidget(self.but)
QtCore.QObject.connect(self.but, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)
QtCore.QObject.connect(self.tree_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.row_clicked)
def row_clicked(self, index):
'''
when a row is clicked... show the name
'''
real_index = self.tree_view.model().mapToSource(index)
print self.tree_view.model().sourceModel().data(real_index, QtCore.Qt.UserRole)
def but_clicked(self):
'''
when a name button is clicked, I iterate over the model,
find the person with this name, and set the treeviews current item
'''
name = self.sender().text()
print "BUTTON CLICKED:", name
result, index = self.model.findAssetName(name)
if result:
if index:
self.tree_view.setCurrentIndex(index)
return
self.tree_view.clearSelection()
def addButtonClicked(self):
new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')
self.model.addSubRow(new_asset)
self.proxy1.invalidate()
@QtCore.pyqtSlot()
def deleteMenuButtonClicked(self):
self.deleteButtonClicked()
def deleteButtonClicked(self):
current = self.tree_view.currentIndex()
source_index = self.proxy1.mapToSource(current)
self.model.customRemoveRow(source_index)
self.proxy1.invalidate()
def customFilter(self):
self.proxy1.setKeyword(self.search_line_edit.text())
def updateFilter(self, text):
self.proxy1.setFilterRegExp(text)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
dialog = Dialog()
dialog.show()
sys.exit(app.exec_())
经过一些检查,我能够在没有任何明显错误的情况下完成这项工作。通过直接调整源模型,并在
QSortFilterProxyModel
上调用invalidate()
,我基本上可以插入项,这似乎只是重新绘制/刷新它(但不确定这是否正确)。我做了类似于删除现有行的操作
完整代码:
# http://rowinggolfer.blogspot.com/2010/05/qtreeview-and-qabractitemmodel-example.html
import sys
from functools import partial
from PyQt4 import QtGui, QtCore
HORIZONTAL_HEADERS = ("Asset Name", "Date Added")
class AssetClass(object):
'''
a trivial custom data object
'''
def __init__(self, **kwargs):
if not kwargs.get('name') or not kwargs.get('type'):
return
self.name = kwargs.get('name')
self.date_added = kwargs.get('date_added')
self.type = kwargs.get('type')
def __repr__(self):
return "%s - %s %s" % (self.type, self.name, self.date_added)
class TreeItem(object):
'''
a python object used to return row/column data, and keep note of
it's parents and/or children
'''
def __init__(self, asset, header, parent_item):
self.asset = asset
self.parent_item = parent_item
self.header = header
self.child_items = []
def appendChild(self, item):
self.child_items.append(item)
def removeChild(self, item):
self.child_items.remove(item)
def child(self, row):
return self.child_items[row]
def childCount(self):
return len(self.child_items)
def columnCount(self):
return 2
def data(self, column):
if self.asset == None:
if column == 0:
return QtCore.QVariant(self.header)
if column == 1:
return QtCore.QVariant("")
else:
if column == 0:
return QtCore.QVariant(self.asset.name)
if column == 1:
return QtCore.QVariant(self.asset.date_added)
return QtCore.QVariant()
def parent(self):
return self.parent_item
def row(self):
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
'''
a model to display a few names, ordered by sex
'''
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.assets = []
model_data = (("VEHICLE", "Truck", 'May 27th, 2020'),
("VEHICLE", "Car", 'May 25th, 2020'),
("CHARACTER", "Peter", 'May 27th, 2020'),
("CHARACTER", "Rachel", 'May 29th, 2020'),
("PROP", "Chair", 'May 27th, 2020'),
("PROP", "Axe", 'May 17th, 2020'))
for asset_type, name, date in model_data:
asset = AssetClass(type=asset_type, name=name, date_added=date)
self.assets.append(asset)
self.rootItem = TreeItem(None, "ALL", None)
self.parents = {0: self.rootItem}
self.setupModelData()
def columnCount(self, parent=None):
if parent and parent.isValid():
return parent.internalPointer().columnCount()
else:
return len(HORIZONTAL_HEADERS)
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
item = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return item.data(index.column())
if role == QtCore.Qt.UserRole:
if item:
return item.asset
return QtCore.QVariant()
def headerData(self, column, orientation, role):
if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.DisplayRole):
try:
return QtCore.QVariant(HORIZONTAL_HEADERS[column])
except IndexError:
pass
return QtCore.QVariant()
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parent_item = self.rootItem
else:
parent_item = parent.internalPointer()
childItem = parent_item.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
if not childItem:
return QtCore.QModelIndex()
parent_item = childItem.parent()
if parent_item == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parent_item.row(), 0, parent_item)
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
p_Item = self.rootItem
else:
p_Item = parent.internalPointer()
return p_Item.childCount()
def setupModelData(self):
for asset in self.assets:
asset_type = asset.type
if not self.parents.has_key(asset_type):
new_parent = TreeItem(None, asset_type, self.rootItem)
self.rootItem.appendChild(new_parent)
self.parents[asset_type] = new_parent
parent_item = self.parents[asset_type]
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def addSubRow(self, new_asset):
asset_type, name, date = new_asset
asset = AssetClass(type=asset_type, name=name, date_added=date)
parent_item = self.parents[asset_type]
already_exists = False
for child in parent_item.child_items:
if child.asset.name == name and child.asset.type == asset_type:
already_exists = True
if already_exists:
print 'this asset already exists'
return
new_item = TreeItem(asset, "", parent_item)
parent_item.appendChild(new_item)
def customRemoveRow(self, rowIndex):
child_tree_item = rowIndex.internalPointer()
asset_type = rowIndex.parent().data().toString()
parent_item = self.parents[str(asset_type)]
self.beginRemoveRows(rowIndex.parent(), rowIndex.row(), rowIndex.row() + 1)
parent_item.removeChild(child_tree_item)
self.endRemoveRows()
def searchModel(self, asset):
'''
get the modelIndex for a given appointment
'''
def searchNode(node):
'''
a function called recursively, looking at all nodes beneath node
'''
for child in node.child_items:
print child.childCount()
if asset == child.asset:
index = self.createIndex(child.row(), 0, child)
return index
if child.childCount() > 0:
result = searchNode(child)
if result:
return result
retarg = searchNode(self.parents[0])
print retarg
return retarg
def findAssetName(self, name):
app = None
for asset in self.assets:
if asset.name == name:
app = asset
break
if app != None:
index = self.searchModel(app)
return (True, index)
return (False, None)
class TreeView(QtGui.QTreeView):
right_button_clicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(TreeView, self).__init__(parent)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.openMenu)
def selectedRows(self):
rows = []
for index in self.selectedIndexes():
if index.column() == 0:
rows.append(index.row())
print type(rows)
return rows
def openMenu(self, position):
indexes = self.selectedIndexes()
if len(indexes) > 0:
level = 0
index = indexes[0]
while index.parent().isValid():
index = index.parent()
level += 1
menu = QtGui.QMenu()
editMenu = None
if level == 0:
editMenu = QtGui.QAction("Edit person", self)
menu.addAction(editMenu)
elif level == 1:
editMenu = QtGui.QAction("Delete", self)
menu.addAction(editMenu)
elif level == 2:
editMenu = QtGui.QAction("Edit object", self)
menu.addAction(editMenu)
if editMenu:
editMenu.triggered.connect(self._on_right_click)
menu.exec_(self.viewport().mapToGlobal(position))
def _on_right_click(self):
self.right_button_clicked.emit()
class Proxy01(QtGui.QSortFilterProxyModel):
def __init__(self):
super(Proxy01, self).__init__()
self.keyword = None
self.searched_parents = None
def setFilterRegExp(self, pattern):
if isinstance(pattern, str):
pattern = QtCore.QRegExp(pattern, QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.FixedString)
super(Proxy01, self).setFilterRegExp(pattern)
def _accept_index(self, idx):
if idx.isValid():
text = idx.data(QtCore.Qt.DisplayRole).toString()
if self.filterRegExp().indexIn(text) >= 0:
return True
for row in range(idx.model().rowCount(idx)):
if self._accept_index(idx.model().index(row, 0, idx)):
return True
return False
def filterAcceptsRow(self, source_row, source_parent):
idx = self.sourceModel().index(source_row, 0, source_parent)
return self._accept_index(idx)
class Dialog(QtGui.QDialog):
add_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setMinimumSize(300, 150)
self.model = TreeModel()
self.proxy1 = Proxy01()
self.proxy1.setSourceModel(self.model)
self.proxy1.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
layout = QtGui.QVBoxLayout(self)
self.tree_view = TreeView(self)
self.tree_view.setModel(self.proxy1)
self.tree_view.setAlternatingRowColors(True)
self.tree_view.right_button_clicked.connect(self.deleteMenuButtonClicked)
layout.addWidget(self.tree_view)
label = QtGui.QLabel("Search for the following person")
layout.addWidget(label)
search_layout = QtGui.QHBoxLayout(self)
self.search_line_edit = QtGui.QLineEdit()
self.search_line_edit.textChanged.connect(self.updateFilter)
search_layout.addWidget(self.search_line_edit)
search_line_button = QtGui.QPushButton('Search')
search_line_button.setMaximumWidth(75)
search_layout.addWidget(search_line_button)
layout.addLayout(search_layout)
self.add_button = QtGui.QPushButton("Add \"Character - Smith\"")
layout.addWidget(self.add_button)
QtCore.QObject.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.addButtonClicked)
self.delete_button = QtGui.QPushButton("Delete Selected")
layout.addWidget(self.delete_button)
QtCore.QObject.connect(self.delete_button, QtCore.SIGNAL("clicked()"), self.deleteButtonClicked)
self.but = QtGui.QPushButton("Clear Selection")
layout.addWidget(self.but)
QtCore.QObject.connect(self.but, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)
QtCore.QObject.connect(self.tree_view, QtCore.SIGNAL("clicked(QModelIndex)"), self.row_clicked)
def row_clicked(self, index):
'''
when a row is clicked... show the name
'''
real_index = self.tree_view.model().mapToSource(index)
print self.tree_view.model().sourceModel().data(real_index, QtCore.Qt.UserRole)
def but_clicked(self):
'''
when a name button is clicked, I iterate over the model,
find the person with this name, and set the treeviews current item
'''
name = self.sender().text()
print "BUTTON CLICKED:", name
result, index = self.model.findAssetName(name)
if result:
if index:
self.tree_view.setCurrentIndex(index)
return
self.tree_view.clearSelection()
def addButtonClicked(self):
new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')
self.model.addSubRow(new_asset)
self.proxy1.invalidate()
@QtCore.pyqtSlot()
def deleteMenuButtonClicked(self):
self.deleteButtonClicked()
def deleteButtonClicked(self):
current = self.tree_view.currentIndex()
source_index = self.proxy1.mapToSource(current)
self.model.customRemoveRow(source_index)
self.proxy1.invalidate()
def customFilter(self):
self.proxy1.setKeyword(self.search_line_edit.text())
def updateFilter(self, text):
self.proxy1.setFilterRegExp(text)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
dialog = Dialog()
dialog.show()
sys.exit(app.exec_())