root/gaphor/trunk/gaphor/diagram/diagramitem.py

Revision 2225, 9.1 kB (checked in by arj..@yirdis.nl, 9 months ago)

moved code that removes model elements when no more presentations exist to a separate service. It was in diagramtab.py, which is the wrong place and caused hard to reproduce errors.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """
2 DiagramItem provides basic functionality for presentations.
3 Such as a modifier 'subject' property and a unique id.
4 """
5
6 from zope import component
7 from gaphor import UML
8 from gaphor.application import Application
9 from gaphor.misc import uniqueid
10 from gaphor.diagram import DiagramItemMeta
11 from gaphor.diagram.textelement import EditableTextSupport
12 from gaphor.diagram.style import ALIGN_CENTER, ALIGN_TOP
13
14 STEREOTYPE_OPEN  = '\xc2\xab' # '<<'
15 STEREOTYPE_CLOSE = '\xc2\xbb' # '>>'
16
17
18 class StereotypeSupport(object):
19     """
20     Support methods for stereotypes.
21     """
22     STEREOTYPE_ALIGN = {
23         'text-align'  : (ALIGN_CENTER, ALIGN_TOP),
24         'text-padding': (5, 10, 2, 10),
25         'text-outside': False,
26         'text-align-group': 'stereotype',
27     }
28
29     def __init__(self):
30         self._stereotype = self.add_text('stereotype',
31                 style=self.STEREOTYPE_ALIGN,
32                 pattern='%s%%s%s' % (STEREOTYPE_OPEN, STEREOTYPE_CLOSE),
33                 visible=self.is_stereotype_visible)
34
35
36     def is_stereotype_visible(self):
37         """
38         Display stereotype if it is not empty.
39         """
40         return self._stereotype.text
41
42
43     def set_stereotype(self, text=None):
44         """
45         Set the stereotype text for the diagram item.
46
47         Note, that text is not Stereotype object.
48
49         @arg text: stereotype text
50         """
51         self._stereotype.text = text
52         self.request_update()
53
54     stereotype = property(lambda s: s._stereotype, set_stereotype)
55
56     def update_stereotype(self):
57         """
58         Update the stereotype definitions (text) of this item.
59
60         Note, that this method is also called from
61         ExtensionItem.confirm_connect_handle method.
62         """
63         if self.subject:
64             applied_stereotype = self.subject.appliedStereotype
65         else:
66             applied_stereotype = None
67
68         def stereotype_name(name):
69             """
70             Return a nice name to display as stereotype. First will be
71             character lowercase unless the second character is uppercase.
72             """
73             if len(name) > 1 and name[1].isupper():
74                 return name
75             else:
76                 return name[0].lower() + name[1:]
77
78         # by default no stereotype, however check for __stereotype__
79         # attribute to assign some static stereotype see interfaces,
80         # use case relationships, package or class for examples
81         stereotype = getattr(self, '__stereotype__', None)
82         if stereotype:
83             stereotype = self.parse_stereotype(stereotype)
84
85         if applied_stereotype:
86             # generate string with stereotype names separated by coma
87             sl = ', '.join(stereotype_name(s.name) for s in applied_stereotype)
88             if stereotype:
89                 stereotype = '%s, %s' % (stereotype, sl)
90             else:
91                 stereotype = sl
92
93         # Phew! :]
94         self.set_stereotype(stereotype)
95
96     def parse_stereotype(self, data):
97         if isinstance(data, str): # return data as stereotype if it is a string
98             return data
99
100         subject = self.subject
101
102         for stereotype, condition in data.items():
103             if isinstance(condition, tuple):
104                 cls, predicate = condition
105             elif isinstance(condition, type):
106                 cls = condition
107                 predicate = None
108             elif callable(condition):
109                 cls = None
110                 predicate = condition
111             else:
112                 assert False, 'wrong conditional %s' % condition
113
114             ok = True
115             if cls:
116                 ok = type(subject) is cls #isinstance(subject, cls)
117             if predicate:
118                 ok = predicate(self)
119
120             if ok:
121                 return stereotype
122         return None
123
124
125 class DiagramItem(UML.Presentation, StereotypeSupport, EditableTextSupport):
126     """
127     Basic functionality for all model elements (lines and elements!).
128
129     This class contains common functionallity for model elements and
130     relationships.
131     It provides an interface similar to UML.Element for connecting and
132     disconnecting signals.
133
134     This class is not very useful on its own. It contains some glue-code for
135     diacanvas.DiaCanvasItem and gaphor.UML.Element.
136
137     Example:
138         class ElementItem(diacanvas.CanvasElement, DiagramItem):
139             connect = DiagramItem.connect
140             disconnect = DiagramItem.disconnect
141             ...
142
143     @cvar style: styles information (derived from DiagramItemMeta)
144     """
145
146     __metaclass__ = DiagramItemMeta
147
148     def __init__(self, id=None):
149         UML.Presentation.__init__(self)
150         EditableTextSupport.__init__(self)
151         StereotypeSupport.__init__(self)
152
153         self._id = id
154
155         # properties, which should be saved in file
156         self._persistent_props = set()
157         self._watched_properties = dict()
158
159         self.add_watch(UML.Element.appliedStereotype, self.on_element_applied_stereotype)
160
161     id = property(lambda self: self._id, doc='Id')
162
163
164     def set_prop_persistent(self, name):
165         """
166         Specify property of diagram item, which should be saved in file.
167         """
168         self._persistent_props.add(name)
169
170
171     # UML.Element interface used by properties:
172
173     # TODO: Use adapters for load/save functionality
174     def save(self, save_func):
175         if self.subject:
176             save_func('subject', self.subject)
177
178         # save persistent properties
179         for p in self._persistent_props:
180             save_func(p, getattr(self, p.replace('-', '_')), reference=True)
181
182
183     def load(self, name, value):
184         if name == 'subject':
185             type(self).subject.load(self, value)
186         else:
187             #log.debug('Setting unknown property "%s" -> "%s"' % (name, value))
188             try:
189                 setattr(self, name.replace('-', '_'), eval(value))
190             except:
191                 log.warning('%s has no property named %s (value %s)' % (self, name, value))
192
193
194     def postload(self):
195         if self.subject:
196             self.on_presentation_subject(None)
197
198
199     def save_property(self, save_func, name):
200         """
201         Save a property, this is a shorthand method.
202         """
203         save_func(name, getattr(self, name.replace('-', '_')))
204
205
206     def save_properties(self, save_func, *names):
207         """
208         Save a property, this is a shorthand method.
209         """
210         for name in names:
211             self.save_property(save_func, name)
212
213
214     def unlink(self):
215         """
216         Remove the item from the canvas and set subject to None.
217         """
218         if self.canvas:
219             self.canvas.remove(self)
220         super(DiagramItem, self).unlink()
221
222
223     def request_update(self):
224         """
225         Placeholder for gaphor.Item's request_update() method.
226         """
227         pass
228
229     def pre_update(self, context):
230         EditableTextSupport.pre_update(self, context)
231
232     def post_update(self, context):
233         EditableTextSupport.post_update(self, context)
234
235     def draw(self, context):
236         EditableTextSupport.draw(self, context)
237
238
239     def item_at(self, x, y):
240         return self
241
242
243     def on_element_applied_stereotype(self, event):
244         if self.subject:
245             self.update_stereotype()
246             self.request_update()
247
248
249     def add_watch(self, property, handler=None):
250         """
251         Add a property (umlproperty) to be watched. a handler may be provided
252         that will be called with the event as argument (handler(event)).
253         """
254         assert isinstance(property, UML.properties.umlproperty)
255         #print 'Registering. Old val is', self._watched_properties.get(property)
256         self._watched_properties[property] = handler
257
258
259     def register_handlers(self):
260         Application.register_handler(self.on_model_factory_event)
261         Application.register_handler(self.on_element_change)
262         Application.register_handler(self.on_presentation_subject)
263         # FixMe: calls to request_update() cause tests to fail
264 #        if self.subject:
265 #            self.on_presentation_subject(None)
266
267
268     def unregister_handlers(self):
269         Application.unregister_handler(self.on_model_factory_event)
270         Application.unregister_handler(self.on_presentation_subject)
271         Application.unregister_handler(self.on_element_change)
272
273
274     @component.adapter(UML.interfaces.IModelFactoryEvent)
275     def on_model_factory_event(self, event):
276         self.on_presentation_subject(None)
277
278
279     @component.adapter(UML.interfaces.IAssociationSetEvent)
280     def on_presentation_subject(self, event):
281         if event is None or \
282                 (event.property is UML.Presentation.subject and \
283                  event.element is self):
284             for prop, handler in self._watched_properties.iteritems():
285                 if handler:
286                     # Provide event?
287                     handler(None)
288
289
290     @component.adapter(UML.interfaces.IElementChangeEvent)
291     def on_element_change(self, event):
292         """
293         Called when a model element has changed.
294         """
295         if event.property in self._watched_properties:
296             handler = self._watched_properties[event.property]
297             if handler:
298                 handler(event)
299             elif self.subject and self.subject is event.element:
300                 self.request_update()
301
302
303 # vim:sw=4:et:ai
Note: See TracBrowser for help on using the browser.