This question has been asked before at:
https://stackoverflow.com/questions/26538667/pyqt-populate-qtreeview-from-txt-file-that-contains-file-paths
But didn't seem to get a response.
I have a dataset of file paths that are formatted, like so:
hon_dev/Bob Dylan/Concept
hon_dev/Andromeda/Modeling
hon_dev/Andromeda/Lookdev
hon_dev/Andromeda/Rigging
hon_dev/Andromeda/Animation
hon_dev/Andromeda/FX
hon_dev/fsafasfas/production
hon_dev/Magebane: Acheron of Mana Aeacus/Model
hon_dev/Magebane: Acheron of Mana Aeacus/Concept
hon_dev/Magebane: Acheron of Mana Aeacus/Texture
hon_dev/Skrull/Modeling
hon_dev/Skrull/Lookdev
hon_dev/Skrull/Rigging
hon_dev/Skrull/Animation
hon_dev/Skrull/FX
hon_dev/Bob Mylan/Modeling
hon_dev/Bob Mylan/Lookdev
hon_dev/Bob Mylan/Rigging
hon_dev/Bob Mylan/Animation
hon_dev/Bob Mylan/FX
hon_dev/Handsome Man/Concept
hon_dev/Handsome Man/Modeling
hon_dev/Handsome Man/Lookdev
hon_dev/Handsome Man/Rigging
hon_dev/Handsome Man/Animation
hon_dev/Handsome Man/FX
demo-sync/Drone Craft/Modelling Drone Craft
demo-sync/Drone Craft/Texturing and Shading of Drone Craft
demo-sync/Drone Craft/Rigging Drone Parts
And I'm trying to get them to fill up a QTreeView (PySide). The current code I have is as such, with a simple recursive function:
def doIt(self):
self.model = QtGui.QStandardItemModel()
# self.model.setHorizontalHeaderLabels = ['test']
topLevelParentItem = self.model.invisibleRootItem()
# create all itewms first
# iterate over each string url
for item in data:
splitName = item.split('/')
# first part of string is defo parent item
# check to make sure not to add duplicate
if len(self.model.findItems(splitName[0], flags=QtCore.Qt.MatchFixedString)) == 0:
parItem = QtGui.QStandardItem(splitName[0])
topLevelParentItem.appendRow(parItem)
def addItems(parent, elements):
# check if not reached last item in the list of items to add
if len(elements) != 0:
print "currently eval addItems({0}, {1}".format(parent.text(), elements)
# check if item already exists, if so do not create
# new item and use existing item as parent
if len(self.model.findItems(elements[0], flags=QtCore.Qt.MatchFixedString)) == 0:
print "item being created for {0}".format(elements[0])
item = QtGui.QStandardItem(elements[0])
else:
print "not adding duplicate of: {0}".format(elements[0])
item = self.model.findItems(elements[0], flags=QtCore.Qt.MatchFixedString)[0]
print "the item to act as non-duplicate is: {0}".format(item.text())
child = elements[1:]
print "child is {0}".format(child)
# call recursive function to add
addItems(item, child)
print "parenting: {0} to {1}".format(item.text(), parent.text())
parent.appendRow(item)
addItems(parItem, splitName[1:])
print 'done: ' + item + '\n'
self.inst.col_taskList.setModel(self.model)
However, because I can't find any way to look through a QStandardItem for existing rows, I'm getting this in the UI as a result:
Is there a way to find duplicates rows in a QStandardItem or traverse the QStandardItemModel to find the existing QStandardItem? I've been struggling with this problem for the past 2 days and trying to find an existing example, and I can't really wrap my head around how this could be such a complication...
Any help/advice on this would be appreciated! Thanks!
Hmm, after a bit of faffing about, I've come up with something that works for now, though the file paths must be in order for this to work:
def doIt(self):
print "\n\n\n\n"
self.model = QtGui.QStandardItemModel()
topLevelParentItem = self.model.invisibleRootItem()
# iterate over each string url
for item in data:
splitName = item.split('/')
# first part of string is defo parent item
# check to make sure not to add duplicate
if len(self.model.findItems(splitName[0], flags=QtCore.Qt.MatchFixedString)) == 0:
parItem = QtGui.QStandardItem(splitName[0])
topLevelParentItem.appendRow(parItem)
def addItems(parent, elements):
"""
This method recursively adds items to a QStandardItemModel from a list of paths.
:param parent:
:param elements:
:return:
"""
for element in elements:
# first check if this element already exists in the hierarchy
noOfChildren = parent.rowCount()
# if there are child objects under specified parent
if noOfChildren != 0:
# create dict to store all child objects under parent for testing against
childObjsList = {}
# iterate over indexes and get names of all child objects
for c in range(noOfChildren):
childObj = parent.child(c)
childObjsList[childObj.text()] = childObj
if element in childObjsList.keys():
# only run recursive function if there are still elements to work on
if elements[1:]:
addItems(childObjsList[element], elements[1:])
return
else:
# item does not exist yet, create it and parent
newObj = QtGui.QStandardItem(element)
parent.appendRow(newObj)
# only run recursive function if there are still elements to work on
if elements[1:]:
addItems(newObj, elements[1:])
return
else:
# if there are no existing child objects, it's safe to create the item and parent it
newObj = QtGui.QStandardItem(element)
parent.appendRow(newObj)
# only run recursive function if there are still elements to work on
if elements[1:]:
# now run the recursive function again with the latest object as the parent and
# the rest of the elements as children
addItems(newObj, elements[1:])
return
# call proc to add remaining items after toplevel item to the hierarchy
print "### calling addItems({0}, {1})".format(parItem.text(), splitName[1:])
addItems(parItem, splitName[1:])
print 'done: ' + item + '\n'
self.inst.col_taskList.setModel(self.model)
Related
I have a tree class in which the class gets initialized with a data, left, and right attributes.
in the same class I have a "save" method.
I am using a list as a queue.
I am attempting to create a "save" method which takes only one argument "data".
The purpose of this save method is to dequeue from my list, check that node to see if its empty and if it is then it saves my data there. Otherwise it enqueues the 2 children of that node into the list.
The purpose of this is to save data in level order into the tree.
Because the class gets initialized there is always at least 1 element in the tree which is the root node.
The issue i keep running into is that whenever i append the self.data (the root node, not the data im currently trying to add) into my list at the beginning of the save method it only saves the data there.
and obviously when I then try to append the left and right child of this int i get an error because the int has no left or right attributes.
I am wondering how to save the node in the list instead of the data at the node.
class Tree():
aqueue = []
def __init__(self, item):
self.item = item
self.leftchild = None
self.rightchild = None
self.aqueue.append(self.item)
def add(self, newitem):
temp = self.myqueue.pop(0)
if temp is None:
temp = Tree(newitem)
else:
self.aqueue.append(temp.leftchild)
self.aqueue.append(temp.rightcild)
temp.add(newitem)
self.aqueue.clear() #this is meant to clear queue of all nodes after the recursions are complete
self.aqueue.append(self.item) #this is meant to return the root node to the queue so that it is the only item for next time
There are a couple of obvious issues with your code: both the if and else branch return, so the code after will never run, temp == newitem is an equality expression, but even if it was an assignment it wouldn't do anything:
def add(self, newitem):
temp = self.myqueue.pop(0)
if temp == None: # should use temp is None
temp == newitem # temp = newitem still wouldn't do anything
return True
else:
self.aqueue.append(temp.leftchild)
self.aqueue.append(temp.rightcild)
return temp.add(newitem)
# you will never get here, since both branches of the if returns
self.aqueue.clear() # delete everything in the list..?
self.aqueue.append(self.item)
I'm currently developing plugin for QGIS with Python 2.7 and PyQt 4. My plugin should have checkable list of map layers (could be list of items, irrelevant) that would be regenerated each time user opens Settings tab. Although I managed to create checkable list of QCheckBoxes which regenerates each time I click Settings button, it's still far from good and functional one. As I figured out, my problem is mostly parent-child relationship and layout deletion.
self.layers = qgis_api.get_layers()
#ScrollArea setup
if (api.selected_upload_layers == [] and
api.project.layerTreeRoot().children() != []):
self.tmp_layers = qgis_api.get_layers()
self.layout = QVBoxLayout(self.settingsDock.groupBox)
self.scroll = QScrollArea()
self.layout.addWidget(self.scroll)
self.scroll.setWidgetResizable(True)
self.scroll.setFixedHeight(111)
self.scrollContent = QWidget(self.scroll)
self.scrollLayout = QVBoxLayout(self.scrollContent)
self.scrollContent.setLayout(self.scrollLayout)
self.scroll.setWidget(self.scrollContent)
i = 0
self.checkboxes = []
for layer in self.layers:
self.checkboxes.append(QCheckBox("{0}".format(layer.name())))
self.checkboxes[i].stateChanged.connect(lambda checked, i = i : self.cBoxChecked(self.checkboxes[i])) #inverts logic if run every time
# check logic
if i < len(self.layers)-1:
i += 1
# Create checkboxes first time
if not api.upload: #api.upload becomes true when clicked save in settings
for i in range(0, len(self.layers)):
try:
self.scrollLayout.addWidget(self.checkboxes[i])
self.checkboxes[i].setChecked(True)
except Exception as e:
print str(e)
# compare layer list at creation and now to notice difference
elif self.tmp_layers != self.layers:
for i in range(0, self.scrollLayout.count()):
self.scrollLayout.removeItem(self.scrollLayout.itemAt(0))
try: # delete old layer items
for i in range(0, len(self.layers)):
self.scrollLayout.addWidget(self.checkboxes[i])
self.checkboxes[i].setChecked(True)
except Exception as e:
print str(e)
Function cBoxChecked() is as follows:
def cBoxChecked(self, cBox):
""" Add functionality to ScrollArea checkboxes."""
if cBox.isChecked():
if cBox.text() not in api.selected_upload_layers:
api.selected_upload_layers.append(cBox.text())
else:
try:
api.selected_upload_layers.remove(cBox.text())
except Exception as e:
print str(e)
Alhtough now i don't get any exceptions, and the list is regenerated. I'm noticing old list underneath the new one, which means that I'm not deleting layout correctly (there are various questions for layout deletion) but I couldn't figure it out completely. To sum it up. What is the most convenient way of destroying and recreating layout and how to figure out parent-child QObject relationships in this concrete example? And one more question that bothers me is that with every new opening of settings tab, there seems to be logic revertion in lambda function that does all the heavy lifting for selecting certain CheckBox. How to fix that?
Thank you for your time :)
I fixed it. The main problem was removeItem method which didn't worked as planned. Although the whole thing was a mess. What I did now? I created checkboxes list at the class initialization as empty list and with correct conditions I'm checking is it first time the SettingsDock is invoked, or is it invoked after something changed in layer interface list. The code is as folows.If something is not explained properly in comments, feel free to ask why i did it that way. Cheers
self.layers = qgis_api.get_layers()
reduced_layers = []
# reduced_layers is list variable which is populated with all layers
# without redundant ones (same names of multiple files .shp/.shx/.dbf)
# shapefile file format
for layer in self.layers:
if layer.name() not in reduced_layers:
reduced_layers.append(layer.name())
# ScrollArea setup
# Set up settingsDock.groupBox as a parent of Vertical layout
# Check if Settings was clicked before api.upload would be handy for
# that, scroll is QScrollArea and is added as widget with layout as
# parent, after that I set up Scroll Content as widget with scroll
# as parent, while scroll Layout is Vertical layout with scrollContent
# as parent, but then i use scroll.setWidget method to define it as
# a parent to scrollContent
if (api.selected_upload_layers == [] and
api.project.layerTreeRoot().children() != []):
self.tmp_layers = qgis_api.get_layers()
self.layout = QVBoxLayout(self.settingsDock.groupBox)
self.scroll = QScrollArea()
# self.layout.addWidget(self.scroll)
self.scroll.setWidgetResizable(True)
self.scroll.setFixedHeight(111)
self.layout.addWidget(self.scroll)
self.scrollContent = QWidget(self.scroll)
self.scrollLayout = QVBoxLayout(self.scrollContent)
self.scroll.setWidget(self.scrollContent)
# As self.checkboxes are initialized as empty list, here are we
# generating a list of QCheckBox items with layer names(if there are
# multiple layers with same name(shapefile format), only one will be
# added as checkbox
# After generating checkboxes list we use it to populate widgets,
# QCheckBoxes in ScrollLayout, and set it in default as Checked
# This is basically 1st time initialization
if self.checkboxes == []:
try:
for i in range(0, len(self.layers)):
if self.layers[i].name() not in map(lambda x: x.text(),
self.checkboxes):
self.checkboxes.append(QCheckBox('{}'.format(
self.layers[i].name())))
for i in range(0, len(self.checkboxes)):
self.scrollLayout.addWidget(self.checkboxes[i])
self.checkboxes[i].setChecked(True)
api.selected_upload_layers = map(lambda x: x.text(),
self.checkboxes)
except Exception as e:
print str(e)
# if checkboxes are different from layer list (that is generated) in
# the moment of clicking show settings which basically indicates that
# interface layer list has been chaged, we must update checkboxes
# To update checkboxes list it's firstly deleted with ScrollContent
elif map(lambda x: x.text(), self.checkboxes) != reduced_layers:
num = self.scrollLayout.count()
self.scrollLayout.removeWidget(self.scrollContent)
self.scrollContent.deleteLater()
self.scrollContent = QWidget(self.scroll)
self.scrollLayout = QVBoxLayout(self.scrollContent)
self.scroll.setWidget(self.scrollContent)
try:
self.checkboxes = []
for i in range(0, len(self.layers)):
if self.layers[i].name() not in map(lambda x: x.text(), self.checkboxes):
self.checkboxes.append(QCheckBox('{}'.format(
self.layers[i].name())))
for i in range(0, len(self.checkboxes)):
self.scrollLayout.addWidget(self.checkboxes[i])
self.checkboxes[i].setChecked(True)
except Exception as e:
print (e)
for i in range(0, len(self.checkboxes)):
self.checkboxes[i].stateChanged.connect(lambda checked, i=i:
self.checkBoxChecked())
Function checkBoxChecked() is as follows:
def checkBoxChecked(self):
"""Add functionality to ScrollArea checkboxes."""
#print api.selected_upload_layers
indices = []
for i in range(0, len(self.checkboxes)):
if self.checkboxes[i].isChecked():
# print cBox.isChecked()
print self.checkboxes[i].text() + " is selected"
indices.append(i)
else:
print self.checkboxes[i].text() + " is deselected"
api.selected_upload_layers = [map(lambda x: x.text(), self.checkboxes)[i] for i in indices]
I have a python tkinter application that contains a ttk.treeview widget.
The widget displays a list of files found on a specific directory tree with a specific extension - this was trivial to build with tt.treeview widget.
There is a request to enable "on-the-fly" filtering of the tree - e.g., the user types in an Entry some string, and as he/she types, the tree removes elements that don't match the typed string so far.
I was exploring the Treeview documentation, tried the detach and reattach methods but with no luck.
detach indeed removes the non-matched elements from the tree, but if the user hit Backspace, I can no longer iterate correctly on the tree to restore those detached elements as get_children method will not return them.
def filter_tree(self):
search_by = self.search_entry.get()
self.tree_detach_leaf_by_regex(self.current_loaded_folder, search_by, "")
def tree_detach_leaf_by_regex(self, root, regex, parent):
if self.treeview.get_children(root):
for child in self.treeview.get_children(root):
self.tree_detach_leaf_by_regex(child, regex, root)
else:
if not re.match(regex, self.treeview.item(root)["text"]):
self.elements_index_within_parent[root] = self.treeview.index(root)
self.elements_parents[parent] = 1
self.treeview.detach(root)
else:
self.treeview.reattach(root, parent, self.elements_index_within_parent[root])
Looking forward to read your advice.
To make my answer reusable by anybody, I have to tell more than directly answering your question. If you directly want to see how I do to get detached items (thus without using the method get_children which cannot get detached items' id), jump to the definition of the method whose name is _columns_searcher.
Introduction
Let's first define some attributes.
#property
def _to_search(self):
key = 'to_search'
if key not in self._cache:
self._cache[key] = tk.StringVar()
return self._cache[key]
def _set_search_entry(self):
ent = ttk.Entry(
self.root, # or canvas, or frame ...
#...
textvariable=self._to_search
)
ent.grid(
#...
)
ent.bind(
'<Return>',
self._columns_searcher
)
return ent
#property
def search_entry(self):
key = 'search_entry'
if key not in self._cache:
self._cache[key] = self._set_search_entry()
return self._cache[key]
Core answer
What follows is the part which directly show how to re-attach user-detached items. First note that, as the OP mentions, get_children only return ids of attached items. Second note that the only thing you need to re-attach detached items is their id. Which implies thus to trace/save them when they are detached so as to be able to re-attach them.
_detached = set()
def _columns_searcher(self, event):
# originally a set returns a tuple
children = list(self._detached) + list(self.tree.get_children())
self._detached = set()
query = self._to_search.get()
self._brut_searcher(children, query.lower())
Note that children above contains all items, be them detached.
def _brut_searcher(self, children, query):
i_r = -1
for item_id in children:
text = self.tree.item(item_id)['text'] # already contains the strin-concatenation (over columns) of the row's values
if query in text:
i_r += 1
self.tree.reattach(item_id, '', i_r)
else:
self._detached.add(item_id)
self.tree.detach(item_id)
From what I see, detach is almost same as delete.
row is gone and you have no access to it.
You have to make a copy of "detached" items, just id, name or more, if you have advance treeview structure, and then go over elements in both lists and sort it out.
Difference is that if you detach item and check it's id with "exists" function, it should return true
I try to parse an XML file.
The main goal is to get an specific tag with with an other specific parent tag.
I have to use an already existing file and i have to modify it.
def get_item_with_parents(self, parent, name, items=None, parents=None):
self.parents=parents
if items == None:
top_level = 1
items = []
self.parents = []
else:
top_level = 0
for child in self.children:
self._append_list(child.name)
if child.name == name and parent in self.parents:
print "inside if"
self._del_list()
items.append(child)
#print self.parents
child.get_item_with_parents(parent, name, items, self.parents)
if top_level:
return PacketList(items)
def _append_list(self, item):
self.parents.append(item)
def _del_list(self):
self.parents=[]
print "test"
print self.parents
The code should do the following (the child variable should basically mean the same as the parent thing):
I get the parent tag an the name of the tag which should be returned.
Then I check if I am the first recursion otherwise i will not return the packetlist at the end.
For each child I try to append its name to my self.parents list. Then I check if my packet has the right "name" and if yes I look if the "parent" is in self.parent.
So there occurs the problem now.
If the parent was in the list, I have to delete everything in my list. But this wont work.
The list will not be cleared. So if there is a next occurance of the tag with the name "name", it will be appended again even if it should not do, because the parent should not be in the list anymore
Can someone tell me why?
I found my misstake.
To clear a list i hav to call del parents[:]
parents=[] does not work(it seams it does simply nothing).
I'm trying to keep this as simple as possible. Basically I want the data to be saved to a file, and the retrieve it so that questor.py works and can "remember" everything it was ever taught on your machine. The original code is available on the web at http://www.strout.net/info/coding/python/questor.py
If I'm reading the code right, you end up with an object that looks something like {key:{key:{key:class instance},class instance},class instance} . (rough estimate)
Please ignore the unfished method Save, I'm working on that as soon as I figure out how to pickle the dictionary without losing any of the imbedded instances.
The following is my attempt at trying to save the dict via pickler. Some of the code is unfinished, but you should be able to get an idea of what I was trying to do. So far all I am able to do is retrieve the last question/answer set. Either my pickle isn't saving the imbedded instances, or they're not actually there when I save the pickle. I've followed the spaghetti lines as much as possible, but can't seem to figure out how to set up a way to save to file without losing anything.
Also my file doesn't have to be .txt originally I was going to use .data for the pickle.
# questor.py
# define some constants for future use
kQuestion = 'question'
kGuess = 'guess'
questfile = 'questfile.txt'
## Added
import cPickle as p
# create a file for questor
def questor_file():
try:
questor = open(questfile,'rb')
try:
q = p.Unpickler(questor)
quest = q.load()
questor.close()
return quest
except:
print 'P.load failed'
except:
print 'File did not open'
questor = open('questfile.data', 'wb')
questor.close()
return Qnode('python')
# define a function for asking yes/no questions
def yesno(prompt):
ans = raw_input(prompt)
return (ans[0]=='y' or ans[0]=='Y')
# define a node in the question tree (either question or guess)
class Qnode:
# initialization method
def __init__(self,guess):
self.nodetype = kGuess
self.desc = guess
##Added
## Not sure where I found this, but was going to attempt to use this as a retreival method
## haven't gotten this to work yet
def Load(self):
f = open(self.questfile,'rb')
tmp_dict = cPickle.load(f)
f.close()
self.__dict__.update(tmp_dict)
##Added
# was going to use this as a save method, and call it each time I added a new question/answer
def Save(self,node):
f = open(self.questfile,'wb')
quest = p.pickler(f)
# get the question to ask
def query(self):
if (self.nodetype == kQuestion):
return self.desc + " "
elif (self.nodetype == kGuess):
return "Is it a " + self.desc + "? "
else:
return "Error: invalid node type!"
# return new node, given a boolean response
def nextnode(self,answer):
return self.nodes[answer]
# turn a guess node into a question node and add new item
# give a question, the new item, and the answer for that item
def makeQuest( self, question, newitem, newanswer ):
# create new nodes for the new answer and old answer
newAnsNode = (Qnode(newitem))
oldAnsNode = (Qnode(self.desc))
# turn this node into a question node
self.nodetype = kQuestion
self.desc = question
# assign the yes and no nodes appropriately
self.nodes = {newanswer:newAnsNode, not newanswer:oldAnsNode}
self.save(self.nodes)
def traverse(fromNode):
# ask the question
yes = yesno( fromNode.query() )
# if this is a guess node, then did we get it right?
if (fromNode.nodetype == kGuess):
if (yes):
print "I'm a genius!!!"
return
# if we didn't get it right, return the node
return fromNode
# if it's a question node, then ask another question
return traverse( fromNode.nextnode(yes) )
def run():
# start with a single guess node
# This was supposed to assign the data from the file
topNode = questor_file()
done = 0
while not done:
# ask questions till we get to the end
result = traverse( topNode )
# if result is a node, we need to add a question
if (result):
item = raw_input("OK, what were you thinking of? ")
print "Enter a question that distinguishes a",
print item, "from a", result.desc + ":"
q = raw_input()
ans = yesno("What is the answer for " + item + "? ")
result.makeQuest( q, item, ans )
print "Got it."
# repeat until done
print
done = not yesno("Do another? ")
# Added
# give me the dictionary
return result
# immediate-mode commands, for drag-and-drop or execfile() execution
if __name__ == '__main__':
print "Let's play a game."
print 'Think of something, just one thing.'
print 'It can be anything, and I will try to guess what it is.'
raw_input('Press Enter when ready.')
print
questdata = run()
print
# Added
# Save the dictionary
questor = open(questfile,'wb')
q = p.Pickler(questor)
q.dump(questdata)
questor.close()
raw_input("press Return>")
else:
print "Module questor imported."
print "To run, type: questor.run()"
print "To reload after changes to the source, type: reload(questor)"
# end of questor.py
one way that comes to mind is creating a list of all the nodes and saving that ... they should keep their internal pointers on their own.
declare a list of nodes at the top of your file (and use pickle... just cause Im more familiar with that)
import pickle
kQuestion = 'question'
kGuess = 'guess'
questfile = 'questfile.txt'
nodes = []
....
change your load method to something like
def questor_file():
global nodes
try:
questor = open(questfile,'rb')
try:
nodes= pickle.load(questor)
quest = nodes[0]
questor.close()
return quest
except:
print 'P.load failed'
nodes = []
except:
print 'File did not open'
nodes = []
return Qnode('python')
change your class constructor so that it adds each node to nodes
class Qnode:
# initialization method
def __init__(self,guess):
self.nodetype = kGuess
self.desc = guess
nodes.append(self)
at the end where it says #added save dictionary , save your list of nodes
questor = open(questfile,'wb')
q = pickle.dump(nodes,questor)
make sure you exit the program by typing no when prompted ...
you could also save it to a database or whatever but you would still have to store each node and it might be more complicated... this method should really be fine I think , (although there may be a more natural way to save a tree structure) ...