I've written my own QAbstractItemModel to show a tree in TreeView. It shows the top level items, but when you expand a directory, the app closes, the the following message is written to the console: "Segmentation fault" What am I doing wrong that is causing this. Here is a simplifed version of my code:
#!/usr/bin/env python
import sys
from PyQt4 import QtCore, QtGui
class TreeModel(QtCore.QAbstractItemModel):
NAME = 0
FILEID = QtCore.Qt.UserRole + 1
horizontalHeaderLabels = ["File Name",]
inventory = None
def set_tree(self, inventory, root_item):
self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
self.inventory = inventory
self.id2fileid = []
self.fileid2id = {}
self.dir_children_ids = {}
self.parent_ids = []
# Create internal ids for all items in the tree for use in
# ModelIndex's.
root_fileid = root_item.file_id
self.append_fileid(root_fileid, None)
remaining_dirs = [root_fileid,]
while remaining_dirs:
dir_fileid = remaining_dirs.pop(0)
dir_id = self.fileid2id[dir_fileid]
dir_children_ids = []
for child in inventory[dir_fileid].children:
id = self.append_fileid(child.file_id, dir_id)
dir_children_ids.append(id)
if child.children:
remaining_dirs.append(child.file_id)
if len(self.id2fileid) % 100 == 0:
QtCore.QCoreApplication.processEvents()
self.dir_children_ids[dir_id] = dir_children_ids
self.emit(QtCore.SIGNAL("layoutChanged()"))
def append_fileid(self, fileid, parent_id):
ix = len(self.id2fileid)
self.id2fileid.append(fileid)
self.parent_ids.append(parent_id)
self.fileid2id[fileid] = ix
return ix
def columnCount(self, parent):
if parent.isValid():
return 0
return len(self.horizontalHeaderLabels)
def rowCount(self, parent):
if self.inventory is None:
return 0
parent_id = parent.internalId()
if parent_id not in self.dir_children_ids:
return 0
return len(self.dir_children_ids[parent_id])
def _index(self, row, column, parent_id):
item_id = self.dir_children_ids[parent_id][row]
return self.createIndex(row, column, item_id)
def index(self, row, column, parent = QtCore.QModelIndex()):
if self.inventory is None:
return self.createIndex(row, column, 0)
parent_id = parent.internalId()
return self._index(row, column, parent_id)
def sibling(self, row, column, index):
sibling_id = child.internalId()
if sibling_id == 0:
return QtCore.QModelIndex()
parent_id = self.parent_ids[child_id]
return self._index(row, column, parent_id)
def parent(self, child):
child_id = child.internalId()
if child_id == 0:
return QtCore.QModelIndex()
item_id = self.parent_ids[child_id]
if item_id == 0 :
return self.createIndex(0, 0, item_id)
parent_id = self.parent_ids[item_id]
row = self.dir_children_ids[parent_id].index(item_id)
return self.createIndex(row, 0, item_id)
def hasChildren(self, parent):
if self.inventory is None:
return False
parent_id = parent.internalId()
return parent_id in self.dir_children_ids
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
fileid = self.id2fileid[index.internalId()]
if role == self.FILEID:
return QtCore.QVariant(fileid)
item = self.inventory[fileid]
column = index.column()
if column == self.NAME:
if role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(item.file_name)
return QtCore.QVariant()
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemIsEnabled
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(self.horizontalHeaderLabels[section])
return QtCore.QVariant()
inventory = {}
class InventoryItem():
def __init__(self, file_id, file_name, children=[]):
self.file_id = file_id
self.file_name = file_name
self.children = children
global inventory
inventory[file_id] = self
root_item = InventoryItem("root-id", "", [
InventoryItem("dir1-id", "dir1", [
InventoryItem("file1-id", "file1")
]),
InventoryItem("file1-id", "file1")
])
app = QtGui.QApplication(sys.argv)
model = TreeModel()
model.set_tree(inventory, root_item)
tree_view = QtGui.QTreeView()
tree_view.setModel(model)
tree_view.show()
app.exec_()
The full version can be found in this branch: https://code.launchpad.net/~garyvdm/qbzr/trees, in the file lib/browse.py
Use the modeltest.py module!
It exercises your model for different scenarios.
You can find it in the /contrib/ directory inside the PyQt source code package.
Something is being garbage collected by Python which Qt still wants to use. These errors are hard to track down. As a first step, I suggest to add this line before or after setModel(model):
tree_view.model = model
This will keep a reference of the model in the Python part of the tree object.
If that doesn't help, you'll have to strip down your code until it stops crashing.
The problem was that I was returning a valid index for the root in parent() when I should be been returning QtCore.QModelIndex().
It would still be nice to be able to debug this kind of thing, which I still don't know how to do.
Related
I'm using PyQT6 for my application. I'm creating my own QAbstractTableModel where each row has some details and last 3 columns are checkboxes representing received, packed, sent. I successfully implemented the solution. The problem is there is a slight delay(0.3s - rough estimate) which is noticable when (un)checking the checkboxes. Propably the problem is in the performance is there better way to do this? All my data returned from database is in numpy arrays so that shouldn't be a problem.
Here is my class for the table.
class TableModel(QAbstractTableModel):
def __init__(self, data, headers):
super(TableModel, self).__init__()
self._data = data
self._headers = headers
def data(self, index, role):
value = self._data[index.row(), index.column()]
if not index.isValid():
return QVariant()
if role == Qt.ItemDataRole.DisplayRole:
if value == "True" or value == "False":
return QVariant("")
return QVariant(str(value))
# set checkboxes
elif role == Qt.ItemDataRole.CheckStateRole:
if value == "True":
return QVariant(Qt.CheckState.Checked)
if value == "False":
return QVariant(Qt.CheckState.Unchecked)
elif role == Qt.ItemDataRole.BackgroundRole:
if value == "True":
return QVariant(QtGui.QColor("green"))
if value == "False":
return QVariant(QtGui.QColor("yellow"))
elif role == Qt.ItemDataRole.TextAlignmentRole:
return QVariant(Qt.AlignmentFlag.AlignCenter)
# checking and unchecking checkbox
def setData(self, index, value, role):
if role == Qt.ItemDataRole.CheckStateRole:
if value is not None:
if value == 0:
self._data[index.row(), index.column()] = False
elif value == 2:
self._data[index.row(), index.column()] = True
queries.set_checkbox(self._data[index.row()], index.column())
self.dataChanged.emit(index, index)
return True
return False
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
return self._headers[section]
return super().headerData(section, orientation, role)
def rowCount(self, index):
if not self._data.size:
return 0
return self._data.shape[0]
def columnCount(self, index):
if not self._data.size:
return 0
return self._data.shape[1]
def flags(self, index):
return Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable
Here is my code for creating the table:
def get_table(self, view, month):
"""get table from db to show in TableView"""
deliveries = queries.get_deliveries(month)
headers = queries.get_delivery_headers()
self.model = TableModel(deliveries, headers)
proxymodel = QSortFilterProxyModel()
proxymodel.setSourceModel(self.model)
view.setModel(proxymodel)
view.sortByColumn(0, Qt.SortOrder.AscendingOrder)
for x in range(2, len(headers)):
view.resizeColumnToContents(x)
view.show()
I am trying to understand object oriented programming. I am doing this by creating a small poker like program. I have come across a problem whose minimal working example is this:
For this code:
import random
class superthing(object):
def __init__(self,name,listthing=[]):
self.name = name
self.listthing = listthing
def randomlyadd(self):
self.listthing.append(random.randint(1,50))
def __str__(self):
return '\nName: '+str(self.name)+'\nList: '+str(self.listthing)
Aboy = superthing('Aboy')
Aboy.randomlyadd()
print(Aboy)
Anotherboy = superthing('Anotherboy')
Anotherboy.randomlyadd()
print(Anotherboy)
I expect this output :
Name: Aboy
List: [44]
(some number between 1 and 50)
Name: Anotherboy
List: [11]
(again a random number between 1 and 50)
But what I get is:
Name: Aboy
List: [44]
(Meets my expectation)
Name: Anotherboy
List: [44,11]
(it appends this number to the list in the previous instance)
Why is this happening? The context is that two players are dealt a card from a deck. I am sorry if a similar question exists, if it does, I will read up on it if you can just point it out. New to stack overflow. Thanks in advance.
For the non minimal example, I am trying this:
import random
class Card(object):
def __init__(self, suit, value):
self.suit = suit
self.value = value
def getsuit(self):
return self.suit
def getval(self):
return self.value
def __str__(self):
if(self.suit == 'Clubs'):
suitstr = u'\u2663'
elif(self.suit == 'Diamonds'):
suitstr = u'\u2666'
elif(self.suit == 'Hearts'):
suitstr = u'\u2665'
elif(self.suit == 'Spades'):
suitstr = u'\u2660'
if((self.value<11)&(self.value>1)):
valuestr = str(self.value)
elif(self.value == 11):
valuestr = 'J'
elif(self.value == 12):
valuestr = 'Q'
elif(self.value == 13):
valuestr = 'K'
elif((self.value == 1)|(self.value == 14)):
valuestr = 'A'
return(valuestr+suitstr)
class Deck(object):
def __init__(self,DeckCards=[]):
self.DeckCards = DeckCards
def builddeck(self):
suits = ['Hearts','Diamonds','Clubs','Spades']
for suit in suits:
for i in range(13):
self.DeckCards.append(Card(suit,i+1))
def shuffle(self):
for i in range(len(self)):
r = random.randint(0,len(self)-1)
self.DeckCards[i],self.DeckCards[r] = self.DeckCards[r],self.DeckCards[i]
def draw(self):
return self.DeckCards.pop()
def __str__(self):
return str([card.__str__() for card in self.DeckCards])
def __len__(self):
return len(self.DeckCards)
class Player(object):
def __init__(self,Name,PlayerHandcards = [],Balance = 1000):
self.Name = Name
self.Hand = PlayerHandcards
self.Balance = Balance
def deal(self,deck):
self.Hand.append(deck.draw())
def __str__(self):
return 'Name :'+str(self.Name)+'\n'+'Hand: '+str([card.__str__() for card in self.Hand])+'\n'+'Balance: '+str(self.Balance)
deck1 = Deck()
deck1.builddeck()
deck1.shuffle()
Alice = Player('Alice')
Alice.deal(deck1)
print(Alice)
Bob = Player('Bob')
Bob.deal(deck1)
print(Bob)
And after dealing to Bob they both have the same hands. If you have some other suggestions regarding the code, you are welcome to share that as well.
This is a duplicate of “Least Astonishment” and the Mutable Default Argument as indicated by #Mad Physicist. Closing this question for the same.
I am trying to make a simple property editor, where the property list is a nested dict and the data is displayed and edited in a QTreeView. (Before I get to my question -- if anyone already has a working implementation of this in Python 3 I'd love to be pointed at it).
Anyway, after much work I have my QAbstractItemModel and I can open a QTreeView with this model and it shows the data. If I click on a label in the first column (the key) then it opens up an editor, either a text editor or a spinbox etc depending on the datatype. When I finish editing it calls my "model.setData" where I reject it because I don't want to allow editable keys. I can disable the editing of this by using flags and that works fine. I just wanted to check that everything works the way that I'd expect it to.
Here is what doesn't happen: if I click on a cell in the second column (the value that I actually want to edit) then it bypasses the loading of an editor and simply calls model.setData with the current value. I am baffled. I've tried changing the tree selectionBehavior and selectionMode but no dice. I'm returning Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable in flags. It seems to display fine. It just won't open up an editor.
Any thoughts about what stupid mistake I must be making? I'll include the code below, with some print statements that I'm using to try to debug the thing.
Thanks
PS One thing that hung me up for a long time was that my QModelIndex members would just disappear, so the indices that I got back were garbage. I found that by keeping a reference to them (throwing them in a list) that they worked. This seems to be a problem that springs up a lot in Qt work (I had the same problem with menus disappearing -- I guess that means that I should think about it sooner). Is there a "best practices" way of dealing with this?
# -*- coding: utf-8 -*-
from collections import OrderedDict
from PyQt4.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt4.QtGui import QAbstractItemView
class PropertyList(OrderedDict):
def __init__(self, *args, **kwargs):
OrderedDict.__init__(self, *args, **kwargs)
self.myModel = PropertyListModel(self)
def __getitem__(self,index):
if issubclass(type(index), list):
item = self
for key in index:
item = item[key]
return item
else:
return OrderedDict.__getitem__(self, index)
class PropertyListModel(QAbstractItemModel):
def __init__(self, propList, *args, **kwargs):
QAbstractItemModel.__init__(self, *args, **kwargs)
self.propertyList = propList
self.myIndexes = [] # Needed to stop garbage collection
def index(self, row, column, parent):
"""Returns QModelIndex to row, column in parent (QModelIndex)"""
if not self.hasIndex(row, column, parent):
return QModelIndex()
if parent.isValid():
indexPtr = parent.internalPointer()
parentDict = self.propertyList[indexPtr]
else:
parentDict = self.propertyList
indexPtr = []
rowKey = list(parentDict.keys())[row]
childPtr = indexPtr+[rowKey]
newIndex = self.createIndex(row, column, childPtr)
self.myIndexes.append(childPtr)
return newIndex
def get_row(self, key):
"""Returns the row of the given key (list of keys) in its parent"""
if key:
parent = key[:-1]
return list(self.propertyList[parent].keys()).index(key[-1])
else:
return 0
def parent(self, index):
"""
Returns the parent (QModelIndex) of the given item (QModelIndex)
Top level returns QModelIndex()
"""
if not index.isValid():
return QModelIndex()
childKeylist = index.internalPointer()
if childKeylist:
parentKeylist = childKeylist[:-1]
self.myIndexes.append(parentKeylist)
return self.createIndex(self.get_row(parentKeylist), 0,
parentKeylist)
else:
return QModelIndex()
def rowCount(self, parent):
"""Returns number of rows in parent (QModelIndex)"""
if parent.column() > 0:
return 0 # only keys have children, not values
if parent.isValid():
indexPtr = parent.internalPointer()
try:
parentValue = self.propertyList[indexPtr]
except:
return 0
if issubclass(type(parentValue), dict):
return len(self.propertyList[indexPtr])
else:
return 0
else:
return len(self.propertyList)
def columnCount(self, parent):
return 2 # Key & value
def data(self, index, role):
"""Returns data for given role for given index (QModelIndex)"""
# print('Looking for data in role {}'.format(role))
if not index.isValid():
return None
if role in (Qt.DisplayRole, Qt.EditRole):
indexPtr = index.internalPointer()
if index.column() == 1: # Column 1, send the value
return self.propertyList[indexPtr]
else: # Column 0, send the key
if indexPtr:
return indexPtr[-1]
else:
return ""
else: # Not display or Edit
return None
def setData(self, index, value, role):
"""Sets the value of index in a given role"""
print('In SetData')
if not index.isValid():
return False
print('Trying to set {} to {}'.format(index,value))
print('That is column {}'.format(index.column()))
if not index.column(): # Only change column 1
return False
try:
ptr = index.internalPointer()
self.propertyList[ptr[:-1]][ptr[-1]] = value
self.emit(self.dataChanged(index, index))
return True
except:
return False
def flags(self, index):
"""Indicates what can be done with the data"""
if not index.isValid():
return Qt.NoItemFlags
if index.column(): # only enable editing of values, not keys
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable #Qt.NoItemFlags
if __name__ == '__main__':
p = PropertyList({'k1':'v1','k2':{'k3':'v3','k4':4}})
import sys
from PyQt4 import QtGui
qApp = QtGui.QApplication(sys.argv)
treeView = QtGui.QTreeView()
# I've played with all the settings on these to no avail
treeView.setHeaderHidden(False)
treeView.setAllColumnsShowFocus(True)
treeView.setUniformRowHeights(True)
treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
treeView.setSelectionMode(QAbstractItemView.SingleSelection)
treeView.setAlternatingRowColors(True)
treeView.setEditTriggers(QAbstractItemView.DoubleClicked |
QAbstractItemView.SelectedClicked |
QAbstractItemView.EditKeyPressed |
QAbstractItemView.AnyKeyPressed)
treeView.setTabKeyNavigation(True)
treeView.setModel(p.myModel)
treeView.show()
sys.exit(qApp.exec_())
#strubbly was real close but forgot to unpack the tuple in his index method.
Here's the working code for Qt5. There are probably a couple of imports and stuff that would need to be fixed. Only cost me a couple weeks of my life :)
import sys
from collections import OrderedDict
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
class TupleKeyedOrderedDict(OrderedDict):
def __init__(self, *args, **kwargs):
super().__init__(sorted(kwargs.items()))
def __getitem__(self, key):
if isinstance(key, tuple):
item = self
for k in key:
if item != ():
item = item[k]
return item
else:
return super().__getitem__(key)
def __setitem__(self, key, value):
if isinstance(key, tuple):
item = self
previous_item = None
for k in key:
if item != ():
previous_item = item
item = item[k]
previous_item[key[-1]] = value
else:
return super().__setitem__(key, value)
class SettingsModel(QtCore.QAbstractItemModel):
def __init__(self, data, parent=None):
super().__init__(parent)
self.root = data
self.my_index = {} # Needed to stop garbage collection
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if parent.isValid():
index_pointer = parent.internalPointer()
parent_dict = self.root[index_pointer]
else:
parent_dict = self.root
index_pointer = ()
row_key = list(parent_dict.keys())[row]
child_pointer = (*index_pointer, row_key)
try:
child_pointer = self.my_index[child_pointer]
except KeyError:
self.my_index[child_pointer] = child_pointer
index = self.createIndex(row, column, child_pointer)
return index
def get_row(self, key):
if key:
parent = key[:-1]
if not parent:
return 0
return list(self.root[parent].keys()).index(key[-1])
else:
return 0
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
child_key_list = index.internalPointer()
if child_key_list:
parent_key_list = child_key_list[:-1]
try:
parent_key_list = self.my_index[parent_key_list]
except KeyError:
self.my_index[parent_key_list] = parent_key_list
return self.createIndex(self.get_row(parent_key_list), 0,
parent_key_list)
else:
return QtCore.QModelIndex()
def rowCount(self, parent):
if parent.column() > 0:
return 0 # only keys have children, not values
if parent.isValid():
indexPtr = parent.internalPointer()
parentValue = self.root[indexPtr]
if isinstance(parentValue, OrderedDict):
return len(self.root[indexPtr])
else:
return 0
else:
return len(self.root)
def columnCount(self, parent):
return 2 # Key & value
def data(self, index, role):
if not index.isValid():
return None
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
indexPtr = index.internalPointer()
if index.column() == 1: # Column 1, send the value
return self.root[indexPtr]
else: # Column 0, send the key
if indexPtr:
return indexPtr[-1]
else:
return None
else: # Not display or Edit
return None
def setData(self, index, value, role):
pointer = self.my_index[index.internalPointer()]
self.root[pointer] = value
self.dataChanged.emit(index, index)
return True
def flags(self, index):
if not index.isValid():
return 0
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
data = TupleKeyedOrderedDict(**{'1': OrderedDict({'sub': 'b'}), '2': OrderedDict({'subsub': '3'})})
model = SettingsModel(data)
tree_view = QtWidgets.QTreeView()
tree_view.setModel(model)
tree_view.show()
sys.exit(app.exec_())
You keep a list of indexes to prevent them being garbage collected. This is needed because, as the documentation explains, the Python object referenced by the internalPointer of a QModelIndex is not protected from garbage collection by that reference. However, your list is added to every time your Model is asked for an index so a new internalPointer is created even for the same Item in the Model. Whereas Qt expects the index and therefore the internalPointer to be the same. This is also problematic since it means the indexes list just keeps growing (as you can see if you add a debug print printing out the contents of self.myIndexes).
This is not trivial to fix in your case. In most models, the internalPointer just stores a pointer to the parent item which is therefore never duplicated. But that won't work in your case because items in the PropertyList don't know their parent. The easiest solution might be to change that, but the PropertyList shouldn't really be affected by its use in the Qt model.
Instead, I have built a dict which is used to find an "original" key list for any key list you build. This looks a bit odd but it works and fixes your code with the fewest changes. I have mentioned some alternative approaches at the bottom.
So these are my changes (really just the lines changing self.myIndexes but also changing the key list to be a tuple rather than a list so it can be hashed):
def __init__(self, propList, *args, **kwargs):
QAbstractItemModel.__init__(self, *args, **kwargs)
self.propertyList = propList
self.myIndexes = {} # Needed to stop garbage collection
def index(self, row, column, parent):
"""Returns QModelIndex to row, column in parent (QModelIndex)"""
if not self.hasIndex(row, column, parent):
return QModelIndex()
if parent.isValid():
indexPtr = parent.internalPointer()
parentDict = self.propertyList[indexPtr]
else:
parentDict = self.propertyList
indexPtr = ()
rowKey = list(parentDict.keys())[row]
childPtr = indexPtr+(rowKey,)
try:
childPtr = self.myIndexes[childPtr]
except KeyError:
self.myIndexes[childPtr] = childPtr
newIndex = self.createIndex(row, column, childPtr)
return newIndex
def parent(self, index):
"""
Returns the parent (QModelIndex) of the given item (QModelIndex)
Top level returns QModelIndex()
"""
if not index.isValid():
return QModelIndex()
childKeylist = index.internalPointer()
if childKeylist:
parentKeylist = childKeylist[:-1]
try:
parentKeylist = self.myIndexes[parentKeylist]
except KeyError:
self.myIndexes[parentKeylist] = parentKeylist
return self.createIndex(self.get_row(parentKeylist), 0,
parentKeylist)
else:
return QModelIndex()
This seems to work, though I've not done too much testing.
Alternatively you could use the internalPointer to store the parent model item (dictionary) and keep a mapping from model item to key list. Or a mapping from model item to parent item. Both of these need a little fiddling (not least because dictionaries are not immediately hashable) but both are possible.
Here is a code I ended up after two days of TreeView/Model madness. The subject appeared to be much more broad than I thought. I barely can spend so much time creating a singe widget. Anyway. The drag-and-drop functionality of TreeView items has been enabled. But other than few interesting printout there is not much there. The double click on an item allows the user to enter a new item name which won't be picked up.
EDITED A DAY LATER WITH A REVISED CODE.
It is now by 90% functional tool.
The user can manipulate the TreeView items by drag and dropping, creating/duplicating/deleting and renaming. The TreeView items are representing the directories or folders in hierarchical fashion before they are created on a drive by hitting 'Print' button (instead of os.makedirs() the tool still simply prints each directory as a string.
I would say I am pretty happy with the result. Thanks to hackyday and to everyone who responded and helped with my questions.
A few last wishes...
A wish number 01:
I wish the PrintOut() method would use a more elegant smarter function to loop through the TreeView items to build a dictionary that is being passed to make_dirs_from_dict() method.
A wish number 02:
I wish deleting the items would be more stable. By some unknown reason a tool crashes on third/fourth Delete button clicks. So far, I was unable to trace the problem down.
A wish number 03:
3. I wish everyone the best and thanks for your help :
import sys, os
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from copy import deepcopy
import cPickle
class TreeItem(object):
def __init__(self, name, parent=None):
self.name = QtCore.QString(name)
self.parent = parent
self.children = []
self.setParent(parent)
def setParent(self, parent):
if parent != None:
self.parent = parent
self.parent.appendChild(self)
else: self.parent = None
def appendChild(self, child):
self.children.append(child)
def childAtRow(self, row):
if len(self.children)>row:
return self.children[row]
def rowOfChild(self, child):
for i, item in enumerate(self.children):
if item == child: return i
return -1
def removeChild(self, row):
value = self.children[row]
self.children.remove(value)
return True
def __len__(self):
return len(self.children)
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self):
QtCore.QAbstractItemModel.__init__(self)
self.columns = 1
self.clickedItem=None
self.root = TreeItem('root', None)
levelA = TreeItem('levelA', self.root)
levelB = TreeItem('levelB', levelA)
levelC1 = TreeItem('levelC1', levelB)
levelC2 = TreeItem('levelC2', levelB)
levelC3 = TreeItem('levelC3', levelB)
levelD = TreeItem('levelD', levelC3)
levelE = TreeItem('levelE', levelD)
levelF = TreeItem('levelF', levelE)
def nodeFromIndex(self, index):
return index.internalPointer() if index.isValid() else self.root
def index(self, row, column, parent):
node = self.nodeFromIndex(parent)
return self.createIndex(row, column, node.childAtRow(row))
def parent(self, child):
# print '\n parent(child)', child # PyQt4.QtCore.QModelIndex
if not child.isValid(): return QModelIndex()
node = self.nodeFromIndex(child)
if node is None: return QModelIndex()
parent = node.parent
if parent is None: return QModelIndex()
grandparent = parent.parent
if grandparent==None: return QModelIndex()
row = grandparent.rowOfChild(parent)
assert row != - 1
return self.createIndex(row, 0, parent)
def rowCount(self, parent):
node = self.nodeFromIndex(parent)
if node is None: return 0
return len(node)
def columnCount(self, parent):
return self.columns
def data(self, index, role):
if role == Qt.DecorationRole:
return QVariant()
if role == Qt.TextAlignmentRole:
return QVariant(int(Qt.AlignTop | Qt.AlignLeft))
if role != Qt.DisplayRole:
return QVariant()
node = self.nodeFromIndex(index)
if index.column() == 0:
return QVariant(node.name)
elif index.column() == 1:
return QVariant(node.state)
elif index.column() == 2:
return QVariant(node.description)
else: return QVariant()
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def flags(self, index):
defaultFlags = QAbstractItemModel.flags(self, index)
if index.isValid(): return Qt.ItemIsEditable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else: return Qt.ItemIsDropEnabled | defaultFlags
def setData(self, index, value, role):
if role == Qt.EditRole:
if value.toString() and len(value.toString())>0:
self.nodeFromIndex(index).name = value.toString()
self.dataChanged.emit(index, index)
return True
def mimeTypes(self):
return ['bstream', 'text/xml']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
bstream = cPickle.dumps(self.nodeFromIndex(indexes[0]))
mimedata.setData('bstream', bstream)
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction: return True
droppedNode=cPickle.loads(str(mimedata.data('bstream')))
droppedIndex = self.createIndex(row, column, droppedNode)
parentNode = self.nodeFromIndex(parentIndex)
newNode = deepcopy(droppedNode)
newNode.setParent(parentNode)
self.insertRow(len(parentNode)-1, parentIndex)
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex)
return True
def insertRow(self, row, parent):
return self.insertRows(row, 1, parent)
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, (row + (count - 1)))
self.endInsertRows()
return True
def removeRow(self, row, parentIndex):
return self.removeRows(row, 1, parentIndex)
def removeRows(self, row, count, parentIndex):
self.beginRemoveRows(parentIndex, row, row)
node = self.nodeFromIndex(parentIndex)
node.removeChild(row)
self.endRemoveRows()
return True
class GUI(QtGui.QDialog):
def build(self, myWindow):
myWindow.resize(600, 400)
self.myWidget = QWidget(myWindow)
self.boxLayout = QtGui.QVBoxLayout(self.myWidget)
self.treeView = QtGui.QTreeView()
self.treeModel = TreeModel()
self.treeView.setModel(self.treeModel)
self.treeView.expandAll()
self.treeView.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.treeView.connect(self.treeView.model(), SIGNAL("dataChanged(QModelIndex,QModelIndex)"), self.onDataChanged)
QtCore.QObject.connect(self.treeView, QtCore.SIGNAL("clicked (QModelIndex)"), self.treeItemClicked)
self.boxLayout.addWidget(self.treeView)
self.PrintButton= QtGui.QPushButton("Print")
self.PrintButton.clicked.connect(self.PrintOut)
self.boxLayout.addWidget(self.PrintButton)
self.DeleteButton= QtGui.QPushButton("Delete")
self.DeleteButton.clicked.connect(self.DeleteLevel)
self.boxLayout.addWidget(self.DeleteButton)
self.insertButton= QtGui.QPushButton("Insert")
self.insertButton.clicked.connect(self.insertLevel)
self.boxLayout.addWidget(self.insertButton)
self.duplicateButton= QtGui.QPushButton("Duplicate")
self.duplicateButton.clicked.connect(self.duplicateLevel)
self.boxLayout.addWidget(self.duplicateButton)
myWindow.setCentralWidget(self.myWidget)
def make_dirs_from_dict(self, dirDict, current_dir='/'):
for key, val in dirDict.items():
#os.mkdir(os.path.join(current_dir, key))
print "\t\t Creating directory: ", os.path.join(current_dir, key)
if type(val) == dict:
self.make_dirs_from_dict(val, os.path.join(current_dir, key))
def PrintOut(self):
result_dict = {}
for a1 in self.treeView.model().root.children:
result_dict[str(a1.name)]={}
for a2 in a1.children:
result_dict[str(a1.name)][str(a2.name)]={}
for a3 in a2.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)]={}
for a4 in a3.children:
result_dict[ str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)]={}
for a5 in a4.children:
result_dict[ str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)]={}
for a6 in a5.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)][str(a6.name)]={}
for a7 in a6.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)][str(a6.name)][str(a7.name)]={}
self.make_dirs_from_dict(result_dict)
def DeleteLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentRow=currentIndex.row()
currentColumn=currentIndex.column()
currentNode = currentIndex.internalPointer()
parentNode = currentNode.parent
parentIndex = self.treeView.model().createIndex(currentRow, currentColumn, parentNode)
print '\n\t\t\t CurrentNode:', currentNode.name, ', ParentNode:', currentNode.parent.name, ', currentColumn:', currentColumn, ', currentRow:', currentRow
# self.treeView.model().removeRow(len(parentNode)-1, parentIndex)
self.treeView.model().removeRows(currentRow, 1, parentIndex )
#self.treeView.model().removeRow(len(parentNode), parentIndex)
#self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex)
def insertLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentNode = currentIndex.internalPointer()
newItem = TreeItem('Brand New', currentNode)
self.treeView.model().insertRow(len(currentNode)-1, currentIndex)
self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), currentIndex, currentIndex)
def duplicateLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentRow=currentIndex.row()
currentColumn=currentIndex.column()
currentNode=currentIndex.internalPointer()
parentNode=currentNode.parent
parentIndex=self.treeView.model().createIndex(currentRow, currentColumn, parentNode)
parentRow=parentIndex.row()
parentColumn=parentIndex.column()
newNode = deepcopy(currentNode)
newNode.setParent(parentNode)
self.treeView.model().insertRow(len(parentNode)-1, parentIndex)
self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex)
print '\n\t\t\t CurrentNode:', currentNode.name, ', ParentNode:', parentNode.name, ', currentColumn:', currentColumn, ', currentRow:', currentRow, ', parentColumn:', parentColumn, ', parentRow:', parentRow
self.treeView.update()
self.treeView.expandAll()
def treeItemClicked(self, index):
print "\n clicked item ----------->", index.internalPointer().name
def onDataChanged(self, indexA, indexB):
print "\n onDataChanged NEVER TRIGGERED! ####################### \n ", index.internalPointer().name
self.treeView.update(indexA)
self.treeView.expandAll()
self.treeView.expanded()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
myWindow = QMainWindow()
myGui = GUI()
myGui.build(myWindow)
myWindow.show()
sys.exit(app.exec_())
I am not totally sure what you are trying to achieve, but it sounds like you want to retrieve the dragged item in the drop operation, and have double click save a new node name.
Firstly, you need to save the dragged item into the mimeData. Currently, you are only saving the string 'mimeData', which doesn't tell you much. The mimeType string that it is saved as (here I used 'bstream') can actually be anything. As long as it matches what you use to retrieve the data, and is in the list returned by the mimeTypes method of the model. To pass the object itself, you must first serialize it (you can convert your object to xml alternatively, if that was something you are planning on doing), since it is not a standard type for mime data.
In order for the data you enter to be saved you must re-implement the setData method of the model and define behaviour for EditRole.
The relevant methods:
def setData(self, index, value, role):
if role == Qt.EditRole:
self.nodeFromIndex(index).name = value
self.dataChanged.emit(index, index)
return True
def mimeTypes(self):
return ['bstream', 'text/xml']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
# assuming single dragged item ...
# only pass the node name
# mimedata.setData('text/xml', str(self.nodeFromIndex(indexes[0]).name))
# pass the entire object
bstream = cPickle.dumps(self.nodeFromIndex(indexes[0]))
mimedata.setData('bstream', bstream)
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction: return True
parentNode = self.nodeFromIndex(parentIndex)
# data = mimedata.data('text/xml')
data = cPickle.loads(str(mimedata.data('bstream')))
print '\n\t incoming row number:', row, ', incoming column:', column, \
', action:', action, ' mimedata: ', data.name
print "\n\t Item's name on which drop occurred: ", parentNode.name, \
', number of its childred:', len(parentNode.children)
if len(parentNode.children)>0: print '\n\t zero indexed child:', parentNode.children[0].name
return True
EDIT:
That is a lot of code you updated, but I will oblige on the points you highlighted. Avoid calling createIndex outside of the model class. This is a protected method in Qt; Python doesn't enforce private/protected variables or methods, but when using a library from another language that does, I try to respect the intended organization of the classes, and access to them.
The purpose of the model is to provide an interface to your data. You should access it using the index, data, parent etc. public functions of the model. To get the parent of a given index, use that index's (or the model's) parent function, which will also return a QModelIndex. This way, you don't have to go through (or indeed know about) the internal structure of the data. This is what I did in the deleteLevel method.
From the qt docs:
To ensure that the representation of the data is kept separate from the way it is accessed, the concept of a model index is introduced. Each piece of information that can be obtained via a model is represented by a model index... only the model needs to know how to obtain data, and the type of data managed by the model can be defined fairly generally.
Also, you can use recursion to simplify the print method.
def printOut(self):
result_dict = dictify(self.treeView.model().root)
self.make_dirs_from_dict(result_dict)
def deleteLevel(self):
if len(self.treeView.selectedIndexes()) == 0:
return
currentIndex = self.treeView.selectedIndexes()[0]
self.treeView.model().removeRow(currentIndex.row(), currentIndex.parent())
I had this separate from class
def dictify(node):
kids = {}
for child in node.children:
kids.update(dictify(child))
return {str(node.name): kids}
I'm working on a binary tree in Python3 and so far almost everything has been working like expected; however, I have a function that is supposed to return a list of all children for any given node and for whatever reason I'm only getting a list of the object addresses, and not calling my overridden __str__(self) method.
from collections import deque # http://docs.python.org/3.1/tutorial/datastructures.html
class BinaryNode: # binary tree functionality via iterative means
def __init__(self, name, data):
self.Left = None
self.Right = None
self.Parent = None
self.Name = name
self.Data = data
return
def AddNew(self, name, data):
q = []
q.append(self)
while q:
i = q.pop()
if i.Name == name:
i.Data = data
return i
elif name < i.Name:
if i.Left:
q.append(i.Left)
else:
i.Left = BinaryNode(name, data)
i.Left.Parent = i
return i.Left
else:
if i.Right:
q.append(i.Right)
else:
i.Right = BinaryNode(name, data)
i.Right.Parent = i
return i.Right
def Find(self, name):
q = deque()
q.append(self)
'''if self.Left: q.append(self.Left)
if self.Right: q.append(self.Right)'''
while q:
i = q.pop()
print(i)
if i.Name == name:
return i
elif name < i.Name:
if i.Left: q.append(i.Left)
else: return None
else:
if i.Right: q.append(i.Left)
else: return None
def Children(self):
children = []
q = deque()
if self.Left: q.append(self.Left)
if self.Right: q.append(self.Right)
while q:
i = q.popleft()
if i.Left: q.append(i.Left)
if i.Right: q.append(i.Right)
children.append(i)
return children
def Parents(self):
lst = []
i = self.Parent
while i is not None:
lst.append(i)
i = i.Parent
return lst
def __str__(self): return "{} : {}".format(self.Name, self.Data)
and I'm testing it by calling
test = BinaryNode("Jesse", 21)
print(test)
print(test.AddNew("David", 22))
print(test.AddNew("Marli", 23))
print(str(test.Children()))
print(test.Find("David"))
print(test.Find("David").Children())
print(test.Find("Gary")) #Will return None
with the resulting console output
Jesse : 21
David : 22
Marli : 23
[<__main__.BinaryNode object at 0x000000000333E160>, <__main__.BinaryNode object at 0x000000000333E1D0>, <__main__.BinaryNode object at 0x000000000333E198>]
David : 22
[<__main__.BinaryNode object at 0x000000000333E1D0>]
None
UPDATE:
Here is the answer I implemented:
def __repr__ (self): return str(self)
Python containers always use the representation of contained objects.
Implement a __repr__ method too and that'll be used when printing the list; you can make it an alias for __str__ if you wish:
__repr__ = __str__
or explicitly print each element in your list:
print(', '.join(map(str, test.Children())))