1
# PiTiVi , Non-linear video editor
3
# ui/projectsettings.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., 59 Temple Place - Suite 330,
20
# Boston, MA 02111-1307, USA.
23
class RippleUpdateGroup(object):
25
"""Allows for event-driven spreadsheet-like ripple updates without
28
This class allows you to express an event-driven sequence of operations in
29
terms of a directed graph. It is not a constraint solver: The goal is to
30
allow the programmer to reduce complex logic to a set of simple functions
31
and predicates combined declaratively.
33
Events propagate through the graph in breadth first order. During an
34
update cycle, each vertex is visited only once, so cycles can exist in the
35
graph without creating infinite loops.
37
Each vertex represents a unique object. The following may also be
38
associated with a vertex:
40
- the name of a signal on the object. when this signal fires, it
41
triggers an update cycle beginning at this object. during an update
42
cycle, further signal emissions from this or any other vertex will
43
be ignored to prevent infinite loops.
45
- an update function, which will be called when the vertex is visited
46
as part of an update cycle. It will not be called when the object
49
- zero or more user-specified arguments, passed to the
52
An edge between two verticies represents a sequence of operations. If an
53
edge exists from object A to object B, then whenever A is perfomred, B
54
should be performed too -- unless it has already been visited as part of
57
In addition to a a pair of objects, each edge also has the following
60
- a predicate function. called during an update cycle when this edge
61
is reached, and before any other processing is done. If this
62
function returns false, it will be as if this edge otherwise did not
65
- a function to be called whenver the edge is visited during an update
66
cycle. this function will not be called if the condition function
69
def __init__(self, *widgets):
71
self.update_funcs = {}
72
self.ignore_new_signals = False
73
for widget in widgets:
74
self.add_vertex(*widget)
76
def add_vertex(self, widget, update_func=None, signal=None, *args):
78
widget.connect(signal, self._widget_value_changed)
79
self.update_funcs[widget] = (update_func, args)
80
self.arcs[widget] = []
82
def add_edge(self, a, b, predicate = None,
84
self.arcs[a].append((b, predicate, edge_func))
86
def add_bi_edge(self, a, b, predicate = None,
88
self.add_edge(a, b, predicate, edge_func)
89
self.add_edge(b, a, predicate, edge_func)
91
def _widget_value_changed(self, widget, *unused):
92
if self.ignore_new_signals:
95
self.ignore_new_signals = True
96
self._updateValues(widget)
97
self.ignore_new_signals = False
99
def _updateValues(self, widget):
100
queue = [(widget, v) for v in self.arcs[widget]]
101
visited = set([widget])
103
parent, (cur, predicate, edge_func) = queue.pop(0)
105
# ignore nodes we've seen
109
# check whether conditions permit this edge to be followed
110
if predicate and (not predicate()):
113
# if so call the edge function
118
update_func, args = self.update_funcs[cur]
120
update_func(parent, cur, *args)
124
queue.extend(((cur, v) for v in self.arcs[cur]))