Changeset 445
- Timestamp:
- 11/02/04 02:52:18 (4 years ago)
- Files:
-
- trunk/gaphor/ChangeLog (modified) (2 diffs)
- trunk/gaphor/HACKING (modified) (2 diffs)
- trunk/gaphor/NEWS (modified) (1 diff)
- trunk/gaphor/README (modified) (1 diff)
- trunk/gaphor/TODO (modified) (2 diffs)
- trunk/gaphor/data/icons.xml (modified) (4 diffs)
- trunk/gaphor/data/plugins/xmiexport/exportmodel.py (modified) (1 diff)
- trunk/gaphor/gaphor/diagram/actions.py (modified) (2 diffs)
- trunk/gaphor/gaphor/diagram/association.py (modified) (6 diffs)
- trunk/gaphor/gaphor/diagram/dependency.py (modified) (1 diff)
- trunk/gaphor/gaphor/diagram/interface.py (modified) (1 diff)
- trunk/gaphor/gaphor/storage.py (modified) (5 diffs)
- trunk/gaphor/gaphor/ui/diagramactions.py (modified) (1 diff)
- trunk/gaphor/gaphor/ui/mainactions.py (modified) (1 diff)
- trunk/gaphor/setup.py (modified) (1 diff)
- trunk/gaphor/uml2/collection.py (modified) (10 diffs)
- trunk/gaphor/uml2/element.py (modified) (7 diffs)
- trunk/gaphor/uml2/properties.py (modified) (18 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/gaphor/ChangeLog
r443 r445 1 2004-11-02 Arjan Molenaar <arjanmolenaar@hetnet.nl> 2 3 * gaphor/diagram/association.py: added name and made a start with 4 direction. 5 * gaphor/ui/mainactions.py: confirmation dialog when starting a new 6 model. 7 1 8 2004-10-29 Arjan Molenaar <arjanmolenaar@hetnet.nl> 2 9 … … 9 16 * gaphor/UML/uml2.gaphor: added Classifier.appliedStereotype (in stead 10 17 of Class.appliedStereotype). 18 * gaphor/storage.py: made conversion from ClassItem to InterfaceItem 19 for pre-0.6.2 classes 20 * setup.py: bump version to 0.6.2 to ensure the conversion works. 11 21 12 22 2004-10-26 Arjan Molenaar <arjanmolenaar@hetnet.nl> trunk/gaphor/HACKING
r284 r445 12 12 here. 13 13 14 NOTE: The code is generated from a Gaphor model: uml2.gaphor. This 15 file can be loaded in gaphor. 16 14 17 diagram 15 18 ------- … … 20 23 ui 21 24 -- 22 The user interface. This is where most of the work is .25 The user interface. This is where most of the work is to be done. 23 26 24 27 misc 25 28 ---- 26 29 Some utility stuff, such as Actions and aspects are put in here. 30 trunk/gaphor/NEWS
r379 r445 1 0.6.0 2 ----- 3 - Enhanced Plugin support 4 - Interfaces 5 - Support for Stereotypes 1 6 2 7 0.5.0 3 8 ----- 4 - Support for Stereotypes5 9 - Support for UseCases 6 10 - Plugins trunk/gaphor/README
r271 r445 11 11 started. 12 12 13 Documentation is stored in a different module: gaphor-doc. It can also be 14 read on-line on http://gaphor.sourceforge.net/manual. 15 13 16 Have fun, 14 17 trunk/gaphor/TODO
r436 r445 10 10 - write really good test cases for undo functionality. Have some already. 11 11 12 - confirmation window when creating a new model.12 #- confirmation window when creating a new model. 13 13 14 14 - An option that shows the selected item (in the namespace view) in a diagram. 15 15 16 16 - Copy/Paste for diagramitems 17 - in order to make copy/paste work, the load/save functions should be 18 generatlised to allow a subset to be saved/loaded (which is needed 19 anyway for exporting/importing stereotype Profiles). 20 - How many data should be saved? (e.g. we copy a diagram item, remove it 21 (the underlaying UML element is removed) and the paste the copied item. 22 The diagram should act as if we have placed a copy of the removed item 23 on the canvas and make the uml element visible again. 24 25 - Stereotype export and import function. Allow to export packages and import 26 them into other models. 27 28 - Automatically draw relations when an item is DND-ed from the tree-view 29 to the diagram. 30 31 - Create distinctive icons for Include and Extend relationships (use cases). 17 32 18 33 - How to figure out if a module exists without loading it? … … 28 43 AJM: Looks like it really works now. 29 44 30 - Exporting diagrams to UML XMI (work in progress), code, images (SVG/png), etc. - make a plugin! 45 - Exporting diagrams to UML XMI (work in progress), 46 code, images (SVG/png), etc. - make a plugin! 31 47 32 48 #- Make text selected when starting to edit it: fixed in DiaCanvas2 trunk/gaphor/data/icons.xml
r356 r445 31 31 <file>commentline24.png</file> 32 32 </icon> 33 <icon id="gaphor-component"> 34 <file>component24.png</file> 35 <element>Component</element> 36 </icon> 33 37 <icon id="gaphor-control-flow"> 34 38 <file>controlflow24.png</file> … … 47 51 <element>Diagram</element> 48 52 </icon> 53 <icon id="gaphor-extend"> 54 <file>dependency24.png</file> 55 <element>Extend</element> 56 </icon> 49 57 <icon id="gaphor-extension"> 50 58 <file>extension24.png</file> … … 58 66 <file>implementation24.png</file> 59 67 <element>Implementation</element> 68 </icon> 69 <icon id="gaphor-include"> 70 <file>dependency24.png</file> 71 <element>Include</element> 60 72 </icon> 61 73 <icon id="gaphor-initial-node"> … … 95 107 <element>UseCase</element> 96 108 </icon> 97 <icon id="gaphor-component">98 <file>component24.png</file>99 <element>Component</element>100 </icon>101 109 </stock-icons> trunk/gaphor/data/plugins/xmiexport/exportmodel.py
r435 r445 174 174 attributes['xmi.id']=str(id(attributes)) # No id in Gaphor for this 175 175 values=('lower','upper') 176 -:1: parser error : Start tag expected, '<' not found177 176 for value in values: 178 ^179 177 try: 180 178 data=getattr(end, '%sValue'%value).value trunk/gaphor/gaphor/diagram/actions.py
r432 r445 145 145 label = '_Include' 146 146 tooltip = 'Create a new Include' 147 stock_id = 'gaphor- dependency'147 stock_id = 'gaphor-include' 148 148 name = 'Include' 149 149 type = diagram.IncludeItem … … 156 156 label = '_Extend' 157 157 tooltip = 'Create a new Extend' 158 stock_id = 'gaphor- dependency'158 stock_id = 'gaphor-extend' 159 159 name = 'Extend' 160 160 type = diagram.ExtendItem trunk/gaphor/gaphor/diagram/association.py
r437 r445 22 22 from relationship import RelationshipItem 23 23 24 class AssociationItem(RelationshipItem, diacanvas.CanvasGroupable ):24 class AssociationItem(RelationshipItem, diacanvas.CanvasGroupable, diacanvas.CanvasEditable): 25 25 """AssociationItem represents associations. 26 26 An AssociationItem has two AssociationEnd items. Each AssociationEnd item … … 42 42 } 43 43 44 FONT='sans bold 10' 45 44 46 association_popup_menu = ( 45 47 'separator', … … 68 70 self._tail_end.set_child_of(self) 69 71 72 self._label = diacanvas.shape.Text() 73 self._label.set_font_description(pango.FontDescription(AssociationItem.FONT)) 74 #self._label.set_alignment(pango.ALIGN_CENTER) 75 self._label.set_markup(False) 76 #self._label.set_max_width(100) 77 #self._label.set_max_height(100) 78 79 # Direction depends on the ends that hold the ownedEnd attributes. 80 self._dir = diacanvas.shape.Path() 81 self._dir.set_line_width(2.0) 82 self._dir.line(((10, 0), (10, 10), (0, 5))) 83 self._dir.set_fill_color(diacanvas.color(0,0,0)) 84 self._dir.set_fill(diacanvas.shape.FILL_SOLID) 85 self._dir.set_cyclic(True) 86 70 87 def save (self, save_func): 71 88 RelationshipItem.save(self, save_func) … … 115 132 tail_end = property(lambda self: self._tail_end) 116 133 134 def get_popup_menu(self): 135 if self.subject: 136 return self.popup_menu + self.association_popup_menu 137 else: 138 return self.popup_menu 139 117 140 def on_subject_notify(self, pspec, notifiers=()): 118 141 RelationshipItem.on_subject_notify(self, pspec, 119 notifiers + ('ownedEnd',)) 142 notifiers + ('name', 'ownedEnd',)) 143 144 def on_subject_notify__name(self, subject, pspec): 145 log.debug('Association name = %s' % (self.subject and self.subject.name)) 146 if self.subject: 147 self._label.set_text(self.subject.name or '') 148 else: 149 self._label.set_text('') 150 self.request_update() 120 151 121 152 def on_subject_notify__ownedEnd(self, subject, pspec): 122 153 self.request_update() 154 155 def update_label(self, p1, p2): 156 w, h = self._label.to_pango_layout(True).get_pixel_size() 157 158 x = p1[0] > p2[0] and w + 2 or -2 159 x = (p1[0] + p2[0]) / 2.0 - x 160 y = p1[1] <= p2[1] and h or 0 161 y = (p1[1] + p2[1]) / 2.0 - y 162 163 self._label.set_pos((x, y)) 164 log.debug('label pos = (%d, %d)' % (x, y)) 165 #return x, y, max(x + 10, x + w), max(y + 10, y + h) 166 return x, y, x + w, y + h 123 167 124 168 def on_update (self, affine): … … 168 212 self.update_child(self._tail_end, affine) 169 213 214 # update name label: 215 middle = len(handles)/2 216 self._label_bounds = self.update_label(handles[middle-1].get_pos_i(), 217 handles[middle].get_pos_i()) 170 218 # bounds calculation 171 219 b1 = self.bounds 172 220 b2 = self._head_end.get_bounds(self._head_end.affine) 173 221 b3 = self._tail_end.get_bounds(self._tail_end.affine) 174 self.set_bounds((min(b1[0], b2[0], b3[0]), min(b1[1], b2[1], b3[1]),175 max(b1[2], b2[2], b3[2]), max(b1[3], b2[3], b3[3])))222 bv = zip(self._label_bounds, b1, b2, b3) 223 self.set_bounds((min(bv[0]), min(bv[1]), max(bv[2]), max(bv[3]))) 176 224 225 def on_shape_iter(self): 226 for s in RelationshipItem.on_shape_iter(self): 227 yield s 228 yield self._label 229 #yield self._dir 230 177 231 # Gaphor Connection Protocol 178 232 … … 283 337 return iter([self._head_end, self._tail_end]) 284 338 285 def get_popup_menu(self): 286 if self.subject: 287 return self.popup_menu + self.association_popup_menu 288 else: 289 return self.popup_menu 339 # Editable 340 341 def on_editable_get_editable_shape(self, x, y): 342 log.debug('association edit on (%d,%d)' % (x, y)) 343 #p = (x, y) 344 #drp = diacanvas.geometry.distance_rectangle_point 345 #if drp(self._label_bounds, p) < 1.0: 346 return self._label 347 348 def on_editable_start_editing(self, shape): 349 pass 350 351 def on_editable_editing_done(self, shape, new_text): 352 if self.subject and self.subject.name != new_text: 353 self.subject.name = new_text 290 354 291 355 trunk/gaphor/gaphor/diagram/dependency.py
r436 r445 126 126 self._stereotype.set_pos((x, y)) 127 127 128 return x, y, w,h128 return x, y, x + w, y + h 129 129 130 130 def on_update (self, affine): trunk/gaphor/gaphor/diagram/interface.py
r436 r445 85 85 """ 86 86 ClassItem.set_drawing_style(self, style) 87 # TODO: adjust offsets so the center point is the same 87 88 if self.drawing_style == self.DRAW_ICON: 88 89 r2 = self.RADIUS * 2 trunk/gaphor/gaphor/storage.py
r436 r445 165 165 166 166 #log.info('0%') 167 168 # Fix version inconsistencies 169 version_0_6_2(elements, factory, gaphor_version) 167 170 168 171 # First create elements and canvas items in the factory … … 185 188 186 189 #log.info('0% ... 33%') 187 188 # Fix version inconsistencies189 version_0_6_1(elements, factory, gaphor_version)190 190 191 191 # load attributes and create references: … … 303 303 # Version inconsistencies can be fixed: 304 304 305 def version_0_6_ 1(elements, factory, gaphor_version):306 """Before 0.6. 1an Interface could be represented by a ClassItem and305 def version_0_6_2(elements, factory, gaphor_version): 306 """Before 0.6.2 an Interface could be represented by a ClassItem and 307 307 a InterfaceItem. Now only InterfaceItems are used. 308 308 """ 309 if tuple( gaphor_version.split('.')) < (0, 6, 1):309 if tuple(map(int, gaphor_version.split('.'))) < (0, 6, 2): 310 310 for elem in elements.values(): 311 311 try: … … 315 315 if p.type == 'ClassItem': 316 316 p.type = 'InterfaceItem' 317 p.values['drawing-style'] = '0' 318 elif p.type == 'InterfaceItem': 319 p.values['drawing-style'] = '2' 317 320 except Exception, e: 318 321 log.error('Error while updating InterfaceItems', e) … … 323 326 holding the aggregation information. 324 327 """ 325 if tuple( gaphor_version.split('.')) < (0, 5, 2):328 if tuple(map(int, gaphor_version.split('.'))) < (0, 5, 2): 326 329 log.info('Fix composition on Associations (file version: %s)' % gaphor_version) 327 330 for elem in elements.values(): trunk/gaphor/gaphor/ui/diagramactions.py
r424 r445 228 228 229 229 230 class CopyAction(Action): 231 """Copy/Cut/Paste functionality required a lot of thinking: 232 - in order to make copy/paste work, the load/save functions should be 233 generatlised to allow a subset to be saved/loaded (which is needed 234 anyway for exporting/importing stereotype Profiles). 235 - How many data should be saved? (e.g. we copy a diagram item, remove it 236 (the underlaying UML element is removed) and the paste the copied item. 237 The diagram should act as if we have placed a copy of the removed item 238 on the canvas and make the uml element visible again. 239 """ 240 id = 'EditCopy' 241 label = '_Copy' 242 accel = 'C-x' 243 stock_id = 'gtk-cut' 244 245 def init(self, window): 246 self._window = window 247 self._stack = [] 248 249 def update(self): 250 diagram_tab = self._window.get_current_diagram_tab() 251 self.sensitive = diagram_tab and len(diagram_tab.get_view().selected_items) > 0 252 253 def execute(self): 254 view = self._window.get_current_diagram_view() 255 if view.is_focus(): 256 items = view.selected_items 257 for i in items: 258 i.item.save(save_func) 259 tab = self._window.get_current_diagram_tab() 260 261 register_action(CopyAction) 262 263 230 264 class ZoomInAction(Action): 231 265 id = 'ViewZoomIn' trunk/gaphor/gaphor/ui/mainactions.py
r426 r445 76 76 def execute(self): 77 77 factory = gaphor.resource(UML.ElementFactory) 78 if factory.size(): 79 dialog = gtk.MessageDialog(self._window.get_window(), 80 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 81 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 82 _("Opening a new model will flush the currently loaded model.\nAny changes made will not be saved. Do you want to continue?")) 83 answer = dialog.run() 84 dialog.destroy() 85 if answer != gtk.RESPONSE_YES: 86 return 87 88 factory = gaphor.resource(UML.ElementFactory) 78 89 factory.flush() 79 90 gc.collect() trunk/gaphor/setup.py
r428 r445 9 9 MAJOR_VERSION = 0 10 10 MINOR_VERSION = 6 11 MICRO_VERSION = 111 MICRO_VERSION = 2 12 12 13 13 VERSION = '%d.%d.%d' % ( MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION ) trunk/gaphor/uml2/collection.py
r192 r445 1 # vi m:sw=41 # vi:sw=4:et 2 2 3 3 import inspect … … 6 6 pass 7 7 8 class Collection(object):8 class collection(object): 9 9 10 10 def __init__(self, property, object, type): 11 self.property = property12 self.object = object13 self.type = type14 self.items = []11 self.property = property 12 self.object = object 13 self.type = type 14 self.items = [] 15 15 16 16 def __len__(self): … … 18 18 19 19 def __setitem__(self, key, value): 20 raise CollectionError, 'items should not be overwritten.'20 raise CollectionError, 'items should not be overwritten.' 21 21 22 22 def __delitem__(self, key): … … 30 30 31 31 def __setslice__(self, i, j, s): 32 raise CollectionError, 'items should not be overwritten.'32 raise CollectionError, 'items should not be overwritten.' 33 33 34 34 def __delslice__(self, i, j): 35 raise CollectionError, 'items should not be deleted this way.'35 raise CollectionError, 'items should not be deleted this way.' 36 36 37 37 def __contains__(self, obj): … … 39 39 40 40 def __iter__(self): 41 return iter(self.items)41 return iter(self.items) 42 42 43 43 def __str__(self): 44 return str(self.items) 44 return str(self.items) 45 46 __repr__ = __str__ 45 47 46 48 def __nonzero__(self): 47 return self.items!=[]49 return self.items!=[] 48 50 49 51 def append(self, value): 50 if isinstance(value, self.type):51 self.property._set(self.object, value)52 else:53 raise CollectionError, 'Object is not of type %s' % self.type.__name__52 if isinstance(value, self.type): 53 self.property._set(self.object, value) 54 else: 55 raise CollectionError, 'Object is not of type %s' % self.type.__name__ 54 56 55 57 def remove(self, value): 56 if value in self.items: 57 self.property.__delete__(self.object, value) 58 else: 59 raise AttributeError, '%s not in collection' % value 58 if value in self.items: 59 self.property.__delete__(self.object, value) 60 else: 61 raise AttributeError, '%s not in collection' % value 62 60 63 61 64 def index(self, key): 62 """Given an object, return the position of that object in the 63 collection.""" 64 return self.items.index(key) 65 65 """Given an object, return the position of that object in the 66 collection.""" 67 return self.items.index(key) 68 69 66 70 # OCL members (from SMW by Ivan Porres, http://www.abo.fi/~iporres/smw) 67 71 … … 128 132 def forAll(self,f): 129 133 if not self.items or not inspect.getargspec(f)[0]: 130 return 1134 return True 131 135 132 136 nargs=len(inspect.getargspec(f)[0]) … … 143 147 args.append(self.items[x]) 144 148 if not apply(f,args): 145 return 0149 return False 146 150 c=len(index)-1 147 151 index[c]=index[c]+1 … … 150 154 c=c-1 151 155 if c<0: 152 return 1156 return True 153 157 else: 154 158 index[c]=index[c]+1 155 159 if index[c]==nitems-1: 156 160 c=c-1 161 return False 157 162 158 163 def exist(self,f): 159 164 if not self.items or not inspect.getargspec(f)[0]: 160 return 0165 return False 161 166 162 167 nargs=len(inspect.getargspec(f)[0]) … … 172 177 args.append(self.items[x]) 173 178 if apply(f,args): 174 return 1179 return True 175 180 c=len(index)-1 176 181 index[c]=index[c]+1 … … 179 184 c=c-1 180 185 if c<0: 181 return 0186 return False 182 187 else: 183 188 index[c]=index[c]+1 184 189 if index[c]==nitems-1: 185 190 c=c-1 191 return False 192 193 194 def moveUp(self, value): 195 """ 196 Move element up. Owner is notified about the change. 197 """ 198 i1 = self.items.index(value) 199 i2 = i1 - 1 200 if i2 >= 0: 201 self.items[i1], self.items[i2] = self.items[i2], self.items[i1] 202 self.property.notify(self.object) # send a notification that this list has changed 203 else: 204 log.warning('Cannot move up first element') 205 206 207 def moveDown(self, value): 208 """ 209 Move element down. Owner is notified about the change. 210 """ 211 i1 = self.items.index(value) 212 i2 = i1 + 1 213 if i2 < len(self.items): 214 self.items[i1], self.items[i2] = self.items[i2], self.items[i1] 215 self.property.notify(self.object) # send a notification that this list has changed 216 else: 217 log.warning('Cannot move down last element') trunk/gaphor/uml2/element.py
r196 r445 6 6 with a completely new data model here. 7 7 8 save()/load()/postload() 8 save(save_func) 9 load(name, value) 10 postload() 9 11 Load/save the element. 10 12 11 13 unlink() 12 Remove all references to the element. First of all the signal 13 '__unlink__' is send to all attached signals. 14 15 attach ('name', callback, *data) or 16 attach (('name', 'other_property'), callback, *data) 14 Remove all references to the element. This is done by emiting the 15 '__unlink__' signal to all attached signals. unlink() can not be called 16 recursively. 17 #relink() 18 # Inverse operation of unlink(). Used by diagram items during undo operations. 19 20 connect ('name', callback, *data) or 21 connect (('name', 'other_property'), callback, *data) 17 22 Connect 'callback' to recieve notifications if one of the properties 18 23 change (the latter being more memory efficient if you want to listen on … … 20 25 first argument, followed by *data. 21 26 22 d etach(callback, *data)27 disconnect (callback, *data) 23 28 Disconnect callback as listener. 24 29 25 30 notify (name) 26 31 Notify all listeners the property 'name' has changed. 32 The notifier calls callbacks registered by connect() by sending 33 callback(self, pspec, *data) 34 where self is the data object (Element) and pspec is the property spec. 27 35 """ 28 36 29 import types 37 __all__ = [ 'Element' ] 38 39 import types, mutex 40 import gaphor.misc.uniqueid as uniqueid 41 from properties import umlproperty, association 30 42 31 43 class Element(object): 32 44 """Base class for UML data classes.""" 33 45 34 def __init__(self): 35 self._observers = dict() 46 def __init__(self, id=None, factory=None): 47 self.id = id or uniqueid.generate_id() 48 # The factory this element belongs to. 49 self._factory = factory 50 self._observers = dict() 51 self.__in_unlink = mutex.mutex() 52 53 factory = property(lambda self: self._factory, 54 doc="The factory that created this element") 36 55 37 56 def save(self, save_func): 38 """Save the state by calling save_func(name, value).""" 39 lst = dir(self.__class__) 40 for prop in map(isinstance, lst, [umlproperty] * len(lst)): 57 """Save the state by calling save_func(name, value).""" 58 umlprop = umlproperty 59 clazz = type(self) 60 for propname in dir(clazz): 61 prop = getattr(clazz, propname) 62 if isinstance(prop, umlprop): 41 63 prop.save(self, save_func) 42 64 43 65 def load(self, name, value): 44 """Loads value in name. Make sure that for every load postload() 45 should be called.""" 46 try: 47 prop = getattr(self.__class__, name) 66 """Loads value in name. Make sure that for every load postload() 67 should be called. 68 """ 69 try: 70 prop = getattr(type(self), name) 48 71 except AttributeError, e: 49 72 raise AttributeError, "'%s' has no property '%s'" % \ 50 ( self.__class__.__name__, name)73 (type(self).__name__, name) 51 74 else: 52 75 prop.load(self, value) 53 76 54 77 def postload(self): 55 pass 78 """Fix up the odds and ends. 79 """ 80 for name in dir(type(self)): 81 try: 82 prop = getattr(type(self), name) 83 except AttributeError, e: 84 raise AttributeError, "'%s' has no property '%s'" % \ 85 (type(self).__name__, name) 86 else: 87 if isinstance(prop, umlproperty): 88 prop.postload(self) 89 90 def __unlink(self, signal): 91 """Unlink the element. For both the __unlink__ and __relink__ signal 92 the __unlink__ callback list is used. 93 """ 94 # Uses a mutex to make sure it is not called recursively 95 if self.__in_unlink.testandset(): 96 try: 97 self.notify(signal, '__unlink__') 98 finally: 99 self.__in_unlink.unlock() 56 100 57 101 def unlink(self): 58 self.notify('__unlink__') 59 60 def attach(self, names, callback, *data): 61 """Attach 'callback' to a list of names. Names may also be a string.""" 102 """Unlink the element.""" 103 #log.debug('Element.unlink(%s)' % self) 104 self.__unlink('__unlink__') 105 106 # def relink(self): 107 # """Undo the unlink operation.""" 108 # log.debug('Element.relink(%s)' % self) 109 # self.__unlink('__relink__') 110 111 def connect(self, names, callback, *data): 112 """Attach 'callback' to a list of names. Names may also be a string. 113 A name is the name od a property of the object or '__unlink__'. 114 """ 115 #log.debug('Element.connect(%s, %s, %s)' % (names, callback, data)) 62 116 if type(names) is types.StringType: 63 117 names = (names,) … … 72 126 self._observers[name] = [cb] 73 127 74 def detach(self, callback, *data): 75 """Detach a callback identified by it's data.""" 76 #print 'detach', callback, data 128 def disconnect(self, callback, *data): 129 """Detach a callback identified by it's data.""" 77 130 cb = (callback,) + data 78 for values in self._observers.values():131 for values in self._observers.values(): 79 132 # Remove all occurences of 'cb' from values 80 133 # (if none is found ValueError is raised). … … 85 138 pass 86 139 87 def notify(self, name ):140 def notify(self, name, cb_name=None, pspec=None): 88 141 """Send notification to attached callbacks that a property 89 has changed.""" 90 cb_list = self._observers.get(name) or () 91 for cb_data in cb_list: 92 try: 93 apply(cb_data[0], (name,) + cb_data[1:]) 94 except: 95 pass 142 has changed. the __relink__ signal uses the callbacks for __unlink__. 143 """ 144 cb_list = self._observers.get(cb_name or name, ()) 145 #log.debug('Element.notify: %s' % cb_list) 146 if not pspec: 147 try: 148 pspec = getattr(type(self), name) 149 except AttributeError: 150 pspec = name 151 152 # Use a copy of the list to ensure all items are notified 153 for cb_data in list(cb_list): 154 try: 155 apply(cb_data[0], (self, pspec) + cb_data[1:]) 156 except Exception, e: 157 log.error('failed notification for %s' % cb_data[0], e) 96 158 97 159 # OCL methods: (from SMW by Ivan Porres (http://www.abo.fi/~iporres/smw)) 98 160 99 def isKindOf(self, clazz):161 def isKindOf(self, class_): 100 162 """Returns true if the object is an instance of clazz.""" 101 return isinstance(self, cla zz)163 return isinstance(self, class_) 102 164 103 165 def isTypeOf(self, other): … … 105 167 return type(self) == type(other) 106 168 169 # def __setattr__(self, key, value): 170 # if key.startswith('_') or key == 'id': 171 # object.__setattr__(self, key, value) 172 # else: 173 # raise AttributeError, 'Invalid attribute "%s"' % key 174 175 try: 176 import psyco 177 except ImportError: 178 pass 179 else: 180 psyco.bind(Element) 107 181 108 182 if __name__ == '__main__': … … 112 186 print ' cb_func:', name, args 113 187 114 a. attach('ev1', cb_func, a)115 a. attach('ev1', cb_func, a)116 a. attach('ev2', cb_func, 'ev2', a)188 a.connect('ev1', cb_func, a) 189 a.connect('ev1', cb_func, a) 190 a.connect('ev2', cb_func, 'ev2', a) 117 191 118 192 print 'notify: ev1' … … 121 195 a.notify('ev2') 122 196 123 a.d etach(cb_func, a)197 a.disconnect(cb_func, a) 124 198 125 199 print 'notify: ev1' trunk/gaphor/uml2/properties.py
r197 r445 24 24 load(value): load 'value' as the current value for this property 25 25 save(save_func): send the value of the property to save_func(name, value) 26 unlink(): remove references to other elements.26 #unlink(): remove references to other elements. 27 27 """ 28 28 29 from collection import Collection 29 __all__ = [ 'attribute', 'enumeration', 'association', 'derivedunion', 'redefine' ] 30 31 from collection import collection 30 32 import operator 33 from gaphor.undomanager import get_undo_manager 31 34 32 35 #infinite = 100000 36 37 class undoaction(object): 38 """This undo action contains one undo action for an attribute 39 property. 40 """ 41 42 def __init__(self, prop, obj, value): 43 self.prop = prop 44 self.obj = obj 45 self.value = value 46 #print 'adding new property', prop.name, obj, value 47 get_undo_manager().add_undo_action(self) 48 33 49 34 50 class umlproperty(object): … … 45 61 self.deriviates = [ deriviate ] 46 62 47 def __get__(self, obj, cla zz=None):63 def __get__(self, obj, class_=None): 48 64 if not obj: 49 65 return self … … 56 72 self._del(obj, value) 57 73 74 def save(self, obj, save_func): 75 if hasattr(obj, self._name): 76 save_func(self.name, self._get(obj)) 77 58 78 def load(self, obj, value): 59 79 self._set(obj, value) 60 80 61 def save(self, obj, save_func): 62 if hasattr(obj, '_' + self.name): 63 save_func(self.name, self._get(obj)) 64 65 def unlink(self, obj): 66 if hasattr(obj, '_' + self.name): 67 self.__delete__(obj) 81 def postload(self, obj): 82 pass 83 84 # def unlink(self, obj): 85 # if hasattr(obj, self._name): 86 # self.__delete__(obj) 68 87 69 88 def notify(self, obj): 70 89 """Notify obj that the property's value has been changed. 71 Deriviates are also triggered to send a notify signal.""" 72 try: 73 obj.notify(self.name) 74 except: 75 pass 90 Deriviates are also triggered to send a notify signal. 91 """ 92 try: 93 obj.notify(self.name, pspec=self) 94 except Exception, e: 95 log.error(str(e), e) 76 96 77 97 # we need to check if a deriviate is part of the current object: 78 98 # TODO: can we do this faster? 79 mro = obj.__class__.__mro__ 80 try: 81 for d in self.deriviates: 82 for c in mro: 83 if d in c.__dict__.values(): 99 try: 100 deriviates = self.deriviates 101 except AttributeError: 102 pass # no derivedunion or redefine attribute 103 else: 104 values = [] 105 for c in type(obj).__mro__: 106 values.extend(c.__dict__.values()) 107 108 for d in deriviates: 109 if d in values: 110 try: 84 111 d.notify(obj) 85 except AttributeError: 86 pass # no derivedunion or redefine attribute 112 except Exception, e: 113 log.error(e, e) 114 115 116 class undoattributeaction(undoaction): 117 """This undo action contains one undo action for an attribute 118 property. 119 """ 120 121 def undo(self): 122 self.redo_value = self.prop._get(self.obj) 123 setattr(self.obj, self.prop._name, self.value) 124 self.prop.notify(self.obj) 125 126 def redo(self): 127 setattr(self.obj, self.prop._name, self.redo_value) 128 self.prop.notify(self.obj) 87 129 88 130 … … 92 134 93 135 # TODO: check if lower and upper are actually needed for attributes 94 def __init__(self, name, type, default, lower
