A warning: adding signals and slots to this was my first experience of why you shouldn't
do the lazy from X import * thing: it causes a very obscure error when you try to connect.
In particular, if I put the NumberSpinner class below in its own file number_spinner.py
in which I do the import *, and then in another file I also do from PySide6.... import *
and then import number_spinner, then trying to connect the number spinner to something
causes an exception, which doesn't happen with the NumberSpinner class is declared in
the same file. If instead we do the proper thing of not import *ing, the problem goes away.
That said, I failed to reproduce the exception when I went back to check the above, so that might be the problem, or it might be something else. For reference, the exception looked like:
shibokensupport/signature/parser.py:270: RuntimeWarning: pyside_type_init:_resolve_value
UNRECOGNIZED: 'PySide6.QtCore.QMetaObject.Connection'
OFFENDING LINE: '6:PySide6.QtCore.QObject.connect(self,sender:PySide6.QtCore.QObject,signal:char*,member:char*,type:PySide6.QtCore.Qt.ConnectionType=Qt.AutoConnection)->PySide6.QtCore.QMetaObject.Connection'
...
Traceback (most recent call last):
File "/home/john/qt/widgets/signals/a.py", line 42, in <module>
a = A()
^^^
File "/home/john/qt/widgets/signals/a.py", line 35, in __init__
self.spinner.boing.connect(self.spinnerChanged)
TypeError: 'PySide6.QtCore.QObject.connect' called with wrong argument types:
PySide6.QtCore.QObject.connect(NumberSpinner, str, method)
Supported signatures:
PySide6.QtCore.QObject.connect(PySide6.QtCore.QObject, Union[bytes, bytearray, memoryview], Union[bytes, bytearray, memoryview], PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection))
PySide6.QtCore.QObject.connect(Union[bytes, bytearray, memoryview], Callable, PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection))
PySide6.QtCore.QObject.connect(Union[bytes, bytearray, memoryview], PySide6.QtCore.QObject, Union[bytes, bytearray, memoryview], PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection))
PySide6.QtCore.QObject.connect(PySide6.QtCore.QObject, PySide6.QtCore.QMetaMethod, PySide6.QtCore.QObject, PySide6.QtCore.QMetaMethod, PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection))
PySide6.QtCore.QObject.connect(PySide6.QtCore.QObject, Union[bytes, bytearray, memoryview], PySide6.QtCore.QObject, Callable, PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection))
PySide6.QtCore.QObject.connect(PySide6.QtCore.QObject, Union[bytes, bytearray, memoryview], Callable, PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection))
PySide6.QtCore.QObject.connect(PySide6.QtCore.QObject, Union[bytes, bytearray, memoryview], PySide6.QtCore.QObject, Union[bytes, bytearray, memoryview], PySide6.QtCore.Qt.ConnectionType = Instance(Qt.AutoConnection))
Thus, instead of this:
#!/usr/bin/env python
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtNetwork import *
app = QApplication()
# this allows us to remove keyword arguments that exist that we are interested in
# and return defaults for those not specified, so that the resulting dictionary
# can be passed to super() with those keyword arguments removed.
def gd(dictionary,key,default,delete=True):
if key in dictionary:
v = dictionary[key]
if delete:
del(dictionary[key])
return v
else:
return default
class NumberSpinner(QLabel):
def __init__(self,*xs,**kw):
gran = int(gd(kw,"gran",10))
gran = max(1,gran)
vmax = int(gd(kw,"vmax",100))
vmin = int(gd(kw,"vmin",0))
value = int(gd(kw,"init",vmin))
if vmax <= vmin:
raise ValueError(f"Invalid range {vmin}..{vmax}")
roll = int(gd(kw,"roll",False))
super().__init__(*xs,**kw)
self.value = value
self.gran = gran
self.vmax = vmax
self.vmin = vmin
self.roll = roll
self.drag = False
self.x0 = 0
self.y0 = 0
self.v0 = 0
self.update()
def update(self):
self.setText(str(self.value))
return super().update()
def mousePressEvent(self,e):
pos = e.position()
self.x0 = pos.x()
self.y0 = pos.y()
self.v0 = self.value
def mouseMoveEvent(self,e):
pos = e.position()
x = pos.x()
y = pos.y()
dy = y - self.y0
dv = int(-dy/self.gran)
v = self.v0 + dv
vmx = self.vmax + 1 # so that we can attain the maximum bound
if self.roll:
v = self.vmin + ( (v - self.vmin) % (vmx - self.vmin) )
else:
v = max(self.vmin,min(self.vmax,v))
self.value = v
self.update()
ns = NumberSpinner(vmin=1,vmax=16,roll=False)
ns.resize(100,50)
ns.setStyleSheet("font-size: 24px")
ns.setAlignment(Qt.AlignCenter)
ns.show()
app.exec()
if we want a reusable spinner widget, do this
import PySide6.QtWidgets
import PySide6.QtGui
import PySide6.QtCore
from jdautil import gd
class NumberSpinner(PySide6.QtWidgets.QLabel):
changed = PySide6.QtCore.Signal(int)
def __init__(self,*xs,**kw):
gran = int(gd(kw,"gran",10))
gran = max(1,gran)
vmax = int(gd(kw,"vmax",100))
vmin = int(gd(kw,"vmin",0))
value = int(gd(kw,"init",vmin))
if vmax <= vmin:
raise ValueError(f"Invalid range {vmin}..{vmax}")
roll = int(gd(kw,"roll",False))
super().__init__(*xs,**kw)
self.value = value
self.gran = gran
self.vmax = vmax
self.vmin = vmin
self.roll = roll
self.drag = False
self.x0 = 0
self.y0 = 0
self.v0 = 0
self.update()
def update(self):
self.setText(str(self.value))
return super().update()
def mousePressEvent(self,e):
pos = e.position()
self.x0 = pos.x()
self.y0 = pos.y()
self.v0 = self.value
def mouseMoveEvent(self,e):
pos = e.position()
x = pos.x()
y = pos.y()
dy = y - self.y0
dv = int(-dy/self.gran)
v = self.v0 + dv
vmx = self.vmax + 1 # so that we can attain the maximum bound
if self.roll:
v = self.vmin + ( (v - self.vmin) % (vmx - self.vmin) )
else:
v = max(self.vmin,min(self.vmax,v))
self.value = v
self.changed.emit(v)
self.update()
It's probably also a good idea not to be lazy and terse with variable names, but old habits die hard.