Skip to content

Commit 0664668

Browse files
committed
-
1 parent 621af19 commit 0664668

35 files changed

+1514
-4
lines changed

docs/ref/garlicsim_wx_general_misc/cute_window.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
.. _ref-garlicsim_wx_general_misc-cute_window:
88

9-
===========================================================================================================
9+
=============================================================================================================
1010
:class:`CuteWindow <garlicsim_wx.widgets.general_misc.cute_window.CuteWindow>`: An improved wxPython window
11-
===========================================================================================================
11+
=============================================================================================================
1212

1313
:class:`CuteWindow <garlicsim_wx.widgets.general_misc.cute_window.CuteWindow>`
1414
is a wxPython window which subclasses from :class:`wx.Window` and adds a few

misc/IDE files/Wing/python_toolbox.wpr

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ proj.directory-list = [{'dirloc': loc('../../..'),
88
'excludes': [u'nosetests.xml',
99
u'garlicsim_wx/py2exe_dist',
1010
u'.coverage_html_report',
11-
u'build'],
11+
u'build',
12+
u'dist',
13+
u'python_toolbox.egg-info'],
1214
'filter': '*',
1315
'include_hidden': False,
1416
'recursive': True,

python_toolbox/color_tools.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2009-2011 Ram Rachum.
2+
# This program is distributed under the LGPL2.1 license.
3+
4+
'''Defines tools for manipulating colors.'''
5+
6+
7+
def mix_rgb(ratio, rgb1, rgb2):
8+
'''Mix two rgb colors `rgb1` and `rgb2`, according to the given `ratio`.'''
9+
counter_ratio = 1 - ratio
10+
return (
11+
rgb1[0] * ratio + rgb2[0] * counter_ratio,
12+
rgb1[1] * ratio + rgb2[1] * counter_ratio,
13+
rgb1[2] * ratio + rgb2[2] * counter_ratio
14+
)
15+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright 2009-2011 Ram Rachum.
2+
# This program is distributed under the LGPL2.1 license.
3+
4+
'''
5+
An emitter mechanism, a variation on the publisher-subscriber design pattern.
6+
'''
7+
8+
from .emitter import Emitter
9+
from .emitter_system import EmitterSystem

python_toolbox/emitters/emitter.py

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# Copyright 2009-2011 Ram Rachum.
2+
# This program is distributed under the LGPL2.1 license.
3+
4+
'''
5+
Defines the `Emitter` class.
6+
7+
See its documentation for more info.
8+
'''
9+
10+
# todo: there should probably be some circularity check. Maybe actually
11+
# circularity should be permitted?
12+
13+
# todo: make some way to emit from multiple emitters simulataneously, saving
14+
# redundant calls to shared callable outputs.
15+
16+
import itertools
17+
from python_toolbox import cute_iter_tools
18+
from python_toolbox import misc_tools
19+
from python_toolbox import address_tools
20+
21+
22+
class Emitter(object):
23+
'''
24+
An emitter you can `emit` from to call all its callable outputs.
25+
26+
The emitter idea is a variation on the publisher-subscriber design pattern.
27+
28+
Every emitter has a set of inputs and a set of outputs. The inputs, if
29+
there are any, must be emitters themselves. So when you `emit` on any of
30+
this emitter's inputs, it's as if you `emit`ted on this emitter as well.
31+
(Recursively, of course.)
32+
33+
The outputs are a bit different. An emitter can have as outputs both (a)
34+
other emitters and (b) callable objects. (Which means, functions or
35+
function-like objects.)
36+
37+
There's no need to explain (a): If `emitter_1` has as an output
38+
`emitter_2`, then `emitter_2` has as an input `emitter_1`, which works like
39+
how we explained above about inputs.
40+
41+
But now (b): An emitter can have callables as outputs. (Without these, the
42+
emitter idea won't have much use.) These callables simply get called
43+
whenever the emitter or one of its inputs get `emit`ted.
44+
45+
The callables that you register as outputs are functions that need to be
46+
called when the original event that caused the `emit` action happens.
47+
'''
48+
# todo: Let user put a single input/output
49+
50+
51+
_is_atomically_pickleable = False
52+
53+
54+
def __init__(self, inputs=(), outputs=(), name=None):
55+
'''
56+
Construct the emitter.
57+
58+
`inputs` is a list of inputs, all of them must be emitters.
59+
60+
`outputs` is a list of outputs, they must be either emitters or
61+
callables.
62+
63+
`name` is a string name for the emitter.
64+
'''
65+
66+
assert cute_iter_tools.is_iterable(inputs) and \
67+
cute_iter_tools.is_iterable(outputs)
68+
69+
self._inputs = set()
70+
'''The emitter's inputs.'''
71+
72+
self._outputs = set()
73+
'''The emitter's inputs.'''
74+
75+
for output in outputs:
76+
self.add_output(output)
77+
78+
self.__total_callable_outputs_cache = None
79+
'''
80+
A cache of total callable outputs.
81+
82+
This means the callable outputs of this emitter and any output
83+
emitters.
84+
'''
85+
86+
self._recalculate_total_callable_outputs()
87+
88+
# We made sure to create the callable outputs cache before we add
89+
# inputs, so when we update their cache, it could use ours.
90+
for input in inputs:
91+
self.add_input(input)
92+
93+
self.name = name
94+
'''The emitter's name.'''
95+
96+
def get_inputs(self):
97+
'''Get the emitter's inputs.'''
98+
return self._inputs
99+
100+
def get_outputs(self):
101+
'''Get the emitter's outputs.'''
102+
return self._outputs
103+
104+
def _get_input_layers(self):
105+
'''
106+
Get the emitter's inputs as a list of layers.
107+
108+
Every item in the list will be a list of emitters on that layer. For
109+
example, the first item will be a list of direct inputs of our emitter.
110+
The second item will be a list of *their* inputs. Etc.
111+
112+
Every emitter can appear only once in this scheme: It would appear on
113+
the closest layer that it's on.
114+
'''
115+
116+
input_layers = [self._inputs]
117+
current_layer = self._inputs
118+
while current_layer:
119+
120+
next_layer = reduce(
121+
set.union,
122+
(input._inputs for input in current_layer),
123+
set()
124+
)
125+
126+
for ancestor_layer in input_layers:
127+
assert isinstance(next_layer, set)
128+
next_layer -= ancestor_layer
129+
130+
input_layers.append(next_layer)
131+
132+
current_layer = next_layer
133+
134+
135+
# assert sum(len(layer) for layer in input_layers) == \
136+
# len(reduce(set.union, input_layers, set()))
137+
138+
return input_layers
139+
140+
141+
def _recalculate_total_callable_outputs_recursively(self):
142+
'''
143+
Recalculate `__total_callable_outputs_cache` recursively.
144+
145+
This will to do the recalculation for this emitter and all its inputs.
146+
'''
147+
148+
# todo: I suspect this wouldn't work for the following case. `self` has
149+
# inputs `A` and `B`. `A` has input `B`. A callable output `func` was
150+
# just removed from `self`, so this function got called. We update the
151+
# cache here, then take the first input layer, which is `A` and `B` in
152+
# some order. Say `B` is first. Now, we do `recalculate` on `B`, but `A`
153+
# still got the cache with `func`, and `B` will take that. I need to
154+
# test this.
155+
#
156+
# I have an idea how to solve it: In the getter of the cache, check the
157+
# cache exists, otherwise rebuild. The reason we didn't do it up to now
158+
# was to optimize for speed, but only `emit` needs to be fast and it
159+
# doesn't use the getter. We'll clear the caches of all inputs, and
160+
# they'll rebuild as they call each other.
161+
162+
self._recalculate_total_callable_outputs()
163+
input_layers = self._get_input_layers()
164+
for input_layer in input_layers:
165+
for input in input_layer:
166+
input._recalculate_total_callable_outputs()
167+
168+
169+
def _recalculate_total_callable_outputs(self):
170+
'''
171+
Recalculate `__total_callable_outputs_cache` for this emitter.
172+
173+
This will to do the recalculation for this emitter and all its inputs.
174+
'''
175+
children_callable_outputs = reduce(
176+
set.union,
177+
(emitter.get_total_callable_outputs() for emitter
178+
in self._get_emitter_outputs() if emitter is not self),
179+
set()
180+
)
181+
182+
self.__total_callable_outputs_cache = \
183+
children_callable_outputs.union(self._get_callable_outputs())
184+
185+
def add_input(self, emitter):
186+
'''
187+
Add an emitter as an input to this emitter.
188+
189+
Every time that emitter will emit, it will cause this emitter to emit
190+
as well.
191+
'''
192+
assert isinstance(emitter, Emitter)
193+
self._inputs.add(emitter)
194+
emitter._outputs.add(self)
195+
emitter._recalculate_total_callable_outputs_recursively()
196+
197+
def remove_input(self, emitter):
198+
'''Remove an input from this emitter.'''
199+
assert isinstance(emitter, Emitter)
200+
self._inputs.remove(emitter)
201+
emitter._outputs.remove(self)
202+
emitter._recalculate_total_callable_outputs_recursively()
203+
204+
def add_output(self, thing):
205+
'''
206+
Add an emitter or a callable as an output to this emitter.
207+
208+
If adding a callable, every time this emitter will emit the callable
209+
will be called.
210+
211+
If adding an emitter, every time this emitter will emit the output
212+
emitter will emit as well.
213+
'''
214+
assert isinstance(thing, Emitter) or callable(thing)
215+
self._outputs.add(thing)
216+
if isinstance(thing, Emitter):
217+
thing._inputs.add(self)
218+
self._recalculate_total_callable_outputs_recursively()
219+
220+
def remove_output(self, thing):
221+
'''Remove an output from this emitter.'''
222+
assert isinstance(thing, Emitter) or callable(thing)
223+
self._outputs.remove(thing)
224+
if isinstance(thing, Emitter):
225+
thing._inputs.remove(self)
226+
self._recalculate_total_callable_outputs_recursively()
227+
228+
def disconnect_from_all(self): # todo: use the freeze here
229+
'''Disconnect the emitter from all its inputs and outputs.'''
230+
for input in self._inputs:
231+
self.remove_input(input)
232+
for output in self._outputs:
233+
self.remove_output(output)
234+
235+
def _get_callable_outputs(self):
236+
'''Get the direct callable outputs of this emitter.'''
237+
return set((
238+
output for output in self._outputs if callable(output)
239+
))
240+
241+
def _get_emitter_outputs(self):
242+
'''Get the direct emitter outputs of this emitter.'''
243+
return set((
244+
output for output in self._outputs if isinstance(output, Emitter)
245+
))
246+
247+
def get_total_callable_outputs(self):
248+
'''
249+
Get the total of callable outputs of this emitter.
250+
251+
This means the direct callable outputs, and the callable outputs of
252+
emitter outputs.
253+
'''
254+
return self.__total_callable_outputs_cache
255+
256+
def emit(self):
257+
'''
258+
Call all of the (direct or indirect) callable outputs of this emitter.
259+
260+
This is the most important method of the emitter. When you `emit`, all
261+
the callable outputs get called in succession.
262+
'''
263+
# Note that this function gets called many times, so it should be
264+
# optimized for speed.
265+
for callable_output in self.__total_callable_outputs_cache:
266+
# We are using the cache directly instead of calling the getter,
267+
# for speed.
268+
callable_output()
269+
270+
def __repr__(self):
271+
'''
272+
Get a string representation of the emitter.
273+
274+
Example output:
275+
<python_toolbox.emitters.emitter.Emitter 'tree_modified' at
276+
0x1c013d0>
277+
'''
278+
return '<%s %sat %s>' % (
279+
address_tools.describe(type(self), shorten=True),
280+
''.join(("'", self.name, "' ")) if self.name else '',
281+
hex(id(self))
282+
)
283+
"""
284+
Unused:
285+
286+
def _get_total_inputs(self):
287+
288+
total_inputs_of_inputs = reduce(
289+
set.union,
290+
(emitter._get_total_inputs() for emitter
291+
in self._inputs if emitter is not self),
292+
set()
293+
)
294+
295+
return total_inputs_of_inputs.union(self._inputs)
296+
"""
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright 2009-2011 Ram Rachum.
2+
# This program is distributed under the LGPL2.1 license.
3+
4+
'''
5+
Defines an EmitterSystem, which offers some benefits over Emitter.
6+
7+
See documentation of EmitterSystem for more info.
8+
'''
9+
10+
11+
from .emitter_system import EmitterSystem
12+
from .emitter import Emitter

0 commit comments

Comments
 (0)