Dup Ver Goto 📝

Simple Number Spinner 1

To
167 lines, 668 words, 6339 chars Page 'SimpleNumberSpinner_1' does not exist.

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.