SignalProxy: Correct initialization without slot argument and tests (#1392)
* SignalProxy: Correct initialization without slot argument and provide tests * Add missing slot is None case on disconnect * Start new tests * Exception block * Test no module * Different signal * Debugging the signal connect * Re initialize proxy after disconnect * Add more test cases for blockSignal * Change test case for signal count * Give up for python 2 and pyside * Exclude for Python 2.7 and PySide * Convert float to integers in timer start period
This commit is contained in:
parent
4946a57987
commit
325a15a6ef
@ -1,23 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import weakref
|
||||
|
||||
from .Qt import QtCore
|
||||
from .ptime import time
|
||||
from . import ThreadsafeTimer
|
||||
import weakref
|
||||
from .functions import SignalBlock
|
||||
|
||||
__all__ = ['SignalProxy']
|
||||
|
||||
|
||||
class SignalProxy(QtCore.QObject):
|
||||
"""Object which collects rapid-fire signals and condenses them
|
||||
into a single signal or a rate-limited stream of signals.
|
||||
Used, for example, to prevent a SpinBox from generating multiple
|
||||
signals when the mouse wheel is rolled over it.
|
||||
|
||||
Emits sigDelayed after input signals have stopped for a certain period of time.
|
||||
Emits sigDelayed after input signals have stopped for a certain period of
|
||||
time.
|
||||
"""
|
||||
|
||||
|
||||
sigDelayed = QtCore.Signal(object)
|
||||
|
||||
|
||||
def __init__(self, signal, delay=0.3, rateLimit=0, slot=None):
|
||||
"""Initialization arguments:
|
||||
signal - a bound Signal or pyqtSignal instance
|
||||
@ -26,32 +29,36 @@ class SignalProxy(QtCore.QObject):
|
||||
rateLimit - (signals/sec) if greater than 0, this allows signals to stream out at a
|
||||
steady rate while they are being received.
|
||||
"""
|
||||
|
||||
|
||||
QtCore.QObject.__init__(self)
|
||||
signal.connect(self.signalReceived)
|
||||
self.signal = signal
|
||||
self.delay = delay
|
||||
self.rateLimit = rateLimit
|
||||
self.args = None
|
||||
self.timer = ThreadsafeTimer.ThreadsafeTimer()
|
||||
self.timer.timeout.connect(self.flush)
|
||||
self.blockSignal = False
|
||||
self.slot = weakref.ref(slot)
|
||||
self.lastFlushTime = None
|
||||
self.signal = signal
|
||||
self.signal.connect(self.signalReceived)
|
||||
if slot is not None:
|
||||
self.blockSignal = False
|
||||
self.sigDelayed.connect(slot)
|
||||
|
||||
self.slot = weakref.ref(slot)
|
||||
else:
|
||||
self.blockSignal = True
|
||||
self.slot = None
|
||||
|
||||
def setDelay(self, delay):
|
||||
self.delay = delay
|
||||
|
||||
|
||||
def signalReceived(self, *args):
|
||||
"""Received signal. Cancel previous timer and store args to be forwarded later."""
|
||||
"""Received signal. Cancel previous timer and store args to be
|
||||
forwarded later."""
|
||||
if self.blockSignal:
|
||||
return
|
||||
self.args = args
|
||||
if self.rateLimit == 0:
|
||||
self.timer.stop()
|
||||
self.timer.start((self.delay*1000)+1)
|
||||
self.timer.start(int(self.delay * 1000) + 1)
|
||||
else:
|
||||
now = time()
|
||||
if self.lastFlushTime is None:
|
||||
@ -59,10 +66,10 @@ class SignalProxy(QtCore.QObject):
|
||||
else:
|
||||
lastFlush = self.lastFlushTime
|
||||
leakTime = max(0, (lastFlush + (1.0 / self.rateLimit)) - now)
|
||||
|
||||
|
||||
self.timer.stop()
|
||||
self.timer.start((min(leakTime, self.delay)*1000)+1)
|
||||
|
||||
self.timer.start(int(min(leakTime, self.delay) * 1000) + 1)
|
||||
|
||||
def flush(self):
|
||||
"""If there is a signal queued up, send it now."""
|
||||
if self.args is None or self.blockSignal:
|
||||
@ -70,10 +77,9 @@ class SignalProxy(QtCore.QObject):
|
||||
args, self.args = self.args, None
|
||||
self.timer.stop()
|
||||
self.lastFlushTime = time()
|
||||
#self.emit(self.signal, *self.args)
|
||||
self.sigDelayed.emit(args)
|
||||
return True
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
self.blockSignal = True
|
||||
try:
|
||||
@ -81,31 +87,23 @@ class SignalProxy(QtCore.QObject):
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
# XXX: This is a weakref, however segfaulting on PySide and
|
||||
# Python 2. We come back later
|
||||
self.sigDelayed.disconnect(self.slot)
|
||||
except:
|
||||
pass
|
||||
|
||||
finally:
|
||||
self.slot = None
|
||||
|
||||
def connectSlot(self, slot):
|
||||
"""Connect the `SignalProxy` to an external slot"""
|
||||
assert self.slot is None, "Slot was already connected!"
|
||||
self.slot = weakref.ref(slot)
|
||||
self.sigDelayed.connect(slot)
|
||||
self.blockSignal = False
|
||||
|
||||
def block(self):
|
||||
"""Return a SignalBlocker that temporarily blocks input signals to this proxy.
|
||||
"""Return a SignalBlocker that temporarily blocks input signals to
|
||||
this proxy.
|
||||
"""
|
||||
return SignalBlock(self.signal, self.signalReceived)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from .Qt import QtGui
|
||||
app = QtGui.QApplication([])
|
||||
win = QtGui.QMainWindow()
|
||||
spin = QtGui.QSpinBox()
|
||||
win.setCentralWidget(spin)
|
||||
win.show()
|
||||
|
||||
def fn(*args):
|
||||
print("Raw signal:", args)
|
||||
def fn2(*args):
|
||||
print("Delayed signal:", args)
|
||||
|
||||
|
||||
spin.valueChanged.connect(fn)
|
||||
#proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn)
|
||||
proxy = SignalProxy(spin.valueChanged, delay=0.5, slot=fn2)
|
||||
|
153
pyqtgraph/tests/test_signalproxy.py
Normal file
153
pyqtgraph/tests/test_signalproxy.py
Normal file
@ -0,0 +1,153 @@
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
from ..Qt import QtCore
|
||||
from ..Qt import QtGui
|
||||
from ..Qt import QT_LIB, PYSIDE
|
||||
|
||||
from ..SignalProxy import SignalProxy
|
||||
|
||||
|
||||
class Sender(QtCore.QObject):
|
||||
signalSend = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Sender, self).__init__(parent)
|
||||
|
||||
|
||||
class Receiver(QtCore.QObject):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Receiver, self).__init__(parent)
|
||||
self.counter = 0
|
||||
|
||||
def slotReceive(self):
|
||||
self.counter += 1
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def qapp():
|
||||
app = QtGui.QApplication.instance()
|
||||
if app is None:
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
yield app
|
||||
|
||||
app.processEvents(QtCore.QEventLoop.AllEvents, 100)
|
||||
app.deleteLater()
|
||||
|
||||
|
||||
def test_signal_proxy_slot(qapp):
|
||||
"""Test the normal work mode of SignalProxy with `signal` and `slot`"""
|
||||
sender = Sender(parent=qapp)
|
||||
receiver = Receiver(parent=qapp)
|
||||
proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6,
|
||||
slot=receiver.slotReceive)
|
||||
|
||||
assert proxy.blockSignal is False
|
||||
assert proxy is not None
|
||||
assert sender is not None
|
||||
assert receiver is not None
|
||||
|
||||
sender.signalSend.emit()
|
||||
proxy.flush()
|
||||
qapp.processEvents(QtCore.QEventLoop.AllEvents, 10)
|
||||
|
||||
assert receiver.counter > 0
|
||||
del proxy
|
||||
del sender
|
||||
del receiver
|
||||
|
||||
|
||||
@pytest.mark.skipif(QT_LIB == PYSIDE and sys.version_info < (2, 8),
|
||||
reason="Crashing on PySide and Python 2.7")
|
||||
def test_signal_proxy_disconnect_slot(qapp):
|
||||
"""Test the disconnect of SignalProxy with `signal` and `slot`"""
|
||||
sender = Sender(parent=qapp)
|
||||
receiver = Receiver(parent=qapp)
|
||||
proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6,
|
||||
slot=receiver.slotReceive)
|
||||
|
||||
assert proxy.blockSignal is False
|
||||
assert proxy is not None
|
||||
assert sender is not None
|
||||
assert receiver is not None
|
||||
assert proxy.slot is not None
|
||||
|
||||
proxy.disconnect()
|
||||
assert proxy.slot is None
|
||||
assert proxy.blockSignal is True
|
||||
|
||||
sender.signalSend.emit()
|
||||
proxy.flush()
|
||||
qapp.processEvents(QtCore.QEventLoop.AllEvents, 10)
|
||||
|
||||
assert receiver.counter == 0
|
||||
|
||||
del proxy
|
||||
del sender
|
||||
del receiver
|
||||
|
||||
|
||||
def test_signal_proxy_no_slot_start(qapp):
|
||||
"""Test the connect mode of SignalProxy without slot at start`"""
|
||||
sender = Sender(parent=qapp)
|
||||
receiver = Receiver(parent=qapp)
|
||||
proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6)
|
||||
|
||||
assert proxy.blockSignal is True
|
||||
assert proxy is not None
|
||||
assert sender is not None
|
||||
assert receiver is not None
|
||||
|
||||
sender.signalSend.emit()
|
||||
proxy.flush()
|
||||
qapp.processEvents(QtCore.QEventLoop.AllEvents, 10)
|
||||
assert receiver.counter == 0
|
||||
|
||||
# Start a connect
|
||||
proxy.connectSlot(receiver.slotReceive)
|
||||
assert proxy.blockSignal is False
|
||||
sender.signalSend.emit()
|
||||
proxy.flush()
|
||||
qapp.processEvents(QtCore.QEventLoop.AllEvents, 10)
|
||||
assert receiver.counter > 0
|
||||
|
||||
# An additional connect should raise an AssertionError
|
||||
with pytest.raises(AssertionError):
|
||||
proxy.connectSlot(receiver.slotReceive)
|
||||
|
||||
del proxy
|
||||
del sender
|
||||
del receiver
|
||||
|
||||
|
||||
def test_signal_proxy_slot_block(qapp):
|
||||
"""Test the block mode of SignalProxy with `signal` and `slot`"""
|
||||
sender = Sender(parent=qapp)
|
||||
receiver = Receiver(parent=qapp)
|
||||
proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6,
|
||||
slot=receiver.slotReceive)
|
||||
|
||||
assert proxy.blockSignal is False
|
||||
assert proxy is not None
|
||||
assert sender is not None
|
||||
assert receiver is not None
|
||||
|
||||
with proxy.block():
|
||||
sender.signalSend.emit()
|
||||
sender.signalSend.emit()
|
||||
sender.signalSend.emit()
|
||||
proxy.flush()
|
||||
qapp.processEvents(QtCore.QEventLoop.AllEvents, 10)
|
||||
|
||||
assert receiver.counter == 0
|
||||
|
||||
sender.signalSend.emit()
|
||||
proxy.flush()
|
||||
qapp.processEvents(QtCore.QEventLoop.AllEvents, 10)
|
||||
|
||||
assert receiver.counter > 0
|
||||
|
||||
del proxy
|
||||
del sender
|
||||
del receiver
|
Loading…
x
Reference in New Issue
Block a user