Using a choice widget with ultimatelistctrl (wxpython) - events

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

Related

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)

Building a QTreeView from strings

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

How to get actions from dynamically created button in RASA

I have create multiple button response with following , now how can get the action from clicking on this button click
class ActionSearchCat(Action):
def name(self):
return "action_search_cat"
def run(self, dispatcher, tracker, domain):
buttons = []
resp = requests.get('http://localhost:3001/api/categoryList/0')
if resp.status_code != 200:
# This means something went wrong.
raise ApiError('GET /tasks/ {}'.format(resp.status_code))
msg = resp.json().get('category_list').get('text')
for list in resp.json().get('category_list').get('choices').items():
for title, value in list.items():
buttons.append({"title": title, "payload": "/"+value})
dispatcher.utter_button_template(msg,buttons)
return []
So first of all, use dispatcher.utter_button_message(msg, buttons) instead of utter_button_template.
Now the buttons will be displayed on the channel to the end-user. After they click one, the next message your AI assistant receives will have the intent value (the payload from the selected button).
You'll have to write a story to handle this intent. If it is a unique intent to this button (i.e. you're not using something generic like Yes or No for your buttons) then the best approach would be to use the MappingPolicy. Either way, you'll also have to add the following story to your data as well:
## button_specific_action
* value
- corresponding_action_for_value

Nokogiri: Filling in a default value for empty table cells

I'm trying to scrape the cell values from an HTML table. Randomly, some of these cells are empty, and I can't guess which ones with any reliability.
Is there a way to fill a default value in for Nokogiri when it comes across an empty cell?
Thanks for any advice you can provide. Here's my code:
def scrape_stats
stats = []
(2002..2012).to_a.each do |year|
url = "website/#{year}"
doc = Nokogiri::HTML(open(url))
rows = doc.at_css("body tbody").text.split(" ")
(rows.count / 25).times do |i| # there are 25 columns per row
stats << rows.shift(25)
end
end
It sounds like you want something like:
doc.search('td:empty').each{|n| n.content = 'default value'}
This would basically involve using the Nokogiri::XML::Node#add_child method (or the shorter version, Nokogiri::XML::Node#<<) to add a new child node containing the text you want to add to the empty cell.
See this question for an example:
How to add child nodes in NodeSet using Nokogiri

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