root/gaphor/tags/gaphor-0.3.0/gaphor/diagram/klass.py

Revision 251, 10.1 kB (checked in by arjanmol, 5 years ago)

*** empty log message ***

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """ClassItem diagram item
2 """
3 # vim:sw=4:et
4
5 # TODO: make loading of features work (adjust on_groupable_add)
6 #       probably best to do is subclass Feature in OperationItem and A.Item
7
8 from __future__ import generators
9
10 import gobject
11 import pango
12 import diacanvas
13
14 import gaphor.UML as UML
15 from gaphor.diagram import initialize_item
16
17 from classifier import ClassifierItem
18 from feature import FeatureItem
19 from attribute import AttributeItem
20 from operation import OperationItem
21
22 class Compartment(list):
23     """Specify a compartment in a class item.
24     A compartment has a line on top and a list of FeatureItems.
25     """
26
27     def __init__(self, name, owner):
28         self.name = name
29         self.owner = owner
30         self.visible = True
31         self.separator = diacanvas.shape.Path()
32         self.separator.set_line_width(2.0)
33         self.sep_y = 0
34
35     def save(self, save_func):
36         #log.debug('Compartment.save: %s' % self)
37         for item in self:
38             save_func(None, item)
39
40     def pre_update(self, width, height, affine):
41         """Calculate the size of the feates in this compartment.
42         """
43         if self.visible:
44             self.sep_y = height
45             height += ClassItem.COMP_MARGIN_Y
46             for f in self:
47                 w, h = f.get_size(update=True)
48                 #log.debug('feature: %f, %f' % (w, h))
49                 f.set_pos(ClassItem.COMP_MARGIN_X, height)
50                 f.set_property('visible', True)
51                 height += h
52                 width = max(width, w + 2 * ClassItem.COMP_MARGIN_X)
53             height += ClassItem.COMP_MARGIN_Y
54         else:
55             for f in self:
56                 f.set_property('visible', False)
57         return width, height
58
59     def update(self, width, affine):
60         if self.visible:
61             for f in self:
62                 self.owner.update_child(f, affine)
63             self.separator.line(((0, self.sep_y), (width, self.sep_y)))
64
65
66 class ClassItem(ClassifierItem, diacanvas.CanvasGroupable):
67 #class ClassItem(ClassifierItem):
68     """This item visualizes a Class instance.
69
70     A ClassItem contains two compartments (Compartment): one for
71     attributes and one for operations. To add and remove such features
72     the ClassItem implements the CanvasGroupable interface.
73     Items can be added by callling class.add() and class.remove().
74     This is used to handle CanvasItems, not UML objects!
75     """
76     __gproperties__ = {
77         'show-attributes': (gobject.TYPE_BOOLEAN, 'show attributes',
78                             '',
79                             1, gobject.PARAM_READWRITE),
80         'show-operations': (gobject.TYPE_BOOLEAN, 'show operations',
81                             '',
82                             1, gobject.PARAM_READWRITE),
83     }
84     HEAD_MARGIN_X=30
85     HEAD_MARGIN_Y=10
86     COMP_MARGIN_X=5
87     COMP_MARGIN_Y=5
88
89     popup_menu = ClassifierItem.popup_menu + (
90         'separator',
91         'CreateAttribute',
92         'CreateOperation',
93         'separator',
94         'ShowAttributes',
95         'ShowOperations'
96     )
97
98     def __init__(self, id=None):
99         ClassifierItem.__init__(self, id)
100         self.set(height=50, width=100)
101         self._attributes = Compartment('attributes', self)
102         self._operations = Compartment('operations', self)
103         self._border = diacanvas.shape.Path()
104         self._border.set_line_width(2.0)
105
106     def save(self, save_func):
107         # Store the show- properties *before* the width/height properties,
108         # otherwise the classes will unintentionally grow due to "visible"
109         # attributes or operations.
110         self.save_property(save_func, 'show-attributes')
111         self.save_property(save_func, 'show-operations')
112         ClassifierItem.save(self, save_func)
113
114     def postload(self):
115         ClassifierItem.postload(self)
116         self.sync_compartments()
117
118     def do_set_property(self, pspec, value):
119         if pspec.name == 'show-attributes':
120             self.preserve_property('show-attributes')
121             self._attributes.visible = value
122             self.request_update()
123         elif pspec.name == 'show-operations':
124             self.preserve_property('show-operations')
125             self._operations.visible = value
126             self.request_update()
127         else:
128             ClassifierItem.do_set_property(self, pspec, value)
129
130     def do_get_property(self, pspec):
131         if pspec.name == 'show-attributes':
132             return self._attributes.visible
133         elif pspec.name == 'show-operations':
134             return self._operations.visible
135         return ClassifierItem.do_get_property(self, pspec)
136
137     def _create_attribute(self, attribute):
138         """Create a new attribute item.
139         """
140         new = AttributeItem()
141         new.subject = attribute
142         self.add(new)
143
144     def _create_operation(self, operation):
145         """Create a new operation item.
146         """
147         new = OperationItem()
148         new.subject = operation
149         self.add(new)
150        
151     def sync_features(self, features, compartment, creator=None):
152         """Common function for on_subject_notify__ownedAttribute() and
153         on_subject_notify__ownedOperation().
154         - features: the list of attributes or operations in the model
155         - compartment: our local representation
156         - creator: factory method for creating new attr. or oper.'s
157         """
158         # Extract the UML elements from the compartment
159         my_features = map(getattr, compartment,
160                       ['subject'] * len(compartment))
161
162         for a in [f for f in features if f not in my_features]:
163             creator(a)
164
165         tmp = [f for f in my_features if f not in features]
166         if tmp:
167             # Create a feature->item mapping
168             mapping = dict(zip(my_features, compartment))
169             for a in tmp:
170                 self.remove(mapping[a])
171            
172     def sync_compartments(self):
173         """Sync the contents of the attributes and operations compartments
174         with the data in self.subject.
175         """
176         attributes = [a for a in self.subject.ownedAttribute if not a.association]
177         self.sync_features(attributes, self._attributes, self._create_attribute)
178         self.sync_features(self.subject.ownedOperation, self._operations,
179                            self._create_operation)
180
181     def on_subject_notify(self, pspec, notifiers=()):
182         #log.debug('Class.on_subject_notify(%s, %s)' % (pspec, notifiers))
183         ClassifierItem.on_subject_notify(self, pspec, ('ownedAttribute', 'ownedOperation'))
184         # Create already existing attributes and operations:
185         if self.subject:
186             self.sync_compartments()
187         self.request_update()
188
189     def on_subject_notify__ownedAttribute(self, subject, pspec=None):
190         """Called when the ownedAttribute property of our subject changes.
191         """
192         #log.debug('on_subject_notify__ownedAttribute')
193         # Filter attributes that are connected to an association:
194         attributes = [a for a in subject.ownedAttribute if not a.association]
195         self.sync_features(attributes, self._attributes, self._create_attribute)
196
197     def on_subject_notify__ownedOperation(self, subject, pspec=None):
198         """Called when the ownedOperation property of our subject changes.
199         """
200         #log.debug('on_subject_notify__ownedOperation')
201         self.sync_features(subject.ownedOperation, self._operations,
202                                  self._create_operation)
203
204     def on_update(self, affine):
205         """Overrides update callback.
206         """
207
208         width = 0
209         height = ClassItem.HEAD_MARGIN_Y
210
211         compartments = (self._attributes, self._operations)
212
213         # TODO: update stereotype
214
215         # Update class name
216         w, name_height = self.get_name_size()
217         height += name_height
218         name_y = height / 2
219        
220         height += ClassItem.HEAD_MARGIN_Y
221         width = w + ClassItem.HEAD_MARGIN_X
222
223         for comp in compartments:
224             width, height = comp.pre_update(width, height, affine)
225
226         self.set(min_width=width, min_height=height)
227
228         #if affine:
229         width = max(width, self.width)
230         height = max(height, self.height)
231
232         # We know the width of all text components and set it:
233         # Note: here the upadte flag is set for all sub-items (again)!
234         #    self._name.set_property('width', width)
235         self.update_name(x=0, y=name_y, width=width, height=name_height)
236
237         for comp in compartments:
238             comp.update(width, affine)
239
240         ClassifierItem.on_update(self, affine)
241
242         self._border.rectangle((0,0),(width, height))
243         self.expand_bounds(1.0)
244
245     def on_shape_iter(self):
246         yield self._border
247         for s in ClassifierItem.on_shape_iter(self):
248             yield s
249         if self._attributes.visible:
250             yield self._attributes.separator
251         if self._operations.visible:
252             yield self._operations.separator
253
254     # Groupable
255
256     def on_groupable_add(self, item):
257         """Add an attribute or operation.
258         """
259         #if isinstance(item.subject, UML.Property):
260         if isinstance(item, AttributeItem):
261             #log.debug('Adding attribute %s' % item)
262             self._attributes.append(item)
263             item.set_child_of(self)
264         #elif isinstance(item.subject, UML.Operation):
265         elif isinstance(item, OperationItem):
266             #log.debug('Adding operation %s' % item)
267             self._operations.append(item)
268             item.set_child_of(self)
269         else:
270             log.warning('feature %s is not a Feature' % item)
271             return 0
272         self.request_update()
273         return 1
274
275     def on_groupable_remove(self, item):
276         """Remove a feature subitem.
277         """
278         if item in self._attributes:
279             self._attributes.remove(item)
280             item.set_child_of(None)
281         elif item in self._operations:
282             self._operations.remove(item)
283             item.set_child_of(None)
284         else:
285             log.warning('feature %s not found in feature list' % item)
286             return 0
287         self.request_update()
288         #log.debug('Feature removed: %s' % item)
289         return 1
290
291     def on_groupable_iter(self):
292         for i in self._attributes:
293             yield i
294         for i in self._operations:
295             yield i
296
297 initialize_item(ClassItem, UML.Class)
Note: See TracBrowser for help on using the browser.