3
# pitivi/utils/ripple_update_group.py
5
# Copyright (c) 2010, Brandon Lewis <brandon.lewis@collabora.co.uk>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU Lesser General Public
9
# License as published by the Free Software Foundation; either
10
# version 2.1 of the License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# Lesser General Public License for more details.
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this program; if not, write to the
19
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20
# Boston, MA 02110-1301, USA.
23
class RippleUpdateGroup(object):
24
"""Allows for event-driven spreadsheet-like ripple updates without
27
This class allows you to express an event-driven sequence of operations in
28
terms of a directed graph. It is not a constraint solver: The goal is to
29
allow the programmer to reduce complex logic to a set of simple functions
30
and predicates combined declaratively.
32
Events propagate through the graph in breadth first order. During an
33
update cycle, each vertex is visited only once, so cycles can exist in the
34
graph without creating infinite loops.
36
Each vertex represents a unique object. The following may also be
37
associated with a vertex:
39
- the name of a signal on the object. when this signal fires, it
40
triggers an update cycle beginning at this object. during an update
41
cycle, further signal emissions from this or any other vertex will
42
be ignored to prevent infinite loops.
44
- an update function, which will be called when the vertex is visited
45
as part of an update cycle. It will not be called when the object
48
- zero or more user-specified arguments, passed to the
51
An edge between two verticies represents a sequence of operations. If an
52
edge exists from object A to object B, then whenever A is perfomred, B
53
should be performed too -- unless it has already been visited as part of
56
In addition to a a pair of objects, each edge also has the following
59
- a predicate function. called during an update cycle when this edge
60
is reached, and before any other processing is done. If this
61
function returns false, it will be as if this edge otherwise did not
64
- a function to be called whenver the edge is visited during an update
65
cycle. this function will not be called if the condition function
68
@ivar arcs: A map from widget to a list of edges originating in the widget.
69
@ivar update_funcs: A map from widget to a (callable, args) tuple.
74
self.update_funcs = {}
75
self.ignore_new_signals = False
77
def addVertex(self, widget, signal=None, update_func=None,
79
"""Add a widget to the list of vertexes.
81
@param widget: The vertex to be added.
82
@type widget: Gtk.Widget
83
@param signal: A signal of the widget to be monitored.
85
@param update_func: A callable object called when the vertex is visited.
86
@type update_func: function
87
@param update_func_args: The arguments for calling update_func.
88
@type update_func_args: tuple
91
widget.connect(signal, self._widgetValueChanged)
92
self.update_funcs[widget] = (update_func, update_func_args)
93
self.arcs[widget] = []
95
def addEdge(self, widget_a, widget_b, predicate=None, edge_func=None):
96
"""Add a directional edge from widget_a to widget_b.
98
@param widget_a: The source vertex.
99
@type widget_a: Gtk.Widget
100
@param widget_b: The target vertex.
101
@type widget_b: Gtk.Widget
102
@param predicate: A callable object returning whether the edge may be
104
@type predicate: function
105
@param edge_func: A callable object called when the edge is traversed.
106
@type edge_func: function
108
self.arcs[widget_a].append((widget_b, predicate, edge_func))
110
def addBiEdge(self, widget_a, widget_b, predicate=None, edge_func=None):
111
"""Add a bidirectional edge between the specified vertexes.
115
self.addEdge(widget_a, widget_b, predicate, edge_func)
116
self.addEdge(widget_b, widget_a, predicate, edge_func)
118
def _widgetValueChanged(self, widget, *unused):
119
"""Handle an event generated by the specified widget."""
120
if self.ignore_new_signals:
123
self.ignore_new_signals = True
125
self._updateValues(widget)
127
self.ignore_new_signals = False
129
def _updateValues(self, widget):
130
"""Traverse the graph starting from the specified widget."""
131
# Initialize the list of (source_widget, arc) to be traversed.
132
queue = [(widget, arc) for arc in self.arcs[widget]]
133
visited = set([widget])
135
source_widget, arc = queue.pop(0)
136
target_widget, predicate, edge_func = arc
138
# ignore nodes we've seen
139
if target_widget in visited:
142
# check whether conditions permit this edge to be followed
143
if predicate and not predicate():
150
# visit the target node
151
update_func, update_func_args = self.update_funcs[target_widget]
153
update_func(source_widget, target_widget, *update_func_args)
154
visited.add(target_widget)
157
for arc in self.arcs[target_widget]:
158
queue.append((target_widget, arc))