Change the parent of QActions with... signals and slots? - python

I have a bunch of QActions, some of them are (for the moment) in two submenus and main menu.
As you can see in the code below, actions are created without parent None because the actions are shared to the buttons with a same menu.
That slot on_connect should create an instance of a class Wire.
The only thing that stops me to create an instance of wire class is jack_connector, that it should be the button which was pressed and shown the menu. The other parameters are okay, that's the only that I care for the moment.
I figure out that I can obtain the value what I need with self.sender().parent().objectName()
but at this point the QActions have parent set None , so that's why i need to set the button who shows the menu as the parent at runtime.
I already know that can be done with .setParent() method, but I don't know how to do that to all the actions, during the button press event.
Here it is the most relevant code:
scene = MyScene()
menu = QMenu()
widget_container = QWidget()
#dictonaries for dragbuttons (used later for connecting them)
jacks_dic = {}
inputs_dic = collections.OrderedDict()
wire_dic = {}
...
#pyqtSlot(str)
def on_connect(self, input):
print 'connected'
jack_connector = self.sender().parent().objectName() #sender's parent of QAction should be the button
wire_dic['wire_1'] = Wire( jack_connector , widget_container.findChild( DragButton, 'btn_' + input ) , None, scene)
#Load Menu options for Jacks dragbuttons
#create sub-menus
submenus_dic = collections.OrderedDict()
submenus_dic['AIF1TX1_submenu'] = QMenu("AIF1TX1 (L) Record to Device")
submenus_dic['AIF1TX2_submenu'] = QMenu("AIF1TX2 (R) Record to Device")
actions_dic = collections.OrderedDict()
for input in inputs_dic:
#Create an Action
actions_dic[ input ] = QtGui.QAction( input, None)
#TODO: Find a way to set parent a QAction after click
#TODO: Research how to connect every action to a slot()
actions_dic[ input ].triggered[()].connect( lambda input=input: on_connect(actions_dic[ input ], input) )
#Condition to add to a submenu
if input[:-2] == 'AIF1TX1' :
submenus_dic['AIF1TX1_submenu'].addAction( actions_dic[ input ] )
if input[:-2] == 'AIF1TX2' :
submenus_dic['AIF1TX2_submenu'].addAction( actions_dic[ input ] )
#Add SubMenus to Main Menu
for submenu in submenus_dic:
menu.addMenu(submenus_dic[ submenu ] )

The problem is that the one action is being triggered and you are trying to find out what menu was open and activated the action. There are several approaches that you can take.
You might want to explore the QAction's menu(), parentWidget() and associatedWidgets() methods with the QMenu's activeAction() method. You could also check what menu is visible.
QMenus create a widget representing the action when you add an action to the menu. So you could manually create these widgets and create a separate set of triggers to pass another parmeter into your function.
It really sounds like you need separate class.
class MyMenu(QMenu):
newWire = QtCore.Signal(str)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.wire_dic = collections.OrderedDictionary()
self.action1 = QAction("text")
self.action1.triggered.connect(self.action1method)
self.addAction(self.action1)
# end Constructor
def action1method(self):
# Maybe use self.windowTitle()
self.wire_dic['wire_1'] = Wire(self.title(), widget_container.findChild( DragButton, 'btn_' + input ) , None, scene)
self.newWire.emit("wire_1")
# end action1method
# end class MyMenu
m1 = MyMenu("Menu1")
m2 = MyMenu("Menu2")
menubar.addMenu(m1)
menubar.addMenu(m2)
That way each object is associated with it's own action without you manually creating and managing a bunch of duplicate actions. This should be a lot easier to manage and the actions will all operate the same way.

Related

Panel in Python - How to set the order that events are called

I am building a dashboard using panel and trying to figure out how to have a change of a control ("threshold" in the below class) fire a process that updates an attribute of the class before any other functions are called that will use that attribute. Basically, a change in the threshold widget should change an attribute self.table and then more than 1 functions will reference it to create tables and plots for the dashboard. How to make this happen? This is the start of the class where the widgets are declared and the class initialized....
class BinaryPerformDashComponents(param.Parameterized):
bins = param.ObjectSelector(default=10, objects=[], label='Number of Bins')
threshold = param.Number(default=0.5, step=0.01, bounds=(0, 1), allow_None=False)
def __init__(self, actual, pred, df, *args, **kwargs):
super(type(self), self).__init__(*args, **kwargs)
self.param.bins.objects =[5,10,20,50,100] # set the list of objects to select from in the widget
self.df = self.create_df(actual,pred,df)
Here's an example where a change in a parameter threshold, changes the value of a boolean, and because that boolean changes, other updates get triggered after that:
import param
import panel as pn
pn.extension()
class BinaryPerformDashComponents(param.Parameterized):
bins = param.ObjectSelector(default=10, objects=[5,10,20,50,100], label='Number of Bins')
threshold = param.Number(default=0.5, step=0.01, bounds=(0, 1))
boolean_ = param.Boolean(True)
#param.depends('threshold', watch=True)
def _update_boolean(self):
self.boolean_ = not self.boolean_
#param.depends('boolean_', watch=True)
def _update_bins(self):
self.bins = 20
instance = BinaryPerformDashComponents()
pn.Row(instance)
Here's some other questions + answers using the same mechanism:
Use button to trigger action in Panel with Parameterized Class and when button action is finished have another dependency updated (Holoviz)
How do i automatically update a dropdown selection widget when another selection widget is changed? (Python panel pyviz)

Set SingleSelection in QListView

I am writing an application in PySide2 and I have developed a class inheriting from Qdialog to display a list with checkboxes:
The code of the class:
class ListDialog(QDialog):
def __init__(self, items, all_checked = False, parent=None):
super(ListDialog, self).__init__(parent=parent)
self.setWindowTitle(title)
form = QFormLayout(self)
self.listView = QListView(self)
self.listView.setSelectionMode(QTableView.NoSelection)
form.addRow(self.listView)
self.model = QStandardItemModel(self.listView)
for item in items:
# create an item with a caption
standardItem = QStandardItem(item)
standardItem.setCheckable(True)
standardItem.setEditable(False)
if all_checked:
standardItem.setCheckState(Qt.Checked)
self.model.appendRow(standardItem)
self.listView.setModel(self.model)
The result (plus some extra code):
As it is, you can check multiple checkboxes, but I need to make it a single selection.
Note the line:
self.listView.setSelectionMode(QTableView.NoSelection)
At first, I thought setSelectionMode was responsible for this behaviour but this controls only the highlighting on the items of the list and not its checkboxes. Therefore I set it to NoSelection for not highlighting the text part, the checkboxes are working!
Is there an easy way to set the selection mode to single? Or should I overload the signal that controls the box checking to unselect all the boxes and then select the one I clicked?
An easy way to do that it to use a proxy model which will handle the single selection and the signal QStandardItemModel::itemChanged to know when user clicks on an item.
For example:
class SingleCheckProxyModel(QIdentityProxyModel):
def __init__(self, model, parent=None):
super().__init__(parent)
model.itemChanged.connect(self.checkSingleCheck)
self.setSourceModel(model)
self.currentItemChecked = None
def checkSingleCheck(self, item):
if self.currentItemChecked:
self.currentItemChecked.setCheckState(Qt.Unchecked)
if item.checkState(): # Allows the user to uncheck then check the same item
self.currentItemChecked = item
else:
self.currentItemChecked = None
class ListDialog(QDialog):
def __init__(self, items, all_checked = False, parent=None):
super(ListDialog, self).__init__(parent=parent)
self.setWindowTitle("kjnve")
form = QFormLayout(self)
self.listView = QListView(self)
self.listView.setSelectionMode(QTableView.NoSelection)
form.addRow(self.listView)
self.model = QStandardItemModel(self.listView)
for item in items:
# create an item with a caption
standardItem = QStandardItem(item)
standardItem.setCheckable(True)
standardItem.setEditable(False)
if all_checked:
standardItem.setCheckState(Qt.Checked)
self.model.appendRow(standardItem)
self.listView.setModel(SingleCheckProxyModel(self.model)) # Use proxy
The checkSingleCheck method will be called when the user clicks on an item. But, if you want to be able to edit the items, you have to adapt this function.

Reference an object from within the object

So I am making a menu structure. It goes as follows: there is a menu class which has a label, a list of tuples which are its items and a 'previous menu'.
So my idea is that when in Main, previous will be None. The items in the menus are either functions or another menu. (A function can also be something that generates a menu) When you then select option 1 I want to set the 'previous' parameter in the option_1_menu to main_menu
class Menu:
def __init__(self, label, options):
self.label = label
self.options = options
self.previous = None
def set_previous(self,previous_menu):
self.previous = previous_menu
def evaluate(self, choice):
if isinstance(self.options[int(choice) - 1][1],Menu):
print('this is a menu')
# Set the previous menu to this menu
self.options[int(choice) - 1][1].set_previous(self)
# Return the new menu
return self.options[int(choice) - 1][1]
# t_current_menu(self.options[int(choice) - 1][1])
else:
print('This is a function')
self.options[int(choice) - 1][1]()
So my question is about the line where I set the previous menu to this menu. I basically set the 'previous' one to the one I am currently in. Can I reference to self in that way I do there to accomplish this?
Note: the 'this is a function' part is still very much WiP
Sure, self is a perfectly valid "reference to a Menu instance" so there is no problem calling someothermenu.set_previous(self). Adding a unit test as #Nsh suggests is also never a bad idea (for this or any other functionality:-).

(no longer seeking answers) how to reset choices of a combobox in real time using wxpython?

So I'm trying to make a relatively simple TTS program using the speech module. The place I'm getting stuck at is: I want to make a list of saved text available through a combobox in the window, and if you add or delete any presets from it, it won't update the choices until the program is reloaded. Is there a way to update the choices in real time?
The combobox initializes like this:
#I have a previously set txt file with a list of presets, and its partial destination saved in a variable "savefile"
fh = open(savefile + '//presets.txt')
presets = fh.read().split('\n')
self.presetList = presets
#self.presetList is now the list of choices for the combobox, causing it to update upon loading the program
self.presetbox = wx.ComboBox(self, pos=(90, 100), size=(293, -1), choices=self.presetList, style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.presetbox)
and later on, say, to clear all choices, I would need something like this:
self.emptyList = []
self.presetbox.SetChoices(self.emptyList)
Is there a way to do this? If so that'd be great! :)
ComboBox is a mix of TextCtrl and ListBox (http://wxpython-users.1045709.n5.nabble.com/Question-on-wx-ComboBox-Clear-td2353059.html)
To reset the field, you can use self.presetbox.SetValue('')
(in case your ComboBox is not READONLY)
I'm referencing the C++ WxWidgets documentation instead of the wxPython docs, because they are usually better :), but the names should be the same.
wxComboBox derives from wxItemContainer, which has a series of functions simply called Set which you can pass in a list of strings to set the combo box contents. There are also Append and Insert functions you can use, as well as a Clear function that clears all the choices.
wxItemContainer docs are here: http://docs.wxwidgets.org/trunk/classwx_item_container.html
This worked for me, binding the EVT_COMBOBOX_DROPDOWN to update the list, than bind EVT_COMBOBOX to run a function on user's choice. That way updates, then shows the contents updated. If new choices are less, it doesn't leave the white space of the previous ones. Also you can set the combobox as READONLY to behave as a choice widget (not editable by user) as below. This code was tested under Windows, Python 3.6 and the newer wxpython (phoenix).
import wx
class Mywin(wx.Frame):
def __init__(self, parent, title):
super(Mywin, self).__init__(parent, title = title,size = (300,200))
panel = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
self.label = wx.StaticText(panel,label = "Your choice:" ,style = wx.ALIGN_CENTRE)
box.Add(self.label, 0 , wx.EXPAND |wx.ALIGN_CENTER_HORIZONTAL |wx.ALL, 20)
cblbl = wx.StaticText(panel,label = "Combo box",style = wx.ALIGN_CENTRE)
box.Add(cblbl,0,wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,5)
# initial chioces are 5
#------------------------------------------------------
languages = ['C', 'C++', 'Python', 'Java', 'Perl']
#------------------------------------------------------
self.combo = wx.ComboBox(panel,choices = languages, style = wx.CB_READONLY)
box.Add(self.combo,0,wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,5)
self.combo.Bind(wx.EVT_COMBOBOX ,self.OnCombo)
self.combo.Bind(wx.EVT_COMBOBOX_DROPDOWN,self.updatelist)
panel.SetSizer(box)
self.Centre()
self.Show()
def OnCombo(self, event):
self.label.SetLabel("You selected "+self.combo.GetValue()+" from Combobox")
def updatelist(self, event):
# Chioces are now just 2
#------------------------------------------------------
OtrosLanguajes = ['C++', 'Python']
#------------------------------------------------------
self.combo.SetItems(OtrosLanguajes)
app = wx.App()
Mywin(None, 'ComboBox and Choice demo')
app.MainLoop()
The parent class of wx.ComboBox is ItemContainer.
It has the method Clear().
Use the Clear() method on the wx.ComboBox object to clear all data.

PySide One slot to multiple widgets

I am creating a small app in python using PySide. I read lines from a text file and display each line in a separate QLineEdit Widget. Each "entry" has 2 line edits and 2 QPushButtons. For every line I add those widgets. My problem is that I set a signal-slot for the QPushButtons, but when all the "entries" are generated, only the last entries QPushButtons connects to the slot. May someone please help me.
Here is my code
class ItemLogger(QtGui.QMainWindow, Ui.Ui_MainWindow):
def __init__(self, parent = None):
super(ItemLogger, self).__init__(parent)
self.setupUi(self)
self.parseBossItem()
self.comboBox.currentIndexChanged.connect(self.parseBossItem)
self.increase.clicked.connect(self.add_subtract)
def add_subtract(self):
initial = 1
print "kajskasdjflsdkjflk"
def addRow(self, item):
self.frame = QtGui.QFrame()
self.layout = QtGui.QHBoxLayout()
self.itemName = QtGui.QLineEdit(item)
self.itemName.setReadOnly(True)
self.itemCount = QtGui.QLineEdit()
self.itemCount.setText("0")
self.itemCount.setMaximumWidth(40)
self.decrease = QtGui.QPushButton("-")
self.increase = QtGui.QPushButton("+")
self.layout.addWidget(self.itemName)
self.layout.addWidget(self.itemCount)
self.layout.addWidget(self.decrease)
self.layout.addWidget(self.increase)
self.frame.setLayout(self.layout)
self.verticalLayout_3.addWidget(self.frame)
def parseBossItem(self):
if self.comboBox.currentText() == "Item_1":
item_list = open("BossItems/Random_Item")
for line in item_list.readlines():
self.addRow(line)
if self.comboBox.currentText() == "Item_2":
item_list = open("BossItems/Random_Item_2")
for line in item_list.readlines():
self.addRow(line)
This is because you only connected the last entry.
Here is what you are actually doing:
You add row for item 1, and assign button widgets to self.decrease, self.increase.
You add row for item 2, replacing values of self.decrease, self.increase by newly created widgets.
You connect self.increase, which is now the last added widget.
If you don't need access to you widgets after their creation, you should consider using local variables (e.g. without self) and connecting the signal inside the addRow function.
If you need to keep track of widget references, then you could add them to an array:
# Somewhere in __init__ or in parseBossItem
self.increase = []
# in addRow
self.increase.append(QtGui.QPushButton("+"))
self.layout.addWidget(self.increase[-1])
self.increase[-1].clicked.connect(self.add_subtract)
# and so on...
To use the same slot form different senders, you need to identify who sent the signal. You could do something like this:
def onIncrease(self):
button = self.sender()
if isinstance(button, QtGui.QPushButton):
buttonName = button.text()
if buttonName == 'name of button 1':
self.itemCount[0].setText(str(int(self.itemCount[0])+1))
elif buttonName == 'name of button 2':
...
Off course, this is assuming you put each QLineEdit in the array self.itemCount.
Since all your buttons have the same name, we need to use another approach.
# in addRow
self.increase.clicked.connect(lambda: self.onIncrease(itemCount))
def onIncrease(self, edit):
edit.setText(str(int(edit.text()+1))

Categories