*** empty log message ***

git-svn-id: file:///Users/arjan/backup/gaphor/trunk/gaphor@201 a8418922-720d-0410-834f-a69b97ada669
This commit is contained in:
Arjan Molenaar 2003-06-01 18:25:59 +00:00
parent 135c2cb684
commit 38fe12ef1f
32 changed files with 1020 additions and 1146 deletions

View File

@ -1,3 +1,20 @@
2002-06-01 Arjan Molenaar <arjanmolenaar@hetnet.nl>
(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 <arjanmolenaar@hetnet.nl>
* 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 <arjanmolenaar@hetnet.nl>
* gaphor/diagram/actor.py, modelelement.py, diagramitem.py:

4
TODO
View File

@ -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.

File diff suppressed because one or more lines are too long

View File

@ -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."""

View File

@ -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))

View File

@ -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)

View File

@ -201,7 +201,7 @@ class association(umlproperty):
else:
s = '<association %s: %s[%s..%s]' % (self.name, self.type.__name__, self.lower, self.upper)
if self.opposite:
s += ' -> %s' % self.opposite
s += ' %s-> %s' % (self.composite and '<>' 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):

28
gaphor/diagram/README Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.'

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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('<reference name="%s" refid="%s"/>\n' % (name, value.id))
def save_value(name, value):
buffer.write('<value name="%s">' % name)
if isinstance(value, types.StringTypes):
buffer.write('<![CDATA[%s]]>' % value.replace(']]>', '] ]>'))
else:
buffer.write(escape(str(value)))
buffer.write('</value>\n')
def save_element(name, value):
if isinstance (value, UML.Element):
# Save a reference to the object:
buffer.write('<reference name="%s" refid="%s"/>\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('<reflist>')
for v in value:
save_element(name, v)
save_reference(name, v)
#buffer.write('</reflist>')
elif isinstance(value, diacanvas.Canvas):
buffer.write('<canvas>')
value.save(save_element)
value.save(save_canvasitem)
buffer.write('</canvas>')
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('<reflist>')
for v in value:
save_reference(name, v)
#buffer.write('</reflist>')
elif isinstance(value, diacanvas.CanvasItem):
buffer.write('<canvasitem>')
buffer.write('<canvasitem id="%s" type="%s">' % (value.id, value.__class__.__name__))
value.save(save_element)
buffer.write('</canvasitem>')
else:
buffer.write('<value name="%s">' % name)
if isinstance(value, types.StringTypes):
buffer.write('<![CDATA[%s]]>' % value.replace(']]>', '] ]>'))
else:
buffer.write(escape(str(value)))
buffer.write('</value>\n')
save_value(name, value)
if not factory:
factory = GaphorResource(UML.ElementFactory)
buffer = StringIO()
buffer.write('<?xml version="1.0" encoding="utf-8"?>\n')
buffer.write('<?xml version="1.0"?>\n')
buffer.write('<gaphor version="%s">' % 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

View File

@ -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',

View File

@ -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 '<None>')
except:
pass

View File

@ -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())

View File

@ -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, [])

View File

@ -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'),

View File

@ -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()

View File

@ -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

View File

@ -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.'

View File

@ -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