Building a QTreeView from strings - pyside

I'm trying to build a treeview using what's called a slug: which is a string that's split by hyphens (e.g. Fruit-Apple). Then you just loop through the parts and build the tree if the item doesn't already exist. The first item in the list is always the top most parent. Any suggestions or help would be greatly appreciated. I've having issues trying to append the item to the correct parent.
appendCategorySlug('Fruit-Apple')
appendCategorySlug('Fruit-Orange')
appendCategorySlug('Vegetable-Lettuce')
appendCategorySlug('Fruit-Kiwi')
appendCategorySlug('Vegetable-Carrot')
appendCategorySlug('Vegetable-Carrot-Blackbean')
appendCategorySlug('Vegan-Meat-Blackbean')
I'm not quite sure what I've got going wrong here. The results are kind close, but something is off...
import os, sys
from Qt import QtWidgets, QtGui, QtCore
class CategoryView(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(250,400)
self.categoryModel = QtGui.QStandardItemModel()
self.categoryModel.setHorizontalHeaderLabels(['Items'])
self.categoryProxyModel = QtCore.QSortFilterProxyModel()
self.categoryProxyModel.setSourceModel(self.categoryModel)
self.categoryProxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.categoryProxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.categoryProxyModel.setDynamicSortFilter(True)
self.uiTreeView = QtWidgets.QTreeView()
self.uiTreeView.setModel(self.categoryProxyModel)
self.uiTreeView.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.uiTreeView)
self.setLayout(self.layout)
def appendCategorySlug(self, slug):
# sourceIndex = self.categoryProxyModel.mapToSource(proxyIndex)
# item = self.categoryModel.itemFromIndex(sourceIndex)
parts = slug.split('-')
parent = self.categoryModel.invisibleRootItem()
for name in parts:
# find part and it doesn't exist append it
items = self.categoryModel.findItems(name, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
if len(items) == 1:
print items[0].data()
parent = items[0]
item = QtGui.QStandardItem(name)
parent.appendRow(item)
parent = item
def test_CategoryView():
app = QtWidgets.QApplication(sys.argv)
ex = CategoryView()
ex.appendCategorySlug('Fruit-Apple')
ex.appendCategorySlug('Fruit-Orange')
ex.appendCategorySlug('Vegetable-Lettuce')
ex.appendCategorySlug('Fruit-Kiwi')
ex.appendCategorySlug('Vegetable-Carrot')
ex.appendCategorySlug('Vegetable-Carrot-Blackbean')
ex.appendCategorySlug('Vegan-Meat-Blackbean')
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pass
test_CategoryView()

There are a few issues here. Searching recursively is error-prone because descendant items could have the same name as one of their ancestors. And it is also inefficient, because at each step we only want to know whether an item with the same name exists at the current level - the ones below it are irrelevant. Also, we should avoid searching if the current parent has no children, and make sure that new parents are only created when they're missing (which is the main problem with your current implementation).
The above issues can be fixed most easily if the model's match function is used instead of findItems, since it allows greater control over how the searching is done:
def appendCategorySlug(self, slug):
parts = slug.split('-')
parent = self.categoryModel.invisibleRootItem()
for name in parts:
if parent.rowCount():
indexes = self.categoryModel.match(
parent.child(0).index(),
QtCore.Qt.DisplayRole, name, 1,
QtCore.Qt.MatchExactly)
if indexes:
parent = self.categoryModel.itemFromIndex(indexes[0])
continue
item = QtGui.QStandardItem(name)
parent.appendRow(item)
parent = item
This algorithm can be implemented even more simply with a simple for-loop, and is perhaps easier to understand:
def appendCategorySlug(self, slug):
parts = slug.split('-')
parent = self.categoryModel.invisibleRootItem()
for name in parts:
for row in range(parent.rowCount()):
child = parent.child(row)
if child.text() == name:
parent = child
break
else:
item = QtGui.QStandardItem(name)
parent.appendRow(item)
parent = item

Related

Xpath uninion parameter from parent

here xpath one
/Document/Attributes/BlobContent/Property[#Name="FileName"]/parent::*/Reference/#Link
and xpath two
Document/Attributes/BlobContent/Property[#Name="FileName"]/parent::*/Property[#Name="FileName"]/#Value
both bring back the right result !
I would like to avoid the complete chaining [one | two] as that brought back only a list of alternating results.
tried with
/Document/Attributes/BlobContent/Property[#Name="FileName"]/parent::*/Reference/#Link | */Property[#Name="FileName"]/#Value
but that brings back only the later one.
So how would I correctly bring back two child node attributes from a found parent ?
For anyone interested I didn't find any XPATH solution. However that python code did work for me
import xml.etree.ElementTree as ET
tree = ET.parse(file_xml)
root = tree.getroot()
blobs = root.findall("*/Attributes[1]/BlobContent")
for blob in blobs:
try:
filename = blob.find('Property[#Name="FileName"]').attrib["Value"]
exportname = blob.find('Reference[#Type="RELATIVEFILE"]').attrib["Link"]
print(filename + "," + exportname)
except:
#no filename Property
pass

Kv related question - How to bound an on_press/release function to the viewclass of the recycleview?

I've been working on a project which required me to learn kv.
what I'm trying to do is use recycleview to display a list of people that are a part of a dataset I built and allow easy edit of the dataset.
what I've done is read the documentation and simply use the first example from there (with a slight change, the viewclass being a togglebutton:
[The Example][1]
so as for my question, what I want to do is simply bound an on_press/release function to the viewclass objects, for example what I want to do is to bound a function to all of the toggle buttons which appends the button's text to a list when It's being pressed and removes the name from the list when It's being released.
[1]: https://i.stack.imgur.com/55FlM.png
You can do that by adding the on_press to the data:
class RV(RecycleView):
def __init__(self, **kwargs):
self.list = []
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x), 'on_press':partial(self.toggle, str(x))} for x in range(100)]
def toggle(self, name):
print('toggle')
if name in self.list:
self.list.remove(name)
else:
self.list.append(name)
print('list is now:', self.list)

How to add participant.var to data set

New to oTree, so I apologize if this is a super trivial question. I was trying to figure out how to pass my participant.var variable into the admin and exported data fields.
My model.py subsession class has the following:
def creating_session(self):
# randomize treatments
if self.round_number == 1:
for p in self.get_players():
p.participant.vars['treatment'] = random.choice(['no_info', 'full_info', 'info_choice'])
and my player class has
treatment = models.StringField()
participant_vars_treatment = models.LongStringField()
def treatment_allocation(self):
self.player.participant_vars_treatment = str(self.participant.vars['treatment'])
This does not produce the randomized treatments into the new variable participant_vars_treatment. Could someone point me in the right direction? Any help would be great!
Solved solution:
In the subsession class:
def creating_session(self):
# randomize treatments
import itertools
treatments = itertools.cycle(['no_info', 'full_info', 'info_choice'])
if self.round_number == 1:
for p in self.get_players():
p.participant.vars['treatment'] = next(treatments)
treatment = next(treatments)
p.participant.vars['treatment'] = treatment
p.treatment = treatment
and then the following in the play class:
treatment = models.StringField()

Using a choice widget with ultimatelistctrl (wxpython)

I have an UltimateListCtrl with three columns.
The first simply shows the index, the second has a Choice widget to choose an action, and the third has some StaticText widgets (parameters), their number and identity depending on the choice in column 2.
When the Choice is changed, I get a CommandEvent about it, but I can't figure out in which cell I am.
I need this to change the widgets in column three.
Attached is the relevant code:
def addAction(self, action):
# set the Choice in the cell
index = self.list.InsertStringItem(sys.maxint, '')
self.list.SetStringItem(index, self.columns['#'], str(index))
self.list.SetStringItem(index, self.columns['Action'], '')
self.list.SetStringItem(index, self.columns['Parameters'], '')
item = self.list.GetItem(index, self.columns['Action'])
choice = wx.Choice(self.list, -1, name=action.name,
choices=[availableAction.name for availableAction in self.availableActions])
choice.Bind(wx.EVT_CHOICE, self.onActionChange)
item.SetWindow(choice, expand=True)
self.list.SetItem(item)
# set the third column's widgets
self.setItemParameters(index, action)
def onActionChange(self, event):
action = copy.deepcopy(self.availableActionsDict[event.GetString()])
# This doesn't work because this event doesn't have a GetIndex() function
self.setItemParameters(event.GetIndex(), action)
As you can see in the code, I'd like to find the index of the changed Choice widget.
Does anybody know how to do that?
I tried getting the item index by looking at the current selected/focused item in the list but it doesn't corelate to the Choice being changed.
Got it!
I keep it as it is, and simply use the SetClientData() to give each Choice widget its place in the list:
def addAction(self, action):
# set the Choice in the cell
index = self.list.InsertStringItem(sys.maxint, '')
self.list.SetStringItem(index, self.columns['#'], str(index))
self.list.SetStringItem(index, self.columns['Action'], '')
self.list.SetStringItem(index, self.columns['Parameters'], '')
item = self.list.GetItem(index, self.columns['Action'])
choice = wx.Choice(self.list, -1, name=action.name,
choices=[availableAction.name for availableAction in self.availableActions])
choice.SetClientData(0, index)
choice.Bind(wx.EVT_CHOICE, self.onActionChange)
item.SetWindow(choice, expand=True)
self.list.SetItem(item)
# set the third column's widgets
self.setItemParameters(index, action)
def onActionChange(self, event):
action = copy.deepcopy(self.availableActionsDict[event.GetString()])
self.setItemParameters(event.GetEventObject().GetClientData(0), action)
I do need to update this every time the index is changed (like when an item is deleted from the middle of the list), but I can live with that.
Any other solutions will be appreciated

How can I edit a TreeModelFilters child model?

I am currently trying to filter a TreeView based on the input of a text box, while still allowing the cells of the TreeView to be editable by the user.
The problem I'm running into is not being able to translate an edit on the TreeView while it's using the TreeModelFilter into an edit on the child model, which is a ListStore.
The signal_connect for the cell (CellRendererText) editing looks like this:
renderer.signal_connect('edited') do |w, s1, s2|
cell_edited(s1, s2, treeview, $status)
end
def cell_edited(path, str, trvu, cell)
if str != ""
iter = #store.get_iter(path)
iter[cell] = str
end
end
Which I will admit to being something I found doing a search for editing TreeViews in Gtk2, as I am a GTK2 and GUI newbie in general.
How do I go about translating the path in the TreeViewFilter to the path in the child model (the ListStore)?
Or put more simply: when a user edits a cell in the table while it's filtered, how do I update the correct non-filtered entry in the list?
First of all, You could write your code like below. Its more concise, and it checks to make sure that the path is valid:
renderer.signal_connect('edited') do |ren, path, text|
next unless iter = #store.get_iter(path)
iter[$status] = text if text != ""
end
You're doing everything correctly for a normal treeview, but when you make a Gtk::TreeModelFilter, you need to convert from the filtered iter to the child's iter using:
Gtk::TreeModelFilter#convert_iter_to_child_iter(filter_iter)
http://ruby-gnome2.sourceforge.jp/ja/hiki.cgi?Gtk%3A%3ATreeModelFilter
So your code should read:
renderer.signal_connect('edited') do |ren, path, text|
next unless iter = #store.get_iter(path)
child_iter = #child_store.convert_iter_to_child(iter)
child_iter[$status] = text if text != ""
end
You should have a look at visualruby.net. I'll be releasing a new version that has a great listview/treeview where you have a much easier (and more rubyish) api. The Gtk stuff gets very complicated.

Resources