chmedly
chmedly

Reputation: 451

pyqt5 How to connect shortcut to child widget

I have a QtableWidget populated with forms (imported form). Each form is basically a Qgroupbox that contains a couple pushbuttons and a spinbox. I chose to put these in the table so that the arrow keys can be used to navigate between groupboxes. But I want each pushbutton to be checkable via a keyboard shortcut AND the spinbox to be adjusted via some kind of keyboard shortcut as well (perhaps shift plus arrow). I have the navigation mostly working but the shortcuts that I have assigned to the pushbuttons don't work. It seems that I need a way to "pass" the shortcut from the groupbox that has focus to the target widget inside that groupbox. The second issue of adjusting the spinboxes appears to be related but I'm even less clear about how to go about setting this up.

I have attached most of the code (generated with QTCtreator) for the form. It's interesting to me that the shortcut code is in the retranslate method.

image of single form

image of grid of forms

class Ui_FormLoads(object):
    def setupUi(self, FormLoads):
        FormLoads.setObjectName("FormLoads")
        FormLoads.resize(88, 105)
        FormLoads.setFocusPolicy(QtCore.Qt.TabFocus)
        FormLoads.setWindowTitle("LoadChannel")
        self.gridLayout = QtWidgets.QGridLayout(FormLoads)
        self.gridLayout.setContentsMargins(8, 8, 8, 8)
        self.gridLayout.setSpacing(0)
        self.gridLayout.setObjectName("gridLayout")
        self.groupBox = QtWidgets.QGroupBox(FormLoads)
        font = QtGui.QFont()
        font.setPointSize(10)
        self.groupBox.setFont(font)
        self.groupBox.setObjectName("groupBox")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox)
        self.gridLayout_2.setContentsMargins(8, 8, 8, 8)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.pushStop = QtWidgets.QPushButton(self.groupBox)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.pushStop.sizePolicy().hasHeightForWidth())
        self.pushStop.setSizePolicy(sizePolicy)
        self.pushStop.setMinimumSize(QtCore.QSize(30, 0))
        font = QtGui.QFont()
        font.setPointSize(10)
        self.pushStop.setFont(font)
        self.pushStop.setText("Stop")
        self.pushStop.setCheckable(True)
        self.pushStop.setObjectName("pushStop")
        self.gridLayout_2.addWidget(self.pushStop, 1, 0, 1, 1)
        self.pushReload = QtWidgets.QPushButton(self.groupBox)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.pushReload.sizePolicy().hasHeightForWidth())
        self.pushReload.setSizePolicy(sizePolicy)
        self.pushReload.setMinimumSize(QtCore.QSize(30, 0))
        font = QtGui.QFont()
        font.setPointSize(10)
        self.pushReload.setFont(font)
        self.pushReload.setText("Load")
        self.pushReload.setCheckable(True)
        self.pushReload.setObjectName("pushReload")
        self.gridLayout_2.addWidget(self.pushReload, 1, 1, 1, 1)
        self.spinBox = QtWidgets.QSpinBox(self.groupBox)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.spinBox.sizePolicy().hasHeightForWidth())
        self.spinBox.setSizePolicy(sizePolicy)
        self.spinBox.setMinimumSize(QtCore.QSize(51, 0))
        font = QtGui.QFont()
        font.setPointSize(13)
        self.spinBox.setFont(font)
        self.spinBox.setMinimum(-20)
        self.spinBox.setMaximum(12)
        self.spinBox.setObjectName("spinBox")
        self.gridLayout_2.addWidget(self.spinBox, 0, 0, 1, 2)
        self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)

        self.retranslateUi(FormLoads)
        QtCore.QMetaObject.connectSlotsByName(FormLoads)

    def retranslateUi(self, FormLoads):
        _translate = QtCore.QCoreApplication.translate
        self.groupBox.setTitle(_translate("FormLoads", "GroupBox"))
        self.pushStop.setShortcut(_translate("FormLoads", "S"))
        self.pushReload.setShortcut(_translate("FormLoads", "L"))

Upvotes: 0

Views: 2141

Answers (1)

chmedly
chmedly

Reputation: 451

Well, I figured out how to do this. [btw formLoad is the variable name I'm using for the forms when I iterate through to populate the table. actionStop is the variable name I'm using for this particular shortcut.]

self.formLoad.actionStop = QtWidgets.QShortcut(QtGui.QKeySequence("S"), self.formLoad)
self.formLoad.actionStop.activated.connect(self.formLoad.ui.pushStop.click)
self.formLoad.actionStop.setContext(QtCore.Qt.WidgetWithChildrenShortcut)

A) First was understanding the scope and focus. I ended up setting the most senior widget (for the form) to strongfocus and everything else except the spinbox to nofocus. The spinbox is set to wheelfocus which allows it to be wheeled or typed. The focus settings can be set in QtCreator/Designer which means there is less that you have to add in your main py file. Keeping focus off the buttons keeps the key command from shifting focus to an individual button which I suspect may be problematic on a Mac depending on if you have keyboard shortcuts set to "all" in system preferences. Then, setting the context attribute to 'WidgetWithChildrenShortcut' allows the keycommand to filter down from the top widget to the pushbutton. [btw, There doesn't appear to be a way to define context using the shortcuts that you can set in QtCreator so I instead defined the shortcuts explicitly.] When I created the actionStop shortcut you can see that I assigned it to formLoad as it's parent. This assigns the shortcut to the widget that is set for strongfocus.

self.formLoad.actionIncrement = QtWidgets.QShortcut(QtGui.QKeySequence("Alt+Up"), self.formLoad)
self.formLoad.actionIncrement.activated.connect(self.formLoad.ui.spinBox.stepUp)
self.formLoad.actionIncrement.setContext(QtCore.Qt.WidgetWithChildrenShortcut)
self.formLoad.actionDecrement = QtWidgets.QShortcut(QtGui.QKeySequence("Alt+Down"), self.formLoad)
self.formLoad.actionDecrement.activated.connect(self.formLoad.ui.spinBox.stepDown)
self.formLoad.actionDecrement.setContext(QtCore.Qt.WidgetWithChildrenShortcut)

B) Secondly I had to figure out which attributes (and their names) that can be "triggered" by a shortcut. These aren't clearly documented on the main QT page for QPushButton or QSpinBox. I found the "click" attribute in some example code online and I found the up and down button attributes in the QT documentation for the QAbstractSpinBox. 'stepUp' and 'stepDown' are equivalent to clicking the up or down buttons of the spinbox. In this case I used the Alt modifier key with the up and down arrows. Somewhere in the Qt documentation is the list of key commands with their string titles ("Alt+Up" etc).

    self.formLoad.actionZero = QtWidgets.QShortcut(QtGui.QKeySequence("0"), self.formLoad)
    self.formLoad.actionZero.setContext(QtCore.Qt.WidgetWithChildrenShortcut)
    self.formLoad.actionZero.activated.connect(functools.partial(self.spinboxset, 0, self.formLoad.ui.spinBox))

@QtCore.pyqtSlot()
def spinboxset(self, key, spInBox):
    spInBox.setValue(key)

C) Lastly, I decided to implement some key commands that directly load a specific value into the spinbox. This was trickier because you can't use the 'setValue' function directly from the activated.connect line. I tried some variations on lambda but importing functools and using partial with a separate function is what worked. The last element that is being passed by partial is the spinbox that was a subwidget of the cell that was being populated. This way, the method knows which spinbox to apply the value to. Without sending this, it will always apply the value to the last 'formLoad.spinBox' that had been defined with that name. The second element being passed (called key) is the value. This allows the one method (spinboxset) to be used for multiple key commands. For instance, another one can be set for "5".

self.formLoad.actionFive = QtWidgets.QShortcut(QtGui.QKeySequence("5"), self.formLoad)
self.formLoad.actionFive.setContext(QtCore.Qt.WidgetWithChildrenShortcut)
self.formLoad.actionFive.activated.connect(functools.partial(self.spinboxset, 5, self.formLoad.ui.spinBox))

Now, if someone can show me how to copy and paste all the settings of a cell between cells I'll be set...

Upvotes: 1

Related Questions