root/gaphor/trunk/gaphor/diagram/classifier.py

Revision 2290, 11.9 kB (checked in by arj..@yirdis.nl, 8 months ago)

set default style to normal. make name style for classifiers and package bold.

Fixes #118.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """
2 Classifier diagram item.
3 """
4
5 from gaphas.state import observed, reversible_property
6
7 from gaphor import UML
8 from gaphor.diagram.nameditem import NamedItem
9
10 import font
11
12 class Compartment(list):
13     """
14     Specify a compartment in a class item.
15     A compartment has a line on top and a list of FeatureItems.
16     """
17
18     def __init__(self, name, owner):
19         self.name = name
20         self.owner = owner
21         self.visible = True
22         self.width = 0
23         self.height = 0
24
25     def save(self, save_func):
26         #log.debug('Compartment.save: %s' % self)
27         for item in self:
28             save_func(None, item)
29
30
31     def has_item(self, item):
32         """
33         Check if the compartment already contains an item with the
34         same subject as item.
35         """
36         s = item.subject
37         local_elements = [f.subject for f in self]
38         return s and s in local_elements
39
40     def get_size(self):
41         """
42         Get width, height of the compartment. pre_update should have
43         been called so widthand height have been calculated.
44         """
45         if self.visible:
46             return self.width, self.height
47         else:
48             return 0, 0
49
50     def pre_update(self, context):
51         """
52         Pre update, determine width and height of the compartment.
53         """
54         self.width = self.height = 0
55         cr = context.cairo
56         for item in self:
57             item.pre_update(context)
58        
59         if self:
60             # self (=list) contains items
61             sizes = [ (0, 0) ] # to not throw exceptions by max and sum
62             sizes.extend(f.get_size(True) for f in self)
63             self.width = max(size[0] for size in sizes)
64             self.height = sum(size[1] for size in sizes)
65             vspacing = self.owner.style.compartment_vspacing
66             self.height += vspacing * (len(sizes) - 1)
67
68         padding = self.owner.style.compartment_padding
69         self.width += padding[1] + padding[3]
70         self.height += padding[0] + padding[2]
71
72     def post_update(self, context):
73         for item in self:
74             item.post_update(context)
75
76     def draw(self, context):
77         cr = context.cairo
78         padding = self.owner.style.compartment_padding
79         vspacing = self.owner.style.compartment_vspacing
80         cr.translate(padding[1], padding[0])
81         offset = 0
82         for item in self:
83             cr.save()
84             try:
85                 cr.translate(0, offset)
86                 #cr.move_to(0, offset)
87                 item.draw(context)
88                 offset += vspacing + item.height
89             finally:
90                 cr.restore()
91
92     def item_at(self, x, y):
93         if 0 > x > self.width:
94             return None
95        
96         padding = self.owner.style.compartment_padding
97         height = padding[0]
98         if y < height:
99             return None
100
101         vspacing = self.owner.style.compartment_vspacing
102         for f in self:
103             w, h = f.get_size(True)
104             height += h + vspacing
105             if y < height:
106                 return f
107         return None
108
109
110 class ClassifierItem(NamedItem):
111     """
112     This item visualizes a Class instance.
113
114     A ClassifierItem is a superclass for (all) Classifier like objects,
115     such as Class, Interface, Component and Actor.
116
117     ClassifierItem controls the stereotype, namespace and owning package.
118
119     A classifier has three drawing style (ClassifierItem.drawing_style):
120      - The comparttment view, as often used by Classes
121      - A compartment view, but with a little stereotype icon in the right corner
122      - One big icon, as used by Actors and sometimes interfaces.
123
124     To support this behavior a few helper methods are defined which can be
125     called/overridden:
126      - update_compartment_icon (box-style with small icon (see ComponentItem))
127      - update_icon (does nothing by default, an impl. should be provided by
128                     subclasses (see ActorItem))
129     """
130
131     # Do not use preset drawing style
132     DRAW_NONE = 0
133     # Draw the famous box style
134     DRAW_COMPARTMENT = 1
135     # Draw compartment with little icon in upper right corner
136     DRAW_COMPARTMENT_ICON = 2
137     # Draw as icon
138     DRAW_ICON = 3
139
140     __style__ = {
141         'min-size': (100, 50),
142         'icon-size': (20, 20),
143         'from-padding': (7, 2, 7, 2),
144         'compartment-padding': (5, 5, 5, 5), # (top, right, bottom, left)
145         'compartment-vspacing': 3,
146         'name-padding': (10, 10, 10, 10),
147         'stereotype-padding': (10, 10, 2, 10),
148     }
149     # Default size for small icons
150     ICON_WIDTH    = 15
151     ICON_HEIGHT   = 25
152     ICON_MARGIN_X = 10
153     ICON_MARGIN_Y = 10
154
155     def __init__(self, id=None):
156         NamedItem.__init__(self, id)
157         self._compartments = []
158
159         self._drawing_style = ClassifierItem.DRAW_NONE
160         self.add_watch(UML.Classifier.isAbstract, self.on_classifier_is_abstract)
161         self._name.font = font.FONT_NAME
162
163     def save(self, save_func):
164         # Store the show- properties *before* the width/height properties,
165         # otherwise the classes will unintentionally grow due to "visible"
166         # attributes or operations.
167         self.save_property(save_func, 'drawing-style')
168         NamedItem.save(self, save_func)
169
170     def postload(self):
171         NamedItem.postload(self)
172         self.on_classifier_is_abstract(None)
173
174     @observed
175     def set_drawing_style(self, style):
176         """
177         Set the drawing style for this classifier: DRAW_COMPARTMENT,
178         DRAW_COMPARTMENT_ICON or DRAW_ICON.
179         """
180         if style != self._drawing_style:
181             self._drawing_style = style
182             self.request_update()
183 #            if self.canvas:
184 #                request_resolve = self.canvas.solver.request_resolve
185 #                for h in self._handles:
186 #                    request_resolve(h.x)
187 #                    request_resolve(h.y)
188
189         if self._drawing_style == self.DRAW_COMPARTMENT:
190             self.draw       = self.draw_compartment
191             self.pre_update = self.pre_update_compartment
192             self.post_update = self.post_update_compartment
193
194         elif self._drawing_style == self.DRAW_COMPARTMENT_ICON:
195             self.draw       = self.draw_compartment_icon
196             self.pre_update = self.pre_update_compartment_icon
197             self.post_update     = self.post_update_compartment_icon
198
199         elif self._drawing_style == self.DRAW_ICON:
200             self.draw       = self.draw_icon
201             self.pre_update = self.pre_update_icon
202             self.post_update     = self.post_update_icon
203
204
205     drawing_style = reversible_property(lambda self: self._drawing_style, set_drawing_style)
206
207
208     def create_compartment(self, name):
209         """
210         Create a new compartment. Compartments contain data such as
211         attributes and operations.
212
213         It is common to create compartments during the construction of the
214         diagram item. Their visibility can be toggled by Compartment.visible.
215         """
216         c = Compartment(name, self)
217         self._compartments.append(c)
218         return c
219
220     compartments = property(lambda s: s._compartments)
221
222     def sync_uml_elements(self, elements, compartment, creator=None):
223         """
224         This method synchronized a list of elements with the items
225         in a compartment. A creator-function should be passed which is used
226         for creating new compartment items.
227
228         @elements: the list of attributes or operations in the model
229         @compartment: our local representation
230         @creator: factory method for creating new attr. or oper.'s
231         """
232         # extract the UML elements from the compartment
233         local_elements = [f.subject for f in compartment]
234
235         # map local element with compartment element
236         mapping = dict(zip(local_elements, compartment))
237
238         to_add = [el for el in elements if el not in local_elements]
239
240         # sync local elements with elements
241         del compartment[:]
242
243         for el in elements:
244             if el in to_add:
245                 creator(el)
246             else:
247                 compartment.append(mapping[el])
248
249         #log.debug('elements order in model: %s' % [f.name for f in elements])
250         #log.debug('elements order in diagram: %s' % [f.subject.name for f in compartment])
251         assert tuple([f.subject for f in compartment]) == tuple(elements)
252
253         self.request_update()
254
255
256     def on_classifier_is_abstract(self, event):
257         self._name.font = (self.subject and self.subject.isAbstract) \
258                 and font.FONT_ABSTRACT_NAME or font.FONT_NAME
259         self.request_update()
260
261
262     def pre_update_compartment_icon(self, context):
263         self.pre_update_compartment(context)
264         # icon width plus right margin
265         self.min_width = max(self.min_width,
266                 self._header_size[0] + self.ICON_WIDTH + 10)
267
268     def pre_update_icon(self, context):
269         super(ClassifierItem, self).pre_update(context)
270
271     def pre_update_compartment(self, context):
272         """
273         Update state for box-style presentation.
274
275         Calculate minimal size, which is based on header and comparments
276         sizes.
277         """
278         super(ClassifierItem, self).pre_update(context)
279
280         for comp in self._compartments:
281             comp.pre_update(context)
282
283         sizes = [comp.get_size() for comp in self._compartments]
284         sizes.append((self.min_width, self._header_size[1]))
285
286         self.min_width = max(size[0] for size in sizes)
287         h = sum(size[1] for size in sizes)
288         self.min_height = max(self.style.min_size[1], h)
289
290
291     def post_update_compartment_icon(self, context):
292         """
293         Update state for box-style w/ small icon.
294         """
295         super(ClassifierItem, self).post_update(context)
296
297     def post_update_icon(self, context):
298         """
299         Update state for icon-only presentation.
300         """
301         super(ClassifierItem, self).post_update(context)
302
303     def post_update_compartment(self, context):
304         super(ClassifierItem, self).post_update(context)
305
306         assert self.width >= self.min_width, 'failed %s >= %s' % (self.width, self.min_width)
307         assert self.height >= self.min_height, 'failed %s >= %s' % (self.height, self.min_height)
308
309
310     def get_icon_pos(self):
311         """
312         Get icon position.
313         """
314         return self.width - self.ICON_MARGIN_X - self.ICON_WIDTH, \
315             self.ICON_MARGIN_Y
316
317
318     def draw_compartment(self, context):
319         super(ClassifierItem, self).draw(context)
320
321         cr = context.cairo
322
323         cr.rectangle(0, 0, self.width, self.height)
324         cr.stroke()
325
326         # make room for name, stereotype, etc.
327         y = self._header_size[1]
328         cr.translate(0, y)
329
330         if self._drawing_style == self.DRAW_COMPARTMENT_ICON:
331             width = self.width - self.ICON_WIDTH
332         else:
333             width = self.width
334
335         # draw compartments
336         for comp in self._compartments:
337             if not comp.visible:
338                 continue
339             cr.save()
340             cr.move_to(0, 0)
341             cr.line_to(self.width, 0)
342             cr.stroke()
343             try:
344                 comp.draw(context)
345             finally:
346                 cr.restore()
347             cr.translate(0, comp.height)
348
349
350     def item_at(self, x, y):
351         """
352         Find the composite item (attribute or operation) for the
353         classifier.
354         """
355
356         if self.drawing_style not in (self.DRAW_COMPARTMENT, self.DRAW_COMPARTMENT_ICON):
357             return self
358
359         header_height = self._header_size[1]
360
361         compartments = [ comp for comp in self.compartments if comp.visible]
362
363         # Edit is in name compartment -> edit name
364         if y < header_height or not len(compartments):
365             return self
366
367         padding = self.style.compartment_padding
368         vspacing = self.style.compartment_vspacing
369        
370         # place offset at top of first comparement
371         y -= header_height
372         y += vspacing / 2.0
373         for comp in compartments:
374             item = comp.item_at(x, y)
375             if item:
376                 return item
377             y -= comp.height
378         return None
379
380 # vim:sw=4:et
Note: See TracBrowser for help on using the browser.