| 1 |
""" |
|---|
| 2 |
DiagramItem provides basic functionality for presentations. |
|---|
| 3 |
Such as a modifier 'subject' property and a unique id. |
|---|
| 4 |
""" |
|---|
| 5 |
|
|---|
| 6 |
from zope import component |
|---|
| 7 |
from gaphor import UML |
|---|
| 8 |
from gaphor.application import Application |
|---|
| 9 |
from gaphor.misc import uniqueid |
|---|
| 10 |
from gaphor.diagram import DiagramItemMeta |
|---|
| 11 |
from gaphor.diagram.textelement import EditableTextSupport |
|---|
| 12 |
from gaphor.diagram.style import ALIGN_CENTER, ALIGN_TOP |
|---|
| 13 |
|
|---|
| 14 |
STEREOTYPE_OPEN = '\xc2\xab' |
|---|
| 15 |
STEREOTYPE_CLOSE = '\xc2\xbb' |
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
class StereotypeSupport(object): |
|---|
| 19 |
""" |
|---|
| 20 |
Support methods for stereotypes. |
|---|
| 21 |
""" |
|---|
| 22 |
STEREOTYPE_ALIGN = { |
|---|
| 23 |
'text-align' : (ALIGN_CENTER, ALIGN_TOP), |
|---|
| 24 |
'text-padding': (5, 10, 2, 10), |
|---|
| 25 |
'text-outside': False, |
|---|
| 26 |
'text-align-group': 'stereotype', |
|---|
| 27 |
} |
|---|
| 28 |
|
|---|
| 29 |
def __init__(self): |
|---|
| 30 |
self._stereotype = self.add_text('stereotype', |
|---|
| 31 |
style=self.STEREOTYPE_ALIGN, |
|---|
| 32 |
pattern='%s%%s%s' % (STEREOTYPE_OPEN, STEREOTYPE_CLOSE), |
|---|
| 33 |
visible=self.is_stereotype_visible) |
|---|
| 34 |
|
|---|
| 35 |
|
|---|
| 36 |
def is_stereotype_visible(self): |
|---|
| 37 |
""" |
|---|
| 38 |
Display stereotype if it is not empty. |
|---|
| 39 |
""" |
|---|
| 40 |
return self._stereotype.text |
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 |
def set_stereotype(self, text=None): |
|---|
| 44 |
""" |
|---|
| 45 |
Set the stereotype text for the diagram item. |
|---|
| 46 |
|
|---|
| 47 |
Note, that text is not Stereotype object. |
|---|
| 48 |
|
|---|
| 49 |
@arg text: stereotype text |
|---|
| 50 |
""" |
|---|
| 51 |
self._stereotype.text = text |
|---|
| 52 |
self.request_update() |
|---|
| 53 |
|
|---|
| 54 |
stereotype = property(lambda s: s._stereotype, set_stereotype) |
|---|
| 55 |
|
|---|
| 56 |
def update_stereotype(self): |
|---|
| 57 |
""" |
|---|
| 58 |
Update the stereotype definitions (text) of this item. |
|---|
| 59 |
|
|---|
| 60 |
Note, that this method is also called from |
|---|
| 61 |
ExtensionItem.confirm_connect_handle method. |
|---|
| 62 |
""" |
|---|
| 63 |
if self.subject: |
|---|
| 64 |
applied_stereotype = self.subject.appliedStereotype |
|---|
| 65 |
else: |
|---|
| 66 |
applied_stereotype = None |
|---|
| 67 |
|
|---|
| 68 |
def stereotype_name(name): |
|---|
| 69 |
""" |
|---|
| 70 |
Return a nice name to display as stereotype. First will be |
|---|
| 71 |
character lowercase unless the second character is uppercase. |
|---|
| 72 |
""" |
|---|
| 73 |
if len(name) > 1 and name[1].isupper(): |
|---|
| 74 |
return name |
|---|
| 75 |
else: |
|---|
| 76 |
return name[0].lower() + name[1:] |
|---|
| 77 |
|
|---|
| 78 |
|
|---|
| 79 |
|
|---|
| 80 |
|
|---|
| 81 |
stereotype = getattr(self, '__stereotype__', None) |
|---|
| 82 |
if stereotype: |
|---|
| 83 |
stereotype = self.parse_stereotype(stereotype) |
|---|
| 84 |
|
|---|
| 85 |
if applied_stereotype: |
|---|
| 86 |
|
|---|
| 87 |
sl = ', '.join(stereotype_name(s.name) for s in applied_stereotype) |
|---|
| 88 |
if stereotype: |
|---|
| 89 |
stereotype = '%s, %s' % (stereotype, sl) |
|---|
| 90 |
else: |
|---|
| 91 |
stereotype = sl |
|---|
| 92 |
|
|---|
| 93 |
|
|---|
| 94 |
self.set_stereotype(stereotype) |
|---|
| 95 |
|
|---|
| 96 |
def parse_stereotype(self, data): |
|---|
| 97 |
if isinstance(data, str): |
|---|
| 98 |
return data |
|---|
| 99 |
|
|---|
| 100 |
subject = self.subject |
|---|
| 101 |
|
|---|
| 102 |
for stereotype, condition in data.items(): |
|---|
| 103 |
if isinstance(condition, tuple): |
|---|
| 104 |
cls, predicate = condition |
|---|
| 105 |
elif isinstance(condition, type): |
|---|
| 106 |
cls = condition |
|---|
| 107 |
predicate = None |
|---|
| 108 |
elif callable(condition): |
|---|
| 109 |
cls = None |
|---|
| 110 |
predicate = condition |
|---|
| 111 |
else: |
|---|
| 112 |
assert False, 'wrong conditional %s' % condition |
|---|
| 113 |
|
|---|
| 114 |
ok = True |
|---|
| 115 |
if cls: |
|---|
| 116 |
ok = type(subject) is cls |
|---|
| 117 |
if predicate: |
|---|
| 118 |
ok = predicate(self) |
|---|
| 119 |
|
|---|
| 120 |
if ok: |
|---|
| 121 |
return stereotype |
|---|
| 122 |
return None |
|---|
| 123 |
|
|---|
| 124 |
|
|---|
| 125 |
class DiagramItem(UML.Presentation, StereotypeSupport, EditableTextSupport): |
|---|
| 126 |
""" |
|---|
| 127 |
Basic functionality for all model elements (lines and elements!). |
|---|
| 128 |
|
|---|
| 129 |
This class contains common functionallity for model elements and |
|---|
| 130 |
relationships. |
|---|
| 131 |
It provides an interface similar to UML.Element for connecting and |
|---|
| 132 |
disconnecting signals. |
|---|
| 133 |
|
|---|
| 134 |
This class is not very useful on its own. It contains some glue-code for |
|---|
| 135 |
diacanvas.DiaCanvasItem and gaphor.UML.Element. |
|---|
| 136 |
|
|---|
| 137 |
Example: |
|---|
| 138 |
class ElementItem(diacanvas.CanvasElement, DiagramItem): |
|---|
| 139 |
connect = DiagramItem.connect |
|---|
| 140 |
disconnect = DiagramItem.disconnect |
|---|
| 141 |
... |
|---|
| 142 |
|
|---|
| 143 |
@cvar style: styles information (derived from DiagramItemMeta) |
|---|
| 144 |
""" |
|---|
| 145 |
|
|---|
| 146 |
__metaclass__ = DiagramItemMeta |
|---|
| 147 |
|
|---|
| 148 |
def __init__(self, id=None): |
|---|
| 149 |
UML.Presentation.__init__(self) |
|---|
| 150 |
EditableTextSupport.__init__(self) |
|---|
| 151 |
StereotypeSupport.__init__(self) |
|---|
| 152 |
|
|---|
| 153 |
self._id = id |
|---|
| 154 |
|
|---|
| 155 |
|
|---|
| 156 |
self._persistent_props = set() |
|---|
| 157 |
self._watched_properties = dict() |
|---|
| 158 |
|
|---|
| 159 |
self.add_watch(UML.Element.appliedStereotype, self.on_element_applied_stereotype) |
|---|
| 160 |
|
|---|
| 161 |
id = property(lambda self: self._id, doc='Id') |
|---|
| 162 |
|
|---|
| 163 |
|
|---|
| 164 |
def set_prop_persistent(self, name): |
|---|
| 165 |
""" |
|---|
| 166 |
Specify property of diagram item, which should be saved in file. |
|---|
| 167 |
""" |
|---|
| 168 |
self._persistent_props.add(name) |
|---|
| 169 |
|
|---|
| 170 |
|
|---|
| 171 |
|
|---|
| 172 |
|
|---|
| 173 |
|
|---|
| 174 |
def save(self, save_func): |
|---|
| 175 |
if self.subject: |
|---|
| 176 |
save_func('subject', self.subject) |
|---|
| 177 |
|
|---|
| 178 |
|
|---|
| 179 |
for p in self._persistent_props: |
|---|
| 180 |
save_func(p, getattr(self, p.replace('-', '_')), reference=True) |
|---|
| 181 |
|
|---|
| 182 |
|
|---|
| 183 |
def load(self, name, value): |
|---|
| 184 |
if name == 'subject': |
|---|
| 185 |
type(self).subject.load(self, value) |
|---|
| 186 |
else: |
|---|
| 187 |
|
|---|
| 188 |
try: |
|---|
| 189 |
setattr(self, name.replace('-', '_'), eval(value)) |
|---|
| 190 |
except: |
|---|
| 191 |
log.warning('%s has no property named %s (value %s)' % (self, name, value)) |
|---|
| 192 |
|
|---|
| 193 |
|
|---|
| 194 |
def postload(self): |
|---|
| 195 |
if self.subject: |
|---|
| 196 |
self.on_presentation_subject(None) |
|---|
| 197 |
|
|---|
| 198 |
|
|---|
| 199 |
def save_property(self, save_func, name): |
|---|
| 200 |
""" |
|---|
| 201 |
Save a property, this is a shorthand method. |
|---|
| 202 |
""" |
|---|
| 203 |
save_func(name, getattr(self, name.replace('-', '_'))) |
|---|
| 204 |
|
|---|
| 205 |
|
|---|
| 206 |
def save_properties(self, save_func, *names): |
|---|
| 207 |
""" |
|---|
| 208 |
Save a property, this is a shorthand method. |
|---|
| 209 |
""" |
|---|
| 210 |
for name in names: |
|---|
| 211 |
self.save_property(save_func, name) |
|---|
| 212 |
|
|---|
| 213 |
|
|---|
| 214 |
def unlink(self): |
|---|
| 215 |
""" |
|---|
| 216 |
Remove the item from the canvas and set subject to None. |
|---|
| 217 |
""" |
|---|
| 218 |
if self.canvas: |
|---|
| 219 |
self.canvas.remove(self) |
|---|
| 220 |
super(DiagramItem, self).unlink() |
|---|
| 221 |
|
|---|
| 222 |
|
|---|
| 223 |
def request_update(self): |
|---|
| 224 |
""" |
|---|
| 225 |
Placeholder for gaphor.Item's request_update() method. |
|---|
| 226 |
""" |
|---|
| 227 |
pass |
|---|
| 228 |
|
|---|
| 229 |
def pre_update(self, context): |
|---|
| 230 |
EditableTextSupport.pre_update(self, context) |
|---|
| 231 |
|
|---|
| 232 |
def post_update(self, context): |
|---|
| 233 |
EditableTextSupport.post_update(self, context) |
|---|
| 234 |
|
|---|
| 235 |
def draw(self, context): |
|---|
| 236 |
EditableTextSupport.draw(self, context) |
|---|
| 237 |
|
|---|
| 238 |
|
|---|
| 239 |
def item_at(self, x, y): |
|---|
| 240 |
return self |
|---|
| 241 |
|
|---|
| 242 |
|
|---|
| 243 |
def on_element_applied_stereotype(self, event): |
|---|
| 244 |
if self.subject: |
|---|
| 245 |
self.update_stereotype() |
|---|
| 246 |
self.request_update() |
|---|
| 247 |
|
|---|
| 248 |
|
|---|
| 249 |
def add_watch(self, property, handler=None): |
|---|
| 250 |
""" |
|---|
| 251 |
Add a property (umlproperty) to be watched. a handler may be provided |
|---|
| 252 |
that will be called with the event as argument (handler(event)). |
|---|
| 253 |
""" |
|---|
| 254 |
assert isinstance(property, UML.properties.umlproperty) |
|---|
| 255 |
|
|---|
| 256 |
self._watched_properties[property] = handler |
|---|
| 257 |
|
|---|
| 258 |
|
|---|
| 259 |
def register_handlers(self): |
|---|
| 260 |
Application.register_handler(self.on_model_factory_event) |
|---|
| 261 |
Application.register_handler(self.on_element_change) |
|---|
| 262 |
Application.register_handler(self.on_presentation_subject) |
|---|
| 263 |
|
|---|
| 264 |
|
|---|
| 265 |
|
|---|
| 266 |
|
|---|
| 267 |
|
|---|
| 268 |
def unregister_handlers(self): |
|---|
| 269 |
Application.unregister_handler(self.on_model_factory_event) |
|---|
| 270 |
Application.unregister_handler(self.on_presentation_subject) |
|---|
| 271 |
Application.unregister_handler(self.on_element_change) |
|---|
| 272 |
|
|---|
| 273 |
|
|---|
| 274 |
@component.adapter(UML.interfaces.IModelFactoryEvent) |
|---|
| 275 |
def on_model_factory_event(self, event): |
|---|
| 276 |
self.on_presentation_subject(None) |
|---|
| 277 |
|
|---|
| 278 |
|
|---|
| 279 |
@component.adapter(UML.interfaces.IAssociationSetEvent) |
|---|
| 280 |
def on_presentation_subject(self, event): |
|---|
| 281 |
if event is None or \ |
|---|
| 282 |
(event.property is UML.Presentation.subject and \ |
|---|
| 283 |
event.element is self): |
|---|
| 284 |
for prop, handler in self._watched_properties.iteritems(): |
|---|
| 285 |
if handler: |
|---|
| 286 |
|
|---|
| 287 |
handler(None) |
|---|
| 288 |
|
|---|
| 289 |
|
|---|
| 290 |
@component.adapter(UML.interfaces.IElementChangeEvent) |
|---|
| 291 |
def on_element_change(self, event): |
|---|
| 292 |
""" |
|---|
| 293 |
Called when a model element has changed. |
|---|
| 294 |
""" |
|---|
| 295 |
if event.property in self._watched_properties: |
|---|
| 296 |
handler = self._watched_properties[event.property] |
|---|
| 297 |
if handler: |
|---|
| 298 |
handler(event) |
|---|
| 299 |
elif self.subject and self.subject is event.element: |
|---|
| 300 |
self.request_update() |
|---|
| 301 |
|
|---|
| 302 |
|
|---|
| 303 |
|
|---|