set other CheckMenuItems' active state to False in Gtk using Python - python

I have written an app indicator for Ubuntu (basically an icon next to the wifi and battery icons) to set the action for closing the lid. It worked perfectly but I wanted to add a checkmark ✓ to see which state is active. This can be done using CheckMenuItems instead of MenuItems in Gtk. The problem is, in my case, the active state of the CheckMenuItems are mutually exclusive. I.e. when one is active, the other is not. I have listed the most relevant parts of the code.
def build_menu():
menu = gtk.Menu()
item_suspend = gtk.CheckMenuItem('Set lid to suspend')
item_suspend.connect('activate', set_lid_suspend)
menu.append(item_suspend)
item_nothing = gtk.CheckMenuItem('Set lid to nothing')
item_nothing.connect('activate', set_lid_nothing)
menu.append(item_nothing)
menu.show_all()
return menu
def set_lid_suspend(menuItem):
call([os.path.join(__location__, "setLidSuspend.sh")])
noot.update("<b>Set lid to suspend.</b>", "", None)
noot.show()
# adjusting the checkmarks
menu = menuItem.get_parent()
for item in menu.get_children(): # loop over all children
if item.get_label() == 'Set lid to nothing':
item.set_active(False)
break
menuItem.set_active(True)
def set_lid_nothing(menuItem):
call([os.path.join(__location__, "setLidNothing.sh")])
noot.update("<b>Set lid to nothing.</b>", "", None)
noot.show()
# adjusting the checkmarks
menu = menuItem.get_parent()
for item in menu.get_children(): # loop over all children
if item.get_label() == 'Set lid to suspend':
print(item)
item.set_active(False)
break
print("broke from nothing")
menuItem.set_active(True)
The problem is that when I use the appIndicator and call one of both methods, everything is fine and it behaves well; but when I then pick the other method, they will loop forever alternating between both of them. Does anyone know what I'm doing wrong?
Also, is this the right way to find menuItems of a menu? I find it hard to believe that there is no such method implemented as replacement for the for loop I've used.

I think you are using the wrong approach. I would use a RadioMenuItem. Then I would block the 'changing' code if the active state of the radiomenuitem is False. That would result in something like this:
def build_menu():
menu = gtk.Menu()
item_suspend = gtk.RadioMenuItem('Set lid to suspend')
item_suspend.connect('activate', set_lid_suspend)
menu.append(item_suspend)
item_nothing = gtk.RadioMenuItem('Set lid to nothing')
item_nothing.connect('activate', set_lid_nothing)
menu.append(item_nothing)
item_suspend.join_group(item_nothing)
menu.show_all()
return menu
def set_lid_suspend(menuItem):
if menuItem.get_active() == False:
return
call([os.path.join(__location__, "setLidSuspend.sh")])
noot.update("<b>Set lid to suspend.</b>", "", None)
noot.show()
def set_lid_nothing(menuItem):
if menuItem.get_active() == False:
return
call([os.path.join(__location__, "setLidNothing.sh")])
noot.update("<b>Set lid to nothing.</b>", "", None)
noot.show()
Since you posted incomplete code, I cannot test this for sure. Try it and let me know.

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.

Kivy Reset Focus After on_text_validation Deactivates It

Looking for another tip to get the focus to return back to the original TextInput area answer.
Here's what I tried:
def setPracPopBtns(instance):
if (instance.id == "sbt") and (answer.text != ""):
# Make lots of irrelevant changes, and then...
answer.font_name = myTrgF
answer.text = unicode("")
answer.focus = True # This does not return the focus.
# Set the answer TextInput area as the focus.
def setAnswerFocus(self):
answer.focus = True
# This only works on the initial load of the popup.
# That's to be expected. The popup never reopens.
# Build the essential practice layout/widget.
# Define the elements.
practice = BoxLayout(orientation="vertical")
answer = TextInput(id="sbt", multiline=False, size_hint=(.8, .5), text="")
answer.bind(text=lambda instance, text: setattr(sbtLen, "text", str(len(text)) + "/" + ansLen.text),
on_text_validate=setPracPopBtns)
# Lots of irrelevant formatting things to build practice.
# Load practice into the pracPop.
pracPop = Popup(on_open=setAnswerFocus, title_align="center", content=practice)
Will I have to develop a whole focus path, or is there some neater way of getting the focus back to the original TextInput area?
So to clarify, this is what happens:
A popup called pracPop opens.
The setAnswerFocus function correctly places the focus on a
TextInput area called answer. A user hits Enter to submit text, triggering on_text_validate=setPracPopBtns.
answer loses its focus. Resetting the focus with answer.focus = True does nothing.
(4) is the problem for which I have not yet found the solution.

How to keep selected points on a ColumnDataSource object and deselect them with ResetTool?

I have plotted a circle glyph. When I select some points they are marked as selected. But I lose the selection when I change the tool I am using or when I click on some other place of the figure. Which is the best way to keep the selection on the circle glyph?. I want to deselect all the points when the ResetTool is pressed. I am working with the master (0.12.14+25.g675aacf72) branch of bokeh where the Selection class already exists.
def update_selection(self, attr, old, new):
''' I do this to avoid deselection '''
if new.indices == []:
self.source.selected.indices = self.old_selection
else:
self.old_selection = list(new.indices)
source.on_change(
'selected',
update_selection
)
I use this to call a callback when the Reset tool is pressed:
def deselect_points(self, event):
''' I do this to deselect point on Reset event
But when the indices are updated the update_selection method is called
'''
self.source.selected.indices = []
plot.on_event(Reset, deselect_points)
So is there a way to keep the selection and only deselect point on Reset event?
Update 03/14/2018
This is happening only when the Tap Tool is enabled. So I have written an issue on GitHub to check if this is the expected behaviour
Finally I made a workaround like this:
# [...]
self.reset_selection = False
# [...]
def _reset_lines(self, event):
# hide the glyphs which mark the selected points
self.lr.visible = False
self.lr_circle_selected.visible = False
self.lr.data_source.data = self.empty_source.data
self.lr_circle_selected.view = self.empty_view
self.reset_selection = True
def update_selection(self, attr, old, new):
if new.indices == []:
if not self.reset_selection:
new.indices = list(self.selection) # keep the selection
else:
self.reset_selection = False
self.selection = []
else:
self.selection = list(new.indices)
self.plot.on_event(Reset, self._reset_lines)
So I am going to use this in the mean time I don´t find anything better
Update
It seems that the issue is already closed. So I hope this is solved already

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

Categories

Resources