-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsignal_slot.py
159 lines (124 loc) · 4.31 KB
/
signal_slot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
""" A signal/slot implementation
Original: See below
File: signal.py
Author: Thiago Marcos P. Santos
Author: Christopher S. Case
Author: David H. Bronke
Created: August 28, 2008
Updated: December 12, 2011
License: MIT
"""
from __future__ import print_function
# import six
import inspect
import warnings
from weakref import WeakSet, WeakKeyDictionary
# from debug import msg_debug
class Signal(object):
def __init__(self):
self._functions = WeakSet()
self._methods = WeakKeyDictionary()
def __call__(self, *args, **kargs):
# Call handler functions
to_be_removed = []
for func in self._functions.copy():
try:
func(*args, **kargs)
except RuntimeError:
Warning.warn('Signals func->RuntimeError: func "{}" will be removed.'.format(func))
to_be_removed.append(func)
for remove in to_be_removed:
self._functions.discard(remove)
# Call handler methods
to_be_removed = []
emitters = self._methods.copy()
for obj, funcs in emitters.items():
print ('obj is type "{}"'.format(type(obj)))
for func in funcs.copy():
try:
func(obj, *args, **kargs)
except RuntimeError:
warnings.warn('Signals methods->RuntimeError, obj.func "{}.{}" will be removed'.format(obj, func))
to_be_removed.append((obj, func))
for obj, func in to_be_removed:
self._methods[obj].discard(func)
def connect(self, slot):
if inspect.ismethod(slot):
if slot.__self__ not in self._methods:
self._methods[slot.__self__] = set()
self._methods[slot.__self__].add(slot.__func__)
else:
self._functions.add(slot)
def disconnect(self, slot):
if inspect.ismethod(slot):
if slot.__self__ in self._methods:
self._methods[slot.__self__].remove(slot.__func__)
else:
if slot in self._functions:
self._functions.remove(slot)
def clear(self):
self._functions.clear()
self._methods.clear()
class SignalsErrorBase(Exception):
'''Base Signals Error'''
default_message = ''
def __init__(self, *args):
if len(args):
super(SignalsErrorBase, self).__init__(*args)
else:
super(SignalsErrorBase, self).__init__(self.default_message)
class SignalsNotAClass(SignalsErrorBase):
'''Must add a Signal Class'''
default_message = 'Signal must be a class.'
class Signals(dict):
'''Manage the signals.'''
def __setitem__(self, key, value):
if key not in self:
super(Signals, self).__setitem__(key, value)
else:
warnings.warn('Signals: signal "{}" already exists.'.format(key))
def __getattr__(self, key):
for signal in self:
if signal.__name__ == key:
return self[signal]
raise KeyError('{}'.format(key))
def add(self, signal_class):
if inspect.isclass(signal_class):
self.__setitem__(signal_class, signal_class())
else:
raise SignalsNotAClass
# Sample usage:
if __name__ == '__main__':
class Model(object):
def __init__(self, value):
self.__value = value
self.changed = Signal()
def set_value(self, value):
self.__value = value
self.changed() # Emit signal
def get_value(self):
return self.__value
class View(object):
def __init__(self, model):
self.model = model
model.changed.connect(self.model_changed)
def model_changed(self):
print(" New value:", self.model.get_value())
print("Beginning Tests:")
model = Model(10)
view1 = View(model)
view2 = View(model)
view3 = View(model)
print("Setting value to 20...")
model.set_value(20)
print("Deleting a view, and setting value to 30...")
del view1
model.set_value(30)
print("Clearing all listeners, and setting value to 40...")
model.changed.clear()
model.set_value(40)
print("Testing non-member function...")
def bar():
print(" Calling Non Class Function!")
model.changed.connect(bar)
model.set_value(50)