| 1 |
|
|---|
| 2 |
"""elementfactory.py |
|---|
| 3 |
""" |
|---|
| 4 |
|
|---|
| 5 |
import gaphor |
|---|
| 6 |
import gaphor.misc.uniqueid as uniqueid |
|---|
| 7 |
|
|---|
| 8 |
from element import Element |
|---|
| 9 |
from diagram import Diagram |
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
class ElementFactory(object): |
|---|
| 13 |
"""The ElementFactory is used to create elements ans do lookups to |
|---|
| 14 |
elements. A model can contain only one Model element, though. |
|---|
| 15 |
|
|---|
| 16 |
Notifications are send with as arguments (name, element, *user_data). |
|---|
| 17 |
The following names are used: |
|---|
| 18 |
create - a new model element is created (element is newly created element) |
|---|
| 19 |
remove - a model element is removed (element is to be removed element) |
|---|
| 20 |
model - a new model has been loaded (element is None) |
|---|
| 21 |
flush - model is flushed: all element are removed from the factory |
|---|
| 22 |
(element is None) |
|---|
| 23 |
""" |
|---|
| 24 |
def __init__(self): |
|---|
| 25 |
self._elements = dict() |
|---|
| 26 |
self._observers = list() |
|---|
| 27 |
|
|---|
| 28 |
def create(self, type): |
|---|
| 29 |
"""Create a new Model element of type type""" |
|---|
| 30 |
return self.create_as(type, uniqueid.generate_id()) |
|---|
| 31 |
|
|---|
| 32 |
def create_as(self, type, id): |
|---|
| 33 |
"""Create a new model element of type 'type' with 'id' as its ID. |
|---|
| 34 |
This method should only be used when loading models. If the ID is |
|---|
| 35 |
higher that the current id that should be used for the next item, the |
|---|
| 36 |
ID for the next item is set to id + 1.""" |
|---|
| 37 |
assert issubclass(type, Element) |
|---|
| 38 |
obj = type(id, self) |
|---|
| 39 |
self._elements[id] = obj |
|---|
| 40 |
|
|---|
| 41 |
obj.connect('__unlink__', self.__element_signal) |
|---|
| 42 |
self.notify(obj, 'create') |
|---|
| 43 |
return obj |
|---|
| 44 |
|
|---|
| 45 |
def lookup(self, id): |
|---|
| 46 |
try: |
|---|
| 47 |
return self._elements[id] |
|---|
| 48 |
except KeyError: |
|---|
| 49 |
return None |
|---|
| 50 |
|
|---|
| 51 |
def select(self, expression): |
|---|
| 52 |
"""Create a list of elements that comply with expression.""" |
|---|
| 53 |
l = [] |
|---|
| 54 |
for e in self._elements.values(): |
|---|
| 55 |
if expression(e): |
|---|
| 56 |
l.append(e) |
|---|
| 57 |
return l |
|---|
| 58 |
|
|---|
| 59 |
def keys(self): |
|---|
| 60 |
return self._elements.keys() |
|---|
| 61 |
|
|---|
| 62 |
def iterkeys(self): |
|---|
| 63 |
return self._elements.iterkeys() |
|---|
| 64 |
|
|---|
| 65 |
def values(self): |
|---|
| 66 |
return self._elements.values() |
|---|
| 67 |
|
|---|
| 68 |
def itervalues(self): |
|---|
| 69 |
return self._elements.itervalues() |
|---|
| 70 |
|
|---|
| 71 |
def is_empty(self): |
|---|
| 72 |
"""Returns True if the factory holds no elements.""" |
|---|
| 73 |
if self._elements: |
|---|
| 74 |
return False |
|---|
| 75 |
else: |
|---|
| 76 |
return True |
|---|
| 77 |
|
|---|
| 78 |
def flush(self): |
|---|
| 79 |
"""Flush all elements (remove them from the factory).""" |
|---|
| 80 |
self.notify(None, 'flush') |
|---|
| 81 |
|
|---|
| 82 |
for value in self.select(lambda e: isinstance(e, Diagram)): |
|---|
| 83 |
value.unlink() |
|---|
| 84 |
|
|---|
| 85 |
for key, value in self._elements.items(): |
|---|
| 86 |
|
|---|
| 87 |
|
|---|
| 88 |
value.unlink() |
|---|
| 89 |
|
|---|
| 90 |
assert len(self._elements) == 0, 'Still items in the factory: %s' % str(self._elements.values()) |
|---|
| 91 |
|
|---|
| 92 |
import gc |
|---|
| 93 |
for i in range(4): gc.collect() |
|---|
| 94 |
|
|---|
| 95 |
def connect(self, callback, *data): |
|---|
| 96 |
"""Attach 'callback'.""" |
|---|
| 97 |
self._observers.append((callback,) + data) |
|---|
| 98 |
|
|---|
| 99 |
def disconnect(self, callback, *data): |
|---|
| 100 |
"""Detach a callback identified by it's data.""" |
|---|
| 101 |
|
|---|
| 102 |
cb = (callback,) + data |
|---|
| 103 |
|
|---|
| 104 |
|
|---|
| 105 |
try: |
|---|
| 106 |
while True: |
|---|
| 107 |
self._observers.remove(cb) |
|---|
| 108 |
except ValueError: |
|---|
| 109 |
pass |
|---|
| 110 |
|
|---|
| 111 |
def notify(self, element, name): |
|---|
| 112 |
"""Send notification to attached callbacks that a property |
|---|
| 113 |
has changed. This is usually only called by the properties.""" |
|---|
| 114 |
for cb_data in self._observers: |
|---|
| 115 |
try: |
|---|
| 116 |
apply(cb_data[0], (element, name) + cb_data[1:]) |
|---|
| 117 |
except: |
|---|
| 118 |
pass |
|---|
| 119 |
|
|---|
| 120 |
def notify_model(self): |
|---|
| 121 |
self.notify(None, 'model') |
|---|
| 122 |
|
|---|
| 123 |
def __element_signal(self, element, pspec): |
|---|
| 124 |
"""Remove an element from the factory """ |
|---|
| 125 |
|
|---|
| 126 |
|
|---|
| 127 |
|
|---|
| 128 |
if pspec == '__unlink__' and self._elements.has_key(element.id): |
|---|
| 129 |
|
|---|
| 130 |
del self._elements[element.id] |
|---|
| 131 |
self.notify(element, 'remove') |
|---|
| 132 |
elif pspec == '__relink__' and not self._elements.has_key(element.id): |
|---|
| 133 |
log.debug('Relinking element: %s' % element) |
|---|
| 134 |
self._elements[element.id] = element |
|---|
| 135 |
self.notify(element, 'create') |
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 138 |
gaphor.resource(ElementFactory) |
|---|