Changeset 445

Show
Ignore:
Timestamp:
11/02/04 02:52:18 (4 years ago)
Author:
arjanmol
Message:

*** empty log message ***

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/gaphor/ChangeLog

    r443 r445  
     12004-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 
    182004-10-29  Arjan Molenaar  <arjanmolenaar@hetnet.nl> 
    29 
     
    916        * gaphor/UML/uml2.gaphor: added Classifier.appliedStereotype (in stead 
    1017        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. 
    1121 
    12222004-10-26  Arjan Molenaar  <arjanmolenaar@hetnet.nl> 
  • trunk/gaphor/HACKING

    r284 r445  
    1212here. 
    1313 
     14  NOTE: The code is generated from a Gaphor model: uml2.gaphor. This 
     15        file can be loaded in gaphor. 
     16 
    1417diagram 
    1518------- 
     
    2023ui 
    2124-- 
    22 The user interface. This is where most of the work is
     25The user interface. This is where most of the work is to be done
    2326 
    2427misc 
    2528---- 
    2629Some utility stuff, such as Actions and aspects are put in here. 
     30 
  • trunk/gaphor/NEWS

    r379 r445  
     10.6.0 
     2----- 
     3- Enhanced Plugin support 
     4- Interfaces 
     5- Support for Stereotypes 
    16 
    270.5.0 
    38----- 
    4 - Support for Stereotypes 
    59- Support for UseCases 
    610- Plugins 
  • trunk/gaphor/README

    r271 r445  
    1111started. 
    1212 
     13Documentation is stored in a different module: gaphor-doc. It can also be 
     14read on-line on http://gaphor.sourceforge.net/manual. 
     15 
    1316Have fun, 
    1417 
  • trunk/gaphor/TODO

    r436 r445  
    1010- write really good test cases for undo functionality. Have some already. 
    1111 
    12 - confirmation window when creating a new model. 
     12#- confirmation window when creating a new model. 
    1313 
    1414- An option that shows the selected item (in the namespace view) in a diagram. 
    1515 
    1616- 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). 
    1732 
    1833- How to figure out if a module exists without loading it? 
     
    2843  AJM: Looks like it really works now. 
    2944 
    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! 
    3147 
    3248#- Make text selected when starting to edit it: fixed in DiaCanvas2 
  • trunk/gaphor/data/icons.xml

    r356 r445  
    3131    <file>commentline24.png</file> 
    3232  </icon> 
     33  <icon id="gaphor-component"> 
     34    <file>component24.png</file> 
     35    <element>Component</element> 
     36  </icon> 
    3337  <icon id="gaphor-control-flow"> 
    3438    <file>controlflow24.png</file> 
     
    4751    <element>Diagram</element> 
    4852  </icon> 
     53  <icon id="gaphor-extend"> 
     54    <file>dependency24.png</file> 
     55    <element>Extend</element> 
     56  </icon> 
    4957  <icon id="gaphor-extension"> 
    5058    <file>extension24.png</file> 
     
    5866    <file>implementation24.png</file> 
    5967    <element>Implementation</element> 
     68  </icon> 
     69  <icon id="gaphor-include"> 
     70    <file>dependency24.png</file> 
     71    <element>Include</element> 
    6072  </icon> 
    6173  <icon id="gaphor-initial-node"> 
     
    95107    <element>UseCase</element> 
    96108  </icon> 
    97   <icon id="gaphor-component"> 
    98     <file>component24.png</file> 
    99     <element>Component</element> 
    100   </icon> 
    101109</stock-icons> 
  • trunk/gaphor/data/plugins/xmiexport/exportmodel.py

    r435 r445  
    174174            attributes['xmi.id']=str(id(attributes)) # No id in Gaphor for this 
    175175            values=('lower','upper') 
    176 -:1: parser error : Start tag expected, '<' not found 
    177176            for value in values: 
    178             ^ 
    179177                try: 
    180178                    data=getattr(end, '%sValue'%value).value 
  • trunk/gaphor/gaphor/diagram/actions.py

    r432 r445  
    145145    label = '_Include' 
    146146    tooltip = 'Create a new Include' 
    147     stock_id = 'gaphor-dependency
     147    stock_id = 'gaphor-include
    148148    name = 'Include' 
    149149    type = diagram.IncludeItem 
     
    156156    label = '_Extend' 
    157157    tooltip = 'Create a new Extend' 
    158     stock_id = 'gaphor-dependency
     158    stock_id = 'gaphor-extend
    159159    name = 'Extend' 
    160160    type = diagram.ExtendItem 
  • trunk/gaphor/gaphor/diagram/association.py

    r437 r445  
    2222from relationship import RelationshipItem 
    2323 
    24 class AssociationItem(RelationshipItem, diacanvas.CanvasGroupable): 
     24class AssociationItem(RelationshipItem, diacanvas.CanvasGroupable, diacanvas.CanvasEditable): 
    2525    """AssociationItem represents associations.  
    2626    An AssociationItem has two AssociationEnd items. Each AssociationEnd item 
     
    4242    } 
    4343 
     44    FONT='sans bold 10' 
     45 
    4446    association_popup_menu = ( 
    4547        'separator', 
     
    6870        self._tail_end.set_child_of(self) 
    6971 
     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 
    7087    def save (self, save_func): 
    7188        RelationshipItem.save(self, save_func) 
     
    115132    tail_end = property(lambda self: self._tail_end) 
    116133 
     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 
    117140    def on_subject_notify(self, pspec, notifiers=()): 
    118141        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() 
    120151 
    121152    def on_subject_notify__ownedEnd(self, subject, pspec): 
    122153        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 
    123167 
    124168    def on_update (self, affine): 
     
    168212        self.update_child(self._tail_end, affine) 
    169213 
     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()) 
    170218        # bounds calculation 
    171219        b1 = self.bounds 
    172220        b2 = self._head_end.get_bounds(self._head_end.affine) 
    173221        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]))) 
    176224                     
     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 
    177231    # Gaphor Connection Protocol 
    178232 
     
    283337        return iter([self._head_end, self._tail_end]) 
    284338 
    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 
    290354 
    291355 
  • trunk/gaphor/gaphor/diagram/dependency.py

    r436 r445  
    126126        self._stereotype.set_pos((x, y)) 
    127127 
    128         return x, y, w,
     128        return x, y, x + w, y +
    129129 
    130130    def on_update (self, affine): 
  • trunk/gaphor/gaphor/diagram/interface.py

    r436 r445  
    8585        """ 
    8686        ClassItem.set_drawing_style(self, style) 
     87        # TODO: adjust offsets so the center point is the same 
    8788        if self.drawing_style == self.DRAW_ICON: 
    8889            r2 = self.RADIUS * 2 
  • trunk/gaphor/gaphor/storage.py

    r436 r445  
    165165 
    166166    #log.info('0%') 
     167 
     168    # Fix version inconsistencies 
     169    version_0_6_2(elements, factory, gaphor_version) 
    167170 
    168171    # First create elements and canvas items in the factory 
     
    185188 
    186189    #log.info('0% ... 33%') 
    187  
    188     # Fix version inconsistencies 
    189     version_0_6_1(elements, factory, gaphor_version) 
    190190 
    191191    # load attributes and create references: 
     
    303303# Version inconsistencies can be fixed: 
    304304 
    305 def version_0_6_1(elements, factory, gaphor_version): 
    306     """Before 0.6.1 an Interface could be represented by a ClassItem and 
     305def version_0_6_2(elements, factory, gaphor_version): 
     306    """Before 0.6.2 an Interface could be represented by a ClassItem and 
    307307    a InterfaceItem. Now only InterfaceItems are used. 
    308308    """ 
    309     if tuple(gaphor_version.split('.')) < (0, 6, 1): 
     309    if tuple(map(int, gaphor_version.split('.'))) < (0, 6, 2): 
    310310        for elem in elements.values(): 
    311311            try: 
     
    315315                        if p.type == 'ClassItem': 
    316316                            p.type = 'InterfaceItem' 
     317                            p.values['drawing-style'] = '0' 
     318                        elif p.type == 'InterfaceItem': 
     319                            p.values['drawing-style'] = '2' 
    317320            except Exception, e: 
    318321                log.error('Error while updating InterfaceItems', e) 
     
    323326    holding the aggregation information. 
    324327    """ 
    325     if tuple(gaphor_version.split('.')) < (0, 5, 2): 
     328    if tuple(map(int, gaphor_version.split('.'))) < (0, 5, 2): 
    326329        log.info('Fix composition on Associations (file version: %s)' % gaphor_version) 
    327330        for elem in elements.values(): 
  • trunk/gaphor/gaphor/ui/diagramactions.py

    r424 r445  
    228228 
    229229 
     230class 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 
     261register_action(CopyAction) 
     262 
     263 
    230264class ZoomInAction(Action): 
    231265    id = 'ViewZoomIn' 
  • trunk/gaphor/gaphor/ui/mainactions.py

    r426 r445  
    7676    def execute(self): 
    7777        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) 
    7889        factory.flush() 
    7990        gc.collect() 
  • trunk/gaphor/setup.py

    r428 r445  
    99MAJOR_VERSION = 0 
    1010MINOR_VERSION = 6 
    11 MICRO_VERSION = 1 
     11MICRO_VERSION = 2 
    1212 
    1313VERSION = '%d.%d.%d' % ( MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION ) 
  • trunk/gaphor/uml2/collection.py

    r192 r445  
    1 # vim:sw=4 
     1# vi:sw=4:et 
    22 
    33import inspect 
     
    66    pass 
    77 
    8 class Collection(object): 
     8class collection(object): 
    99 
    1010    def __init__(self, property, object, type): 
    11        self.property = property 
    12        self.object = object 
    13        self.type = type 
    14        self.items = [] 
     11        self.property = property 
     12        self.object = object 
     13        self.type = type 
     14        self.items = [] 
    1515 
    1616    def __len__(self): 
     
    1818 
    1919    def __setitem__(self, key, value): 
    20        raise CollectionError, 'items should not be overwritten.' 
     20        raise CollectionError, 'items should not be overwritten.' 
    2121 
    2222    def __delitem__(self, key): 
     
    3030 
    3131    def __setslice__(self, i, j, s): 
    32        raise CollectionError, 'items should not be overwritten.' 
     32        raise CollectionError, 'items should not be overwritten.' 
    3333 
    3434    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.' 
    3636 
    3737    def __contains__(self, obj): 
     
    3939 
    4040    def __iter__(self): 
    41        return iter(self.items) 
     41        return iter(self.items) 
    4242 
    4343    def __str__(self): 
    44         return str(self.items) 
     44        return str(self.items) 
     45 
     46    __repr__ = __str__ 
    4547 
    4648    def __nonzero__(self): 
    47        return self.items!=[] 
     49        return self.items!=[] 
    4850 
    4951    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__ 
    5456 
    5557    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 
    6063 
    6164    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 
    6670    # OCL members (from SMW by Ivan Porres, http://www.abo.fi/~iporres/smw) 
    6771 
     
    128132    def forAll(self,f): 
    129133        if not self.items or not inspect.getargspec(f)[0]: 
    130             return 1 
     134            return True 
    131135 
    132136        nargs=len(inspect.getargspec(f)[0]) 
     
    143147                args.append(self.items[x]) 
    144148            if not apply(f,args): 
    145                 return 0 
     149                return False 
    146150            c=len(index)-1 
    147151            index[c]=index[c]+1 
     
    150154                c=c-1 
    151155                if c<0: 
    152                     return 1 
     156                    return True 
    153157                else: 
    154158                    index[c]=index[c]+1  
    155159                if index[c]==nitems-1: 
    156160                    c=c-1 
     161        return False 
    157162 
    158163    def exist(self,f): 
    159164        if not self.items or not inspect.getargspec(f)[0]: 
    160             return 0 
     165            return False 
    161166 
    162167        nargs=len(inspect.getargspec(f)[0]) 
     
    172177                args.append(self.items[x]) 
    173178            if apply(f,args): 
    174                 return 1 
     179                return True 
    175180            c=len(index)-1 
    176181            index[c]=index[c]+1 
     
    179184                c=c-1 
    180185                if c<0: 
    181                     return 0 
     186                    return False 
    182187                else: 
    183188                    index[c]=index[c]+1  
    184189                if index[c]==nitems-1: 
    185190                    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  
    66with a completely new data model here. 
    77 
    8 save()/load()/postload() 
     8save(save_func) 
     9load(name, value) 
     10postload() 
    911    Load/save the element. 
    1012 
    1113unlink() 
    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 
     20connect ('name', callback, *data) or 
     21connect (('name', 'other_property'), callback, *data) 
    1722    Connect 'callback' to recieve notifications if one of the properties 
    1823    change (the latter being more memory efficient if you want to listen on 
     
    2025    first argument, followed by *data. 
    2126 
    22 detach (callback, *data) 
     27disconnect (callback, *data) 
    2328    Disconnect callback as listener. 
    2429 
    2530notify (name) 
    2631    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. 
    2735""" 
    2836 
    29 import types 
     37__all__ = [ 'Element' ] 
     38 
     39import types, mutex 
     40import gaphor.misc.uniqueid as uniqueid 
     41from properties import umlproperty, association 
    3042 
    3143class Element(object): 
    3244    """Base class for UML data classes.""" 
    3345 
    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") 
    3655 
    3756    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): 
    4163                prop.save(self, save_func) 
    4264 
    4365    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) 
    4871        except AttributeError, e: 
    4972            raise AttributeError, "'%s' has no property '%s'" % \ 
    50                                         (self.__class__.__name__, name) 
     73                                        (type(self).__name__, name) 
    5174        else: 
    5275            prop.load(self, value) 
    5376 
    5477    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() 
    56100 
    57101    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)) 
    62116        if type(names) is types.StringType: 
    63117            names = (names,) 
     
    72126                self._observers[name] = [cb] 
    73127 
    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.""" 
    77130        cb = (callback,) + data 
    78        for values in self._observers.values(): 
     131        for values in self._observers.values(): 
    79132            # Remove all occurences of 'cb' from values 
    80133            # (if none is found ValueError is raised). 
     
    85138                pass 
    86139 
    87     def notify(self, name): 
     140    def notify(self, name, cb_name=None, pspec=None): 
    88141        """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) 
    96158 
    97159    # OCL methods: (from SMW by Ivan Porres (http://www.abo.fi/~iporres/smw)) 
    98160 
    99     def isKindOf(self,clazz): 
     161    def isKindOf(self, class_): 
    100162        """Returns true if the object is an instance of clazz.""" 
    101         return isinstance(self, clazz
     163        return isinstance(self, class_
    102164 
    103165    def isTypeOf(self, other): 
     
    105167        return type(self) == type(other) 
    106168 
     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 
     175try: 
     176    import psyco 
     177except ImportError: 
     178    pass 
     179else: 
     180    psyco.bind(Element) 
    107181 
    108182if __name__ == '__main__': 
     
    112186        print '  cb_func:', name, args 
    113187 
    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) 
    117191 
    118192    print 'notify: ev1' 
     
    121195    a.notify('ev2') 
    122196  
    123     a.detach(cb_func, a) 
     197    a.disconnect(cb_func, a) 
    124198 
    125199    print 'notify: ev1' 
  • trunk/gaphor/uml2/properties.py

    r197 r445  
    2424    load(value):      load 'value' as the current value for this property 
    2525    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. 
    2727""" 
    2828 
    29 from collection import Collection 
     29__all__ = [ 'attribute', 'enumeration', 'association', 'derivedunion', 'redefine' ] 
     30 
     31from collection import collection 
    3032import operator 
     33from gaphor.undomanager import get_undo_manager 
    3134 
    3235#infinite = 100000 
     36 
     37class 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 
    3349 
    3450class umlproperty(object): 
     
    4561            self.deriviates = [ deriviate ] 
    4662 
    47     def __get__(self, obj, clazz=None): 
     63    def __get__(self, obj, class_=None): 
    4864        if not obj: 
    4965            return self 
     
    5672        self._del(obj, value) 
    5773 
     74    def save(self, obj, save_func): 
     75        if hasattr(obj, self._name): 
     76            save_func(self.name, self._get(obj)) 
     77 
    5878    def load(self, obj, value): 
    5979        self._set(obj, value) 
    6080 
    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) 
    6887 
    6988    def notify(self, obj): 
    7089        """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) 
    7696 
    7797        # we need to check if a deriviate is part of the current object: 
    7898        # 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: 
    84111                        d.notify(obj) 
    85         except AttributeError: 
    86             pass # no derivedunion or redefine attribute 
     112                    except Exception, e: 
     113                        log.error(e, e) 
     114 
     115 
     116class 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) 
    87129 
    88130 
     
    92134 
    93135    # TODO: check if lower and upper are actually needed for attributes 
    94     def __init__(self, name, type, default, lower