Is there a focus changed event in urwid? - python

Is it possible to track the change of the highlighted item in an urwid.ListBox object? Or even via a ListWalker object?
I would like to call a callback when the user moves from one item to another using the arrow keys [🠉], [🠋], not when the user hits [Enter] on one item.

After some research and experimentation, it's possible to do this by registering the modified signal with the ListWalker object.
import urwid
def callback():
index = str(listBox.get_focus()[1])
debug.set_text("Index of selected item: " + index)
debug = urwid.Text("Debug")
captions = "A B C D E F".split()
items = [urwid.Button(caption) for caption in captions]
walker = urwid.SimpleListWalker(items)
listBox = urwid.ListBox(walker)
urwid.connect_signal(walker, "modified", callback)
frame = urwid.Frame(body=listBox, header=debug)
urwid.MainLoop(frame).run()
Reference: Urwid > Signal functions > connect

Related

Why the output is not shown while using nested streamlit buttons?

This is the streamlit code that I'm currently working on.
When I click the button "About Python" It is working fine and showing the "Generate Syntax" button. But then when I click on the "Generate Syntax" button it is not showing anything and my program is running from the first again.
left, center, right = st.beta_columns(3)
left_one = left.button("About Python", key="1")
center_one = center.button("Learn Python", key="2")
right_one = right.button("Practice Python", key="3")
if left_one:
generate_syntax = st.button("Generate Syntax", key="4")
if generate_syntax:
st.write("Hello All..")
How can I fix the problem?
This can be seen quite often in Streamlit discussions because it looks like a bug at the first glance, but it's not. A simple and silly answer would be "That's the way streamlit is designed".
So every time you press the button, streamlit will re-run the whole web page and it doesn't remember the previous state(information in the previous button).
Although I never found any official answer on how to exactly solve this, I did manage to do some workarounds.
Using checkboxes rather than buttons
Using Session State
Creating small functions and calling them during your if conditions.
When you click About Python:
the value of left_one becomes True
the value of all other buttons (including generate_syntax) becomes False
When you click Generate Syntax:
the value of generate_syntax becomes True
the value of left_one becomes False
The issue is that you never meet both conditions of generate_syntax being True AND left one being True. So you never print "Hello All" Streamlit only thinks of one statechange at a time when it comes to buttons.
The way to get around this is by using an object to store state. #pathe_rao has already suggesting using SessionState object from this gist:
import streamlit as st
### Copy and Paste Session state code here
session_state = SessionState.get(left =False, right = False, center = False)
left, center, right = st.beta_columns(3)
left_one = left.button("About Python",key = "1")
center_one = center.button("Learn Python", key = "2")
right_one = right.button("Practice Python", key = "3")
if left_one or session_state.left:
session_state.left = True
session_state.right = False
session_state.center = False
generate_syntax = st.button("Generate Syntax", key = "4")
if generate_syntax:
st.write("Hello All..")
if center_one or session_state.center:
session_state.left = False
session_state.right = False
session_state.center = True
if right_one or session_state.right:
session_state.left = False
session_state.right = True
session_state.center = False
For starters, two of your buttons aren't really wired into anything, so I'll just focus on a single nested button.
Unlike most other UI libraries, Streamlit isn't event-driven and instead works like Leonard Shelby from the film Memento. In that movie, the protagonist repeatedly blacks out and forgets recent events. He uses tattoos to store information that should persist between amnesia blackouts.
Similarly, in Streamlit, triggered events cause the script to re-execute from the start, totally forgetting everything except st.session_state, which works like Leonard's tattoos to keep track of important information, like which menus are in an open state.
Here's one approach using state:
import streamlit as st # 1.18.1
def handle_about_python_open():
state.about_python = True
def handle_generate_syntax_open():
state.about_python = True
def close():
state.about_python = False
state.generate_syntax = False
state = st.session_state
state.about_python = state.get("about_python")
state.generate_syntax = state.get("generate_syntax")
if (
st.button("About Python", on_click=handle_about_python_open) or
state.about_python
):
if (
st.button("Generate Syntax", on_click=handle_generate_syntax_open)
or state.generate_syntax
):
st.write("Hello All..")
# close the outer button, optionally
st.button("Close", on_click=close)
Note that the on_click callback changes state but the condition version doesn't seem to:
if st.button("Close"):
state.about_python = False
If you want to generalize this, you can:
def create_nested_button_col(i, col, text):
with col:
if st.button(text) or state.open[i][0]:
state.open[i][0] = True
if st.button(f"{text} inner") or state.open[i][1]:
st.write(f"Hello {text}..")
state.open[i][1] = True
def handle_close():
state.open[i][0] = False
state.open[i][1] = False
st.button(f"Close {text}", on_click=handle_close)
state = st.session_state
button_text = "foo", "bar", "baz"
state.open = state.get("open", [[False] * 2 for _ in range(len(button_text))])
pairs = zip(st.columns(len(button_text)), button_text)
for i, (col, text) in enumerate(pairs):
create_nested_button_col(i, col, text)
From this, it should be clear that we can arbitrarily generalize with as many nested buttons as we want, as long as we keep state on open/closed state for each button. A 2d grid of booleans could do it.

How to insert a scrollbar to a menu?

I want to add another widget, in this case a scale widget, to a menu widget in tkinter.
Right now the only solutions I see are creating a new command and open a new window with the scale widget or creating the scale widget elsewhere. Both don't seem too appealing to me.
Any ideas how to archive this are welcome :)
You cant add a scrollbar to it, but I have coded something similar to this. Its a hacky way and maybe its hard to understand but I can try to explain.
Note as Bryan mentioned in the linked Thread, this seems to be a a Windows only solution.
import tkinter as tk
def my_first_function():
print('first')
def my_second_function():
print('second')
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
def scroll_down():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_last_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
next_command_label = list(dict_of_commands)[i+1]
next_command = list(dict_of_commands.values())[i+1]
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
except:
pass
space = ' '
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.bind('<<MenuSelect>>', check_for_scroll)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
So this code creates your window and a menubar on it as usal:
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
Important for you, is this line here:
file_menu.bind('<<MenuSelect>>', check_for_scroll)
This line binds the event MenuSelect and it happens/triggers if your cursor hovers over a command of your menu. To this event I have bound a function called check_for_scroll and it looks like this:
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
The line below checks for the index of the command that has triggered the event. With this we check if its button of our interest like the first or last, with the arrows.
check = root.call(event.widget, "index","active")
if its the first for example this code here is executed:
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
it calls/triggers the function scroll_up and uses then the after method of tkinter to retrigger itself, like a loop. The scroll_up function is build like the scroll_down just in the opposite direction. Lets have a closer look:
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
In this function we need to know the first and the last position of commands, because we want to delete one and insert another on that position/index. To achieve this I had created a dictionary that contains the label and the the function of the command item of tkinter like below. (This could be created dynamically, but lets keep it for another question)
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
So we iterate over this enumerated/indexed dictionary in our function and check if the k/key/label is our item of interest. If true, we get the previous_command by listing the dictionary keys and get the extract the key before by this line:
next_command_label = list(dict_of_commands)[i+1]
similar to the value of the dictionary with this line:
next_command = list(dict_of_commands.values())[i+1]
After all we can delete one and insert one where we like to with this:
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
I know that this code can improved by a lot but it seems hard enough to understand as it is. So dont judge me please.
I hope this solves your question, even if it isnt in the way you wanted to. But this code avoids you from hardly code a own menubar.
If there are questions left on my answer, let me know.

Map values from an UI into QTreeWidget Column

sorry if my following question sounds stupid but I am pretty much in a lost, big way!
I have created 2 UIs using the Qt-Designer.
Main UI (anmgToolUI) - http://pastebin.com/raw.php?i=eXVWj99Q
Sub UI (publishInfoUI) - http://pastebin.com/raw.php?i=KsnJC8wR
And the following is the main code:
Main Code - http://pastebin.com/mbg2fuvh
And I am using the following to run it in Maya:
import sys
sys.path.insert(0, '/user_data/test')
import UI_test
reload(UI_test)
win = UI_test.MigrationUi()
win.show()
So basically what I am trying to achieve here is that the values I have typed in the Sub UI is not mapping the values onto the Main UI.
To replicate the case:
Run the execution code
Select any of the 2 items that I have already inserted in the Search Filter QTreeWidget and click on "Add Selected" in which it will transfers it into the Migrate ANMG QTreeWidget
Select an item in the Migrate ANMG QTreeWidget and click onto the Edit Selected button
Try inputting some words etc into either of the 3 fields - Description/ Comment/ Version Comment
I had thought that if I click the OK button, it will mapped the values into columns but it does not seems to be that case, despite me setting a signal/slot in the Qt Designer for accepted() / accept()
Greatly appreciate for any pointers...
I've implemented two solutions, you'll need to comment/uncomment the code. The result is the same in both case.
Solution 1 is based on the reference of the asset you are using in PublishInfoUI class and. Once you press "OK", the modifcations are done inside the PublishInfoUI class on self.assets.
On the other side in solution 2, once you press "OK", it creates a new QTableWidgetItem which is set then returned to your MigrationUI class. Then the modifications are done in this class.
IMO, I prefer the first solution if you rename getValues to updateValues or something like this. But as I said, the result is the same in both case.
class MigrationUi(QtGui.QWidget):
### Some stuff
def editSelected(self):
selected_item = self.ui.treeWidget_migrateAnmg.currentItem()
if selected_item:
inputWin = PublishInfoUI(selected_item)
############################
# Here is the core modifications
if inputWin.exec_(): #Triggered if we press "OK"
#Solution 1:
inputWin.getValues()
#End solution 1
#Solution 2:
returnedAsset = inputWin.getValues()
print "Returned asset: "
print returnedAsset.text(0)
print returnedAsset.text(1)
print returnedAsset.text(2)
print returnedAsset.text(3)
print returnedAsset.text(4)
print returnedAsset.text(5)
print returnedAsset.text(6)
print returnedAsset.text(7)
print returnedAsset.text(8)
selected_item.setText(6, returnedAsset.text(6) )
selected_item.setText(7, returnedAsset.text(7) )
selected_item.setText(8, returnedAsset.text(8) )
#End solution 2
else:
print "Canceled/closed operation"
############################
else:
cmds.warning("Please select an item in ANMG field")
def slotCancel(self):
self.close()
class PublishInfoUI(QtGui.QDialog):
def __init__(self, asset, parent = None, modal = False):
QtGui.QWidget.__init__(self, parent, modal = modal)
self.ui = publishInfoUI_test.Ui_PublishInfo()
self.ui.setupUi(self)
self.assets = asset
self.fill_details()
def fill_details(self):
self.ui.lineEdit_rigUsed.setText(self.assets.text(0))
self.ui.lineEdit_anmLocation.setText(self.assets.text(5))
self.ui.textEdit_comment.setText(self.assets.text(7))
def getValues(self):
#Solution 1:
#Do the modification here
self.assets.setText(6, self.ui.lineEdit_description.text() )
self.assets.setText(7, self.ui.textEdit_comment.toPlainText() )
self.assets.setText(8, self.ui.textEdit_Vcomment.toPlainText() )
#End solution 1
#Solution2:
#Return a new asset and do the modification in MigrationUi class
assetToReturn = QtGui.QTreeWidgetItem()
assetToReturn.setText(6, self.ui.lineEdit_description.text() ) #Feel free to add more infos if necessary
assetToReturn.setText(7, self.ui.textEdit_comment.toPlainText() )
assetToReturn.setText(8, self.ui.textEdit_Vcomment.toPlainText() )
return assetToReturn
#End solution 2

PySide: How Could I trigger the current clicked QPushbutton, not other later added

I am new to PySide. In my program, I encountered a problem that when I click one button, it triggers other button later added. Thanks!
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
def addContent(self):
'''
slot to add a row that include a lineedit, combobox, two buttons
'''
self.contentTabHBoxWdgt = QtGui.QWidget()
self.contentName = QtGui.QLineEdit('line edit')
self.conetentTypeBox = QtGui.QComboBox()
self.conetentTypeBox.addItem('elem1')
self.conetentTypeBox.addItem('elem2')
self.contentSave = QtGui.QPushButton('save',parent = self.contentTabHBoxWdgt)
self.contentSave.clicked.connect(self.contntSaveAct)
self.contentDelete = QtGui.QPushButton('delete',parent=self.contentTabHBoxWdgt)
self.contentDelete.clicked.connect(self.contntDel)
self.contentTabHBox = QtGui.QHBoxLayout()
self.contentTabHBox.addWidget(self.contentName)
self.contentTabHBox.addWidget(self.conetentTypeBox)
self.contentTabHBox.addWidget(self.contentSave)
self.contentTabHBox.addWidget(self.contentDelete)
self.contentTabHBoxWdgt.setLayout(self.contentTabHBox)
self.contentTabVBox.addWidget(self.contentTabHBoxWdgt)
def contntDel(self):
'''
slot to delete a row
'''
msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, '', 'Be sure to delete')
okBttn = msgBox.addButton('Yes', QtGui.QMessageBox.AcceptRole)
noBttn = msgBox.addButton('Cancel', QtGui.QMessageBox.RejectRole)
ret = msgBox.exec_()
if msgBox.clickedButton() == okBttn:
self.contentTabVBox.removeWidget(self.contentDelete.parentWidget());
When I Add one row and click its delete button, it does not work as expected.While I add two or three row , I click one delete button , it remove one row that is not the clicked delete button belong to. How could I achieve this function. Ths!
Your problem is because you aren't really taking advantage of object oriented programming properly.
All rows in your example call the same instance of the method contntDel. This method uses self.contentDelete which always contains a reference to the last row added.
What you need to do is separate out everything related to a row to a new class. When you add a row, create a new instance of this class and pass in the contentTabVBox. That way each row (or instance of the new class you will write) will have it's own delete method.
Without a complete code example, I can't provide a complete solution, but this should give you a rough idea:
class MyRow(object):
def __init__(self,contentTabVBox, rows):
self.contentTabVBox = contentTabVBox
self.my_list_of_rows = rows
self.addContent()
def addContent(self):
# The code for your existing addContent method here
def contntDel(self):
# code from your existing contntDel function here
# also add (if Ok button clicked):
self.my_list_of_rows.remove(self)
class MyExistingClass(??whatever you have here normally??):
def __init__(....):
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
self.my_list_of_rows = []
def addContent(self):
my_new_row = MyRow(self.contentTabVBox,self.my_list_of_rows)
# You mustsave a reference to my_new_row in a list or else it will get garbage collected.
self.my_list_of_rows.append(my_new_row)
Hope that helps!

Extract value from a list in another function in Python

I am programming a robot and I want to use an Xbox Controller using pygame. So far this is what I got (original code credits to Daniel J. Gonzalez):
"""
Gamepad Module
Daniel J. Gonzalez
dgonz#mit.edu
Based off code from: http://robots.dacloughb.com/project-1/logitech-game-pad/
"""
import pygame
"""
Returns a vector of the following form:
[LThumbstickX, LThumbstickY, Unknown Coupled Axis???,
RThumbstickX, RThumbstickY,
Button 1/X, Button 2/A, Button 3/B, Button 4/Y,
Left Bumper, Right Bumper, Left Trigger, Right Triller,
Select, Start, Left Thumb Press, Right Thumb Press]
Note:
No D-Pad.
Triggers are switches, not variable.
Your controller may be different
"""
def get():
out = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
it = 0 #iterator
pygame.event.pump()
#Read input from the two joysticks
for i in range(0, j.get_numaxes()):
out[it] = j.get_axis(i)
it+=1
#Read input from buttons
for i in range(0, j.get_numbuttons()):
out[it] = j.get_button(i)
it+=1
first = out[1]
second = out[2]
third = out[3]
fourth = out[4]
return first, second, third, fourth
def test():
while True:
first, second, third, fourth = get()
pygame.init()
j = pygame.joystick.Joystick(0)
j.init()
print 'Initialized Joystick : %s' % j.get_name()
test()
Do you see the list called "out"? Each element in it is a button on the Xbox Controller. I want to extract those elements and put them on variables, one variable to each element/button so I can control my robot.
How could I do it?
I've tried to use global variables but then everything turned down to a mess.
Please note that I am a beginner in Python.
If you want to have out in your program then just return it from your function get:
def get():
# rest of the code ...
return out
Also change your function test:
def test():
while True:
out = get()
LThumbstickX = out[0]
LThumbstickY = out[1]
# and so on
Then run your program as before. What the function test does is constantly (while True) read the keypad. You could for example do:
def test():
while True:
out = get()
LThumbstickX = out[0]
if LThumbstickX != 0:
print 'Left button has been pressed'
# and so on
You can just return the list and use python's unpacking feature:
def get():
out = [1,2,3,4]
return out
first, second, third, fourth = get()

Categories

Resources