From 38fe12ef1f12f4b87d918249aeaa14db49a1af9d Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Sun, 1 Jun 2003 18:25:59 +0000 Subject: [PATCH] *** empty log message *** git-svn-id: file:///Users/arjan/backup/gaphor/trunk/gaphor@201 a8418922-720d-0410-834f-a69b97ada669 --- ChangeLog | 17 ++ TODO | 4 + doc/UML2.gaphor | 2 +- gaphor/UML/diagram.py | 12 +- gaphor/UML/element.py | 45 ++++- gaphor/UML/elementfactory.py | 27 ++- gaphor/UML/properties.py | 75 ++++--- gaphor/diagram/README | 28 +++ gaphor/diagram/actor.py | 152 +++------------ gaphor/diagram/association.py | 2 +- gaphor/diagram/classifier.py | 83 ++++++++ gaphor/diagram/comment.py | 105 +++++----- gaphor/diagram/commentline.py | 234 ++++++++-------------- gaphor/diagram/dependency.py | 39 ++-- gaphor/diagram/diagramitem.py | 147 +++++++++----- gaphor/diagram/diagramline.py | 112 +++++++++++ gaphor/diagram/generalization.py | 51 +++-- gaphor/diagram/modelelement.py | 48 ++--- gaphor/diagram/package.py | 102 ++-------- gaphor/diagram/placementtool.py | 39 ++-- gaphor/diagram/relationship.py | 146 +++----------- gaphor/diagram/usecase.py | 96 ++------- gaphor/storage.py | 57 ++++-- gaphor/ui/command/diagram.py | 2 +- gaphor/ui/diagramwindow.py | 18 +- gaphor/ui/mainwindow.py | 7 +- gaphor/ui/namespace.py | 63 +++--- gaphor/ui/stock.py | 2 +- setup.py | 16 +- tests/test-ns.py | 323 +++++++++---------------------- tests/test-properties.py | 55 ++++-- tests/test-uml2.py | 57 +++++- 32 files changed, 1020 insertions(+), 1146 deletions(-) create mode 100644 gaphor/diagram/README create mode 100644 gaphor/diagram/classifier.py create mode 100644 gaphor/diagram/diagramline.py diff --git a/ChangeLog b/ChangeLog index fcef51cbe..501225d92 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2002-06-01 Arjan Molenaar + + (here, only the most important changes are noted) + * gaphor/diagram/classifier.py, diagramline.py: new class + * gaphor/diagram/*.py: updated Actor, Package, Dependency, Comment, + CommentLine, Generalization, UseCase: now work with the new datamodel + * gaphor/UML/element.py: Notify signals are send using GTK+ like + signals (element, param_spec). All other objects are updated to work + with it. + +2002-05-30 Arjan Molenaar + + * setup.py: added run --testfile option + * gaphor/ui/namespace.py: fix ownedMember remove actions + * tests/test-ns.py: added tests for NamespaceModel + * tests/test-properties.py: fixed tests. + 2002-05-19 Arjan Molenaar * gaphor/diagram/actor.py, modelelement.py, diagramitem.py: diff --git a/TODO b/TODO index 445147b45..eb4453547 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,9 @@ As always, there is much to do... +- Make sure than, when a model is loaded and saved again, both save files do + not differ. +- port AssociationItem and ClassItem + - The application crashes every now and then when items are freed. The cause is probably GnomeCanvasItem::canvas, which points to an invalid canvas (the canvas has been freed before the items are freed. diff --git a/doc/UML2.gaphor b/doc/UML2.gaphor index 530b6efad..e432a4a56 100644 --- a/doc/UML2.gaphor +++ b/doc/UML2.gaphor @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/gaphor/UML/diagram.py b/gaphor/UML/diagram.py index 7352140e9..f22ac7632 100644 --- a/gaphor/UML/diagram.py +++ b/gaphor/UML/diagram.py @@ -12,7 +12,7 @@ import gobject import diacanvas import gaphor.misc.uniqueid as uniqueid -class _Canvas(diacanvas.Canvas): +class DiagramCanvas(diacanvas.Canvas): ''' Some additions to diacanvas.Canvas class, esp. load and save functionallity. ''' @@ -21,7 +21,7 @@ class _Canvas(diacanvas.Canvas): 'grid_ofs_y', 'grid_color', 'grid_bg' ] def save(self, save_func): - for prop in _Canvas._savable_canvas_properties: + for prop in DiagramCanvas._savable_canvas_properties: save_func(prop, self.get_property(prop)) save_func('root_affine', self.root.get_property('affine')) # Save child items: @@ -48,17 +48,21 @@ class _Canvas(diacanvas.Canvas): self.clear_redo() self.set_property ("allow_undo", 1) -gobject.type_register(_Canvas) +gobject.type_register(DiagramCanvas) class Diagram(Namespace, PackageableElement): def __init__(self, id): super(Diagram, self).__init__(id) - self.canvas = _Canvas() + self.canvas = DiagramCanvas() self.canvas.set_undo_stack_depth(10) self.canvas.set_property ("allow_undo", 1) + def save(self, save_func): + super(Diagram, self).save(save_func) + save_func('canvas', self.canvas) + def create(self, type): """Create a new canvas item on the canvas. It is created with a unique ID and it is attached to the diagram's root item.""" diff --git a/gaphor/UML/element.py b/gaphor/UML/element.py index c7192a93b..011811166 100644 --- a/gaphor/UML/element.py +++ b/gaphor/UML/element.py @@ -12,6 +12,8 @@ unlink() Remove all references to the element. This is done by emiting the '__unlink__' signal to all attached signals. unlink() can not be called recursively. +relink() + Inverse operation of unlink(). Used by diagram items during undo operations. connect ('name', callback, *data) or connect (('name', 'other_property'), callback, *data) @@ -25,6 +27,9 @@ disconnect (callback, *data) notify (name) Notify all listeners the property 'name' has changed. + The notifier calls callbacks registered by connect() by sending + callback(self, pspec, *data) + where self is the data object (Element) and pspec is the property spec. """ __all__ = [ 'Element' ] @@ -51,7 +56,8 @@ class Element(object): def load(self, name, value): """Loads value in name. Make sure that for every load postload() - should be called.""" + should be called. + """ try: prop = getattr(self.__class__, name) except AttributeError, e: @@ -63,18 +69,29 @@ class Element(object): def postload(self): pass - def unlink(self): - """Unlink the element.""" + def __unlink(self, signal): + """Unlink the element. For both the __unlink__ and __relink__ signal + the __unlink__ callback list is used. + """ # Uses a mutex to make sure it is not called recursively if self.__in_unlink.testandset(): try: - self.notify('__unlink__') + self.notify(signal, '__unlink__') finally: self.__in_unlink.unlock() + def unlink(self): + """Unlink the element.""" + self.__unlink('__unlink__') + + def relink(self): + """Undo the unlink operation.""" + self.__unlink('__relink__') + def connect(self, names, callback, *data): """Attach 'callback' to a list of names. Names may also be a string. - A name is the name od a property of the object or '__unlink__'.""" + A name is the name od a property of the object or '__unlink__'. + """ if type(names) is types.StringType: names = (names,) cb = (callback,) + data @@ -89,7 +106,6 @@ class Element(object): def disconnect(self, callback, *data): """Detach a callback identified by it's data.""" - #print 'disconnect', callback, data cb = (callback,) + data for values in self._observers.values(): # Remove all occurences of 'cb' from values @@ -100,16 +116,25 @@ class Element(object): except ValueError: pass - def notify(self, name): + def notify(self, name, cb_name=None): """Send notification to attached callbacks that a property - has changed.""" + has changed. the __relink__ signal uses the callbacks for __unlink__. + """ + if not cb_name: + cb_name = name cb_list = self._observers.get(name) or () + try: + pspec = getattr(self.__class__, name) + except AttributeError: + pspec = name + # Use a copy of the list to ensure all items are notified for cb_data in list(cb_list): try: - apply(cb_data[0], (name,) + cb_data[1:]) + apply(cb_data[0], (self, pspec) + cb_data[1:]) except: - pass + import traceback + traceback.print_exc() # OCL methods: (from SMW by Ivan Porres (http://www.abo.fi/~iporres/smw)) diff --git a/gaphor/UML/elementfactory.py b/gaphor/UML/elementfactory.py index 81c994b76..adafe97d6 100644 --- a/gaphor/UML/elementfactory.py +++ b/gaphor/UML/elementfactory.py @@ -36,8 +36,8 @@ class ElementFactory(object): obj = type(id) self._elements[id] = obj #obj.connect('__unlink__', self.__element_signal, weakref.ref(obj)) - obj.connect('__unlink__', self.__element_signal, obj) - self.notify('create', obj) + obj.connect('__unlink__', self.__element_signal) + self.notify(obj, 'create') return obj def lookup (self, id): @@ -69,7 +69,7 @@ class ElementFactory(object): def flush(self): """Flush all elements (remove them from the factory).""" - self.notify_flush() + self.notify(None, 'flush') for key, value in self._elements.items(): #print 'ElementFactory: unlinking', value #print 'references:', gc.get_referrers(value) @@ -95,34 +95,31 @@ class ElementFactory(object): except ValueError: pass - def notify(self, name, element): + def notify(self, element, name): """Send notification to attached callbacks that a property has changed. This is usually only called by the properties.""" for cb_data in self._observers: try: - apply(cb_data[0], (name, element) + cb_data[1:]) + apply(cb_data[0], (element, name) + cb_data[1:]) except: pass def notify_model(self): - self.notify('model', None) + self.notify(None, 'model') - def notify_flush(self): - self.notify('flush', None) - - def __element_signal (self, name, element): + def __element_signal (self, element, pspec): """Remove an element from the factory """ #element = weak_element() #if not element: return - - if name == '__unlink__' and self._elements.has_key(element.id): + #log.debug('element %s send signal %s' % (element, name)) + if pspec == '__unlink__' and self._elements.has_key(element.id): log.debug('Unlinking element: %s' % element) del self._elements[element.id] - self.notify ('remove', element) - elif name == '__relink__' and not self._elements.has_key(element.id): + self.notify(element, 'remove') + elif pspec == '__relink__' and not self._elements.has_key(element.id): log.debug('Relinking element: %s' % element) self._elements[element.id] = element - self.notify('create', element) + self.notify(element, 'create') # Make one ElementFactory instance an application-wide resource GaphorResource(ElementFactory) diff --git a/gaphor/UML/properties.py b/gaphor/UML/properties.py index ce3591399..4d43057ba 100644 --- a/gaphor/UML/properties.py +++ b/gaphor/UML/properties.py @@ -201,7 +201,7 @@ class association(umlproperty): else: s = '' or '', self.opposite) return s + '>' def __get__(self, obj, clazz=None): @@ -221,23 +221,31 @@ class association(umlproperty): # Remove old value only for uni-directional associations if self.upper == 1: old = self._get(obj) + # do nothing if we are assigned our existing value: + if value is old: + return if old: self.__delete__(obj, old) - self._set2(obj, value) - # Set opposite side: - if self.opposite: - getattr(self.type, self.opposite)._set(value, obj) - self.notify(obj) + # if we needed to set our own side, set the opposite + if self._set2(obj, value): + # Set opposite side. + # Use value.__class__ since the property may be overridden: + if self.opposite: + getattr(value.__class__, self.opposite)._set(value, obj) + self.notify(obj) def __delete__(self, obj, value=None): - """Delete is used for element deletion and for removal of elements from - a list.""" + """Delete is used for element deletion and for removal of + elements from a list. + """ #print '__delete__', self, obj, value if self.upper > 1 and not value: raise Exception, 'Can not delete collections' + if not value: + value = self._get(obj) if self.opposite: - getattr(self.type, self.opposite)._del(value or self._get(obj), obj) - self._del(obj, value or self._get(obj)) + getattr(value.__class__, self.opposite)._del(value, obj) + self._del(obj, value) def _get(self, obj): #print '_get', self, obj @@ -256,57 +264,78 @@ class association(umlproperty): #print '_set', self, obj, value if not isinstance(value, self.type): raise AttributeError, 'Value should be of type %s' % self.type.__name__ - self._set2(obj, value) - self.notify(obj) + if self._set2(obj, value): + self.notify(obj) def _set2(self, obj, value): - """Real setter, avoid doing the assertion check twice.""" + """Real setter, avoid doing the assertion check twice. + Return True if notification should be send, False otherwise.""" if self.upper > 1: if value in self._get(obj): - return + return False self._get(obj).items.append(value) else: setattr(obj, '_' + self.name, value) - value.connect('__unlink__', self.__on_unlink, obj, value) + value.connect('__unlink__', self.__on_unlink, obj) if self.composite: obj.connect('__unlink__', self.__on_composite_unlink, value) + return True def _del(self, obj, value): """Delete value from the association.""" #print '_del', self, obj, value if self.upper > 1: items = self._get(obj).items - items.remove(value) + try: + items.remove(value) + except: + pass if not items: delattr(obj, '_' + self.name) else: - delattr(obj, '_' + self.name) - value.disconnect(self.__on_unlink, obj, value) + try: + delattr(obj, '_' + self.name) + except: + pass + #print 'association._del: delattr failed for %s' % self.name + value.disconnect(self.__on_unlink, obj) if self.composite: obj.disconnect(self.__on_composite_unlink, value) self.notify(obj) def unlink(self, obj): + #print 'unlink', self, obj lst = getattr(obj, '_' + self.name) while lst: self.__delete__(obj, lst[0]) - def __on_unlink(self, name, obj, value): + def __on_unlink(self, value, pspec, obj): """Disconnect when the element on the other end of the association (value) sends the '__unlink__' signal. This is especially important for uni-directional associations. """ - #print '__on_unlink:', self, name, obj, value - self.__delete__(obj, value) + #print '__on_unlink', name, obj, value + if pspec == '__unlink__': + self.__delete__(obj, value) + # re-establish unlink handler: + value.connect('__unlink__', self.__on_unlink, obj) + else: + print 'RELINK' + self.__set__(obj, value) - def __on_composite_unlink(self, name, value): + def __on_composite_unlink(self, obj, pspec, value): """Unlink value if we have a part-whole (composite) relationship (value is a composite of obj). The implementation of value.unlink() should ensure that no deadlocks occur. """ #print '__on_composite_unlink:', self, name, value - value.unlink() + if pspec == '__unlink__': + value.unlink() + obj.connect('__unlink__', self.__on_composite_unlink, value) + else: + print 'RELINK' + value.relink() class derivedunion(umlproperty): diff --git a/gaphor/diagram/README b/gaphor/diagram/README new file mode 100644 index 000000000..a8788b93e --- /dev/null +++ b/gaphor/diagram/README @@ -0,0 +1,28 @@ +Diagram items + +Diagrams typically contain a diacanvas.Canvas. This Canvas contains a bunch of +items. Those items are one way or another related to some UML.Element. + +In the UML module a class Presentation is defined. It defines the relation +between one Element and multiple Presentations. + +In the UML module all relations between objects are presented as associations. + +There are some conflicts that have to be resolved when using Presentation in +conjunction with diacanvas.CanvasItem: + +Both classes have the methods connect(), disconnect() and emit(). This can +be resolved by creating a signal named 'subject' for the CanvasItem and +enforcing the methods to be directed to the CanvasItem's ones. + +Element.presentation connects to '__unlink__' of Presentation.subject. + +Both connect() and disconnect() have a slightly different syntax: + +CanvasItem: Presentation: +connect('name', handler, *args) connect(('name',..), handler, *args) + connect('name', handler, *args) +disconnect(id) disconnect(handler, *args) +emit('name', *args) notify('name') +handler(*args, *user_args) hanler('name', *user_args) + diff --git a/gaphor/diagram/actor.py b/gaphor/diagram/actor.py index 91353bcf7..8a730c96c 100644 --- a/gaphor/diagram/actor.py +++ b/gaphor/diagram/actor.py @@ -3,18 +3,18 @@ ActorItem diagram item ''' # vim:sw=4 -from modelelement import ModelElementItem -import diacanvas -import pango -import gobject import sys +import gobject +import pango +import diacanvas -class ActorItem(ModelElementItem): +from classifier import ClassifierItem + +class ActorItem(ClassifierItem): HEAD=11 ARM=19 NECK=10 BODY=20 - FONT='sans bold 10' __gproperties__ = { 'name-width': (gobject.TYPE_DOUBLE, 'name width', @@ -22,171 +22,81 @@ class ActorItem(ModelElementItem): 1, gobject.PARAM_READWRITE), } def __init__(self, id=None): - ModelElementItem.__init__(self, id) + ClassifierItem.__init__(self, id) self.set(height=(ActorItem.HEAD + ActorItem.NECK + ActorItem.BODY + ActorItem.ARM), width=(ActorItem.ARM * 2), min_height=(ActorItem.HEAD + ActorItem.NECK + ActorItem.BODY + ActorItem.ARM), min_width=(ActorItem.ARM * 2)) # Head - self.__head = diacanvas.shape.Ellipse() - self.__head.set_line_width(2.0) + self._head = diacanvas.shape.Ellipse() + self._head.set_line_width(2.0) # Body - self.__body = diacanvas.shape.Path() - self.__body.set_line_width(2.0) + self._body = diacanvas.shape.Path() + self._body.set_line_width(2.0) # Arm - self.__arms = diacanvas.shape.Path() - self.__arms.set_line_width(2.0) + self._arms = diacanvas.shape.Path() + self._arms.set_line_width(2.0) # Legs - self.__legs = diacanvas.shape.Path() - self.__legs.set_line_width(2.0) - # Name - self.__name = diacanvas.CanvasText() - self.add_construction(self.__name) - assert self.__name != None - font = pango.FontDescription(ActorItem.FONT) - self.__name.set(font=font, multiline=0, - alignment=pango.ALIGN_CENTER) - self.__name.connect('text_changed', self.on_text_changed) - self.connect('notify::subject', ActorItem.on_subject_notify) - self.subject_name_changed_id = 0 + self._legs = diacanvas.shape.Path() + self._legs.set_line_width(2.0) def save (self, save_func): - ModelElementItem.save(self, save_func) + ClassifierItem.save(self, save_func) self.save_property(save_func, 'name-width') def do_set_property (self, pspec, value): #print 'Actor: Trying to set property', pspec.name, value if pspec.name == 'name-width': - self.__name.set_property('width', value) + self._name.set_property('width', value) else: - ModelElementItem.do_set_property (self, pspec, value) + ClassifierItem.do_set_property (self, pspec, value) def do_get_property(self, pspec): if pspec.name == 'name-width': - return self.__name.get_property('width') + return self._name.get_property('width') else: - return ModelElementItem.do_get_property (self, pspec) - - def on_subject_notify(self, subject): - """See DiagramItem.on_subject_changed(). - """ - if self.subject_name_changed_id: - self.disconnect(self.subject_name_changed_id) - if self.subject: - self.subject_name_changed_id = self.subject.connect('name', self.on_subject_name_changed) - self.__name.set(text=self.subject and self.subject.name or '') - self.request_update() - - def on_subject_name_changed(self, name): - self.__name.set(text=self.subject.name) - - def on_text_changed(self, text_item, text): - if text != self.subject.name: - self.subject.name = text + return ClassifierItem.do_get_property (self, pspec) # DiaCanvasItem callbacks: def on_update(self, affine): # Center the text: - layout = self.__name.get_property('layout') - #layout.set_width(-1) + layout = self._name.get_property('layout') w, h = layout.get_pixel_size() - a = self.__name.get_property('affine') + a = self._name.get_property('affine') if w < self.width: w = self.width aa = (a[0], a[1], a[2], a[3], (self.width - w) / 2, self.height) - self.__name.set(affine=aa, width=w, height=h) + self._name.set(affine=aa, width=w, height=h) - self.update_child(self.__name, affine) - ModelElementItem.on_update(self, affine) + ClassifierItem.on_update(self, affine) # scaling factors (also compenate the line width): fx = self.width / (ActorItem.ARM * 2 + 2); fy = self.height / (ActorItem.HEAD + ActorItem.NECK + ActorItem.BODY + ActorItem.ARM + 2); - self.__head.ellipse((ActorItem.ARM * fx, (ActorItem.HEAD / 2) * fy), + self._head.ellipse((ActorItem.ARM * fx, (ActorItem.HEAD / 2) * fy), ActorItem.HEAD * fx, ActorItem.HEAD * fy) - self.__body.line(((ActorItem.ARM * fx, ActorItem.HEAD * fy), + self._body.line(((ActorItem.ARM * fx, ActorItem.HEAD * fy), (ActorItem.ARM * fx, (ActorItem.HEAD + ActorItem.NECK + ActorItem.BODY) * fy))) - self.__arms.line(((0, (ActorItem.HEAD + ActorItem.NECK) * fy), + self._arms.line(((0, (ActorItem.HEAD + ActorItem.NECK) * fy), (ActorItem.ARM * 2 * fx, (ActorItem.HEAD + ActorItem.NECK) * fy))) - self.__legs.line(((0, (ActorItem.HEAD + ActorItem.NECK + self._legs.line(((0, (ActorItem.HEAD + ActorItem.NECK + ActorItem.BODY + ActorItem.ARM) * fy), (ActorItem.ARM * fx, (ActorItem.HEAD + ActorItem.NECK + ActorItem.BODY) * fy), (ActorItem.ARM * 2 * fx, (ActorItem.HEAD + ActorItem.NECK + ActorItem.BODY + ActorItem.ARM) * fy))) # update the bounding box: ulx, uly, lrx, lry = self.bounds - w, h = self.__name.get_property('layout').get_pixel_size() + w, h = self._name.get_property('layout').get_pixel_size() if w > self.width: ulx = (self.width / 2) - (w / 2) lrx = (self.width / 2) + (w / 2) self.set_bounds ((ulx, uly-1, lrx+1, lry + h)) -# def on_move(self, x, y): -# self.__name.request_update() -# ModelElementItem.on_move(self, x, y) - -# def on_handle_motion (self, handle, wx, wy, mask): -# retval = ModelElementItem.on_handle_motion(self, handle, wx, wy, mask) -# #self.__name_update() -# return retval - - def on_event (self, event): - if event.type == diacanvas.EVENT_KEY_PRESS: - self.__name.focus() - self.__name.on_event (event) - return True - else: - return ModelElementItem.on_event(self, event) - - def on_get_shape_iter(self): - return self.__head - - def on_shape_next(self, iter): - if iter is self.__head: - return self.__body - elif iter is self.__body: - return self.__arms - elif iter is self.__arms: - return self.__legs - return None - - def on_shape_value(self, iter): - return iter - - # Groupable - - def on_groupable_add(self, item): - return 0 - - def on_groupable_remove(self, item): - '''Do not allow the name to be removed.''' - #self.emit_stop_by_name('remove') - ##return 0 - return 1 - - def on_groupable_get_iter(self): - try: - return self.__name - except AttributeError: - return None - - def on_groupable_next(self, iter): - return None - - def on_groupable_value(self, iter): - return iter - - def on_groupable_length(self): - return 1 - - def on_groupable_pos(self, item): - if item == self.__name: - return 0 - else: - return -1 + def on_shape_iter(self): + return iter([self._head, self._body, self._arms, self._legs]) gobject.type_register(ActorItem) diff --git a/gaphor/diagram/association.py b/gaphor/diagram/association.py index b836f0cc2..0432dc64f 100644 --- a/gaphor/diagram/association.py +++ b/gaphor/diagram/association.py @@ -306,7 +306,7 @@ class AssociationItem(RelationshipItem, diacanvas.CanvasAbstractGroup): if tail_end.participant is tail_subject and head_end is not tail_end: # check if this entry is not yet in the diagram # Return if the association is not (yet) on the canvas - for item in assoc.presentations(): + for item in assoc.presentation: if item.canvas is self.canvas and item is not self: break else: diff --git a/gaphor/diagram/classifier.py b/gaphor/diagram/classifier.py new file mode 100644 index 000000000..9be8fe4d4 --- /dev/null +++ b/gaphor/diagram/classifier.py @@ -0,0 +1,83 @@ +''' +ClassifierItem diagram item +''' +# vim:sw=4 + +from modelelement import ModelElementItem +import diacanvas +import pango +import gobject +import sys + +class ClassifierItem(ModelElementItem): + FONT='sans bold 10' + + def __init__(self, id=None): + ModelElementItem.__init__(self, id) + + self._name = diacanvas.CanvasText() + self.add_construction(self._name) + assert self._name != None + font = pango.FontDescription(ClassifierItem.FONT) + self._name.set(font=font, multiline=0, + alignment=pango.ALIGN_CENTER) + self.connect('notify::subject', ClassifierItem.on_subject_notify) + self._name.connect('text_changed', self.on_text_changed) + self.subject_name_changed_id = None + + def on_subject_notify(self, pspec): + """A new subject is set on this model element. + """ + if self.subject_name_changed_id: + self.subject_name_changed_id[0].disconnect(self.subject_name_changed_id[1]) + self.subject_name_changed_id = None + if self.subject: + self.subject_name_changed_id = (self.subject, self.subject.connect('name', self.on_subject_name_changed)) + self._name.set(text=self.subject and self.subject.name or '') + self.request_update() + + def on_subject_name_changed(self, subject, pspec): + assert self.subject is subject + self._name.set(text=self.subject.name) + + def on_text_changed(self, text_item, text): + if self.subject and text != self.subject.name: + self.subject.name = text + + # DiaCanvasItem callbacks: + + def on_update(self, affine): + self.update_child(self._name, affine) + ModelElementItem.on_update(self, affine) + + def on_event (self, event): + if event.type == diacanvas.EVENT_KEY_PRESS: + self._name.focus() + self._name.on_event (event) + return True + else: + return ModelElementItem.on_event(self, event) + + # Groupable + + def on_groupable_add(self, item): + return 0 + + def on_groupable_remove(self, item): + '''Do not allow the name to be removed.''' + return 1 + + def on_groupable_iter(self): + return iter([self._name]) + + def on_groupable_length(self): + return 1 + + def on_groupable_pos(self, item): + if item == self._name: + return 0 + else: + return -1 + + +gobject.type_register(ClassifierItem) diff --git a/gaphor/diagram/comment.py b/gaphor/diagram/comment.py index 7d06660c0..c457a4535 100644 --- a/gaphor/diagram/comment.py +++ b/gaphor/diagram/comment.py @@ -13,74 +13,78 @@ class CommentItem(ModelElementItem): OFFSET=5 FONT='sans 10' - def __init__(self): - ModelElementItem.__init__(self) + def __init__(self, id=None): + ModelElementItem.__init__(self, id) self.set(min_width=CommentItem.EAR + 2 * CommentItem.OFFSET, height=50, width=100) - self.__border = diacanvas.shape.Path() - self.__border.set_line_width(2.0) - self.__body = diacanvas.CanvasText() - self.add_construction(self.__body) - assert self.__body != None + self.connect('notify::subject', self.on_subject_notify) + self.subject_name_changed_id = None + self._border = diacanvas.shape.Path() + self._border.set_line_width(2.0) + self._body = diacanvas.CanvasText() + self.add_construction(self._body) font = pango.FontDescription(CommentItem.FONT) - self.__body.set(font=font, width=self.width - (CommentItem.OFFSET * 2), + self._body.set(font=font, width=self.width - (CommentItem.OFFSET * 2), alignment=pango.ALIGN_LEFT) - self.__body.move(CommentItem.OFFSET, CommentItem.OFFSET) - self.__body.connect('text_changed', self.on_text_changed) + self._body.move(CommentItem.OFFSET, CommentItem.OFFSET) + self._body.connect('text_changed', self.on_text_changed) def postload(self): - self.__body.set(text=self.subject.body) + self._body.set(text=self.subject.body) - def _set_subject(self, subject): + def on_subject_notify(self, subject, pspec): """Set a new subject. We make sure that the body of the comment - is visible here.""" - ModelElementItem._set_subject(self, subject) - self.__body.set(text=self.subject and self.subject.body or '') + is visible here. + """ + if self.subject_name_changed_id: + # disconnect old (subject, id) pair + #self.disconnect(self.subject_name_changed_id) + self.subject_name_changed_id[0].disconnect(self.subject_name_changed_id[1]) + self.subject_name_changed_id = None + if self.subject: + #self.subject_name_changed_id = self.subject.connect('name', self.on_subject_name_changed) + self.subject_name_changed_id = (self.subject, self.subject.connect('body', self.on_subject_name_changed)) + + self._body.set(text=self.subject and self.subject.body or '') self.request_update() + def on_subject_name_changed(self, subject, pspec): + self._body.set(text=self.subject.body) + + def on_text_changed(self, text_item, text): + if self.subject and text != self.subject.body: + self.subject.body = text + + # DiaCanvasItem callbacks: + def on_update(self, affine): # Outline the text, first tell the text how width it may become: - self.__body.set_property('width', + self._body.set_property('width', self.width - CommentItem.EAR - CommentItem.OFFSET) - w, h = self.__body.get_property('layout').get_pixel_size() + w, h = self._body.get_property('layout').get_pixel_size() self.set(min_height=h + CommentItem.OFFSET * 2) - self.__body.set_property('height', self.height - CommentItem.OFFSET * 2) - self.update_child(self.__body, affine) + self._body.set_property('height', self.height - CommentItem.OFFSET * 2) + self.update_child(self._body, affine) ModelElementItem.on_update(self, affine) # Width and height, adjusted for line width... w = self.width h = self.height ear = CommentItem.EAR - self.__border.line(((w - ear, 0), (w- ear, ear), (w, ear), (w - ear, 0), + self._border.line(((w - ear, 0), (w- ear, ear), (w, ear), (w - ear, 0), (0, 0), (0, h), (w, h), (w, ear))) self.expand_bounds(1) -# def on_move(self, x, y): -# self.__body.request_update() -# ModelElementItem.on_move(self, x, y) - -# def on_handle_motion (self, handle, wx, wy, mask): -# retval = ModelElementItem.on_handle_motion(self, handle, wx, wy, mask) -# self.__body_update() -# return retval - def on_event (self, event): if event.type == diacanvas.EVENT_KEY_PRESS: - self.__body.focus() - self.__body.on_event (event) + self._body.focus() + self._body.on_event(event) return True else: return ModelElementItem.on_event(self, event) - def on_get_shape_iter(self): - return self.__border - - def on_shape_next(self, iter): - return None - - def on_shape_value(self, iter): - return iter + def on_shape_iter(self): + return iter([self._border]) # Groupable @@ -92,34 +96,17 @@ class CommentItem(ModelElementItem): #self.emit_stop_by_name('remove') return 1 - def on_groupable_get_iter(self): - return self.__body - - def on_groupable_next(self, iter): - return None - - def on_groupable_value(self, iter): - return iter + def on_groupable_iter(self): + return iter([self._body]) def on_groupable_length(self): return 1 def on_groupable_pos(self, item): - if item == self.__body: + if item == self._body: return 0 else: return -1 - def on_subject_update(self, name, old_value, new_value): - if name == 'body': - self.__body.set(text=self.subject.body) - #self.__body_update() - else: - ModelElementItem.on_subject_update(self, name, old_value, new_value) - - def on_text_changed(self, text_item, text): - if text != self.subject.body: - self.subject.body = text gobject.type_register(CommentItem) - diff --git a/gaphor/diagram/commentline.py b/gaphor/diagram/commentline.py index 9ff4d42c3..d8d742bb4 100644 --- a/gaphor/diagram/commentline.py +++ b/gaphor/diagram/commentline.py @@ -1,191 +1,113 @@ +# vim:sw=4:et ''' CommentLine -- A line that connects a comment to another model element. TODO: Why do we lose the __id property when we do a get_property after a model has been loaded. It works okay when creating new items. ''' -# vim:sw=4 import gobject import diacanvas import gaphor.UML as UML -class CommentLineItem(diacanvas.CanvasLine): - __gproperties__ = { - 'id': (gobject.TYPE_PYOBJECT, 'id', - 'Identification number of the canvas item', - gobject.PARAM_READWRITE), - } - __savable_properties = [ 'affine', 'line_width', - 'color', 'cap', 'join', 'orthogonal', 'horizontal' ] - def __init__(self): - diacanvas.CanvasLine.__init__(self) - self.__gobject_init__() - self.__id = None - self.__shape = None - self.set_property('dash', (7.0, 5.0)) +from diagramline import DiagramLine - def set_id(self, value): - self.__id = value +class CommentLineItem(DiagramLine): - def save (self, save_func): - for prop in CommentLineItem.__savable_properties: - save_func(prop, self.get_property(prop)) - points = [ ] - for h in self.handles: - pos = h.get_pos_i () - points.append (pos) - save_func('points', points) - c = self.handles[0].connected_to - if c: - save_func('head_connection', c) - c = self.handles[-1].connected_to - if c: - save_func ('tail_connection', c) + def __init__(self, id=None): + #diacanvas.CanvasLine.__init__(self) + DiagramLine.__init__(self) + self._id = id + self.set_property('dash', (7.0, 5.0)) - def load (self, name, value): - if name == 'points': - points = eval(value) - self.set_property('head_pos', points[0]) - self.set_property('tail_pos', points[1]) - for p in points[2:]: - item.set_property ('add_point', p) - elif name == 'head_connection': - self._load_head_connection = value - elif name == 'tail_connection': - self._load_tail_connection = value - else: - self.set_property(name, eval(value)) - - def postload(self): - if hasattr(self, '_load_head_connection'): - self._load_head_connection.connect_handle (self.handles[0]) - del self._load_head_connection - if hasattr(self, '_load_tail_connection'): - self._load_tail_connection.connect_handle (self.handles[-1]) - del self._load_tail_connection - - def do_set_property (self, pspec, value): - if pspec.name == 'id': - #self.__id = value - self.set_data('id', value) - #print self, '__id', self.__dict__ - else: - raise AttributeError, 'Unknown property %s' % pspec.name - - def do_get_property(self, pspec): - if pspec.name == 'id': - return self.get_data('id') - - #print self, 'get_property(id)', self.__dict__ - if not hasattr(self, '_CommentLineItem__id'): - log.warning('No ID found, generating a new one...') - import gaphor.misc.uniqueid as uniqueid - self.__id = uniqueid.generate_id() - return self.__id - else: - raise AttributeError, 'Unknown property %s' % pspec.name - - def has_capability(self, capability): - #log.debug('Relationship: checking capability %s' % capability) - if capability == 'orthogonal': - return self.get_property('orthogonal') - elif capability == 'del_segment': - if self.get_property('orthogonal'): - return len(self.handles) > 3 - else: - return len(self.handles) > 2 - return False + id = property(lambda self: self._id, doc='Id') def on_glue(self, handle, wx, wy): - "No connections are allowed on a CommentLine." - return None + "No connections are allowed on a CommentLine." + return None def on_connect_handle(self, handle): - "No connections are allows to the CommentLine." - return 0 + "No connections are allows to the CommentLine." + return 0 def on_disconnect_handle(self, handle): - "No connections are allows to the CommentLine." - return 0 + "No connections are allows to the CommentLine." + return 0 # Gaphor Connection Protocol def allow_connect_handle(self, handle, connecting_to): - """ - This method is called by a canvas item if the user tries to connect - this object's handle. allow_connect_handle() checks if the line is - allowed to be connected. In this case that means that one end of the - line should be connected to a Comment. - Returns: TRUE if connection is allowed, FALSE otherwise. - """ - h = self.handles - c1 = h[0].connected_to - c2 = h[-1].connected_to - # OK if both sides are not connected yet. - if not c1 and not c2: - return 1 - - if handle is h[0]: - c1 = connecting_to - elif handle is h[-1]: - c2 = connecting_to - else: - raise AttributeError, 'handle should be the first or the last handle of the CommentLine' + """See DiagramLine.allow_connect_handle(). + """ + h = self.handles + c1 = h[0].connected_to + c2 = h[-1].connected_to + # OK if both sides are not connected yet. + if not c1 and not c2: + return 1 + + if handle is h[0]: + c1 = connecting_to + elif handle is h[-1]: + c2 = connecting_to + else: + raise AttributeError, 'handle should be the first or the last handle of the CommentLine' - # We should not connect if both ends will become a Comment - if isinstance(c1.subject, UML.Comment) and \ - isinstance(c2.subject, UML.Comment): - return 0 - # Also do not connect if both ends are non-Comments - if not isinstance(c1.subject, UML.Comment) and \ - not isinstance(c2.subject, UML.Comment): - return 0 + # We should not connect if both ends will become a Comment + if isinstance(c1.subject, UML.Comment) and \ + isinstance(c2.subject, UML.Comment): + return 0 + # Also do not connect if both ends are non-Comments + if not isinstance(c1.subject, UML.Comment) and \ + not isinstance(c2.subject, UML.Comment): + return 0 - # Allow connection - return 1 + # Allow connection + return 1 def confirm_connect_handle (self, handle): - """ - This method is called after a connection is established. This method - sets the internal state of the line and updates the data model. - """ - #print 'confirm_connect_handle', handle - c1 = self.handles[0].connected_to - c2 = self.handles[-1].connected_to - if c1 and c2: - s1 = c1.subject - s2 = c2.subject - if isinstance (s1, UML.Comment): - s1.annotatedElement = s2 - elif isinstance (s2, UML.Comment): - s2.annotatedElement = s1 - else: - raise TypeError, 'One end of the CommentLine should connect to a Comment' + """See DiagramLine.confirm_connect_handle(). + """ + #print 'confirm_connect_handle', handle + c1 = self.handles[0].connected_to + c2 = self.handles[-1].connected_to + if c1 and c2: + s1 = c1.subject + s2 = c2.subject + if isinstance (s1, UML.Comment): + s1.annotatedElement = s2 + elif isinstance (s2, UML.Comment): + s2.annotatedElement = s1 + else: + raise TypeError, 'One end of the CommentLine should connect to a Comment' def allow_disconnect_handle (self, handle): - return 1 + """See DiagramLine.allow_disconnect_handle(). + """ + return 1 def confirm_disconnect_handle (self, handle, was_connected_to): - #print 'confirm_disconnect_handle', handle - c1 = None - c2 = None - if handle is self.handles[0]: - c1 = was_connected_to - c2 = self.handles[-1].connected_to - elif handle is self.handles[-1]: - c1 = self.handles[0].connected_to - c2 = was_connected_to - - if c1 and c2: - s1 = c1.subject - s2 = c2.subject - if isinstance (s1, UML.Comment): - del s1.annotatedElement[s2] - elif isinstance (s2, UML.Comment): - del s2.annotatedElement[s1] - else: - raise TypeError, 'One end of the CommentLine should connect to a Comment. How could this connect anyway?' + """See DiagramLine.confirm_disconnect_handle(). + """ + #print 'confirm_disconnect_handle', handle + c1 = None + c2 = None + if handle is self.handles[0]: + c1 = was_connected_to + c2 = self.handles[-1].connected_to + elif handle is self.handles[-1]: + c1 = self.handles[0].connected_to + c2 = was_connected_to + + if c1 and c2: + s1 = c1.subject + s2 = c2.subject + if isinstance (s1, UML.Comment): + del s1.annotatedElement[s2] + elif isinstance (s2, UML.Comment): + del s2.annotatedElement[s1] + else: + raise TypeError, 'One end of the CommentLine should connect to a Comment. How could this connect anyway?' gobject.type_register(CommentLineItem) diacanvas.set_callbacks(CommentLineItem) diff --git a/gaphor/diagram/dependency.py b/gaphor/diagram/dependency.py index 6309c2da8..d754cecae 100644 --- a/gaphor/diagram/dependency.py +++ b/gaphor/diagram/dependency.py @@ -10,37 +10,38 @@ import relationship class DependencyItem(relationship.RelationshipItem): - def __init__(self): - relationship.RelationshipItem.__init__(self) + def __init__(self, id=None): + relationship.RelationshipItem.__init__(self, id) self.set(dash=(7.0, 5.0), has_head=1, head_fill_color=0, head_a=0.0, head_b=15.0, head_c=6.0, head_d=6.0) # Gaphor Connection Protocol def find_relationship(self, head_subject, tail_subject): - for rel in head_subject.supplierDependency: - if rel.client is tail_subject: - return rel + """See RelationshipItem.find_relationship(). + """ + for supplier in head_subject.supplierDependency: + #print 'supplier', supplier, supplier.client, tail_subject + if tail_subject in supplier.client: + # Check if the dependency is not already in our diagram + for item in self.subject.presentation: + if item.canvas is self.canvas and item is not self: + break + else: + return supplier def allow_connect_handle(self, handle, connecting_to): - """ - This method is called by a canvas item if the user tries to connect - this object's handle. allow_connect_handle() checks if the line is - allowed to be connected. In this case that means that one end of the - line should be connected to a Comment. - Returns: TRUE if connection is allowed, FALSE otherwise. + """See RelationshipItem.allow_connect_handle(). """ try: - return isinstance(connecting_to.subject, UML.ModelElement) + return isinstance(connecting_to.subject, UML.NamedElement) except AttributeError: return 0 def confirm_connect_handle (self, handle): + """See RelationshipItem.confirm_connect_handle(). """ - This method is called after a connection is established. This method - sets the internal state of the line and updates the data model. - """ - #print 'confirm_connect_handle', handle + #print 'confirm_connect_handle', handle, self.subject c1 = self.handles[0].connected_to c2 = self.handles[-1].connected_to if c1 and c2: @@ -51,11 +52,13 @@ class DependencyItem(relationship.RelationshipItem): relation = GaphorResource(UML.ElementFactory).create(UML.Dependency) relation.supplier = s1 relation.client = s2 - self._set_subject(relation) + self.subject = relation def confirm_disconnect_handle (self, handle, was_connected_to): + """See RelationshipItem.confirm_disconnect_handle(). + """ #print 'confirm_disconnect_handle', handle if self.subject: - self._set_subject(None) + del self.subject gobject.type_register(DependencyItem) diff --git a/gaphor/diagram/diagramitem.py b/gaphor/diagram/diagramitem.py index 97cb6ccb7..ce3f66b34 100644 --- a/gaphor/diagram/diagramitem.py +++ b/gaphor/diagram/diagramitem.py @@ -1,39 +1,43 @@ # vim:sw=4:et +"""DiagramItem provides basic functionality for presentations. +Such as a modifier 'subject' property and a unique id. +""" from gaphor.UML import Element, Presentation from diacanvas import CanvasItem import gobject -import gaphor.UML.properties.umlproperty +from gaphor.UML.properties import association -class diagramassociation(gaphor.UML.properties.umlproperty): + +class diagramassociation(association): """Specialized association for use in diagram items. It has the same interface as the association property defined in gaphor/UML/properties.py, but delegates everything to the GObject property handlers. """ - def notify(self, obj): - # notification is done by set_property() - pass - - def load(self, value): - pass - - def save(self, save_func): - pass - def unlink(self, obj): - obj.set_property(self.name, None) - - def _get(self, obj): - return obj.get_property(self.name) + #print 'diagramassociation.unlink', obj, value + obj.preserve_property('subject') + association.unlink(self, obj) def _set2(self, obj, value): - obj.set_property(self.name, value) + #print 'diagramassociation._set2', obj, value + obj.preserve_property('subject') + if obj.canvas and obj.canvas.in_undo and len(value.presentation) == 0: + print 'diagramassociation._set2(): relinking!' + value.relink() + return association._set2(self, obj, value) def _del(self, obj, value): - obj.set_property(self.name, None) - + #print 'diagramassociation._del', obj, value, obj._subject + obj.preserve_property('subject') + association._del(self, obj, value) + if len(value.presentation) == 0 or \ + len(value.presentation) == 1 and obj in value.presentation: + #log.debug('diagramassociation._del: No more presentations: unlinking') + value.unlink() + class DiagramItem(Presentation): """Basic functionality for all model elements (lines and elements!). @@ -48,34 +52,82 @@ class DiagramItem(Presentation): class ModelElement(diacanvas.CanvasElement, DiagramItem): ... """ + __gproperties__ = { + 'subject': (gobject.TYPE_PYOBJECT, 'subject', + 'subject held by the model element', + gobject.PARAM_READWRITE), + } - # Prototype for signals emited by UML.Element - signal_prototype = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)) + __gsignals__ = { + '__unlink__': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, (gobject.TYPE_STRING,)) + } # Override the original subject as defined in UML.Presentation: + # Note that subject calls GObject.notify to emit changes subject = diagramassociation('subject', Element, upper=1, opposite='presentation') def __init__(self, id=None): - Presentation.__init__(self, id) - self.saved_subject = None + Presentation.__init__(self) + self._id = id self.handler_to_id = { } - self.connect('notify::parent', DiagramItem.on_parent_notify) - self.connect('__unlink__', self.on_unlink) + #self.connect('notify::parent', DiagramItem.on_parent_notify) + #self.connect('__unlink__', self.on_unlink) + + id = property(lambda self: self._id, doc='Id') + + def do_set_property(self, pspec, value): + if pspec.name == 'subject': + #print 'set subject:', value + if value: + self.subject = value + elif self.subject: + del self.subject + else: + raise AttributeError, 'Unknown property %s' % pspec.name + + def do_get_property(self, pspec): + if pspec.name == 'subject': + return self.subject + else: + raise AttributeError, 'Unknown property %s' % pspec.name # UML.Element interface used by properties: - def unlink(self): - """Send the unlink signal and remove itself from the canvas - """ - self.emit('__unlink__', '__unlink__') + def save(self, save_func): + if self.subject: + save_func('subject', self.subject) + + def load(self, name, value): + if name == 'subject': + self.subject = value + + def postload(self): + pass + + def unlink(self): + """Send the unlink signal and remove itself from the canvas. + """ + log.debug('DiagramItem.unlink(%s)' % self) + # emit the __unlink__ signal the way UML.Element would have done: + self.emit('__unlink__', '__unlink__') + # remove the subject if we have one + if self.subject: + del self.subject + self.set_property('parent', None) + + def relink(self): + """Relinking is done by popping the undo stack... + """ + log.info('RELINK DiagramItem') + #self.emit('__unlink__', '__relink__') - def on_unlink(self, signal_name): - if self.parent: - self.parent.remove(self) def connect(self, name, handler, *args): """Let UML.Element specific signals be handled by the Element and other signals by CanvasItem. + Note that in order to connect to the subject property, you have + to use "notify::subject". """ id = CanvasItem.connect(self, name, handler, *args) key = (handler,) + args @@ -90,7 +142,9 @@ class DiagramItem(Presentation): """Call GObject.disconnect() or UML.Element.disconnect() depending on if handler_or_id is an id or a method. """ - if type(handler_or_id) == type(1): + if isinstance(handler_or_id, int): + CanvasItem.disconnect(self, handler_or_id) + else: try: ids = self.handler_to_id[(handler_or_id,) + args] except KeyError, e: @@ -98,13 +152,16 @@ class DiagramItem(Presentation): else: for id in ids: CanvasItem.disconnect(self, id) - else: - CanvasItem.disconnect(self, handler_or_id) + del self.handler_to_id[(handler_or_id,) + args] - def notify(self, name): - """Emit a signal with as first argument the signal's name. - """ - self.emit(name, name) +# def notify(self, name): +# """Emit a signal with as first argument the signal's name. +# """ +# # Use GObject.notify() for the subject property +# if name == 'subject': +# DiaCanvasItem.notify(self, name) +# else: +# self.emit(name, name) def get_subject(self, x=None, y=None): """Get the subject that is represented by this diagram item. @@ -125,18 +182,8 @@ class DiagramItem(Presentation): """ save_func(name, self.get_property(name)) - def on_parent_notify (self, parent): - if self.parent and self.subject: - self.saved_subject = self.subject - del self.subject[self] - elif not self.parent and self.saved_subject: - self.subject = self.saved_subject - self.saved_subject = None - -# def on_subject_changed(self, name): -# pass - # DiaCanvasItem callbacks + def _on_glue(self, handle, wx, wy, parent_class): if handle.owner.allow_connect_handle (handle, self): #print self.__class__.__name__, 'Glueing allowed.' diff --git a/gaphor/diagram/diagramline.py b/gaphor/diagram/diagramline.py new file mode 100644 index 000000000..275a87024 --- /dev/null +++ b/gaphor/diagram/diagramline.py @@ -0,0 +1,112 @@ +# vim:sw=4:et +"""Basic functionality for line-like objects on a diagram. +""" + +import gobject +import diacanvas + +class DiagramLine(diacanvas.CanvasLine): + """Gaphor wrapper for lines.""" + + def __init__(self): + #diacanvas.CanvasLine.__init__(self) + self.__gobject_init__() + + def save (self, save_func): + for prop in ('affine', 'line_width', 'color', \ + 'cap', 'join', 'orthogonal', 'horizontal'): + save_func(prop, self.get_property(prop)) + points = [ ] + for h in self.handles: + pos = h.get_pos_i () + points.append (pos) + save_func('points', points) + c = self.handles[0].connected_to + if c: + save_func('head_connection', c) + c = self.handles[-1].connected_to + if c: + save_func ('tail_connection', c) + + def load (self, name, value): + if name == 'points': + points = eval(value) + self.set_property('head_pos', points[0]) + self.set_property('tail_pos', points[1]) + for p in points[2:]: + item.set_property ('add_point', p) + elif name == 'head_connection': + self._load_head_connection = value + elif name == 'tail_connection': + self._load_tail_connection = value + else: + self.set_property(name, eval(value)) + + def postload(self): + if hasattr(self, '_load_head_connection'): + self._load_head_connection.connect_handle (self.handles[0]) + del self._load_head_connection + if hasattr(self, '_load_tail_connection'): + self._load_tail_connection.connect_handle (self.handles[-1]) + del self._load_tail_connection + + def has_capability(self, capability): + #log.debug('Relationship: checking capability %s' % capability) + if capability == 'orthogonal': + return self.get_property('orthogonal') + elif capability == 'del_segment': + if self.get_property('orthogonal'): + return len(self.handles) > 3 + else: + return len(self.handles) > 2 + return False + + # Gaphor Connection Protocol + # + # The item a handle is connecting to is in charge of the connection + # cyclus. However it informs the item it is connecting to by means of + # the four methods defined below. The items that are trying to connect + # (mostly Relationship objects or CommentLines) know what kind of item + # they are allowed to connect to. + + def find_relationship(self, head_subject, tail_subject): + """Find an already existing relationship between head_subject and + tail_subject. The following things should be taken into account: + - The returned relationship object will be used for this item. + - The relationship should not already exist in the canvas. + """ + return None + + def allow_connect_handle(self, handle, connecting_to): + """This method is called by a canvas item if the user tries to + connect this object's handle. allow_connect_handle() checks if + the line is allowed to be connected. In this case that means + that one end of the line should be connected to a Relationship. + Returns: True if connection is allowed, False otherwise. + """ + return False + + def confirm_connect_handle (self, handle): + """This method is called after a connection is established. + This method sets the internal state of the line and updates + the data model. Returns nothing + """ + pass + + def allow_disconnect_handle (self, handle): + """ If a handle wants to disconnect, this method is called first. + This method is here mainly for the sake of completeness, since it + is quite unlikely that a handle is not allowed to disconnect. + """ + return 1 + + def confirm_disconnect_handle (self, handle, was_connected_to): + """This method is called to do some cleanup after 'self' has been + disconnected from 'was_connected_to'. + """ + pass + + +gobject.type_register(DiagramLine) +diacanvas.set_callbacks(DiagramLine) + diff --git a/gaphor/diagram/generalization.py b/gaphor/diagram/generalization.py index c067c6fed..5ca7709a4 100644 --- a/gaphor/diagram/generalization.py +++ b/gaphor/diagram/generalization.py @@ -10,60 +10,50 @@ import relationship class GeneralizationItem(relationship.RelationshipItem): - def __init__(self): - relationship.RelationshipItem.__init__(self) + def __init__(self, id=None): + relationship.RelationshipItem.__init__(self, id) self.set(has_head=1, head_fill_color=0, head_a=15.0, head_b=15.0, head_c=10.0, head_d=10.0) # Gaphor Connection Protocol def find_relationship(self, head_subject, tail_subject): + """See RelationshipItem.find_relationship(). """ - Find an existing relationship by iterating the 'specialization's - of @head_subject and check if the @tail_subject is the child. - This does not check if such a relationship is already on the canvas, - in which case a new relationship should be created. - """ - for rel in head_subject.specialization: - if rel.child is tail_subject: + for spec in head_subject.generalization: + if spec.general is tail_subject: # check for this entry on self.canvas - for item in rel.presentations(): + for item in spec.subject.presentation: # Allow self to be returned. Avoids strange # behaviour during loading if item.canvas is self.canvas and item is not self: break else: - return rel + return spec return None def allow_connect_handle(self, handle, connecting_to): - """ - This method is called by a canvas item if the user tries to connect - this object's handle. allow_connect_handle() checks if the line is - allowed to be connected. In this case that means that one end of the - line should be connected to a Comment. - Returns: TRUE if connection is allowed, FALSE otherwise. + """See RelationshipItem.allow_connect_handle(). """ try: - if not isinstance(connecting_to.subject, UML.GeneralizableElement): - return 0 + if not isinstance(connecting_to.subject, UML.Classifier): + return False c1 = self.handles[0].connected_to c2 = self.handles[-1].connected_to if not c1 and not c2: - return 1 + return True if self.handles[0] is handle: return (self.handles[-1].connected_to.subject is not connecting_to.subject) elif self.handles[-1] is handle: return (self.handles[0].connected_to.subject is not connecting_to.subject) assert 1, 'Should never be reached...' - except AttributeError: - return 0 + except AttributeError, e: + log.debug('Generalization.allow_connect_handle: %s' % e) + return False def confirm_connect_handle (self, handle): - """ - This method is called after a connection is established. This method - sets the internal state of the line and updates the data model. + """See RelationshipItem.confirm_connect_handle(). """ #print 'confirm_connect_handle', handle c1 = self.handles[0].connected_to @@ -74,13 +64,16 @@ class GeneralizationItem(relationship.RelationshipItem): relation = self.find_relationship(s1, s2) if not relation: relation = GaphorResource(UML.ElementFactory).create(UML.Generalization) - relation.parent = s1 - relation.child = s2 - self._set_subject(relation) + relation.general = s1 + relation.specific = s2 + self.subject = relation def confirm_disconnect_handle (self, handle, was_connected_to): + """See RelationshipItem.confirm_disconnect_handle(). + """ #print 'confirm_disconnect_handle', handle if self.subject: - self._set_subject(None) + del self.subject + gobject.type_register(GeneralizationItem) diff --git a/gaphor/diagram/modelelement.py b/gaphor/diagram/modelelement.py index 40a9ea83f..77cf22475 100644 --- a/gaphor/diagram/modelelement.py +++ b/gaphor/diagram/modelelement.py @@ -15,73 +15,49 @@ __date__ = '$date$' class ModelElementItem (diacanvas.CanvasElement, diacanvas.CanvasAbstractGroup, DiagramItem): + # Properties, also add the DiagramItem properties here. __gproperties__ = { - 'subject': (gobject.TYPE_PYOBJECT, 'subject', - 'subject held by the model element', - gobject.PARAM_READWRITE), 'auto-resize': (gobject.TYPE_BOOLEAN, 'auto resize', 'Set auto-resize for the diagram item', - 1, gobject.PARAM_READWRITE), + 1, gobject.PARAM_READWRITE) } + __gproperties__.update(DiagramItem.__gproperties__) - __gsignals__ = { '__unlink__': DiagramItem.signal_prototype, - '__relink__': DiagramItem.signal_prototype - } + __gsignals__ = DiagramItem.__gsignals__ def __init__(self, id=None): self.__gobject_init__() #diacanvas.CanvasElement.__init__(self) DiagramItem.__init__(self, id) self.auto_resize = 0 - self._subject = None def save(self, save_func): - self.save_property(save_func, 'affine') - self.save_property(save_func, 'width') - self.save_property(save_func, 'height') - save_func('subject', self.subject) - self.save_property(save_func, 'auto-resize') + for prop in ('affine', 'width', 'height', 'auto-resize'): + self.save_property(save_func, prop) + DiagramItem.save(self, save_func) def load(self, name, value): #if name in ( 'affine', 'width', 'height', 'auto-resize' ): #if name == 'subject': #self.set_property('subject', store.reference('subject')[0]) if name == 'subject': - self._subject = value + self.subject = value else: #log.debug('Setting unknown property "%s" -> "%s"' % (name, value)) self.set_property(name, eval(value)) - def postload(self): - pass - def do_set_property(self, pspec, value): - if pspec.name == 'subject': - #print 'Setting subject:', value - # property is preserved by self.subject's property - if value is not self._subject: - self.preserve_property('subject') - if self._subject: - self._subject.disconnect('__unlink__', self.__on_unlink, obj, value) - s = self._subject - self._subject = value - if len(s.presentation) == 0: - s.unlink() - if value: - value.connect('__unlink__', self.__on_unlink, obj, value) - elif pspec.name == 'auto-resize': + if pspec.name == 'auto-resize': self.preserve_property('auto-resize') self.auto_resize = value else: - raise AttributeError, 'Unknown property %s' % pspec.name + DiagramItem.do_set_property(self, pspec, value) def do_get_property(self, pspec): - if pspec.name == 'subject': - return self._subject - elif pspec.name == 'auto-resize': + if pspec.name == 'auto-resize': return self.auto_resize else: - raise AttributeError, 'Unknown property %s' % pspec.name + return DiagramItem.do_get_property(self, pspec) # Ensure we call the right connect functions: connect = DiagramItem.connect diff --git a/gaphor/diagram/package.py b/gaphor/diagram/package.py index 18d5db3d7..eb562db7a 100644 --- a/gaphor/diagram/package.py +++ b/gaphor/diagram/package.py @@ -3,57 +3,36 @@ PackageItem diagram item ''' # vim:sw=4 -from modelelement import ModelElementItem -import diacanvas import gobject import pango +import diacanvas -class PackageItem(ModelElementItem): +from classifier import ClassifierItem + +class PackageItem(ClassifierItem): TAB_X=50 TAB_Y=20 MARGIN_X=60 MARGIN_Y=30 - FONT='sans bold 10' - def __init__(self): - ModelElementItem.__init__(self) + def __init__(self, id=None): + ClassifierItem.__init__(self, id=None) self.set(height=50, width=100) - self.__border = diacanvas.shape.Path() - self.__border.set_line_width(2.0) - self.__name = diacanvas.CanvasText() - self.add_construction(self.__name) - assert self.__name != None - font = pango.FontDescription(PackageItem.FONT) - self.__name.set(font=font, width=self.width, multiline=0, - alignment=pango.ALIGN_CENTER) - # Center the text: - w, h = self.__name.get_property('layout').get_pixel_size() - self.__name.move(0, (self.height - h + PackageItem.TAB_Y) / 2) - self.__name.set(height=h) - self.__name.connect('text_changed', self.on_text_changed) - - def _set_subject(self, subject): - ModelElementItem._set_subject(self, subject) - self.__name.set(text=self.subject and self.subject.name or '') - self.request_update() - - def __name_update (self): - '''Center the name text in the package body.''' + self._border = diacanvas.shape.Path() + self._border.set_line_width(2.0) def on_update(self, affine): # Center the text - layout = self.__name.get_property('layout') - #layout.set_width(-1) + layout = self._name.get_property('layout') w, h = layout.get_pixel_size() self.set(min_width=w + PackageItem.MARGIN_X, min_height=h + PackageItem.MARGIN_Y) - a = self.__name.get_property('affine') + a = self._name.get_property('affine') aa = (a[0], a[1], a[2], a[3], a[4], \ (self.height - h + PackageItem.TAB_Y) / 2) - self.__name.set(affine=aa, width=self.width, height=h) + self._name.set(affine=aa, width=self.width, height=h) - self.update_child(self.__name, affine) - ModelElementItem.on_update(self, affine) + ClassifierItem.on_update(self, affine) o = 0.0 h = self.height @@ -61,62 +40,11 @@ class PackageItem(ModelElementItem): x = PackageItem.TAB_X y = PackageItem.TAB_Y line = ((x, y), (x, o), (o, o), (o, h), (w, h), (w, y), (o, y)) - self.__border.line(line) + self._border.line(line) self.expand_bounds(1.0) - def on_event (self, event): - if event.type == diacanvas.EVENT_KEY_PRESS: - self.__name.focus() - self.__name.on_event (event) - return True - else: - return ModelElementItem.on_event(self, event) + def on_shape_iter(self): + return iter([self._border]) - def on_get_shape_iter(self): - return self.__border - - def on_shape_next(self, iter): - return None - - def on_shape_value(self, iter): - return iter - - # Groupable - - def on_groupable_add(self, item): - return 0 - - def on_groupable_remove(self, item): - '''Do not allow the name to be removed.''' - #self.emit_stop_by_name('remove') - return 1 - - def on_groupable_get_iter(self): - return self.__name - - def on_groupable_next(self, iter): - return None - - def on_groupable_value(self, iter): - return iter - - def on_groupable_length(self): - return 1 - - def on_groupable_pos(self, item): - if item == self.__name: - return 0 - else: - return -1 - - def on_subject_update(self, name, old_value, new_value): - if name == 'name': - self.__name.set(text=self.subject.name) - else: - ModelElementItem.on_subject_update(self, name, old_value, new_value) - - def on_text_changed(self, text_item, text): - if text != self.subject.name: - self.subject.name = text gobject.type_register(PackageItem) diff --git a/gaphor/diagram/placementtool.py b/gaphor/diagram/placementtool.py index c167b6b34..f3d37b0d0 100644 --- a/gaphor/diagram/placementtool.py +++ b/gaphor/diagram/placementtool.py @@ -1,28 +1,29 @@ -# vim:sw=4: +# vim:sw=4:et import diacanvas import gaphor.UML as UML class PlacementTool(diacanvas.PlacementTool): def __init__(self, type, subject_class, **properties): - diacanvas.PlacementTool.__init__(self, type, **properties) - self.subject_class = subject_class - self.connect ('button_press_event', self.__button_press) - self.connect ('button_release_event', self.__button_release) + diacanvas.PlacementTool.__init__(self, type, **properties) + self.subject_class = subject_class + self.connect ('button_press_event', self.__button_press) + self.connect ('button_release_event', self.__button_release) def _create_item(self, view, event): - if event.button == 3: - return None - elemfact = GaphorResource('ElementFactory') - item = view.get_diagram().create(self.type) - if self.subject_class: - subject = elemfact.create(self.subject_class) - item.set_property ('subject', subject) - if isinstance(subject, UML.Namespace): - subject.package = view.get_diagram().namespace + if event.button == 3: + return None - if self.properties and len(self.properties) > 0: + elemfact = GaphorResource('ElementFactory') + item = view.get_diagram().create(self.type) + if self.subject_class: + subject = elemfact.create(self.subject_class) + item.set_property ('subject', subject) + if isinstance(subject, UML.Namespace): + subject.package = view.get_diagram().namespace + + if self.properties and len(self.properties) > 0: try: for (k,v) in self.properties.items(): item.set_property(k, v) @@ -31,10 +32,10 @@ class PlacementTool(diacanvas.PlacementTool): return item def __button_press (self, tool, view, event): - view.unselect_all() - return 0 + view.unselect_all() + return 0 def __button_release (self, tool, view, event): - view.set_tool (None) - return 0 + view.set_tool(None) + return 0 diff --git a/gaphor/diagram/relationship.py b/gaphor/diagram/relationship.py index 9ec2dd3c7..b246b084a 100644 --- a/gaphor/diagram/relationship.py +++ b/gaphor/diagram/relationship.py @@ -7,97 +7,44 @@ import gobject import diacanvas import gaphor.UML as UML from diagramitem import DiagramItem +from diagramline import DiagramLine -class RelationshipItem(diacanvas.CanvasLine, DiagramItem): - __gproperties__ = { - 'id': (gobject.TYPE_PYOBJECT, 'id', - 'Identification number of the canvas item', - gobject.PARAM_READWRITE), - 'subject': (gobject.TYPE_PYOBJECT, 'subject', - 'subject held by the relationship', - gobject.PARAM_READWRITE), - } - __savable_properties = [ 'affine', 'line-width', - 'color', 'cap', 'join', 'orthogonal', 'horizontal' ] - def __init__(self): - self.__gobject_init__() - #diacanvas.CanvasLine.__init__(self) - DiagramItem.__init__(self) - self.__id = None +class RelationshipItem(DiagramLine, DiagramItem): + __gproperties__ = DiagramItem.__gproperties__ - def set_id(self, value): - self.__id = value + __gsignals__ = DiagramItem.__gsignals__ + + def __init__(self, id=None): + DiagramLine.__init__(self) + DiagramItem.__init__(self, id) def save (self, save_func): - for prop in RelationshipItem.__savable_properties: - self.save_property(save_func, prop) - points = [ ] - for h in self.handles: - pos = h.get_pos_i () - points.append (pos) - save_func ('points', points) - c = self.handles[0].connected_to - if c: - save_func('head_connection', c) - c = self.handles[-1].connected_to - if c: - save_func('tail_connection', c) - if self.subject: - save_func('subject', self.subject) + DiagramItem.save(self, save_func) + DiagramLine.save(self, save_func) def load (self, name, value): - if name == 'points': - points = eval(value) - assert len(points) >= 2 - self.set_property('head_pos', points[0]) - self.set_property('tail_pos', points[1]) - for p in points[2:]: - self.set_property ('add_point', p) - elif name == 'head_connection': - self._load_head_connection = value - elif name == 'tail_connection': - self._load_tail_connection = value - elif name == 'subject': - self.set_property(name, value) - else: - #log.debug('Setting unknown property "%s" -> "%s"' % (name, value)) - self.set_property(name, eval(value)) + if name == 'subject': + self.subject = value + else: + super(RelationshipItem, self).load(name, value) def postload(self): - if hasattr(self, '_load_head_connection'): - self._load_head_connection.connect_handle (self.handles[0]) - del self._load_head_connection - if hasattr(self, '_load_tail_connection'): - self._load_tail_connection.connect_handle (self.handles[-1]) - del self._load_tail_connection + super(RelationshipItem, self).postload() - def do_set_property (self, pspec, value): - if pspec.name == 'id': - self.__id = value - elif pspec.name == 'subject': - self._set_subject(value) - else: - raise AttributeError, 'Unknown property %s' % pspec.name +# def do_set_property (self, pspec, value): +# DiagramItem.do_set_property(self, pspec, value) - def do_get_property(self, pspec): - if pspec.name == 'id': - return self.__id - elif pspec.name == 'subject': - return self.subject - else: - raise AttributeError, 'Unknown property %s' % pspec.name +# def do_get_property(self, pspec): +# return DiagramItem.do_get_property(self, pspec) + + # Ensure we call the right connect functions: + connect = DiagramItem.connect + disconnect = DiagramItem.disconnect def has_capability(self, capability): - #log.debug('Relationship: checking capability %s' % capability) - if capability == 'orthogonal': - return self.get_property('orthogonal') - elif capability == 'del_segment': - if self.get_property('orthogonal'): - return len(self.handles) > 3 - else: - return len(self.handles) > 2 - return DiagramItem.has_capability(self, capability) + return super(RelationshipItem, self).has_capability(capability) + # DiaCanvasItem callbacks def on_glue(self, handle, wx, wy): return self._on_glue(handle, wx, wy, diacanvas.CanvasLine) @@ -107,49 +54,6 @@ class RelationshipItem(diacanvas.CanvasLine, DiagramItem): def on_disconnect_handle (self, handle): return self._on_disconnect_handle(handle, diacanvas.CanvasLine) - # Gaphor Connection Protocol - # - # The item a handle is connecting to is in charge of the connection - # cyclus. However it informs the item it is connecting to by means of - # the four methods defined below. The items that are trying to connect - # (mostly Relationship objects or CommentLines) know what kind of item - # they are allowed to connect to. - - def find_relationship(self, head_subject, tail_subject): - pass - - def allow_connect_handle(self, handle, connecting_to): - """ - This method is called by a canvas item if the user tries to connect - this object's handle. allow_connect_handle() checks if the line is - allowed to be connected. In this case that means that one end of the - line should be connected to a Relationship. - Returns: TRUE if connection is allowed, FALSE otherwise. - """ - return 0 - - def confirm_connect_handle (self, handle): - """ - This method is called after a connection is established. This method - sets the internal state of the line and updates the data model. - """ - pass - - def allow_disconnect_handle (self, handle): - """ - If a handle wants to disconnect, this method is called first. This - method is here mainly for the sake of completeness, since it is - quite unlikely that a handle is not allowed to disconnect. - """ - return 1 - - def confirm_disconnect_handle (self, handle, was_connected_to): - """ - This method is called to do some cleanup after 'self' has been - disconnected from 'was_connected_to'. - """ - pass - gobject.type_register(RelationshipItem) diacanvas.set_callbacks(RelationshipItem) diff --git a/gaphor/diagram/usecase.py b/gaphor/diagram/usecase.py index 0a38c1134..20292f4fd 100644 --- a/gaphor/diagram/usecase.py +++ b/gaphor/diagram/usecase.py @@ -3,107 +3,43 @@ eseCaseItempango diagram item ''' # vim:sw=4 -from modelelement import ModelElementItem -import diacanvas import gobject import pango +import diacanvas -class UseCaseItem(ModelElementItem): +from classifier import ClassifierItem + +class UseCaseItem(ClassifierItem): MARGIN_X=60 MARGIN_Y=30 - FONT='sans bold 10' - def __init__(self): - ModelElementItem.__init__(self) + def __init__(self, id=None): + ClassifierItem.__init__(self, id) self.set(height=50, width=100) - self.__border = diacanvas.shape.Ellipse() - self.__border.set_line_width(2.0) - self.__name = diacanvas.CanvasText() - self.add_construction(self.__name) - assert self.__name != None - font = pango.FontDescription(UseCaseItem.FONT) - self.__name.set(font=font, width=self.width, #multiline=0, - alignment=pango.ALIGN_CENTER) - self.__name.connect('text_changed', self.on_text_changed) + self._border = diacanvas.shape.Ellipse() + self._border.set_line_width(2.0) - def _set_subject(self, subject): - ModelElementItem._set_subject(self, subject) - self.__name.set(text=self.subject and self.subject.name or '') - self.request_update() + # DiaCanvasItem callbacks: def on_update(self, affine): # Center the text - layout = self.__name.get_property('layout') + layout = self._name.get_property('layout') #layout.set_width(-1) w, h = layout.get_pixel_size() self.set(min_width=w + UseCaseItem.MARGIN_X, min_height=h + UseCaseItem.MARGIN_Y) - a = self.__name.get_property('affine') + a = self._name.get_property('affine') aa = (a[0], a[1], a[2], a[3], 0.0, (self.height - h) / 2) - self.__name.set(affine=aa, width=self.width, height=h) + self._name.set(affine=aa, width=self.width, height=h) - self.update_child(self.__name, affine) - ModelElementItem.on_update(self, affine) + ClassifierItem.on_update(self, affine) - self.__border.ellipse(center=(self.width / 2, self.height / 2), + self._border.ellipse(center=(self.width / 2, self.height / 2), width=self.width, height=self.height) self.expand_bounds(1.0) - def on_event (self, event): - if event.type == diacanvas.EVENT_KEY_PRESS: - self.__name.focus() - self.__name.on_event (event) - return True - else: - return ModelElementItem.on_event(self, event) + def on_shape_iter(self): + return iter([self._border]) - def on_get_shape_iter(self): - return self.__border - - def on_shape_next(self, iter): - return None - - def on_shape_value(self, iter): - return iter - - # Groupable - - def on_groupable_add(self, item): - return 0 - - def on_groupable_remove(self, item): - '''Do not allow the name to be removed.''' - #self.emit_stop_by_name('remove') - return 1 - - def on_groupable_get_iter(self): - return self.__name -# return None - - def on_groupable_next(self, iter): - return None - - def on_groupable_value(self, iter): - return iter - - def on_groupable_length(self): - return 1 - - def on_groupable_pos(self, item): - if item == self.__name: - return 0 - else: - return -1 - - def on_subject_update(self, name, old_value, new_value): - if name == 'name': - self.__name.set(text=self.subject.name) - self.request_update() - else: - ModelElementItem.on_subject_update(self, name, old_value, new_value) - - def on_text_changed(self, text_item, text): - if text != self.subject.name: - self.subject.name = text gobject.type_register(UseCaseItem) diff --git a/gaphor/storage.py b/gaphor/storage.py index b55b98985..19dc0ec37 100644 --- a/gaphor/storage.py +++ b/gaphor/storage.py @@ -14,7 +14,7 @@ verify(filename) import UML import parser import diagram -#import diacanvas +import diacanvas import os.path import types #import xml.dom.minidom as dom @@ -27,40 +27,56 @@ def save(filename=None, factory=None): standard out is used.''' from cStringIO import StringIO from xml.sax.saxutils import escape - #from gaphor.UML import collection - #from element import Element + + def save_reference(name, value): + # Save a reference to the object: + buffer.write('\n' % (name, value.id)) + + def save_value(name, value): + buffer.write('' % name) + if isinstance(value, types.StringTypes): + buffer.write('' % value.replace(']]>', '] ]>')) + else: + buffer.write(escape(str(value))) + buffer.write('\n') def save_element(name, value): - if isinstance (value, UML.Element): - # Save a reference to the object: - buffer.write('\n' % (name, value.id)) + if isinstance (value, (UML.Element, diacanvas.CanvasItem)): + save_reference(name, value) elif isinstance(value, UML.collection): # Save a list of references: #buffer.write('') for v in value: - save_element(name, v) + save_reference(name, v) #buffer.write('') elif isinstance(value, diacanvas.Canvas): buffer.write('') - value.save(save_element) + value.save(save_canvasitem) buffer.write('') + else: + save_value(name, value) + + def save_canvasitem(name, value): + if isinstance (value, UML.Element): + save_reference(name, value) + elif isinstance(value, UML.collection): + # Save a list of references: + #buffer.write('') + for v in value: + save_reference(name, v) + #buffer.write('') elif isinstance(value, diacanvas.CanvasItem): - buffer.write('') + buffer.write('' % (value.id, value.__class__.__name__)) value.save(save_element) buffer.write('') else: - buffer.write('' % name) - if isinstance(value, types.StringTypes): - buffer.write('' % value.replace(']]>', '] ]>')) - else: - buffer.write(escape(str(value))) - buffer.write('\n') + save_value(name, value) if not factory: factory = GaphorResource(UML.ElementFactory) buffer = StringIO() - buffer.write('\n') + buffer.write('\n') buffer.write('' % FILE_FORMAT_VERSION) for e in factory.values(): @@ -96,8 +112,8 @@ def _load (elements, factory): elem.element = factory.create_as (type, id) elif isinstance(elem, parser.canvasitem): cls = getattr(diagram, elem.type) - elem.element = cls() - elem.element.set_property('id', id) + elem.element = cls(id) + #elem.element.id = idset_property('id', id) # Add a canvas property so we can be lasy and do not check... elem.canvas = None @@ -108,7 +124,7 @@ def _load (elements, factory): # establish parent/child relations on canvas items: if elem.canvas: for item in elem.canvas.canvasitems: - print 'item:', item.element + #print 'item:', item.element item.element.set_property('parent', elem.element.canvas.root) if isinstance(elem, parser.canvasitem): for item in elem.canvasitems: @@ -145,7 +161,8 @@ def load (filename, factory=None): except Exception, e: print e log.error('File could no be parsed') - raise GaphorError, 'File %s is probably no valid XML.' % filename + raise + #raise GaphorError, 'File %s is probably no valid XML.' % filename try: # For some reason, loading the model in a temp. factory will diff --git a/gaphor/ui/command/diagram.py b/gaphor/ui/command/diagram.py index d57054b44..3777d0ee5 100644 --- a/gaphor/ui/command/diagram.py +++ b/gaphor/ui/command/diagram.py @@ -157,7 +157,7 @@ class DeleteCommand(Command): self._view.canvas.push_undo('delete') items = self._view.selected_items for i in items: - i.item.parent.remove(i.item) + i.item.unlink() CommandInfo (name='EditDelete', _label='_Delete', pixname='gtk-delete', context='diagram.menu', accel='*Control*d', diff --git a/gaphor/ui/diagramwindow.py b/gaphor/ui/diagramwindow.py index 55e38499b..8d49d8d75 100644 --- a/gaphor/ui/diagramwindow.py +++ b/gaphor/ui/diagramwindow.py @@ -76,9 +76,8 @@ class DiagramWindow(AbstractWindow): params={ 'window': self }) def _on_window_destroy(self, window): - """ - Window is destroyed. Do the same thing that would be done if - File->Close was pressed. + """Window is destroyed. Do the same thing that would + be done if File->Close was pressed. """ AbstractWindow._on_window_destroy(self, window) # Set diagram to None, so all refrences to the diagram are destroyed. @@ -141,9 +140,12 @@ class DiagramWindow(AbstractWindow): log.debug('snap_to_grid: %s' % canvas.get_property('snap_to_grid')) self.set_capability('snap_to_grid', canvas.get_property('snap_to_grid')) - def __on_diagram_event(self, name, old, new): - if name == 'name': - self.get_window().set_title(new) - elif name == '__unlink__': + def __on_diagram_event(self, element, pspec): + if pspec == '__unlink__': self.close() - + else: + try: + if pspec.name == 'name': + self.get_window().set_title(self.__diagram.name or '') + except: + pass diff --git a/gaphor/ui/mainwindow.py b/gaphor/ui/mainwindow.py index 23450eef0..4601916fd 100644 --- a/gaphor/ui/mainwindow.py +++ b/gaphor/ui/mainwindow.py @@ -19,7 +19,7 @@ class MainWindow(AbstractWindow): self.__transient_windows = list() # Act on changes in the element factory resource factory = GaphorResource(UML.ElementFactory) - factory.connect(self.__on_element_factory_signal, factory) + factory.connect(self.__on_element_factory_signal) def get_model(self): self._check_state(AbstractWindow.STATE_ACTIVE) @@ -116,7 +116,8 @@ class MainWindow(AbstractWindow): def __on_transient_window_notify_title(self, window): pass - def __on_element_factory_signal(self, key, obj, factory): - print '__on_element_factory_signal', key + def __on_element_factory_signal(self, obj, key): + #print '__on_element_factory_signal', key + factory = GaphorResource(UML.ElementFactory) self.set_capability('model', not factory.is_empty()) diff --git a/gaphor/ui/namespace.py b/gaphor/ui/namespace.py index cf10a3a91..3b31f47c3 100644 --- a/gaphor/ui/namespace.py +++ b/gaphor/ui/namespace.py @@ -1,4 +1,4 @@ -# vim: sw=4:et +# vim: sw=4:et:ai """This is the TreeView that is most common (for example: it is used in Rational Rose). This is a tree based on namespace relationships. As a result only classifiers are shown here. @@ -31,7 +31,7 @@ class NamespaceModel(gtk.GenericTreeModel): self.factory = factory - factory.connect(self.on_factory_signals, factory) + factory.connect(self.on_factory_signals) self.root = (None, []) @@ -44,19 +44,19 @@ class NamespaceModel(gtk.GenericTreeModel): path = self.path_from_element(element) #print 'new_node_from_element', path self.row_inserted(path, self.get_iter(path)) - element.connect('name', self.on_name_changed, element) + element.connect('name', self.on_name_changed) if isinstance(element, UML.Namespace): - element.connect('ownedMember', self.on_ownedmember_changed, element) + element.connect('ownedMember', self.on_ownedmember_changed) for om in element.ownedMember: self.new_node_from_element(om, node) return node def detach_notifiers_from_node(self, node): """Detach notifiers for node""" - node[0].disconnect('name', self.on_name_changed, node[0]) + node[0].disconnect(self.on_name_changed) if isinstance(node[0], UML.Namespace): - node[0].disconnect('ownedMember', self.on_ownedmember_changed, node[0]) + node[0].disconnect(self.on_ownedmember_changed) for child in node[1]: self.detach_notifiers_from_node(child) @@ -70,6 +70,8 @@ class NamespaceModel(gtk.GenericTreeModel): if child[0] is element: break; index += 1 + else: + raise AttributeError, 'Element %s is not part of the NamespaceModel' % element #print 'parent_path', parent_node[1], parent_path + (index,) return child, parent_path + (index,) else: @@ -115,7 +117,7 @@ class NamespaceModel(gtk.GenericTreeModel): doit(child, depth + 1) doit(self.root) - def on_name_changed(self, name, element): + def on_name_changed(self, element, pspec): """the name of element has changed, update the tree model""" path = self.path_from_element(element) if path: @@ -128,30 +130,37 @@ class NamespaceModel(gtk.GenericTreeModel): new_order = [] for o in original: new_order.append(children.index(o)) - self.rows_reordered(parent_path, self.get_iter(parent_path), new_order) +#TODO: self.rows_reordered(parent_path, self.get_iter(parent_path), new_order) - def on_ownedmember_changed(self, name, element): + def on_ownedmember_changed(self, element, pspec): """update the tree model when the ownedMember list changes. - Element is the object whose ownedMember property has changed.""" + Element is the object whose ownedMember property has changed. + """ + print 'on_ownedmember_changed', element node = self.node_from_element(element) + # The elements currently known by the tree model node_members = map(lambda e: e[0], node[1]) + # The elements that are now children of the element owned_members = element.ownedMember - if len(node[1]) < len(owned_members): + if len(node_members) < len(owned_members): # element added - print 'element added' + print 'NamespaceModel: element added' for om in owned_members: if om not in node_members: # we have found the newly added element self.new_node_from_element(om, node) assert len(node[1]) == len(owned_members) break - elif len(node[1]) > len(owned_members): + elif len(node_members) > len(owned_members): # element removed print 'element removed', element.name, element.namespace, node + index = 0 for child in node[1]: if child[0] not in owned_members: # we have found the removed element - path = self.path_from_element(child[0]) + #path = self.path_from_element(child[0]) + path = self.path_from_element(element) + (index,) + log.debug('NS: removing node "%s" from "%s" (path=%s)' % (child[0].name, element.name, path)) print 'path=', child[0].name, path try: self.detach_notifiers_from_node(child) @@ -163,24 +172,26 @@ class NamespaceModel(gtk.GenericTreeModel): import traceback traceback.print_exc() break + index += 1 if len(owned_members) == 0: path = self.path_from_element(element) self.row_has_child_toggled(path, self.get_iter(path)) - #else: - # print 'model is in sync' + else: + print 'model is in sync for "%s"' % element.name - def on_factory_signals (self, key, obj, factory): - if key == 'model': + def on_factory_signals (self, obj, pspec): + if pspec == 'model': toplevel = self.factory.select(lambda e: isinstance(e, UML.Namespace) and not e.namespace) - #if len(toplevel) != 1: - # log.error('Multiple toplevel namespace elements, can handle only one') - for t in toplevel: - print 'factory::model toplevel', t - self.new_node_from_element(t, self.root) - print 'self.root', self.root - elif key == 'flush': - for i in self.root: + for t in toplevel: + #print 'factory::model toplevel', t, t.name + self.new_node_from_element(t, self.root) + #print 'self.root', self.root + + elif pspec == 'flush': + for i in self.root[1]: + self.detach_notifiers_from_node(i) + # remove the node, it is now the first in the list: self.row_deleted((0,)) self.root = (None, []) diff --git a/gaphor/ui/stock.py b/gaphor/ui/stock.py index b3f51287c..97cb561de 100644 --- a/gaphor/ui/stock.py +++ b/gaphor/ui/stock.py @@ -28,7 +28,7 @@ _default_stock_icons = ( (STOCK_ACTOR, UML.Actor, 'actor24.png', 'actor16.png'), (STOCK_ASSOCIATION, UML.Association, 'association24.png', 'association16.png'), (STOCK_CLASS, UML.Class, 'class24.png', 'class16.png'), -# (STOCK_DEPENDENCY, UML.Dependency, 'dependency24.png', 'dependency16.png'), + (STOCK_DEPENDENCY, UML.Dependency, 'dependency24.png', 'dependency16.png'), (STOCK_DIAGRAM, UML.Diagram, 'diagram24.png', 'diagram16.png'), # (STOCK_EXTEND, UML.Extend, None), (STOCK_COMMENT, UML.Comment, 'comment24.png', 'comment16.png'), diff --git a/setup.py b/setup.py index 074e0f8a2..b6d609b89 100644 --- a/setup.py +++ b/setup.py @@ -212,12 +212,15 @@ class run_Gaphor(Command): ('build-dir=', None, ''), ('command=', 'c', 'execute command'), ('file=', 'f', 'execute file'), + ('testfile=', 't', 'execute unittest file'), ] def initialize_options(self): self.build_lib = None self.command = None self.file = None + self.testfile = None + self.verbosity = 2 def finalize_options(self): self.set_undefined_options('build', @@ -233,8 +236,19 @@ class run_Gaphor(Command): if self.command: print 'Executing command: %s...' % self.command exec self.command + elif self.testfile: + # Running a unit test is done by opening the unit test file + # as a module and running the tests within that module. + print 'Running test cases in unittest file: %s...' % self.testfile + import imp, unittest + fp = open(self.testfile) + test_module = imp.load_source('gaphor_test', self.testfile, fp) + test_suite = unittest.TestLoader().loadTestsFromModule(test_module) + test_runner = unittest.TextTestRunner(verbosity=self.verbosity) + result = test_runner.run(test_suite) + sys.exit(not result.wasSuccessful()) elif self.file: - print 'Starting execution of file: %s...' % self.file + print 'Executing file: %s...' % self.file execfile(self.file, {}) else: Gaphor().main() diff --git a/tests/test-ns.py b/tests/test-ns.py index a2cfb7ad4..e19b419dc 100644 --- a/tests/test-ns.py +++ b/tests/test-ns.py @@ -1,249 +1,106 @@ -# vim:sw=4:et +# vim:sw=4:et:ai + import gaphor.UML as UML -import pdb +from gaphor.ui.namespace import NamespaceModel -# TODO: -# row_has_child_toggled() when no more ownedMembers +factory = UML.ElementFactory() -class NamespaceModel: - """The NamespaceModel holds a view on the data model based on namespace - relationships (such as a Package containing a Class).""" +m = factory.create(UML.Package) +m.name = 'm' +a = factory.create(UML.Package) +a.name = 'a' +a.package = m +assert a.package is m +assert a in m.ownedMember +assert a.namespace is m - def __init__(self, model): - self.root = self.new_node_from_element(model) +b = factory.create(UML.Package) +b.name = 'b' +b.package = a +assert b in a.ownedMember +assert b.namespace is a - def new_node_from_element(self, element): - """Create a new node for an element. Owned members are also created.""" - node = (element, []) - if isinstance(element, UML.Namespace): - element.attach('ownedMember', self.on_ownedmember_changed, element) - element.attach('name', self.on_name_changed, element) - for om in element.ownedMember: - node[1].append(self.new_node_from_element(om)) - self.sort_node(node) - return node +c = factory.create(UML.Class) +c.name = 'c' +c.package = b +d = factory.create(UML.Class) +d.name = 'd' +d.package = a +e = factory.create(UML.Class) +e.name = 'e' +e.package = b - def detach_notifiers_from_node(self, node): - """Detach notifiers for node""" - node[0].detach('ownedMember', self.on_ownedmember_changed, node[0]) - node[0].detach('name', self.on_name_changed, node[0]) - for child in node[1]: - self.detach_notifiers_from_node(child) +assert c in b.ownedMember +assert c.namespace is b +assert d in a.ownedMember +assert d.namespace is a +assert e in b.ownedMember +assert e.namespace is b - def node_and_path_from_element(self, element): - """Return (node, path) of an element.""" - assert isinstance(element, UML.NamedElement) - if element.namespace: - parent_node, parent_path = self.node_and_path_from_element(element.namespace) - index = 0 - for child in parent_node[1]: - if child[0] is element: - break; - index += 1 - return child, parent_path + (index,) - else: - return self.root, () - def node_from_element(self, element): - """Get the node for an element.""" - return self.node_and_path_from_element(element)[0] +ns = NamespaceModel(factory) +# We have a model loaded. Use it! +factory.notify_model() - def path_from_element(self, element): - """Get the path to an element as a tuple (e.g. (0, 1, 1)).""" - return self.node_and_path_from_element(element)[1] +print '---' +ns.dump() +assert ns.path_from_element(m) == (0,) +assert ns.path_from_element(a) == (0, 0) +assert ns.path_from_element(b) == (0, 0, 0) +assert ns.path_from_element(c) == (0, 0, 0, 0) +assert ns.path_from_element(d) == (0, 0, 1) +assert ns.path_from_element(e) == (0, 0, 0, 1) - def node_from_path(self, path): - """Get the node form a path.""" - try: - node = self.root - for index in path: - node = node[1][index] - return node - except IndexError: - print 'No path to node %s' % path +print '--- del.b.ownedClassifier[c]' +del b.ownedClassifier[c] +ns.dump() +assert ns.path_from_element(m) == (0,) +assert ns.path_from_element(a) == (0, 0) +assert ns.path_from_element(b) == (0, 0, 0) +assert ns.path_from_element(d) == (0, 0, 1) +assert ns.path_from_element(e) == (0, 0, 0, 0) +try: + ns.path_from_element(c) +except AttributeError: + pass # Yes, should raise an exception +else: + assert ns.path_from_element(c) is not None - def sort_node(self, node): - """Sort nodes based on their names.""" - node[1].sort(lambda a, b: a[0].name == b[0].name and 0 or \ - a[0].name < b[0].name and -1 or 1) +print '--- c.package = a' +c.package = a +ns.dump() +assert ns.path_from_element(m) == (0,) +assert ns.path_from_element(a) == (0, 0) +assert ns.path_from_element(b) == (0, 0, 0) +assert ns.path_from_element(c) == (0, 0, 1) +assert ns.path_from_element(d) == (0, 0, 2) +assert ns.path_from_element(e) == (0, 0, 0, 0) - def dump(self): - def doit(node, depth=0): - print ' `' * depth, node[0].name - for child in node[1]: - doit(child, depth + 1) - doit(self.root) +print '--- b.package = m' +b.package = m +ns.dump() +assert ns.path_from_element(m) == (0,) +assert ns.path_from_element(a) == (0, 0) +assert ns.path_from_element(b) == (0, 1) +assert ns.path_from_element(c) == (0, 0, 0) +assert ns.path_from_element(d) == (0, 0, 1) +assert ns.path_from_element(e) == (0, 1, 0) - def on_name_changed(self, name, element): - """the name of element has changed, update the tree model""" - path = self.path_from_element(element) - if path: - self.row_changed(path, self.get_iter(path)) - parent_node, parent_path = self.node_and_path_from_element(element.namespace) - original = list(parent_node[1]) - #print 'original', original - self.sort_node(parent_node) - children = parent_node[1] - #print 'original', original - #print 'children', children - if children != original: - new_order = [] - for o in original: - new_order.append(children.index(o)) - #print 'new_order', new_order - self.rows_reordered(parent_path, self.get_iter(parent_path), new_order) +print '--- e.unlink()' +e.unlink() +ns.dump() - def on_ownedmember_changed(self, name, element): - """update the tree model when the ownedMember list changes.""" - node = self.node_from_element(element) - node_members = map(lambda e: e[0], node[1]) - owned_members = element.ownedMember - if len(node[1]) < len(owned_members): - # element added - for om in owned_members: - if om not in node_members: - # we have found the newly added element - node[1].append(self.new_node_from_element(om)) - self.sort_node(node) - path = self.path_from_element(om) - self.row_inserted(path, self.get_iter(path)) - assert len(node[1]) == len(owned_members) - break - elif len(node[1]) > len(owned_members): - # element removed - for child in node[1]: - if child[0] not in owned_members: - # we have found the removed element - path = self.path_from_element(child[0]) - self.detach_notifiers_from_node(child) - node[1].remove(child) - self.row_deleted(path) - assert len(node[1]) == len(owned_members) - break - if len(owned_members) == 0: - path = self.path_from_element(element) - self.row_has_child_toggled(path, self.get_iter(path)) - #else: - # print 'model is in sync' +print '--- a.unlink()' +def on_unlink(name, element): + print 'unlink: %s' % element.name +a.connect('__unlink__', on_unlink, a) +b.connect('__unlink__', on_unlink, b) +c.connect('__unlink__', on_unlink, c) +d.connect('__unlink__', on_unlink, d) - # TreeModel methods: +a.unlink() +ns.dump() +print '--- TODO: e.relink()' - def on_get_flags(self): - """returns the GtkTreeModelFlags for this particular type of model""" - return 0 - - def on_get_n_columns(self): - """returns the number of columns in the model""" - return 1 - - def on_get_column_type(self, index): - """returns the type of a column in the model""" - return gobject.TYPE_PYOBJECT - - def on_get_path (self, node): - """returns the path for a node as a tuple (0, 1, 1)""" - return self.path_from_element(node[0]) - - def on_get_iter(self, path): - """returns the node corresponding to the given path. - The path is a tuple of values, like (0 1 1).""" - return self.node_from_path(path) - - def on_get_value(self, node, column): - """returns the model element that matches 'node'.""" - assert column == 0 - return node[0] - - def on_iter_next(self, node): - """returns the next node at this level of the tree""" - #print 'on_iter_next:', node, node.namespace - try: - parent = node_from_element(node[0].namespace) - index = parent[1].index(node) - return parent.ownedElement[index + 1] - except IndexError: - return None - - def on_iter_has_child(self, node): - """returns true if this node has children""" - #print 'on_iter_has_child', node - return len(node[1]) > 0 - - def on_iter_children(self, node): - """returns the first child of this node""" - #print 'on_iter_children' - return node[1][0] - - def on_iter_n_children(self, node): - """returns the number of children of this node""" - #print 'on_iter_n_children' - return len (node[1]) - - def on_iter_nth_child(self, node, n): - """returns the nth child of this node""" - #print "on_iter_nth_child", node, n - try: - return node[1][n] - except IndexError: - return None - - def on_iter_parent(self, node): - """returns the parent of this node""" - #print "on_iter_parent", node - return self.node_from_element(node[0].namespace) - -if __name__ == '__main__': - m = UML.Package() - m.name = 'm' - a = UML.Package() - a.name = 'a' - a.package = m - b = UML.Package() - b.name = 'b' - b.package = a - c = UML.Class() - c.name = 'c' - c.package = b - d = UML.Class() - d.name = 'd' - d.package = a - e = UML.Class() - e.name = 'e' - e.package = b - - print 'm', m - print 'a', a - print 'b', b - print 'c', c - print 'd', d - print 'e', e - - ns = NamespaceModel(m) - - print 'a', ns.path_from_element(a) - print 'b', ns.path_from_element(b) - print 'c', ns.path_from_element(c) - print 'd', ns.path_from_element(d) - print 'e', ns.path_from_element(e) - - print a, ns.node_from_path(ns.path_from_element(a)) - print b, ns.node_from_path(ns.path_from_element(b)) - print c, ns.node_from_path(ns.path_from_element(c)) - print d, ns.node_from_path(ns.path_from_element(d)) - print e, ns.node_from_path(ns.path_from_element(e)) - - print '---' - ns.dump() - print '---' - del b.ownedClassifier[c] - ns.dump() - print '---' - c.package = a - ns.dump() - print '---' - #del a.ownedClassifier[b] - b.package = m - ns.dump() - print '---' - d.name = 'aa' - ns.dump() +print UML.Class.package +print UML.Package.ownedClassifier diff --git a/tests/test-properties.py b/tests/test-properties.py index df3656c94..a654e6d55 100644 --- a/tests/test-properties.py +++ b/tests/test-properties.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# vim:sw=4:et +# vim:sw=4:et:ai from gaphor.UML.properties import * from gaphor.UML.element import Element @@ -83,8 +83,8 @@ def test_associations(): class B(Element): pass class C(Element): pass - A.one = association('one', B, 0, 1, opposite='two') - B.two = association('two', A, 0, '*', opposite='one') + A.one = association('one', B, lower=0, upper=1, opposite='two') + B.two = association('two', A, lower=0, upper='*', opposite='one') a1 = A() a2 = A() @@ -92,11 +92,14 @@ def test_associations(): b2 = B() b1.two = a1 + assert len(b1.two) == 1, 'len(b1.two) == %d' % len(b1.two) + assert a1 in b1.two + assert a1.one is b1, '%s/%s' % (a1.one, b1) b1.two = a1 b1.two = a1 assert len(b1.two) == 1, 'len(b1.two) == %d' % len(b1.two) assert a1 in b1.two - assert a1.one is b1 + assert a1.one is b1, '%s/%s' % (a1.one, b1) assert len(a1._observers.get('__unlink__')) == 1 assert len(b1._observers.get('__unlink__')) == 1 @@ -228,8 +231,8 @@ def test_associations(): # also the signal should be removed b1.unlink() - assert len(a1._observers.get('__unlink__')) == 1 - assert len(b1._observers.get('__unlink__')) == 1 + assert len(a1._observers.get('__unlink__')) == 1, a1._observers.get('__unlink__') + #assert len(b1._observers.get('__unlink__')) == 0, b1._observers.get('__unlink__') assert b1 not in a1.one assert b2 in a1.one @@ -303,7 +306,7 @@ def test_derivedunion(): A.a = association('a', A) A.b = association('b', A, 0, 1) - A.u = derivedunion('u', A.a, A.b) + A.u = derivedunion('u', 0, '*', A.a, A.b) a = A() assert len(a.a) == 0, 'a.a = %s' % a.a @@ -334,17 +337,39 @@ def test_derivedunion(): self.notified = True E.a = association('a', A) - E.u = derivedunion('u', E.a) + E.u = derivedunion('u', 0, '*', E.a) e = E() assert e.notified == False e.a = a assert e.notified == True -if __name__ == '__main__': - test_associations() - test_attributes() - test_enumerations() - test_notify() - test_derivedunion() - print 'All tests passed.' +def test_composite(): + class A(Element): + is_unlinked = False + def unlink(self): + self.is_unlinked = True + Element.unlink(self) + + A.comp = association('comp', A, composite=True, opposite='other') + A.other = association('other', A, composite=False, opposite='comp') + + a = A() + a.name = 'a' + b = A() + b.name = 'b' + a.comp = b + assert b in a.comp + assert a in b.other + + a.unlink() + assert a.is_unlinked + assert b.is_unlinked + +test_associations() +test_attributes() +test_enumerations() +test_notify() +test_derivedunion() +test_composite() +print 'All tests passed.' diff --git a/tests/test-uml2.py b/tests/test-uml2.py index cbd3c4002..78b1bc9e3 100644 --- a/tests/test-uml2.py +++ b/tests/test-uml2.py @@ -1,11 +1,52 @@ -from gaphor.UML.uml2 import * -c = Class() -p = Package() +# vim:sw=4:et:ai -print c.owner , ' == None' +import unittest -c.package = p +import gaphor.UML as UML + +class TestUML2(unittest.TestCase): + + def test1(self): + factory = UML.ElementFactory() + c = factory.create(UML.Class) + p = factory.create(UML.Package) + c.package = p + self.assertEquals(c.package, p) + self.assertEquals(c.namespace, p) + self.failUnless(c in p.ownedElement) + + def __on_owned_member(self, name): + self.owned_member_called = True + + def testOwnedMember(self): + factory = UML.ElementFactory() + c = factory.create(UML.Class) + p = factory.create(UML.Package) + self.owned_member_called = False + p.connect('ownedMember', self.__on_owned_member) + c.package = p + self.assertEquals(self.owned_member_called, True) + + def testOwnedMember_Unlink(self): + factory = UML.ElementFactory() + c = factory.create(UML.Class) + p = factory.create(UML.Package) + self.owned_member_called = False + p.connect('ownedMember', self.__on_owned_member) + c.package = p + self.assertEquals(self.owned_member_called, True) + self.owned_member_called = False + c.unlink() + self.assertEquals(self.owned_member_called, True) + + +if __name__ == '__main__': + unittest.main() + +def on_modelelement(*args): + print 'on_modelelement', args + +p.connect('ownedMember', on_modelelement) + +c.unlink() -print c.package -print c.owner -print c.namespace