| 1 |
""" |
|---|
| 2 |
Adapters for the Property Editor |
|---|
| 3 |
|
|---|
| 4 |
# TODO: make all labels align top-left |
|---|
| 5 |
# Add hidden columns for list stores where i can put the actual object |
|---|
| 6 |
# being edited. |
|---|
| 7 |
|
|---|
| 8 |
TODO: |
|---|
| 9 |
- stereotypes |
|---|
| 10 |
- association / association ends. |
|---|
| 11 |
- Follow HIG guidelines: |
|---|
| 12 |
* Leave a 12-pixel border between the edge of the window and |
|---|
| 13 |
the nearest controls. |
|---|
| 14 |
* Leave a 12-pixel horizontal gap between a control and its label. (The gap |
|---|
| 15 |
may be bigger for other controls in the same group, due to differences in |
|---|
| 16 |
the lengths of the labels.) |
|---|
| 17 |
* Labels must be concise and make sense when taken out of context. |
|---|
| 18 |
Otherwise, users relying on screenreaders or similar assistive |
|---|
| 19 |
technologies will not always be able to immediately understand the |
|---|
| 20 |
relationship between a control and those surrounding it. |
|---|
| 21 |
* Assign access keys to all editable controls. Ensure that using the access |
|---|
| 22 |
key focuses its associated control. |
|---|
| 23 |
|
|---|
| 24 |
""" |
|---|
| 25 |
|
|---|
| 26 |
import gtk |
|---|
| 27 |
from gaphor.core import _, inject, transactional |
|---|
| 28 |
from gaphor.application import Application |
|---|
| 29 |
from gaphor.ui.interfaces import IPropertyPage |
|---|
| 30 |
from gaphor.diagram import items |
|---|
| 31 |
from zope import interface, component |
|---|
| 32 |
from gaphor import UML |
|---|
| 33 |
from gaphor.UML.interfaces import IAttributeChangeEvent |
|---|
| 34 |
from gaphor.UML.umllex import parse_attribute, render_attribute |
|---|
| 35 |
import gaphas.item |
|---|
| 36 |
|
|---|
| 37 |
|
|---|
| 38 |
class EditableTreeModel(gtk.ListStore): |
|---|
| 39 |
""" |
|---|
| 40 |
Editable GTK tree model based on ListStore model. |
|---|
| 41 |
|
|---|
| 42 |
Every row is represented by a list of editable values. Last column |
|---|
| 43 |
contains an object, which is being edited (this column is not |
|---|
| 44 |
displayed). When editable value in first column is set to empty string |
|---|
| 45 |
then object is deleted. |
|---|
| 46 |
|
|---|
| 47 |
Last row is empty and contains no object to edit. It allows to enter |
|---|
| 48 |
new values. |
|---|
| 49 |
|
|---|
| 50 |
When model is edited, then item is requested to be updated on canvas. |
|---|
| 51 |
|
|---|
| 52 |
Attributes: |
|---|
| 53 |
- _item: diagram item owning tree model |
|---|
| 54 |
""" |
|---|
| 55 |
element_factory = inject('element_factory') |
|---|
| 56 |
|
|---|
| 57 |
def __init__(self, item, cols=None): |
|---|
| 58 |
""" |
|---|
| 59 |
Create new model. |
|---|
| 60 |
|
|---|
| 61 |
Parameters: |
|---|
| 62 |
- _item: diagram item owning tree model |
|---|
| 63 |
- cols: model columns, defaults to [str, object] |
|---|
| 64 |
""" |
|---|
| 65 |
if cols is None: |
|---|
| 66 |
cols = (str, object) |
|---|
| 67 |
super(EditableTreeModel, self).__init__(*cols) |
|---|
| 68 |
self._item = item |
|---|
| 69 |
|
|---|
| 70 |
for data in self._get_rows(): |
|---|
| 71 |
self.append(data) |
|---|
| 72 |
self._add_empty() |
|---|
| 73 |
|
|---|
| 74 |
|
|---|
| 75 |
def _get_rows(self): |
|---|
| 76 |
""" |
|---|
| 77 |
Return rows to be edited. Last row has to contain object being |
|---|
| 78 |
edited. |
|---|
| 79 |
""" |
|---|
| 80 |
raise NotImplemented |
|---|
| 81 |
|
|---|
| 82 |
|
|---|
| 83 |
def _create_object(self): |
|---|
| 84 |
""" |
|---|
| 85 |
Create new object. |
|---|
| 86 |
""" |
|---|
| 87 |
raise NotImplemented |
|---|
| 88 |
|
|---|
| 89 |
|
|---|
| 90 |
def _set_object_value(self, row, col, value): |
|---|
| 91 |
""" |
|---|
| 92 |
Update row's column with a value. |
|---|
| 93 |
""" |
|---|
| 94 |
raise NotImplemented |
|---|
| 95 |
|
|---|
| 96 |
|
|---|
| 97 |
def _swap_objects(self, o1, o2): |
|---|
| 98 |
""" |
|---|
| 99 |
Swap two objects. If objects are swapped, then return ``True``. |
|---|
| 100 |
""" |
|---|
| 101 |
raise NotImplemented |
|---|
| 102 |
|
|---|
| 103 |
|
|---|
| 104 |
def _get_object(self, iter): |
|---|
| 105 |
""" |
|---|
| 106 |
Get object from ``iter``. |
|---|
| 107 |
""" |
|---|
| 108 |
path = self.get_path(iter) |
|---|
| 109 |
return self[path][-1] |
|---|
| 110 |
|
|---|
| 111 |
|
|---|
| 112 |
def swap(self, a, b): |
|---|
| 113 |
""" |
|---|
| 114 |
Swap two list rows. |
|---|
| 115 |
Parameters: |
|---|
| 116 |
- a: path to first row |
|---|
| 117 |
- b: path to second row |
|---|
| 118 |
""" |
|---|
| 119 |
if not a or not b: |
|---|
| 120 |
return |
|---|
| 121 |
o1 = self[a][-1] |
|---|
| 122 |
o2 = self[b][-1] |
|---|
| 123 |
if o1 and o2 and self._swap_objects(o1, o2): |
|---|
| 124 |
self._item.request_update(matrix=False) |
|---|
| 125 |
super(EditableTreeModel, self).swap(a, b) |
|---|
| 126 |
|
|---|
| 127 |
|
|---|
| 128 |
def _add_empty(self): |
|---|
| 129 |
""" |
|---|
| 130 |
Add empty row to the end of the model. |
|---|
| 131 |
""" |
|---|
| 132 |
self.append([None] * self.get_n_columns()) |
|---|
| 133 |
|
|---|
| 134 |
|
|---|
| 135 |
def iter_prev(self, iter): |
|---|
| 136 |
""" |
|---|
| 137 |
Get previous GTK tree iterator to ``iter``. |
|---|
| 138 |
""" |
|---|
| 139 |
i = self.get_path(iter)[0] |
|---|
| 140 |
if i == 0: |
|---|
| 141 |
return None |
|---|
| 142 |
return self.get_iter((i - 1,)) |
|---|
| 143 |
|
|---|
| 144 |
|
|---|
| 145 |
def set_value(self, iter, value, col): |
|---|
| 146 |
path = self.get_path(iter) |
|---|
| 147 |
row = self[path] |
|---|
| 148 |
|
|---|
| 149 |
if col == 0 and not value and row[-1]: |
|---|
| 150 |
|
|---|
| 151 |
self.remove(iter) |
|---|
| 152 |
|
|---|
| 153 |
elif value and not row[-1]: |
|---|
| 154 |
|
|---|
| 155 |
obj = self._create_object() |
|---|
| 156 |
row[-1] = obj |
|---|
| 157 |
self._set_object_value(row, col, value) |
|---|
| 158 |
self._add_empty() |
|---|
| 159 |
|
|---|
| 160 |
elif value: |
|---|
| 161 |
self._set_object_value(row, col, value) |
|---|
| 162 |
self._item.request_update(matrix=False) |
|---|
| 163 |
|
|---|
| 164 |
|
|---|
| 165 |
def remove(self, iter): |
|---|
| 166 |
""" |
|---|
| 167 |
Remove object from GTK model and destroy it. |
|---|
| 168 |
""" |
|---|
| 169 |
obj = self._get_object(iter) |
|---|
| 170 |
if obj: |
|---|
| 171 |
obj.unlink() |
|---|
| 172 |
self._item.request_update(matrix=False) |
|---|
| 173 |
return super(EditableTreeModel, self).remove(iter) |
|---|
| 174 |
else: |
|---|
| 175 |
return iter |
|---|
| 176 |
|
|---|
| 177 |
|
|---|
| 178 |
|
|---|
| 179 |
class ClassAttributes(EditableTreeModel): |
|---|
| 180 |
""" |
|---|
| 181 |
GTK tree model to edit class attributes. |
|---|
| 182 |
""" |
|---|
| 183 |
def _get_rows(self): |
|---|
| 184 |
for attr in self._item.subject.ownedAttribute: |
|---|
| 185 |
if not attr.association: |
|---|
| 186 |
yield [attr.render(), attr] |
|---|
| 187 |
|
|---|
| 188 |
|
|---|
| 189 |
def _create_object(self): |
|---|
| 190 |
attr = self.element_factory.create(UML.Property) |
|---|
| 191 |
self._item.subject.ownedAttribute = attr |
|---|
| 192 |
return attr |
|---|
| 193 |
|
|---|
| 194 |
|
|---|
| 195 |
def _set_object_value(self, row, col, value): |
|---|
| 196 |
attr = row[-1] |
|---|
| 197 |
attr.parse(value) |
|---|
| 198 |
row[0] = attr.render() |
|---|
| 199 |
|
|---|
| 200 |
|
|---|
| 201 |
def _swap_objects(self, o1, o2): |
|---|
| 202 |
return self._item.subject.ownedAttribute.swap(o1, o2) |
|---|
| 203 |
|
|---|
| 204 |
|
|---|
| 205 |
|
|---|
| 206 |
class ClassOperations(EditableTreeModel): |
|---|
| 207 |
""" |
|---|
| 208 |
GTK tree model to edit class operations. |
|---|
| 209 |
""" |
|---|
| 210 |
def _get_rows(self): |
|---|
| 211 |
for operation in self._item.subject.ownedOperation: |
|---|
| 212 |
yield [operation.render(), operation] |
|---|
| 213 |
|
|---|
| 214 |
|
|---|
| 215 |
def _create_object(self): |
|---|
| 216 |
operation = self.element_factory.create(UML.Operation) |
|---|
| 217 |
self._item.subject.ownedOperation = operation |
|---|
| 218 |
return operation |
|---|
| 219 |
|
|---|
| 220 |
|
|---|
| 221 |
def _set_object_value(self, row, col, value): |
|---|
| 222 |
operation = row[-1] |
|---|
| 223 |
operation.parse(value) |
|---|
| 224 |
row[0] = operation.render() |
|---|
| 225 |
|
|---|
| 226 |
|
|---|
| 227 |
def _swap_objects(self, o1, o2): |
|---|
| 228 |
return self._item.subject.ownedOperation.swap(o1, o2) |
|---|
| 229 |
|
|---|
| 230 |
|
|---|
| 231 |
|
|---|
| 232 |
class TaggedValues(EditableTreeModel): |
|---|
| 233 |
""" |
|---|
| 234 |
GTK tree model to edit tagged values. |
|---|
| 235 |
""" |
|---|
| 236 |
def __init__(self, item): |
|---|
| 237 |
super(TaggedValues, self).__init__(item, [str, str, object]) |
|---|
| 238 |
|
|---|
| 239 |
|
|---|
| 240 |
def _get_rows(self): |
|---|
| 241 |
for tv in self._item.subject.taggedValue: |
|---|
| 242 |
tag, value = tv.value.split("=") |
|---|
| 243 |
yield [tag, value, tv] |
|---|
| 244 |
|
|---|
| 245 |
|
|---|
| 246 |
def _create_object(self): |
|---|
| 247 |
tv = self.element_factory.create(UML.LiteralSpecification) |
|---|
| 248 |
self._item.subject.taggedValue.append(tv) |
|---|
| 249 |
return tv |
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 |
def _set_object_value(self, row, col, value): |
|---|
| 253 |
tv = row[-1] |
|---|
| 254 |
row[col] = value |
|---|
| 255 |
tv.value = '%s=%s' % (row[0], row[1]) |
|---|
| 256 |
|
|---|
| 257 |
|
|---|
| 258 |
def _swap_objects(self, o1, o2): |
|---|
| 259 |
return self._item.subject.taggedValue.swap(o1, o2) |
|---|
| 260 |
|
|---|
| 261 |
|
|---|
| 262 |
|
|---|
| 263 |
class CommunicationMessageModel(EditableTreeModel): |
|---|
| 264 |
""" |
|---|
| 265 |
GTK tree model for list of messages on communication diagram. |
|---|
| 266 |
""" |
|---|
| 267 |
def __init__(self, item, cols=None, inverted=False): |
|---|
| 268 |
self.inverted = inverted |
|---|
| 269 |
super(CommunicationMessageModel, self).__init__(item, cols) |
|---|
| 270 |
|
|---|
| 271 |
def _get_rows(self): |
|---|
| 272 |
if self.inverted: |
|---|
| 273 |
for message in self._item._inverted_messages: |
|---|
| 274 |
yield [message.name, message] |
|---|
| 275 |
else: |
|---|
| 276 |
for message in self._item._messages: |
|---|
| 277 |
yield [message.name, message] |
|---|
| 278 |
|
|---|
| 279 |
|
|---|
| 280 |
def remove(self, iter): |
|---|
| 281 |
""" |
|---|
| 282 |
Remove message from message item and destroy it. |
|---|
| 283 |
""" |
|---|
| 284 |
message = self._get_object(iter) |
|---|
| 285 |
item = self._item |
|---|
| 286 |
super(CommunicationMessageModel, self).remove(iter) |
|---|
| 287 |
item.remove_message(message, self.inverted) |
|---|
| 288 |
|
|---|
| 289 |
|
|---|
| 290 |
def _create_object(self): |
|---|
| 291 |
item = self._item |
|---|
| 292 |
subject = item.subject |
|---|
| 293 |
message = self.element_factory.create(UML.Message) |
|---|
| 294 |
if self.inverted: |
|---|
| 295 |
|
|---|
| 296 |
message.sendEvent = subject.receiveEvent |
|---|
| 297 |
message.receiveEvent = subject.sendEvent |
|---|
| 298 |
else: |
|---|
| 299 |
|
|---|
| 300 |
message.sendEvent = subject.sendEvent |
|---|
| 301 |
message.receiveEvent = subject.receiveEvent |
|---|
| 302 |
item.add_message(message, self.inverted) |
|---|
| 303 |
return message |
|---|
| 304 |
|
|---|
| 305 |
|
|---|
| 306 |
def _set_object_value(self, row, col, value): |
|---|
| 307 |
message = row[-1] |
|---|
| 308 |
message.name = value |
|---|
| 309 |
row[0] = value |
|---|
| 310 |
self._item.set_message_text(message, value, self.inverted) |
|---|
| 311 |
|
|---|
| 312 |
|
|---|
| 313 |
def _swap_objects(self, o1, o2): |
|---|
| 314 |
return self._item.swap_messages(o1, o2, self.inverted) |
|---|
| 315 |
|
|---|
| 316 |
|
|---|
| 317 |
|
|---|
| 318 |
def remove_on_keypress(tree, event): |
|---|
| 319 |
""" |
|---|
| 320 |
Remove selected items from GTK model on ``backspace`` keypress. |
|---|
| 321 |
""" |
|---|
| 322 |
k = gtk.gdk.keyval_name(event.keyval).lower() |
|---|
| 323 |
if k == 'backspace' or k == 'kp_delete': |
|---|
| 324 |
model, iter = tree.get_selection().get_selected() |
|---|
| 325 |
if iter: |
|---|
| 326 |
model.remove(iter) |
|---|
| 327 |
|
|---|
| 328 |
|
|---|
| 329 |
def swap_on_keypress(tree, event): |
|---|
| 330 |
""" |
|---|
| 331 |
Swap selected and previous (or next) items. |
|---|
| 332 |
""" |
|---|
| 333 |
k = gtk.gdk.keyval_name(event.keyval).lower() |
|---|
| 334 |
if k == 'equal' or k == 'kp_add': |
|---|
| 335 |
model, iter = tree.get_selection().get_selected() |
|---|
| 336 |
model.swap(iter, model.iter_next(iter)) |
|---|
| 337 |
return True |
|---|
| 338 |
elif k == 'minus': |
|---|
| 339 |
model, iter = tree.get_selection().get_selected() |
|---|
| 340 |
model.swap(iter, model.iter_prev(iter)) |
|---|
| 341 |
return True |
|---|
| 342 |
|
|---|
| 343 |
|
|---|
| 344 |
@transactional |
|---|
| 345 |
def on_cell_edited(renderer, path, value, model, col): |
|---|
| 346 |
""" |
|---|
| 347 |
Update editable tree model based on fresh user input. |
|---|
| 348 |
""" |
|---|
| 349 |
iter = model.get_iter(path) |
|---|
| 350 |
model.set_value(iter, value, col) |
|---|
| 351 |
|
|---|
| 352 |
|
|---|
| 353 |
class UMLComboModel(gtk.ListStore): |
|---|
| 354 |
""" |
|---|
| 355 |
UML combo box model. |
|---|
| 356 |
|
|---|
| 357 |
Model allows to easily create a combo box with values and their labels, |
|---|
| 358 |
for example |
|---|
| 359 |
|
|---|
| 360 |
label1 -> value1 |
|---|
| 361 |
label2 -> value2 |
|---|
| 362 |
label3 -> value3 |
|---|
| 363 |
|
|---|
| 364 |
Labels are displayed by combo box and programmer has easy access to |
|---|
| 365 |
values associated with given label. |
|---|
| 366 |
|
|---|
| 367 |
Attributes: |
|---|
| 368 |
|
|---|
| 369 |
- _data: model data |
|---|
| 370 |
- _indices: dictionary of values' indices |
|---|
| 371 |
""" |
|---|
| 372 |
def __init__(self, data): |
|---|
| 373 |
super(UMLComboModel, self).__init__(str) |
|---|
| 374 |
|
|---|
| 375 |
self._indices = {} |
|---|
| 376 |
self._data = data |
|---|
| 377 |
|
|---|
| 378 |
|
|---|
| 379 |
for i, (label, value) in enumerate(data): |
|---|
| 380 |
self.append([label]) |
|---|
| 381 |
self._indices[value] = i |
|---|
| 382 |
|
|---|
| 383 |
|
|---|
| 384 |
def get_index(self, value): |
|---|
| 385 |
""" |
|---|
| 386 |
Return index of a ``value``. |
|---|
| 387 |
""" |
|---|
| 388 |
return self._indices[value] |
|---|
| 389 |
|
|---|
| 390 |
|
|---|
| 391 |
def get_value(self, index): |
|---|
| 392 |
""" |
|---|
| 393 |
Get value for given ``index``. |
|---|
| 394 |
""" |
|---|
| 395 |
return self._data[index][1] |
|---|
| 396 |
|
|---|
| 397 |
|
|---|
| 398 |
|
|---|
| 399 |
def create_uml_combo(data, callback): |
|---|
| 400 |
""" |
|---|
| 401 |
Create a combo box using ``UMLComboModel`` model. |
|---|
| 402 |
|
|---|
| 403 |
Combo box is returned. |
|---|
| 404 |
""" |
|---|
| 405 |
model = UMLComboModel(data) |
|---|
| 406 |
combo = gtk.ComboBox(model) |
|---|
| 407 |
cell = gtk.CellRendererText() |
|---|
| 408 |
combo.pack_start(cell, True) |
|---|
| 409 |
combo.add_attribute(cell, 'text', 0) |
|---|
| 410 |
combo.connect('changed', callback) |
|---|
| 411 |
return combo |
|---|
| 412 |
|
|---|
| 413 |
|
|---|
| 414 |
def watch_attribute(attribute, widget, handler): |
|---|
| 415 |
""" |
|---|
| 416 |
Watch attribute ``attribute`` for changes. If it changes |
|---|
| 417 |
``handler(event)`` is called. When ``widget`` is destroyed, the |
|---|
| 418 |
handler is unregistered. |
|---|
| 419 |
If ``attribute`` is None, all attribute events are propagated to teh handler. |
|---|
| 420 |
""" |
|---|
| 421 |
@component.adapter(IAttributeChangeEvent) |
|---|
| 422 |
def attribute_watcher(event): |
|---|
| 423 |
if attribute is None or event.property is attribute: |
|---|
| 424 |
handler(event) |
|---|
| 425 |
|
|---|
| 426 |
Application.register_handler(attribute_watcher) |
|---|
| 427 |
|
|---|
| 428 |
def destroy_handler(_widget): |
|---|
| 429 |
Application.unregister_handler(attribute_watcher) |
|---|
| 430 |
widget.connect('destroy', destroy_handler) |
|---|
| 431 |
|
|---|
| 432 |
|
|---|
| 433 |
|
|---|
| 434 |
def create_hbox_label(adapter, page, label): |
|---|
| 435 |
""" |
|---|
| 436 |
Create a HBox with a label for given property page adapter and page |
|---|
| 437 |
itself. |
|---|
| 438 |
""" |
|---|
| 439 |
hbox = gtk.HBox() |
|---|
| 440 |
label = gtk.Label(label) |
|---|
| 441 |
label.set_justify(gtk.JUSTIFY_LEFT) |
|---|
| 442 |
adapter.size_group.add_widget(label) |
|---|
| 443 |
hbox.pack_start(label, expand=False) |
|---|
| 444 |
page.pack_start(hbox, expand=False) |
|---|
| 445 |
return hbox |
|---|
| 446 |
|
|---|
| 447 |
|
|---|
| 448 |
def create_tree_view(model, names, tip=""): |
|---|
| 449 |
""" |
|---|
| 450 |
Create a tree view for a editable tree model. |
|---|
| 451 |
""" |
|---|
| 452 |
tree_view = gtk.TreeView(model) |
|---|
| 453 |
tree_view.set_rules_hint(True) |
|---|
| 454 |
|
|---|
| 455 |
n = model.get_n_columns() - 1 |
|---|
| 456 |
for i in range(n): |
|---|
| 457 |
renderer = gtk.CellRendererText() |
|---|
| 458 |
renderer.set_property('editable', True) |
|---|
| 459 |
renderer.connect('edited', on_cell_edited, model, i) |
|---|
| 460 |
col = gtk.TreeViewColumn(names[i], renderer, text=i) |
|---|
| 461 |
tree_view.append_column(col) |
|---|
| 462 |
|
|---|
| 463 |
tree_view.connect('key_press_event', remove_on_keypress) |
|---|
| 464 |
tree_view.connect('key_press_event', swap_on_keypress) |
|---|
| 465 |
|
|---|
| 466 |
tip = tip + """ |
|---|
| 467 |
Press ENTER to edit item, BS/DEL to remove item. |
|---|
| 468 |
Use -/= to move items up or down.\ |
|---|
| 469 |
""" |
|---|
| 470 |
tooltips = gtk.Tooltips() |
|---|
| 471 |
tooltips.set_tip(tree_view, tip) |
|---|
| 472 |
|
|---|
| 473 |
return tree_view |
|---|
| 474 |
|
|---|
| 475 |
|
|---|
| 476 |
|
|---|
| 477 |
class CommentItemPropertyPage(object): |
|---|
| 478 |
""" |
|---|
| 479 |
Property page for Comments |
|---|
| 480 |
""" |
|---|
| 481 |
interface.implements(IPropertyPage) |
|---|
| 482 |
component.adapts(items.CommentItem) |
|---|
| 483 |
|
|---|
| 484 |
def __init__(self, context): |
|---|
| 485 |
self.context = context |
|---|
| 486 |
|
|---|
| 487 |
def construct(self): |
|---|
| 488 |
subject = self.context.subject |
|---|
| 489 |
page = gtk.VBox() |
|---|
| 490 |
|
|---|
| 491 |
label = gtk.Label(_('Comment')) |
|---|
| 492 |
label.set_justify(gtk.JUSTIFY_LEFT) |
|---|
| 493 |
page.pack_start(label, expand=False) |
|---|
| 494 |
|
|---|
| 495 |
buffer = gtk.TextBuffer() |
|---|
| 496 |
if subject.body: |
|---|
| 497 |
buffer.set_text(subject.body) |
|---|
| 498 |
text_view = gtk.TextView() |
|---|
| 499 |
text_view.set_buffer(buffer) |
|---|
| 500 |
text_view.show() |
|---|
| 501 |
page.pack_start(text_view) |
|---|
| 502 |
|
|---|
| 503 |
changed_id = buffer.connect('changed', self._on_body_change) |
|---|
| 504 |
|
|---|
| 505 |
def handler(event): |
|---|
| 506 |
if event.element is subject and event.new_value is not None: |
|---|
| 507 |
buffer.handler_block(changed_id) |
|---|
| 508 |
buffer.set_text(event.new_value) |
|---|
| 509 |
buffer.handler_unblock(changed_id) |
|---|
| 510 |
watch_attribute(type(subject).body, text_view, handler) |
|---|
| 511 |
|
|---|
| 512 |
page.show_all() |
|---|
| 513 |
return page |
|---|
| 514 |
|
|---|
| 515 |
@transactional |
|---|
| 516 |
def _on_body_change(self, buffer): |
|---|
| 517 |
self.context.subject.body = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter()) |
|---|
| 518 |
|
|---|
| 519 |
component.provideAdapter(CommentItemPropertyPage, name='Properties') |
|---|
| 520 |
|
|---|
| 521 |
|
|---|
| 522 |
class NamedItemPropertyPage(object): |
|---|
| 523 |
""" |
|---|
| 524 |
An adapter which works for any named item view. |
|---|
| 525 |
|
|---|
| 526 |
It also sets up a table view which can be extended. |
|---|
| 527 |
""" |
|---|
| 528 |
|
|---|
| 529 |
interface.implements(IPropertyPage) |
|---|
| 530 |
|
|---|
| 531 |
NAME_LABEL = _('Name') |
|---|
| 532 |
|
|---|
| 533 |
def __init__(self, context): |
|---|
| 534 |
self.context = context |
|---|
| 535 |
self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) |
|---|
| 536 |
|
|---|
| 537 |
def construct(self): |
|---|
| 538 |
page = gtk.VBox() |
|---|
| 539 |
|
|---|
| 540 |
subject = self.context.subject |
|---|
| 541 |
if not subject: |
|---|
| 542 |
return page |
|---|
| 543 |
|
|---|
| 544 |
hbox = create_hbox_label(self, page, self.NAME_LABEL) |
|---|
| 545 |
entry = gtk.Entry() |
|---|
| 546 |
entry.set_text(subject and subject.name or '') |
|---|
| 547 |
hbox.pack_start(entry) |
|---|
| 548 |
|
|---|
| 549 |
|
|---|
| 550 |
changed_id = entry.connect('changed', self._on_name_change) |
|---|
| 551 |
|
|---|
| 552 |
def |
|---|