*** empty log message ***
git-svn-id: file:///Users/arjan/backup/gaphor/trunk/gaphor@201 a8418922-720d-0410-834f-a69b97ada669
This commit is contained in:
parent
135c2cb684
commit
38fe12ef1f
17
ChangeLog
17
ChangeLog
@ -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
4
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.
|
||||
|
File diff suppressed because one or more lines are too long
@ -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."""
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
28
gaphor/diagram/README
Normal 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)
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
83
gaphor/diagram/classifier.py
Normal file
83
gaphor/diagram/classifier.py
Normal 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)
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.'
|
||||
|
112
gaphor/diagram/diagramline.py
Normal file
112
gaphor/diagram/diagramline.py
Normal 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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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, [])
|
||||
|
||||
|
@ -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'),
|
||||
|
16
setup.py
16
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()
|
||||
|
323
tests/test-ns.py
323
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
|
||||
|
@ -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.'
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user