Is there a way in pyside to set all QAction items wherever they may be within the tool to be enabled = False?
For example say i have this written in my code...
# context menu
self.edit_menu_factions = QtGui.QMenu()
self.renameFaction = self.edit_menu_factions.addAction('Rename')
self.renameFaction.triggered.connect(self.menu_action)
self.removeFaction = self.edit_menu_factions.addAction('Remove')
self.removeFaction.triggered.connect(self.menu_action)
self.edit_menu_factions.addSeparator()
self.copyFactionNodes = self.edit_menu_factions.addAction('Copy Nodes')
self.copyFactionNodes.triggered.connect(self.menu_action)
self.pasteFactionNodes = self.edit_menu_factions.addAction('Paste Nodes')
self.pasteFactionNodes.triggered.connect(self.menu_action)
self.edit_menu_factions.addSeparator()
self.removeAllNodes = self.edit_menu_factions.addAction('Remove All Nodes')
self.removeAllNodes.triggered.connect(self.menu_action)
# sub-menu
self.sub_menu_factions = QtGui.QMenu()
self.nice = self.sub_menu_factions.addAction('Nice')
self.nice.triggered.connect(self.menu_action)
self.sub_menu_factions.setTitle("Great")
self.edit_menu_factions.addMenu(self.sub_menu_factions)
I want to go through and disable all Actions, but the main QMenu.
You can get a list of actions attached to a menu with actions() method. You can iterate over this list and disable them one by one.
for action in menu.actions():
action.setDisabled(True)
Edit: this function will recursively disable menu items, but it will skip sub menus so that user can hover and see them:
def disableMenu(menu):
for action in menu.actions():
if action.menu():
disableMenu(action.menu())
else:
action.setDisabled(True)
You can call this function on a specific menu or menuBar() to disable all menus;
disableMenu(mainWindow.menuBar())
Related
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
I have a ListView full of checkable items. I want to place a tristate "check all" checkbox above the ListView, and I want this checkbox to be bi-directional.
That is, if the user toggles the check all checkbox, I want all of the ListView's items to mirror the check all's selection. But if the user manually checks or unchecks items in the ListView, I want the select all checkbox to reflect that state (i.e. checked if all of the ListView items are checked, unchecked if they are all unchecked, or partially checked if some of the ListView items are checked).
This answer shows how to wire the first part (checking/unchecking the select all box propagates its state to the list view's items). However, I'm stumped as to how wire the other direction.
This is how I'm getting the Check All checkbox to propagate to the ListView:
self.layout = QtGui.QVBoxLayout()
self.select_all_cb = QtGui.QCheckBox('Check All', self.ui.tab)
self.select_all_cb.setChecked(True)
self.select_all_cb.setStyleSheet('margin-left: 5px; font: bold')
self.select_all_cb.stateChanged.connect(self.selectAllCheckChanged)
self.layout.addWidget(select_all_cb)
self.listview = QtGui.QListView(self.ui.tab)
self.listview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.listview.setSelectionRectVisible(False)
model = QStandardItemModel()
for checkItem in self.checkItems:
item = QStandardItem(checkItem)
item.setCheckable(True)
item.setSelectable(False)
item.setCheckState(QtCore.Qt.Checked)
model.appendRow(item)
self.listview.setModel(model)
self.layout.addWidget(listview)
def selectAllCheckChanged(self):
model = self.listview.model()
for index in range(model.rowCount()):
item = model.item(index)
if item.isCheckable():
if self.select_all_cb.isChecked():
item.setCheckState(QtCore.Qt.Checked)
else:
item.setCheckState(QtCore.Qt.Unchecked)
Any suggestions on how to go the other way?
You can connect to the itemChanged signal on the QStandardItemModel and test the state of all the checkboxes.
from itertools import product
self.model.itemChanged.connect(self.test_check)
def test_check(self, item):
items = [self.model.item(r,c) for r, c in product(range(self.model.rowCount()), range(self.model.columnCount())]
if all(item.checkState() == Qt.Checked for item in items)
state = Qt.Checked
elif any(item.checkState() == Qt.Checked for item in items):
state = Qt.PartiallyChecked
else:
state = Qt.Unchecked
if self.select_all_cb.checkState() != state:
self.select_all_cb.setCheckState(state)
If you have a very large number of checkboxes, you may be able to optimize this by caching the check state of each item and updating the cache whenever the item state changes, and then checking the cache instead of pulling it from each item every time.
If you know you're going to be making changes to many items at once, you should probably blockSignals on the model and then run this function manually after making all the changes.
In your selectAllCheckChanged handler, you should also block signals on the model so it doesn't trigger this handler
def selectAllCheckChanged(self):
model = self.listview.model()
model.blockSignals(True)
try:
for index in range(model.rowCount()):
item = model.item(index)
if item.isCheckable():
if self.select_all_cb.isChecked():
item.setCheckState(QtCore.Qt.Checked)
else:
item.setCheckState(QtCore.Qt.Unchecked)
finally:
model.blockSignals(False)
In case it will help others, here's how I incorporated Brendan's answer in my code. The differences are the tristate functionality is enabled only when needed (so the user can't enable the partial-checked state), and I wired it with the clicked signal instead of stateChange to avoid selectAllCheckChanged from being triggered by listviewCheckChanged. The model.blockSignals works too, of course, but using clicked seemed more pythonic to me.
self.layout = QtGui.QVBoxLayout()
self.select_all_cb = QtGui.QCheckBox('Check All', self.ui.tab)
self.select_all_cb.setTristate(False) # Only enable tristate when necessary so the user doesn't click it through to partially checked
self.select_all_cb.setChecked(True)
self.select_all_cb.setStyleSheet('margin-left: 5px; font: bold')
self.select_all_cb.clicked.connect(self.selectAllCheckChanged) # clicked instead of stateChanged so this doesn't get triggered by ListView's changes
self.layout.addWidget(select_all_cb)
self.listview = QtGui.QListView(self.ui.tab)
self.listview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.listview.setSelectionRectVisible(False)
model = QStandardItemModel()
for checkItem in self.checkItems:
item = QStandardItem(checkItem)
item.setCheckable(True)
item.setSelectable(False)
item.setCheckState(QtCore.Qt.Checked)
model.appendRow(item)
self.listview.setModel(model)
self.listview.clicked.connect(self.listviewCheckChanged)
self.layout.addWidget(listview)
def selectAllCheckChanged(self):
''' updates the listview based on select all checkbox '''
model = self.listview.model()
for index in range(model.rowCount()):
item = model.item(index)
if item.isCheckable():
if self.select_all_cb.isChecked():
item.setCheckState(QtCore.Qt.Checked)
else:
item.setCheckState(QtCore.Qt.Unchecked)
def listviewCheckChanged(self):
''' updates the select all checkbox based on the listview '''
model = self.listview.model()
items = [model.item(index) for index in range(model.rowCount())]
if all(item.checkState() == QtCore.Qt.Checked for item in items):
self.select_all_cb.setTristate(False)
self.select_all_cb.setCheckState(QtCore.Qt.Checked)
elif any(item.checkState() == QtCore.Qt.Checked for item in items):
self.select_all_cb.setTristate(True)
self.select_all_cb.setCheckState(QtCore.Qt.PartiallyChecked)
else:
self.select_all_cb.setTristate(False)
self.select_all_cb.setCheckState(QtCore.Qt.Unchecked)
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
i'm trying to build multiple option menus sharing the same "base item list". A multiple selection of one item in different menus should not be possible, so all menus have to be updated when an item is selected in one of the available menus.
from tkinter import *
# for example 5 fields
number_of_fields = 5
starting_list = ["item1","item2","item3","item4","item5"]
entry_list = []
option_list = []
option_var = []
def quit():
raise SystemExit()
# if an item is selected in one of the
# menus run this function
def reset_menu(sel_item):
# for each field
for field in range(number_of_fields):
new_list = []
selection = option_var[field].get()
# look for selected items in all menus
# and build new list which contains all
# items from the starting_list minus the
# items which are already selected
# keep the one selected (for a menu itself)
for option in starting_list:
marker = 0
for j in range(number_of_fields):
if(str(option_var[j].get()) == str(option)):
marker = 1
if(marker == 0):
new_list.append(str(option))
else:
pass
if(str(selection) == str(option)):
new_list.append(str(option))
# print new generated item list
# just to be sure it works so far
print("field",field,"new list=",new_list)
# NOW HERE SOMETHING IS WRONG I GUESS
# empty menu
option_list[field]["menu"].delete(0, "end")
# add new menu items
for item in new_list:
option_list[field]['menu'].add_command(label=item, command=lambda value=item:option_var[field].set(value))
root = Tk()
root.title("OptionMenu")
# menu variable for each field
for i in range(number_of_fields):
option_var.append(StringVar(root))
# initial value for each field
for i in range(number_of_fields):
option_var[i].set("")
# create menu for each field
for i in range(number_of_fields):
option_list.append(OptionMenu(root, option_var[i], *starting_list, command=reset_menu))
# create entry for each field
for i in range(number_of_fields):
entry_list.append(Entry(root))
# build gui
for i in range(number_of_fields):
entry_list[i].grid(row=int(i),column=0,sticky=N+S+W+E)
option_list[i].grid(row=int(i), column=1,sticky=N+S+W+E)
button = Button(root, text="OK", command=quit)
button.grid(row=number_of_fields,column=1,sticky=N+S+W+E)
mainloop()
Now everthing seems to be fine until i try to update the menus. The new menu item lists are generated correctly (see print statement) and the menus have the right items, but after selected one menu, the only menu that changes its selected state is the last one. Any ideas?
Regards Spot
I found your question because I too was trying to complete the same task. After doing a bit of poking around in dir(tkinter), I have found a solution, which you have inspired me to create an account to post.
I have left your original comments in the code for sections that I left unchanged.
First, your code for generating your options is unnecessarily cluttered. Instead of manually populating the list from empty, it seems cleaner to remove items from the full list.
You are currently using tkinter.OptionMenu(). If you instead use tkinter.ttk.OptionMenu(), it has a method called set_menu(*values) that takes any number of values as its arguments and sets the choices of that menu to be those arguments.
If you make the switch, there one thing to note - ttk's OptionMenu does not allow its default value to chosen in the dropdown, so it's recommended to make that value blank, as I have done in the declaration for starting_list.
In order to persist the blank option, I added an additional blank option, in order for it to be selectable. This way, if you mistakenly choose the wrong selection, you can revert your choice.
from tkinter import *
from tkinter.ttk import *
# for example 5 fields
number_of_fields = 5
starting_list = ["","item1","item2","item3","item4","item5"]
entry_list = []
option_list = []
option_var = []
def quit():
raise SystemExit()
# if an item is selected in one of the
# menus run this function
def reset_menu(sel_item):
# for each field
for field in range(number_of_fields):
new_list = [x for x in starting_list]
selection = option_var[field].get()
# look for selected items in all menus
# and build new list which contains all
# items from the starting_list minus the
# items which are already selected
# keep the one selected (for a menu itself)
for option in starting_list[1:6]:
#add selectable blank if option is selected
if (str(selection) == str(option)):
new_list.insert(0,"")
for j in range(number_of_fields):
if(str(selection) != str(option) and str(option_var[j].get()) == str(option)):
new_list.remove(option)
# print new generated item list
# just to be sure it works so far
print("field",field,"new list=",new_list)
#set new options
option_list[field].set_menu(*new_list)
root = Tk()
root.title("OptionMenu")
# menu variable for each field
for i in range(number_of_fields):
option_var.append(StringVar(root))
# initial value for each field
for i in range(number_of_fields):
option_var[i].set("")
# create menu for each field
for i in range(number_of_fields):
option_list.append(OptionMenu(root, option_var[i], *starting_list, command=reset_menu))
# create entry for each field
for i in range(number_of_fields):
entry_list.append(Entry(root))
# build gui
for i in range(number_of_fields):
entry_list[i].grid(row=int(i),column=0,sticky=N+S+W+E)
option_list[i].grid(row=int(i), column=1,sticky=N+S+W+E)
button = Button(root, text="OK", command=quit)
button.grid(row=number_of_fields,column=1,sticky=N+S+W+E)
mainloop()
Something you may want to look into is making your option generation a bit more efficient. Right now, for n options, you're looping through your menus n^2 times. I would suggest looking at passing the value that was just selected in the callback instead of searching each menu to see what was previously selected.
As an additional minor note, your "OK" button causes a crash. I'm not sure if that was intentional behavior, a quirk in my system, or something else.
I hope this helps!
its been a while and ive found a possible solution for my problem...here is the code:
from tkinter import *
from tkinter import _setit
# for example 5 fields
number_of_fields = 5
starting_list = ["choose","item1","item2","item3","item4","item5"]
entry_list = []
option_list = []
option_var = []
def quit():
raise SystemExit()
# print entry_field text and selected option_menu item
def output():
print("---------------------------------------")
for nr,item in enumerate(entry_list):
if(item.get() != ""):
print(item.get() + " --> " + option_var[nr].get())
print("---------------------------------------")
# if an item is selected in one of the
# menus run this function
def reset_menu(*some_args):
for field in range(number_of_fields):
new_list = []
selection = option_var[field].get()
for option in starting_list[1:]:
marker = 0
for j in range(number_of_fields):
if(str(option_var[j].get()) == "choose"):
continue
if(str(option_var[j].get()) == str(option)):
marker = 1
if(marker == 0):
new_list.append(str(option))
else:
pass
if(str(selection) == str(option)):
new_list.append(str(option))
option_list[field]["menu"].delete(0, "end")
option_list[field]["menu"].insert(0, "command", label="choose", command=_setit(option_var[field], "choose"))
# add new menu items
for i in range(len(new_list)):
option_list[field]["menu"].insert(i+1, "command", label=new_list[i], command=_setit(option_var[field], new_list[i]))
root = Tk()
root.title("OptionMenu")
# menu variable for each field
for i in range(number_of_fields):
option_var.append(StringVar(root))
# initial value for each field
for i in range(number_of_fields):
# set "choose" as default value
option_var[i].set("choose")
# trace each variable and call "reset_menu" function
# if variable change
option_var[i].trace("w", reset_menu)
# create menu for each field
for i in range(number_of_fields):
option_list.append(OptionMenu(root, option_var[i], *starting_list))
# create entry for each field
for i in range(number_of_fields):
entry_list.append(Entry(root))
entry_list[i].insert(0, "entry"+str(i))
# build gui
for i in range(number_of_fields):
entry_list[i].grid(row=int(i), column=0, sticky=N+S+W+E)
option_list[i].grid(row=int(i), column=1, sticky=N+S+W+E)
button1 = Button(root, text="OK", command=quit)
button2 = Button(root, text="PRINT", command=output)
button1.grid(row=number_of_fields, column=0, sticky=N+S+W+E)
button2.grid(row=number_of_fields, column=1, sticky=N+S+W+E)
mainloop()
This solution also runs under python 2.7, just change "from tkinter ..." to "from Tkinter ...".
Please take a look at the smarter solution sephirothrr has posted (see post above)!
Regards
Spot
I have a function that creates prompts using gtk.MessageDialog in PyGTK. How could I access the predefined buttons? Or would I need to manually construct a gtk.Dialog? I'd rather not, seeing as MessageDialog is a convenience function.
The function:
def gtkPrompt(self, name):
# Create new GTK dialog with all the fixings
prompt = gtk.MessageDialog(None, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, name)
# Set title of dialog
prompt.set_title("Prompt")
# Create and add entry box to dialog
entry = gtk.Entry()
prompt.vbox.add(entry)
# Show all widgets in prompt
prompt.show_all()
# Run dialog until user clicks OK or Cancel
if prompt.run() == gtk.RESPONSE_CANCEL:
# User cancelled dialog
rval = False
else:
# User clicked OK, grab text from entry box
rval = entry.get_text()
# Destory prompt
prompt.destroy()
# Give the good (or bad) news
return rval
You can use get_children() to get to the "OK" button:
def yesNoDialog(window, message, default=False):
dialog=gtk.MessageDialog(window, gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION,
gtk.BUTTONS_YES_NO, message)
if default:
h_button_box=dialog.vbox.get_children()[1]
yes_button=h_button_box.get_children()[0]
yes_button.grab_default()
response=dialog.run()
dialog.destroy()
if response==gtk.RESPONSE_YES:
return True
else:
return False
Since 2.22 you can use get_widget_for_response() method. For example:
cancelButton = dialog.get_widget_for_response(response_id=gtk.RESPONSE_CANCEL)
gtk.MessageDialog is a subclass of gtk.Dialog. gtk.Dialog objects store their buttons in a gtk.HBox under the action_area attribute.
In code:
> prompt.action_area.get_children()
[<gtk.Button object at 0x18c0aa0 (GtkButton at 0x130e990)>, <gtk.Button object at 0x18c0af0 (GtkButton at 0x130e8d0)>]