Merge lp:~spud/spud/slice-view into lp:spud

Proposed by Fraser Waters
Status: Merged
Approved by: Patrick Farrell
Approved revision: 447
Merged at revision: 424
Proposed branch: lp:~spud/spud/slice-view
Merge into: lp:spud
Diff against target: 4544 lines (+2067/-1919)
13 files modified
diamond/diamond/attributewidget.py (+341/-0)
diamond/diamond/choice.py (+73/-1)
diamond/diamond/commentwidget.py (+146/-0)
diamond/diamond/databuttonswidget.py (+47/-0)
diamond/diamond/datatype.py (+75/-0)
diamond/diamond/datawidget.py (+501/-0)
diamond/diamond/descriptionwidget.py (+190/-0)
diamond/diamond/interface.py (+200/-1687)
diamond/diamond/mixedtree.py (+204/-0)
diamond/diamond/schema.py (+1/-1)
diamond/diamond/sliceview.py (+117/-0)
diamond/diamond/tree.py (+164/-10)
diamond/gui/gui.glade (+8/-220)
To merge this branch: bzr merge lp:~spud/spud/slice-view
Reviewer Review Type Date Requested Status
Patrick Farrell Approve
Review via email: mp+69068@code.launchpad.net

Description of the change

Adds slice view.

There is one small issue with this merge. The main view will NOT update the view of the currently selected row if that row is changed in the slice view. If this is merged in that should be marked as a bug.

To post a comment you must log in.
lp:~spud/spud/slice-view updated
442. By Fraser Waters

Added node paramater to choice _on_set_*

443. By Fraser Waters

Slice view is bigger and better aligned.

444. By Fraser Waters

No longer includes inactive nodes.

445. By Fraser Waters

Main view updates when slice view closed.

446. By Fraser Waters

Can't slice if nothing to slice on.

447. By Fraser Waters

If can't slice display message in statusbar.

Revision history for this message
Patrick Farrell (pefarrell) wrote :

Fraser and I have gone through several iterations; I have tried to break it, and can't. I've also read through the code changes and it all looks good. I'm going to merge this so that I can push a new diamond package out, so that users can give us bug reports.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'diamond/diamond/attributewidget.py'
2--- diamond/diamond/attributewidget.py 1970-01-01 00:00:00 +0000
3+++ diamond/diamond/attributewidget.py 2011-07-25 13:56:22 +0000
4@@ -0,0 +1,341 @@
5+#!/usr/bin/env python
6+
7+# This file is part of Diamond.
8+#
9+# Diamond is free software: you can redistribute it and/or modify
10+# it under the terms of the GNU General Public License as published by
11+# the Free Software Foundation, either version 3 of the License, or
12+# (at your option) any later version.
13+#
14+# Diamond is distributed in the hope that it will be useful,
15+# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+# GNU General Public License for more details.
18+#
19+# You should have received a copy of the GNU General Public License
20+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
21+
22+import gobject
23+import gtk
24+
25+import datatype
26+import dialogs
27+
28+class AttributeWidget(gtk.Frame):
29+
30+ __gsignals__ = { "on-store" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
31+ "update-name" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())}
32+
33+ def __init__(self):
34+ gtk.Frame.__init__(self)
35+
36+ scrolledWindow = gtk.ScrolledWindow()
37+ scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
38+
39+ treeview = self.treeview = gtk.TreeView()
40+
41+ model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
42+ treeview.set_model(model)
43+ treeview.connect("motion-notify-event", self.treeview_mouse_over)
44+
45+ key_renderer = gtk.CellRendererText()
46+ key_renderer.set_property("editable", False)
47+
48+ column1 = gtk.TreeViewColumn("Name", key_renderer, text = 0)
49+ column1.set_cell_data_func(key_renderer, self.key_data_func)
50+ column1.set_property("min-width", 75)
51+
52+ entry_renderer = gtk.CellRendererText()
53+ entry_renderer.connect("edited", self.entry_edited)
54+ entry_renderer.connect("editing-started", self.entry_edit_start)
55+
56+ combo_renderer = gtk.CellRendererCombo()
57+ combo_renderer.set_property("text-column", 0)
58+ combo_renderer.connect("edited", self.combo_selected)
59+ combo_renderer.connect("editing-started", self.combo_edit_start)
60+
61+ column2 = gtk.TreeViewColumn("Value", entry_renderer, text = 1)
62+ column2.pack_start(combo_renderer)
63+ column2.set_attributes(combo_renderer, text = 1)
64+ column2.set_cell_data_func(entry_renderer, self.entry_data_func)
65+ column2.set_cell_data_func(combo_renderer, self.combo_data_func)
66+ column2.set_property("expand", True)
67+ column2.set_property("min-width", 75)
68+
69+ icon_renderer = gtk.CellRendererPixbuf()
70+
71+ column3 = gtk.TreeViewColumn("", icon_renderer)
72+ column3.set_cell_data_func(icon_renderer, self.icon_data_func)
73+
74+ treeview.append_column(column1)
75+ treeview.append_column(column2)
76+ treeview.append_column(column3)
77+
78+ scrolledWindow.add(treeview)
79+
80+ label = gtk.Label()
81+ label.set_markup("<b>Attributes</b>")
82+
83+ self.set_label_widget(label)
84+ self.set_shadow_type(gtk.SHADOW_NONE)
85+ self.add(scrolledWindow)
86+
87+ def update(self, node):
88+
89+ self.treeview.get_model().clear()
90+
91+ self.node = node
92+
93+ if node is None or not node.attrs.keys():
94+ self.set_property("visible", False)
95+ else:
96+ self.set_property("visible", True)
97+
98+ for key in node.attrs.keys():
99+ model = self.treeview.get_model()
100+ cell_model = gtk.ListStore(gobject.TYPE_STRING)
101+
102+ iter = model.append()
103+ model.set_value(iter, 0, key)
104+ model.set_value(iter, 2, cell_model)
105+
106+ if isinstance(node.attrs[key][0], tuple):
107+ if node.attrs[key][1] is None:
108+ if isinstance(node.attrs[key][0][0], tuple):
109+ model.set_value(iter, 1, "Select " + datatype.print_type(node.attrs[key][0][1]) + "...")
110+ else:
111+ model.set_value(iter, 1, "Select...")
112+ else:
113+ model.set_value(iter, 1, node.attrs[key][1])
114+
115+ if isinstance(node.attrs[key][0][0], tuple):
116+ opts = node.attrs[key][0][0]
117+ else:
118+ opts = node.attrs[key][0]
119+
120+ for opt in opts:
121+ cell_iter = cell_model.append()
122+ cell_model.set_value(cell_iter, 0, opt)
123+
124+ self.treeview.get_column(2).set_property("visible", True)
125+ elif node.attrs[key][0] is None:
126+ model.set_value(iter, 1, "No data")
127+ elif node.attrs[key][1] is None:
128+ model.set_value(iter, 1, datatype.print_type(node.attrs[key][0]))
129+ else:
130+ model.set_value(iter, 1, node.attrs[key][1])
131+
132+ self.treeview.queue_resize()
133+
134+ return
135+
136+ def treeview_mouse_over(self, widget, event):
137+ """
138+ Called when the mouse moves over the attributes widget. Sets the
139+ appropriate attribute widget tooltip.
140+ """
141+
142+ path_info = self.treeview.get_path_at_pos(int(event.x), int(event.y))
143+ if path_info is None:
144+ try:
145+ self.treeview.set_tooltip_text("")
146+ self.treeview.set_property("has-tooltip", False)
147+ except:
148+ pass
149+ return
150+
151+ path = path_info[0]
152+ col = path_info[1]
153+ if col is not self.treeview.get_column(1):
154+ try:
155+ self.treeview.set_tooltip_text("")
156+ self.treeview.set_property("has-tooltip", False)
157+ except:
158+ pass
159+ return
160+
161+ iter = self.treeview.get_model().get_iter(path)
162+ iter_key = self.treeview.get_model().get_value(iter, 0)
163+
164+ return
165+
166+ def key_data_func(self, col, cell_renderer, model, iter):
167+ """
168+ Attribute name data function. Sets the cell renderer text colours.
169+ """
170+
171+ iter_key = model.get_value(iter, 0)
172+
173+ if not self.node.active or self.node.attrs[iter_key][0] is None or self.node.attrs[iter_key][0] == "fixed":
174+ cell_renderer.set_property("foreground", "grey")
175+ elif self.node.attrs[iter_key][1] is None:
176+ cell_renderer.set_property("foreground", "blue")
177+ else:
178+ cell_renderer.set_property("foreground", "black")
179+
180+ return
181+
182+ def entry_data_func(self, col, cell_renderer, model, iter):
183+ """
184+ Attribute text data function. Hides the renderer if a combo box is required,
185+ and sets colours and editability otherwise.
186+ """
187+
188+ iter_key = model.get_value(iter, 0)
189+
190+ if not self.node.active or self.node.attrs[iter_key][0] is None or self.node.attrs[iter_key][0] == "fixed":
191+ cell_renderer.set_property("editable", False)
192+ cell_renderer.set_property("foreground", "grey")
193+ cell_renderer.set_property("visible", True)
194+ elif not isinstance(self.node.attrs[iter_key][0], tuple):
195+ cell_renderer.set_property("editable", True)
196+ cell_renderer.set_property("visible", True)
197+ if self.node.attrs[iter_key][1] is None:
198+ cell_renderer.set_property("foreground", "blue")
199+ else:
200+ cell_renderer.set_property("foreground", "black")
201+ else:
202+ cell_renderer.set_property("editable", False)
203+ cell_renderer.set_property("visible", False)
204+
205+ return
206+
207+ def combo_data_func(self, col, cell_renderer, model, iter):
208+ """
209+ Attribute combo box data function. Hides the renderer if a combo box is not
210+ required, and sets the combo box options otherwise. Adds an entry if required.
211+ """
212+
213+ iter_key = model.get_value(iter, 0)
214+
215+ if self.node.active and isinstance(self.node.attrs[iter_key][0], tuple):
216+ cell_renderer.set_property("editable", True)
217+ cell_renderer.set_property("visible", True)
218+ if isinstance(self.node.attrs[iter_key][0][0], tuple):
219+ cell_renderer.set_property("has-entry", True)
220+ else:
221+ cell_renderer.set_property("has-entry", False)
222+ if self.node.attrs[iter_key][1] is None:
223+ cell_renderer.set_property("foreground", "blue")
224+ else:
225+ cell_renderer.set_property("foreground", "black")
226+ else:
227+ cell_renderer.set_property("visible", False)
228+ cell_renderer.set_property("editable", False)
229+ cell_renderer.set_property("model", model.get_value(iter, 2))
230+
231+ return
232+
233+ def icon_data_func(self, col, cell_renderer, model, iter):
234+ """
235+ Attribute icon data function. Used to add downward pointing arrows for combo
236+ attributes, for consistency with the LHS.
237+ """
238+
239+ iter_key = model.get_value(iter, 0)
240+
241+ if self.node.active and isinstance(self.node.attrs[iter_key][0], tuple):
242+ cell_renderer.set_property("stock-id", gtk.STOCK_GO_DOWN)
243+ else:
244+ cell_renderer.set_property("stock-id", None)
245+
246+ return
247+
248+ def entry_edit_start(self, cell_renderer, editable, path):
249+ """
250+ Called when editing is started on an attribute text cell. Used to delete the
251+ printable_type placeholder.
252+ """
253+
254+ iter = self.treeview.get_model().get_iter(path)
255+ iter_key = self.treeview.get_model().get_value(iter, 0)
256+
257+ if self.node.attrs[iter_key][1] is None:
258+ editable.set_text("")
259+
260+ return
261+
262+ def combo_edit_start(self, cell_renderer, editable, path):
263+ """
264+ Called when editing is started on an attribute combo cell. Used to delete the
265+ select placeholder for mixed entry / combo attributes.
266+ """
267+
268+ iter = self.treeview.get_model().get_iter(path)
269+ iter_key = self.treeview.get_model().get_value(iter, 0)
270+
271+ if isinstance(self.node.attrs[iter_key][0][0], tuple) and self.node.attrs[iter_key][1] is None:
272+ editable.child.set_text("")
273+
274+ return
275+
276+ def entry_edited(self, cell_renderer, path, new_text):
277+ """
278+ Called when editing is finished on an attribute text cell. Updates data in the
279+ treestore.
280+ """
281+
282+ iter = self.treeview.get_model().get_iter(path)
283+ iter_key = self.treeview.get_model().get_value(iter, 0)
284+
285+ if self.node.get_attr(iter_key) is None and new_text == "":
286+ return
287+
288+ value_check = self.node.validity_check(self.node.attrs[iter_key][0], new_text)
289+
290+ if value_check is not None and value_check != self.node.attrs[iter_key][1]:
291+ if iter_key == "name" and not self._name_check(value_check):
292+ return
293+
294+ self.treeview.get_model().set_value(iter, 1, value_check)
295+ self.node.set_attr(iter_key, value_check)
296+ if iter_key == "name":
297+ self.emit("update-name")
298+
299+ self.emit("on-store")
300+
301+ return
302+
303+ def combo_selected(self, cell_renderer, path, new_text):
304+ """
305+ Called when an attribute combo box element is selected, or combo box entry
306+ element entry is edited. Updates data in the treestore.
307+ """
308+
309+ iter = self.treeview.get_model().get_iter(path)
310+ iter_key = self.treeview.get_model().get_value(iter, 0)
311+
312+ if new_text is None:
313+ return
314+
315+ if isinstance(self.node.attrs[iter_key][0][0], tuple) and new_text not in self.node.attrs[iter_key][0][0]:
316+ if self.node.get_attr(iter_key) is None and new_text == "":
317+ return
318+
319+ new_text = self.node.validity_check(self.node.attrs[iter_key][0][1], new_text)
320+ if iter_key == "name" and not self._name_check(new_text):
321+ return False
322+ if new_text != self.selected_node.attrs[iter_key][1]:
323+ self.treeview.get_model().set_value(iter, 1, new_text)
324+ self.node.set_attr(iter_key, new_text)
325+ if iter_key == "name":
326+ self.emit("update-name")
327+
328+ self.emit("on-store")
329+
330+ return
331+
332+ def _name_check(self, value):
333+ """
334+ Check to see if the supplied data is a valid tree name.
335+ """
336+
337+ valid_chars = "_:[]1234567890qwertyuioplkjhgfdsazxcvbnmMNBVCXZASDFGHJKLPOIUYTREWQ"
338+ for char in value:
339+ if char not in valid_chars:
340+ dialogs.error(None, "Invalid value entered")
341+ return False
342+
343+ return True
344+
345+gobject.type_register(AttributeWidget)
346
347=== modified file 'diamond/diamond/choice.py'
348--- diamond/diamond/choice.py 2009-08-07 14:17:55 +0000
349+++ diamond/diamond/choice.py 2011-07-25 13:56:22 +0000
350@@ -21,10 +21,19 @@
351 import StringIO
352 from lxml import etree
353
354+
355+import gobject
356+
357 import tree
358
359-class Choice:
360+class Choice(gobject.GObject):
361+
362+ __gsignals__ = { "on-set-data" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
363+ "on-set-attr" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, str))}
364+
365 def __init__(self, l, cardinality=''):
366+ gobject.GObject.__init__(self)
367+
368 self.l = l
369 if l == []:
370 raise Exception
371@@ -33,6 +42,9 @@
372 for choice in l:
373 assert choice.__class__ is tree.Tree
374 name = name + choice.name + ":"
375+ choice.connect("on-set-data", self._on_set_data)
376+ choice.connect("on-set-attr", self._on_set_attr)
377+
378 name = name[:-1]
379 self.name = name
380 self.schemaname = name
381@@ -40,6 +52,12 @@
382 self.parent = None
383 self.set_default_active()
384
385+ def _on_set_data(self, node, data):
386+ self.emit("on-set-data", data)
387+
388+ def _on_set_attr(self, node, attr, value):
389+ self.emit("on-set-attr", attr, value)
390+
391 def set_default_active(self):
392 self.active = True
393 if self.cardinality == '?' or self.cardinality == '*':
394@@ -126,3 +144,57 @@
395
396 def choices(self):
397 return self.l
398+
399+ def is_comment(self):
400+ return False
401+
402+ def get_comment(self):
403+ return None
404+
405+ def get_display_name(self):
406+ """
407+ This is a fluidity hack, allowing the name displayed in the treeview on the
408+ left to be different to the element name. If it has an attribute name="xxx",
409+ element_tag (xxx) is displayed.
410+ """
411+
412+ return self.get_current_tree().get_display_name()
413+
414+ def get_name(self):
415+ return self.get_current_tree().get_name()
416+
417+ def get_children(self):
418+ return [self.get_current_tree()]
419+
420+ def get_choices(self):
421+ return self.l
422+
423+ def is_hidden(self):
424+ """
425+ Tests whether the supplied choice should be hidden in view.
426+ """
427+ return False
428+
429+ def get_name_path(self, leaf = True):
430+ name = self.get_display_name() if leaf else self.get_name()
431+
432+ if self.parent is None:
433+ return name
434+ else:
435+
436+ pname = self.parent.get_name_path(False)
437+
438+ if name is None:
439+ return pname
440+ elif pname is None:
441+ return name
442+ else:
443+ return pname + "/" + name
444+
445+ def get_mixed_data(self):
446+ return self
447+
448+ def is_sliceable(self):
449+ return self.get_current_tree().is_sliceable()
450+
451+gobject.type_register(Choice)
452
453=== added file 'diamond/diamond/commentwidget.py'
454--- diamond/diamond/commentwidget.py 1970-01-01 00:00:00 +0000
455+++ diamond/diamond/commentwidget.py 2011-07-25 13:56:22 +0000
456@@ -0,0 +1,146 @@
457+#!/usr/bin/env python
458+
459+# This file is part of Diamond.
460+#
461+# Diamond is free software: you can redistribute it and/or modify
462+# it under the terms of the GNU General Public License as published by
463+# the Free Software Foundation, either version 3 of the License, or
464+# (at your option) any later version.
465+#
466+# Diamond is distributed in the hope that it will be useful,
467+# but WITHOUT ANY WARRANTY; without even the implied warranty of
468+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
469+# GNU General Public License for more details.
470+#
471+# You should have received a copy of the GNU General Public License
472+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
473+
474+import gobject
475+import gtk
476+
477+class CommentWidget(gtk.Frame):
478+
479+ __gsignals__ = { "on-store" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())}
480+
481+ def __init__(self):
482+ gtk.Frame.__init__(self)
483+
484+ scrolledWindow = gtk.ScrolledWindow()
485+ scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
486+
487+ textView = self.textView = gtk.TextView()
488+ textView.set_editable(False)
489+ textView.set_wrap_mode(gtk.WRAP_WORD)
490+ textView.set_cursor_visible(False)
491+ textView.connect("focus-in-event", self.focus_in)
492+ textView.connect("focus-out-event", self.focus_out)
493+ textView.get_buffer().create_tag("tag")
494+
495+ scrolledWindow.add(textView)
496+
497+ label = gtk.Label()
498+ label.set_markup("<b>Comment</b>")
499+
500+ self.set_shadow_type(gtk.SHADOW_NONE)
501+ self.set_label_widget(label)
502+ self.add(scrolledWindow)
503+
504+ self.comment_tree = None
505+ return
506+
507+ def update(self, node):
508+ """
509+ Update the widget with the given node
510+ """
511+
512+ #before updateing store the old
513+ self.store()
514+
515+ if node is None or not node.active:
516+ self.textView.get_buffer().set_text("")
517+ self.textView.set_cursor_visible(False)
518+ self.textView.set_editable(False)
519+ try:
520+ self.textView.set_tooltip_text("")
521+ self.textView.set_property("has-tooltip", False)
522+ except:
523+ pass
524+
525+ return
526+
527+ self.comment_tree = comment_tree = node.get_comment()
528+ text_tag = self.textView.get_buffer().get_tag_table().lookup("tag")
529+ if comment_tree is None:
530+ self.textView.get_buffer().set_text("No comment")
531+ self.textView.set_cursor_visible(False)
532+ self.textView.set_editable(False)
533+ text_tag.set_property("foreground", "grey")
534+ try:
535+ self.textView.set_tooltip_text("")
536+ self.textView.set_property("has-tooltip", False)
537+ except:
538+ pass
539+ else:
540+ if comment_tree.data is None:
541+ self.textView.get_buffer().set_text("(string)")
542+ else:
543+ self.textView.get_buffer().set_text(comment_tree.data)
544+ if node.active:
545+ self.textView.set_cursor_visible(True)
546+ self.textView.set_editable(True)
547+ text_tag.set_property("foreground", "black")
548+ else:
549+ self.textView.set_cursor_visible(False)
550+ self.textView.set_editable(False)
551+ text_tag.set_property("foreground", "grey")
552+
553+ buffer_bounds = self.textView.get_buffer().get_bounds()
554+ self.textView.get_buffer().apply_tag(text_tag, buffer_bounds[0], buffer_bounds[1])
555+
556+ self.interacted = False
557+
558+ return
559+
560+ def store(self):
561+ """
562+ Store data in the node comment.
563+ """
564+
565+ comment_tree = self.comment_tree
566+ if comment_tree is None or not self.interacted:
567+ return
568+
569+ data_buffer_bounds = self.textView.get_buffer().get_bounds()
570+ new_comment = self.textView.get_buffer().get_text(data_buffer_bounds[0], data_buffer_bounds[1])
571+
572+ if new_comment != comment_tree.data:
573+ if new_comment == "":
574+ comment_tree.data = None
575+ comment_tree.active = False
576+ else:
577+ comment_tree.set_data(new_comment)
578+ comment_tree.active = True
579+ self.emit("on-store")
580+ return
581+
582+ def focus_in(self, widget, event):
583+ """
584+ Called when the comment widget gains focus. Removes the printable_type
585+ placeholder.
586+ """
587+
588+ comment_tree = self.comment_tree
589+ if not comment_tree is None and not self.interacted:
590+ self.interacted = True
591+ if comment_tree.data is None:
592+ self.textView.get_buffer().set_text("")
593+
594+ return
595+
596+ def focus_out(self, widget, event):
597+ """"
598+ Called when the comment widget loses focus. Stores the comment.
599+ """
600+ self.store()
601+
602+gobject.type_register(CommentWidget)
603
604=== added file 'diamond/diamond/databuttonswidget.py'
605--- diamond/diamond/databuttonswidget.py 1970-01-01 00:00:00 +0000
606+++ diamond/diamond/databuttonswidget.py 2011-07-25 13:56:22 +0000
607@@ -0,0 +1,47 @@
608+#!/usr/bin/env python
609+
610+# This file is part of Diamond.
611+#
612+# Diamond is free software: you can redistribute it and/or modify
613+# it under the terms of the GNU General Public License as published by
614+# the Free Software Foundation, either version 3 of the License, or
615+# (at your option) any later version.
616+#
617+# Diamond is distributed in the hope that it will be useful,
618+# but WITHOUT ANY WARRANTY; without even the implied warranty of
619+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
620+# GNU General Public License for more details.
621+#
622+# You should have received a copy of the GNU General Public License
623+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
624+
625+import gobject
626+import gtk
627+
628+class DataButtonsWidget(gtk.HBox):
629+
630+ __gsignals__ = { "revert" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
631+ "store" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())}
632+
633+ def __init__(self):
634+ gtk.HBox.__gobject_init__(self)
635+ revertButton = gtk.Button()
636+ revertButton.set_label("Revert data")
637+ revertButton.connect("clicked", self._revert)
638+
639+ storeButton = gtk.Button()
640+ storeButton.set_label("Store data")
641+ storeButton.connect("clicked", self._store)
642+
643+ self.pack_end(revertButton)
644+ self.pack_start(storeButton)
645+
646+ return
647+
648+ def _revert(self, widget = None):
649+ self.emit("revert")
650+
651+ def _store(self, widget = None):
652+ self.emit("store")
653+
654+gobject.type_register(DataButtonsWidget)
655
656=== added file 'diamond/diamond/datatype.py'
657--- diamond/diamond/datatype.py 1970-01-01 00:00:00 +0000
658+++ diamond/diamond/datatype.py 2011-07-25 13:56:22 +0000
659@@ -0,0 +1,75 @@
660+#!/usr/bin/env python
661+
662+# This file is part of Diamond.
663+#
664+# Diamond is free software: you can redistribute it and/or modify
665+# it under the terms of the GNU General Public License as published by
666+# the Free Software Foundation, either version 3 of the License, or
667+# (at your option) any later version.
668+#
669+# Diamond is distributed in the hope that it will be useful,
670+# but WITHOUT ANY WARRANTY; without even the implied warranty of
671+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
672+# GNU General Public License for more details.
673+#
674+# You should have received a copy of the GNU General Public License
675+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
676+
677+import plist
678+
679+def print_type(datatype, bracket = True):
680+ """
681+ Create a string to be displayed in place of empty data / attributes.
682+ """
683+
684+ def familiar_type(type_as_printable):
685+ """
686+ Convert some type names to more familiar equivalents.
687+ """
688+
689+ if type_as_printable == "decim":
690+ return "float"
691+ elif type_as_printable == "int":
692+ return "integer"
693+ elif type_as_printable == "str":
694+ return "string"
695+ else:
696+ return type_as_printable
697+
698+ def type_name(datatype):
699+ """
700+ Return a human readable version of datatype.
701+ """
702+
703+ datatype_string = str(datatype)
704+
705+ if datatype_string[:7] == "<type '" and datatype_string[len(datatype_string) - 2:] == "'>":
706+ value_type_split = datatype_string.split("'")
707+ return familiar_type(value_type_split[1])
708+
709+ value_type_split1 = datatype_string.split(".")
710+ value_type_split2 = value_type_split1[len(value_type_split1) - 1].split(" ")
711+ if len(value_type_split2) == 1:
712+ return familiar_type(value_type_split2[0][0:len(value_type_split2[0]) - 6])
713+ else:
714+ return familiar_type(value_type_split2[0])
715+
716+ #print_type
717+
718+ if isinstance(datatype, plist.List):
719+ if (isinstance(datatype.cardinality, int) and datatype.cardinality == 1) or datatype.cardinality == "":
720+ type_as_printable = type_name(datatype.datatype).lower()
721+ else:
722+ type_as_printable = type_name(datatype).lower() + " of "
723+ list_type_as_printable = type_name(datatype.datatype).lower()
724+ if isinstance(datatype.cardinality, int):
725+ type_as_printable += str(datatype.cardinality) + " " + list_type_as_printable + "s"
726+ else:
727+ type_as_printable += list_type_as_printable + "s"
728+ else:
729+ type_as_printable = type_name(datatype).lower()
730+
731+ if bracket:
732+ type_as_printable = "(" + type_as_printable + ")"
733+
734+ return type_as_printable
735
736=== added file 'diamond/diamond/datawidget.py'
737--- diamond/diamond/datawidget.py 1970-01-01 00:00:00 +0000
738+++ diamond/diamond/datawidget.py 2011-07-25 13:56:22 +0000
739@@ -0,0 +1,501 @@
740+#!/usr/bin/env python
741+
742+# This file is part of Diamond.
743+#
744+# Diamond is free software: you can redistribute it and/or modify
745+# it under the terms of the GNU General Public License as published by
746+# the Free Software Foundation, either version 3 of the License, or
747+# (at your option) any later version.
748+#
749+# Diamond is distributed in the hope that it will be useful,
750+# but WITHOUT ANY WARRANTY; without even the implied warranty of
751+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
752+# GNU General Public License for more details.
753+#
754+# You should have received a copy of the GNU General Public License
755+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
756+
757+import gobject
758+import gtk
759+import pango
760+
761+import dialogs
762+import datatype
763+import mixedtree
764+
765+class DataWidget(gtk.VBox):
766+
767+ __gsignals__ = { "on-store" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())}
768+
769+ def __init__(self):
770+ gtk.VBox.__init__(self)
771+
772+ frame = self.frame = gtk.Frame()
773+
774+ label = gtk.Label()
775+ label.set_markup("<b>Data</b>")
776+
777+ frame.set_label_widget(label)
778+ frame.set_shadow_type(gtk.SHADOW_NONE)
779+
780+ self.pack_start(frame)
781+ self.buttons = None
782+ return
783+
784+ def set_buttons(self, buttons):
785+ self.buttons = buttons
786+ buttons.connect("revert", self.revert)
787+ buttons.connect("store", self.store)
788+ buttons.show_all()
789+
790+ def update(self, node):
791+
792+ self.node = node
793+
794+ if not self.is_node_editable():
795+ self.set_data_fixed()
796+ elif node.is_tensor(self.geometry_dim_tree):
797+ self.set_data_tensor()
798+ elif isinstance(node.datatype, tuple):
799+ self.set_data_combo()
800+ else:
801+ self.set_data_entry()
802+
803+ return
804+
805+ def revert(self, button = None):
806+ """
807+ "Revert Data" button click signal handler. Reverts data in the data frame.
808+ """
809+
810+ self.update(self.node)
811+
812+ return
813+
814+ def store(self, button = None):
815+ """
816+ "Store Data" button click signal handler. Stores data from the data frame
817+ in the treestore.
818+ """
819+
820+ if not self.is_node_editable():
821+ return True
822+ elif self.node.is_tensor(self.geometry_dim_tree):
823+ return self.data_tensor_store()
824+ elif isinstance(self.node.datatype, tuple):
825+ return self.data_combo_store()
826+ else:
827+ return self.data_entry_store()
828+
829+# This would be nice, look at it later
830+# if self.scherror.errlist_is_open():
831+# if self.scherror.errlist_type == 0:
832+# self.scherror.on_validate_schematron()
833+# else:
834+# self.scherror.on_validate()
835+
836+ def is_node_editable(self):
837+ return (self.node is not None
838+ and self.node.active
839+ and self.node.datatype is not None
840+ and self.node.datatype != "fixed"
841+ and (not self.node.is_tensor(self.geometry_dim_tree) or self.geometry_dim_tree.data is not None))
842+ # not A or B == A implies B
843+ # A B T
844+ # 0 0 1
845+ # 0 1 1
846+ # 1 0 0
847+ # 1 1 1
848+
849+ def add_scrolled_window(self):
850+ scrolledWindow = gtk.ScrolledWindow()
851+ scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
852+ self.frame.add(scrolledWindow)
853+ scrolledWindow.show()
854+ return scrolledWindow
855+
856+ def add_text_view(self):
857+ scrolledWindow = self.add_scrolled_window()
858+ self.set_child_packing(self.frame, True, True, 0, gtk.PACK_START)
859+
860+ try:
861+ import gtksourceview2
862+ buf = gtksourceview2.Buffer()
863+ lang_manager = gtksourceview2.LanguageManager()
864+ buf.set_highlight_matching_brackets(True)
865+ if self.node is not None and self.node.is_python_code():
866+ python = lang_manager.get_language("python")
867+ buf.set_language(python)
868+ buf.set_highlight_syntax(True)
869+ textview = gtksourceview2.View(buffer=buf)
870+ textview.set_auto_indent(True)
871+ textview.set_insert_spaces_instead_of_tabs(True)
872+ textview.set_tab_width(2)
873+ if self.node is not None and self.node.is_python_code():
874+ textview.set_show_line_numbers(True)
875+ font_desc = pango.FontDescription("monospace")
876+ if font_desc:
877+ textview.modify_font(font_desc)
878+ except ImportError:
879+ textview = gtk.TextView()
880+
881+ textview.set_pixels_above_lines(2)
882+ textview.set_pixels_below_lines(2)
883+ textview.set_wrap_mode(gtk.WRAP_WORD)
884+ textview.connect("focus-in-event", self.entry_focus_in)
885+
886+ scrolledWindow.add(textview)
887+ textview.show()
888+ return textview
889+
890+ def set_data_empty(self):
891+ """
892+ Empty the data frame.
893+ """
894+
895+ if self.frame.child is not None:
896+ if isinstance(self.data, gtk.TextView):
897+ self.data.handler_block_by_func(self.entry_focus_in)
898+ elif isinstance(self.data, gtk.ComboBox):
899+ self.data.handler_block_by_func(self.combo_focus_child)
900+
901+ self.frame.remove(self.frame.child)
902+
903+ self.interacted = False
904+
905+ return
906+
907+ def set_data_fixed(self):
908+ """
909+ Create a non-editable text view to show help or fixed data.
910+ """
911+
912+ self.set_data_empty()
913+
914+ self.data = self.add_text_view()
915+
916+ self.data.get_buffer().create_tag("tag")
917+ text_tag = self.data.get_buffer().get_tag_table().lookup("tag")
918+
919+ self.data.set_cursor_visible(False)
920+ self.data.set_editable(False)
921+ self.buttons.hide()
922+ text_tag.set_property("foreground", "grey")
923+
924+ if self.node is None:
925+ self.data.get_buffer().set_text("")
926+ elif not self.node.active:
927+ self.data.get_buffer().set_text("Inactive node")
928+ elif self.node.datatype is None:
929+ self.data.get_buffer().set_text("No data")
930+ elif self.node.is_tensor(self.geometry_dim_tree):
931+ self.data.get_buffer().set_text("Dimension not set")
932+ else: # self.node.datatype == "fixed":
933+ self.data.get_buffer().set_text(self.node.data)
934+
935+ buffer_bounds = self.data.get_buffer().get_bounds()
936+ self.data.get_buffer().apply_tag(text_tag, buffer_bounds[0], buffer_bounds[1])
937+
938+ return
939+
940+ def set_data_entry(self):
941+ """
942+ Create a text view for data entry in the data frame.
943+ """
944+
945+ self.set_data_empty()
946+
947+ self.data = self.add_text_view()
948+
949+ self.data.get_buffer().create_tag("tag")
950+ text_tag = self.data.get_buffer().get_tag_table().lookup("tag")
951+
952+ self.data.set_cursor_visible(True)
953+ self.data.set_editable(True)
954+ self.buttons.show()
955+
956+ if self.node.data is None:
957+ self.data.get_buffer().set_text(datatype.print_type(self.node.datatype))
958+ text_tag.set_property("foreground", "blue")
959+ else:
960+ self.data.get_buffer().set_text(self.node.data)
961+
962+ buffer_bounds = self.data.get_buffer().get_bounds()
963+ self.data.get_buffer().apply_tag(text_tag, buffer_bounds[0], buffer_bounds[1])
964+
965+ return
966+
967+ def set_data_tensor(self):
968+ """
969+ Create a table container packed with appropriate widgets for tensor data entry
970+ in the node data frame.
971+ """
972+
973+ self.set_data_empty()
974+
975+ scrolledWindow = self.add_scrolled_window()
976+
977+ dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
978+ self.data = gtk.Table(dim1, dim2)
979+ scrolledWindow.add_with_viewport(self.data)
980+ scrolledWindow.child.set_property("shadow-type", gtk.SHADOW_NONE)
981+
982+ self.set_child_packing(self.frame, True, True, 0, gtk.PACK_START)
983+
984+ is_symmetric = self.node.is_symmetric_tensor(self.geometry_dim_tree)
985+ for i in range(dim1):
986+ for j in range(dim2):
987+ iindex = dim1 - i - 1
988+ jindex = dim2 - j - 1
989+
990+ if not is_symmetric or i >= j:
991+ entry = gtk.Entry()
992+ entry.connect("focus-in-event", self.tensor_element_focus_in, jindex, iindex)
993+ self.data.attach(entry, jindex, jindex + 1, iindex, iindex + 1)
994+
995+ if self.node.data is None:
996+ entry.set_text(datatype.print_type(self.node.datatype.datatype))
997+ entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("blue"))
998+ else:
999+ entry.set_text(self.node.data.split(" ")[jindex + iindex * dim2])
1000+
1001+ self.interacted = [False for i in range(dim1 * dim2)]
1002+ self.show_all()
1003+
1004+ return
1005+
1006+ def set_data_combo(self):
1007+ """
1008+ Create a combo box for node data selection in the node data frame. Add an
1009+ entry if required.
1010+ """
1011+
1012+ self.set_data_empty()
1013+
1014+ if isinstance(self.node.datatype[0], tuple):
1015+ self.data = gtk.combo_box_entry_new_text()
1016+ else:
1017+ self.data = gtk.combo_box_new_text()
1018+
1019+ self.frame.add(self.data)
1020+ self.data.show()
1021+
1022+ self.data.connect("set-focus-child", self.combo_focus_child)
1023+
1024+ self.set_child_packing(self.frame, False, False, 0, gtk.PACK_START)
1025+
1026+ if isinstance(self.node.datatype[0], tuple):
1027+ self.buttons.show()
1028+ else:
1029+ self.buttons.hide()
1030+
1031+ if self.node.data is None:
1032+ if isinstance(self.node.datatype[0], tuple):
1033+ self.data.child.set_text("Select " + datatype.print_type(self.node.datatype[1]) + "...")
1034+ else:
1035+ self.data.append_text("Select...")
1036+ self.data.set_active(0)
1037+ self.data.child.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("blue"))
1038+ self.data.child.modify_text(gtk.STATE_PRELIGHT, gtk.gdk.color_parse("blue"))
1039+
1040+ if isinstance(self.node.datatype[0], tuple):
1041+ options = self.node.datatype[0]
1042+ else:
1043+ options = self.node.datatype
1044+
1045+ for (i, opt) in enumerate(options):
1046+ self.data.append_text(opt)
1047+ if self.node.data == opt:
1048+ self.data.set_active(i)
1049+
1050+ if (isinstance(self.node.datatype[0], tuple)
1051+ and self.node.data is not None
1052+ and self.node.data not in self.node.datatype[0]):
1053+ self.data.child.set_text(self.node.data)
1054+
1055+ self.data.connect("changed", self.combo_changed)
1056+
1057+ return
1058+
1059+ def data_entry_store(self):
1060+ """
1061+ Attempt to store data read from a textview packed in the data frame.
1062+ """
1063+
1064+ new_data = self.data.get_buffer().get_text(*self.data.get_buffer().get_bounds())
1065+
1066+ if new_data == "":
1067+ return True
1068+
1069+ if self.node.data is None and not self.interacted:
1070+ return True
1071+ else:
1072+ value_check = self.node.validity_check(self.node.datatype, new_data)
1073+ if value_check is None:
1074+ dialogs.error(None, "Invalid value entered")
1075+ return False
1076+ elif value_check != self.node.data:
1077+ self.node.set_data(value_check)
1078+ if (isinstance(self.node, mixedtree.MixedTree)
1079+ and "shape" in self.node.child.attrs.keys()
1080+ and self.node.child.attrs["shape"][0] is int
1081+ and isinstance(self.node.datatype, plist.List)
1082+ and self.node.datatype.cardinality == "+"):
1083+ self.node.child.set_attr("shape", str(len(value_check.split(" "))))
1084+
1085+ self.emit("on-store")
1086+ self.interacted = False
1087+
1088+ return True
1089+
1090+ def data_tensor_store(self):
1091+ """
1092+ Attempt to store data read from tensor data entry widgets packed in the
1093+ data frame.
1094+ """
1095+
1096+ dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
1097+ is_symmetric = self.node.is_symmetric_tensor(self.geometry_dim_tree)
1098+
1099+ if True not in self.interacted:
1100+ return True
1101+
1102+ entry_values = []
1103+ for i in range(dim1):
1104+ for j in range(dim2):
1105+ if is_symmetric and i > j:
1106+ entry_values.append(self.data.get_children()[i + j * dim1].get_text())
1107+ else:
1108+ entry_values.append(self.data.get_children()[j + i * dim2].get_text())
1109+
1110+ changed = False
1111+ for i in range(dim1):
1112+ for j in range(dim2):
1113+ if (self.interacted[j + i * dim2]
1114+ and entry_values[j + i * dim2] != ""
1115+ and (self.node.data is None
1116+ or self.node.data.split(" ")[j + i * dim2] != entry_values[j + i * dim2])):
1117+ changed = True
1118+ if not changed:
1119+ return True
1120+ elif (self.node.data is None and False in self.interacted) or "" in entry_values:
1121+ dialogs.error(None, "Invalid value entered")
1122+ return False
1123+
1124+ new_data = ""
1125+ for i in range(dim1):
1126+ for j in range(dim2):
1127+ new_data += " " + entry_values[j + i * dim2]
1128+
1129+ value_check = self.node.validity_check(self.node.datatype, new_data)
1130+ if value_check is None:
1131+ return False
1132+ elif not value_check == self.node.data:
1133+ self.node.set_data(value_check)
1134+
1135+ dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
1136+ if int(self.node.child.attrs["rank"][1]) == 1:
1137+ self.node.child.set_attr("shape", str(dim1))
1138+ else:
1139+ self.node.child.set_attr("shape", str(dim1) + " " + str(dim2))
1140+
1141+ self.emit("on-store")
1142+ self.interacted = [False for i in range(dim1 * dim2)]
1143+
1144+ return True
1145+
1146+ def data_combo_store(self):
1147+ """
1148+ Attempt to store data read from a combo box entry packed in the node data.
1149+ """
1150+
1151+ if not isinstance(self.node.datatype[0], tuple):
1152+ return True
1153+
1154+ new_data = self.data.get_text()
1155+
1156+ if self.node.data is None and not self.interacted:
1157+ return True
1158+ elif not new_data in self.node.datatype[0]:
1159+ new_data = self.node.validity_check(self.node.datatype[1], new_data)
1160+ if new_data is None:
1161+ return False
1162+
1163+ if not new_data == self.node.data:
1164+ self.node.set_data(new_data)
1165+ self.emit("on-store")
1166+ self.interacted = False
1167+
1168+ return True
1169+
1170+ def entry_focus_in(self, widget, event):
1171+ """
1172+ Called when a text view data entry widget gains focus. Used to delete the
1173+ printable_type placeholder.
1174+ """
1175+
1176+ if (self.node is not None
1177+ and self.node.datatype is not None
1178+ and not self.node.is_tensor(self.geometry_dim_tree)
1179+ and self.node.data is None
1180+ and not self.interacted):
1181+ self.data.get_buffer().set_text("")
1182+
1183+ self.interacted = True
1184+
1185+ return
1186+
1187+ def tensor_element_focus_in(self, widget, event, row, col):
1188+ """
1189+ Called when a tensor data entry widget gains focus. Used to delete the
1190+ printable_type placeholder.
1191+ """
1192+
1193+ dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
1194+ if not self.interacted[col + row * dim2]:
1195+ self.interacted[col + row * dim2] = True
1196+ if self.node.is_symmetric_tensor(self.geometry_dim_tree):
1197+ self.interacted[row + col * dim1] = True
1198+ if self.node.data is None:
1199+ widget.set_text("")
1200+ widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
1201+
1202+ return
1203+
1204+ def combo_focus_child(self, container, widget):
1205+ """
1206+ Called when a data selection widget gains focus. Used to delete the select
1207+ placeholder.
1208+ """
1209+ if not self.interacted:
1210+ self.interacted = True
1211+ if self.node.data is None:
1212+ self.data.handler_block_by_func(self.combo_changed)
1213+ if isinstance(self.node.datatype[0], tuple):
1214+ self.data.child.set_text("")
1215+ else:
1216+ self.data.remove_text(0)
1217+
1218+ self.data.child.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
1219+ self.data.child.modify_text(gtk.STATE_PRELIGHT, gtk.gdk.color_parse("black"))
1220+ self.data.handler_unblock_by_func(self.combo_changed)
1221+
1222+ return
1223+
1224+ def combo_changed(self, combo_box):
1225+ """
1226+ Called when a data combo box element is selected. Updates data in the
1227+ treestore.
1228+ """
1229+
1230+ if not isinstance(self.node.datatype[0], tuple):
1231+ text = self.data.get_active_text()
1232+ if text is None:
1233+ return
1234+
1235+ self.node.set_data(text)
1236+ self.emit("on-store")
1237+ self.interacted = False
1238+ return
1239+
1240+gobject.type_register(DataWidget)
1241
1242=== added file 'diamond/diamond/descriptionwidget.py'
1243--- diamond/diamond/descriptionwidget.py 1970-01-01 00:00:00 +0000
1244+++ diamond/diamond/descriptionwidget.py 2011-07-25 13:56:22 +0000
1245@@ -0,0 +1,190 @@
1246+#!/usr/bin/env python
1247+
1248+# This file is part of Diamond.
1249+#
1250+# Diamond is free software: you can redistribute it and/or modify
1251+# it under the terms of the GNU General Public License as published by
1252+# the Free Software Foundation, either version 3 of the License, or
1253+# (at your option) any later version.
1254+#
1255+# Diamond is distributed in the hope that it will be useful,
1256+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1257+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1258+# GNU General Public License for more details.
1259+#
1260+# You should have received a copy of the GNU General Public License
1261+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
1262+
1263+import gtk
1264+import re
1265+import TextBufferMarkup
1266+import webbrowser
1267+
1268+class DescriptionWidget(gtk.Frame):
1269+ def __init__(self):
1270+ gtk.Frame.__init__(self)
1271+
1272+ scrolledWindow = gtk.ScrolledWindow()
1273+ scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
1274+
1275+ textView = self.textView = gtk.TextView()
1276+ textView.set_editable(False)
1277+ textView.set_wrap_mode(gtk.WRAP_WORD)
1278+ textView.set_cursor_visible(False)
1279+
1280+ textView.set_buffer(TextBufferMarkup.PangoBuffer())
1281+ textView.connect("button-release-event", self.mouse_button_release)
1282+ textView.connect("motion-notify-event", self.mouse_over)
1283+
1284+ scrolledWindow.add(textView)
1285+
1286+ label = gtk.Label()
1287+ label.set_markup("<b>Description</b>")
1288+
1289+ self.set_shadow_type(gtk.SHADOW_NONE)
1290+ self.set_label_widget(label)
1291+ self.add(scrolledWindow)
1292+
1293+ return
1294+
1295+ def update(self, node):
1296+ if node is None:
1297+ self.set_description("<span foreground=\"grey\">No node selected</span>")
1298+ elif node.doc is None:
1299+ self.set_description("<span foreground=\"red\">No documentation</span>")
1300+ else:
1301+ self.set_description(node.doc)
1302+
1303+ return
1304+
1305+ def set_description(self, text):
1306+ """
1307+ Set the node description.
1308+ """
1309+
1310+ #text saved in self.text without markup
1311+ self.text = text = self.render_whitespace(text)
1312+ link_bounds = self.link_bounds = self.get_link_bounds(text)
1313+
1314+ if link_bounds:
1315+ new_text = []
1316+ index = 0
1317+ for bounds in link_bounds:
1318+ new_text.append(text[index:bounds[0]])
1319+ new_text.append("<span foreground=\"blue\" underline=\"single\">")
1320+ new_text.append(text[bounds[0]:bounds[1]])
1321+ new_text.append("</span>")
1322+ index = bounds[1]
1323+
1324+ new_text.append(text[index:])
1325+ text = ''.join(new_text)
1326+
1327+ self.textView.get_buffer().set_text(text)
1328+
1329+ return
1330+
1331+ def get_link_bounds(self, text):
1332+ """
1333+ Return a list of tuples corresponding to the start and end points of links in
1334+ the supplied string.
1335+ """
1336+ text = text.lower()
1337+ bounds = []
1338+
1339+ for match in re.finditer(r"\b(" #start at beginging of word
1340+ +r"(?:https?://|www\.)" #http:// https:// or www.
1341+ +r"(?:[a-z0-9][a-z0-9_\-]*[a-z0-9]\.)*" #Domains
1342+ +r"(?:[a-z][a-z0-9\-]*[a-z0-9])" #TLD
1343+ +r"(?:/([a-z0-9$_.+\\*'(),;:@&=\-]|%[0-9a-f]{2})*)*" #path
1344+ +r"(?:\?([a-z0-9$_.+!*'(),;:@&=\-]|%[0-9a-f]{2})*)?" #query
1345+ +r")", text):
1346+ bounds.append(match.span())
1347+
1348+ return bounds
1349+
1350+ def render_whitespace(self, desc):
1351+ ''' Render the line wrapping in desc as follows:
1352+
1353+ * Newlines followed by 0-1 spaces are ignored.
1354+ * Blank lines start new paragraphs.
1355+ * Newlines followed by more than 1 space are honoured.
1356+ '''
1357+
1358+ text = []
1359+ para = False
1360+ literal = False
1361+
1362+ for line in desc.split("\n"):
1363+
1364+ if line == "" or line.isspace(): #Blank line, start paragraph
1365+ if literal: #if following a literal line add newlines
1366+ text.append("\n")
1367+ para = True
1368+
1369+ elif line[0] == " " and line[1] == " ": # >1 space, treat literaly
1370+ text.append("\n")
1371+ text.append(line)
1372+ para = False
1373+ literal = True
1374+
1375+ else: #normal case
1376+ if para: #add if starting a new paragraph
1377+ text.append("\n ")
1378+ text.append(line)
1379+ para = False
1380+ literal = False
1381+
1382+ return ''.join(text)
1383+
1384+ def get_hyperlink(self, x, y):
1385+ """
1386+ Given an x and y window position (eg from a mouse click) return the hyperlink
1387+ at that position if there is one. Else return None.
1388+ """
1389+ if self.text is None:
1390+ return None
1391+
1392+ buffer_pos = self.textView.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
1393+ char_offset = self.textView.get_iter_at_location(buffer_pos[0], buffer_pos[1]).get_offset()
1394+
1395+ for bounds in self.link_bounds:
1396+ if char_offset >= bounds[0] and char_offset <= bounds[1]:
1397+ return self.text[bounds[0]:bounds[1]]
1398+
1399+ return None
1400+
1401+ def mouse_over(self, widget, event):
1402+ """
1403+ Called when the mouse moves over the node description widget. Sets the cursor
1404+ to a hand if the mouse hovers over a link.
1405+
1406+ Based on code from HyperTextDemo class in hypertext.py from PyGTK 2.12 demos
1407+ """
1408+
1409+ if self.get_hyperlink(int(event.x), int(event.y)) is not None:
1410+ self.textView.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
1411+ else:
1412+ self.textView.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
1413+
1414+ return
1415+
1416+ def mouse_button_release(self, widget, event):
1417+ """
1418+ Called when a mouse button is released over the node description widget.
1419+ Launches a browser if the mouse release was over a link, the left mouse button
1420+ was released and no text was selected.
1421+
1422+ Based on code from HyperTextDemo class in hypertext.py from PyGTK 2.12 demos
1423+ """
1424+
1425+ if not event.button == 1:
1426+ return
1427+
1428+ if self.textView.get_buffer().get_selection_bounds():
1429+ return
1430+
1431+ hyperlink = self.get_hyperlink(int(event.x), int(event.y))
1432+ if hyperlink is not None:
1433+ webbrowser.open(hyperlink)
1434+
1435+ return
1436
1437=== modified file 'diamond/diamond/interface.py'
1438--- diamond/diamond/interface.py 2011-07-20 17:19:44 +0000
1439+++ diamond/diamond/interface.py 2011-07-25 13:56:22 +0000
1440@@ -20,7 +20,6 @@
1441 import re
1442 import sys
1443 import tempfile
1444-import webbrowser
1445 import cStringIO as StringIO
1446
1447 import pango
1448@@ -28,18 +27,28 @@
1449 import gtk
1450 import gtk.glade
1451
1452+import choice
1453+import config
1454+import datatype
1455 import debug
1456 import dialogs
1457-import choice
1458-import config
1459+import mixedtree
1460 import plist
1461+import plugins
1462 import schema
1463 import scherror
1464 import tree
1465-import plugins
1466+
1467 import StringIO
1468 import TextBufferMarkup
1469
1470+import attributewidget
1471+import commentwidget
1472+import descriptionwidget
1473+import databuttonswidget
1474+import datawidget
1475+import sliceview
1476+
1477 from lxml import etree
1478
1479 try:
1480@@ -60,14 +69,13 @@
1481 logofile: the GUI logo file
1482 main_window: GUI toplevel window
1483 node_attrs: RHS attributes entry widget
1484- node_comment: RHS comment entry widget
1485- node_comment_interacted: used to determine if the comment widget has been interacted with without the comment being stored
1486+ description: RHS description widget
1487+ data = RHS data widget
1488+ comment: RHS comment entry widget
1489 node_data: RHS data entry widget
1490 node_data_buttons_hbox: container for "Revert Data" and "Store Data" buttons
1491 node_data_interacted: used to determine if a node data widget has been interacted with without data being stored
1492 node_data_frame: frame containing data entry widgets
1493- node_desc: RHS description widget
1494- node_desc_link_bounds: a list of tuples corresponding to the start and end points of links in the current tree.Tree / MixedTree documentation
1495 options_tree_select_func_enabled: boolean, true if the options tree select function is enabled (used to overcome a nasty clash with the treeview clicked signal) - re-enabled on next options_tree_select_func call
1496 selected_node: a tree.Tree or MixedTree containing data to be displayed on the RHS
1497 selected_iter: last iter set by on_select_row
1498@@ -83,7 +91,6 @@
1499
1500 Important routines:
1501 cellcombo_edited: called when a choice is selected on the left-hand pane
1502- init_options_frame: initialise the right-hand side
1503 init_treemodel: set up the treemodel and treeview
1504 on_treeview_clicked: when a row is clicked, process the consequences (e.g. activate inactive instance)
1505 set_treestore: stuff the treestore with a given tree.Tree
1506@@ -105,6 +112,8 @@
1507 self.find = DiamondFindDialog(self, gladefile)
1508 self.popup = self.gui.get_widget("popupmenu")
1509
1510+ self.add_custom_widgets()
1511+
1512 self.plugin_buttonbox = self.gui.get_widget("plugin_buttonbox")
1513 self.plugin_buttonbox.set_layout(gtk.BUTTONBOX_START)
1514 self.plugin_buttonbox.show()
1515@@ -129,7 +138,8 @@
1516 "on_about": self.on_about,
1517 "on_copy_spud_path": self.on_copy_spud_path,
1518 "on_copy": self.on_copy,
1519- "on_paste": self.on_paste}
1520+ "on_paste": self.on_paste,
1521+ "on_slice": self.on_slice}
1522 self.gui.signal_autoconnect(signals)
1523
1524 self.main_window = self.gui.get_widget("mainWindow")
1525@@ -147,7 +157,7 @@
1526 self.suffix = suffix
1527
1528 self.selected_node = None
1529- self.init_options_frame()
1530+ self.update_options_frame()
1531
1532 self.file_path = os.getcwd()
1533 self.schemafile_path = os.getcwd()
1534@@ -338,6 +348,7 @@
1535
1536 self.treeview.freeze_child_notify()
1537 self.treeview.set_model(None)
1538+ self.signals = {}
1539 self.set_treestore(None, [self.tree], True)
1540 self.treeview.set_model(self.treestore)
1541 self.treeview.thaw_child_notify()
1542@@ -426,7 +437,7 @@
1543 one.
1544 """
1545
1546- self.node_data_store()
1547+ self.data.store()
1548
1549 if self.filename is None:
1550 return self.on_save_as(widget)
1551@@ -521,35 +532,10 @@
1552 return
1553
1554 def on_display_properties_toggled(self, widget=None):
1555- self.options_frame.set_property("visible", not self.options_frame.get_property("visible"))
1556+ optionsFrame = self.gui.get_widget("optionsFrame")
1557+ optionsFrame.set_property("visible", not optionsFrame.get_property("visible"))
1558 return
1559
1560- def set_cell_node(self, iter):
1561- node = self.get_painted_tree(iter)
1562-
1563- if isinstance(node, MixedTree):
1564- node = node.child
1565-
1566- if node.data is not None:
1567- self.data_renderer.set_property("foreground", "black")
1568- self.treestore.set_value(iter, 4, node.data)
1569- elif isinstance(node, tree.Tree) and not node.not_editable():
1570- self.data_renderer.set_property("foreground", "gray")
1571-
1572- datatype = ""
1573-
1574- if isinstance(node.datatype, plist.List):
1575- datatype = "(" + node.datatype.datatype.__name__ + ")"
1576- elif isinstance(node.datatype, tuple):
1577- datatype = str(node.datatype)
1578- else:
1579- datatype = node.datatype.__name__
1580-
1581- self.treestore.set_value(iter, 4, datatype)
1582-
1583- def expand_fill_data(self, model, path, iter, user_data):
1584- return self.set_cell_node(iter)
1585-
1586 def on_go_to_node(self, widget=None):
1587 """
1588 Go to a node, identified by an XPath
1589@@ -566,7 +552,6 @@
1590 """
1591
1592 self.treeview.expand_all()
1593- self.treestore.foreach(self.expand_fill_data, None)
1594
1595 return
1596
1597@@ -624,8 +609,6 @@
1598 pass
1599
1600 about.set_logo(logo)
1601- about.connect("destroy", dialogs.close_dialog)
1602- about.connect("response", dialogs.close_dialog)
1603 about.show()
1604
1605 return
1606@@ -652,13 +635,13 @@
1607 return self._get_focus_widget(focus)
1608
1609 def on_copy(self, widget=None):
1610-
1611- widget = self._get_focus_widget(self.main_window)
1612- if widget is not self.treeview and gobject.signal_lookup("copy-clipboard", widget):
1613- widget.emit("copy-clipboard")
1614- return
1615+ if not isinstance(widget, gtk.MenuItem):
1616+ widget = self._get_focus_widget(self.main_window)
1617+ if widget is not self.treeview and gobject.signal_lookup("copy-clipboard", widget):
1618+ widget.emit("copy-clipboard")
1619+ return
1620
1621- if isinstance(self.selected_node, MixedTree):
1622+ if isinstance(self.selected_node, mixedtree.MixedTree):
1623 node = self.selected_node.parent
1624 else:
1625 node = self.selected_node
1626@@ -675,11 +658,11 @@
1627 return
1628
1629 def on_paste(self, widget=None):
1630-
1631- widget = self._get_focus_widget(self.main_window)
1632- if widget is not self.treeview and gobject.signal_lookup("paste-clipboard", widget):
1633- widget.emit("paste-clipboard")
1634- return
1635+ if not isinstance(widget, gtk.MenuItem):
1636+ widget = self._get_focus_widget(self.main_window)
1637+ if widget is not self.treeview and gobject.signal_lookup("paste-clipboard", widget):
1638+ widget.emit("paste-clipboard")
1639+ return
1640
1641 clipboard = gtk.clipboard_get()
1642 ios = StringIO.StringIO(clipboard.wait_for_text())
1643@@ -732,6 +715,19 @@
1644
1645 return
1646
1647+ def on_slice(self, widget = None):
1648+ if not self.selected_node.is_sliceable():
1649+ self.statusbar.set_statusbar("Cannot slice on this element.")
1650+ return
1651+
1652+ window = sliceview.SliceView(self.main_window)
1653+ window.geometry_dim_tree = self.geometry_dim_tree
1654+ window.update(self.selected_node, self.tree)
1655+ window.connect("destroy", self._slice_destroy)
1656+ return
1657+
1658+ def _slice_destroy(self, widget):
1659+ self.on_select_row()
1660
1661 ## LHS ###
1662
1663@@ -747,6 +743,7 @@
1664 l = self.s.valid_children(":start")
1665
1666 self.tree = l[0]
1667+ self.signals = {}
1668 self.set_treestore(None, l)
1669
1670 root_iter = self.treestore.get_iter_first()
1671@@ -764,18 +761,17 @@
1672 """
1673
1674 self.treeview = optionsTree = self.gui.get_widget("optionsTree")
1675- self.treeview.connect("row-collapsed", self.on_treeview_row_collapsed)
1676 self.treeview.connect("key_press_event", self.on_treeview_key_press)
1677 self.treeview.connect("button_press_event", self.on_treeview_button_press)
1678+ self.treeview.connect("row-activated", self.on_activate_row)
1679 self.treeview.connect("popup_menu", self.on_treeview_popup)
1680- try: # allow for possibility of no tooltips (like elsewhere)
1681- self.treeview.connect("query-tooltip", self.on_tooltip)
1682- self.treeview.set_property("has-tooltip", False)
1683- except:
1684- pass
1685
1686 self.treeview.set_property("rules-hint", True)
1687+
1688 self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
1689+ self.treeview.get_selection().connect("changed", self.on_select_row)
1690+ self.treeview.get_selection().set_select_function(self.options_tree_select_func)
1691+ self.options_tree_select_func_enabled = True
1692
1693 model = gtk.ListStore(str, str, gobject.TYPE_PYOBJECT)
1694 self.cellcombo = cellCombo = gtk.CellRendererCombo()
1695@@ -783,6 +779,7 @@
1696 cellCombo.set_property("text-column", 0)
1697 cellCombo.set_property("editable", True)
1698 cellCombo.set_property("has-entry", False)
1699+ cellCombo.connect("edited", self.cellcombo_edited)
1700
1701 # Node column
1702 column = gtk.TreeViewColumn("Node", cellCombo, text=0)
1703@@ -803,54 +800,31 @@
1704 imgcolumn.set_cell_data_func(cellPicture, self.set_cellpicture_cardinality)
1705 optionsTree.append_column(imgcolumn)
1706
1707- # mtw07 - add column for quick preview of data.
1708- self.data_renderer = gtk.CellRendererText()
1709- self.data_renderer.set_property("editable", True)
1710- self.data_renderer.connect("edited", self.on_cell_edit)
1711- self.data_renderer.connect("editing-started", self.on_cell_edit_start)
1712-
1713-# self.data_col = data_col = gtk.TreeViewColumn("Data", self.data_renderer, text=4)
1714-# data_col.set_property("expand", True)
1715-# data_col.set_property("sizing", gtk.TREE_VIEW_COLUMN_AUTOSIZE)
1716-# optionsTree.append_column(data_col)
1717-
1718- # display name, gtk.ListStore containing the display names of possible choices, pointer to node in self.tree -- a choice or a tree, pointer to currently active tree and its data.
1719- self.treestore = gtk.TreeStore(str, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str)
1720+ # 0: display name,
1721+ # 1: gtk.ListStore containing the display names of possible choices
1722+ # 2: pointer to node in self.tree -- a choice or a tree
1723+ # 3: pointer to currently active tree
1724+
1725+ self.treestore = gtk.TreeStore(str, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)
1726 self.treeview.set_model(self.treestore)
1727-# self.treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_VERTICAL)
1728-
1729- optionsTree.get_selection().connect("changed", self.on_select_row)
1730- self.treeview.get_selection().set_select_function(self.options_tree_select_func)
1731- self.options_tree_select_func_enabled = True
1732- optionsTree.connect("button_press_event", self.on_treeview_clicked)
1733- optionsTree.connect("row-activated", self.on_activate_row)
1734- cellCombo.connect("edited", self.cellcombo_edited)
1735-
1736 self.treeview.set_enable_search(False)
1737
1738 return
1739
1740- def create_liststore(self, l):
1741+ def create_liststore(self, choice_or_tree):
1742 """
1743 Given a list of possible choices, create the liststore for the
1744 gtk.CellRendererCombo that contains the names of possible choices.
1745 """
1746
1747 liststore = gtk.ListStore(str, gobject.TYPE_PYOBJECT)
1748- if l.__class__ is choice.Choice:
1749- l = l.l # I'm really sorry about this, blame dham
1750-
1751- if not isinstance(l, list):
1752- l = [l]
1753-
1754- # Ignoring the numerous l's involved in getting the choices,
1755- # l is now a list of possible names.
1756- for t in l:
1757- name = self.get_display_name(t)
1758+
1759+ for t in choice_or_tree.get_choices():
1760+ name = t.get_display_name()
1761 liststore.append([name, t])
1762
1763 return liststore
1764-
1765+
1766 def set_treestore(self, iter=None, new_tree=[], recurse=False, replace=False):
1767 """
1768 Given a list of children of a node in a treestore, stuff them in the treestore.
1769@@ -861,39 +835,50 @@
1770 iter = self.treestore.iter_parent(replacediter)
1771 else:
1772 self.remove_children(iter)
1773-
1774+
1775 for t in new_tree:
1776- if t.__class__ is tree.Tree:
1777- if self.choice_or_tree_is_hidden(t):
1778+ if t is not None and t in self.signals:
1779+ t.disconnect(self.signals[t][0])
1780+ t.disconnect(self.signals[t][1])
1781+
1782+ if isinstance(t, tree.Tree):
1783+ if t.is_hidden():
1784+ attrid = t.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(iter))
1785+ dataid = t.connect("on-set-data", self.on_set_data, self.treestore.get_path(iter))
1786+ self.signals[t] = (attrid, dataid)
1787 continue
1788
1789 liststore = self.create_liststore(t)
1790
1791- # Convert node data, if it exists, to a string
1792- data = ""
1793- node_data = t.data
1794- if node_data is not None:
1795-# if t.__class__ is str:
1796-# if len(t) > 4: # Trim the string if it's long
1797-# node_data = t[:4] + ".."
1798-
1799- data = str(node_data)
1800-
1801 if replace:
1802- child_iter = self.treestore.insert_before(iter, replacediter, [self.get_display_name(t), liststore, t, t, data])
1803+ child_iter = self.treestore.insert_before(iter, replacediter, [t.get_display_name(), liststore, t, t])
1804 else:
1805- child_iter = self.treestore.append(iter, [self.get_display_name(t), liststore, t, t, data])
1806-
1807+ child_iter = self.treestore.append(iter, [t.get_display_name(), liststore, t, t])
1808+
1809+ attrid = t.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(child_iter))
1810+ dataid = t.connect("on-set-data", self.on_set_data, self.treestore.get_path(child_iter))
1811+ self.signals[t] = (attrid, dataid)
1812+
1813 if recurse and t.active: self.set_treestore(child_iter, t.children, recurse)
1814- elif t.__class__ is choice.Choice:
1815+
1816+ elif isinstance(t, choice.Choice):
1817 liststore = self.create_liststore(t)
1818 ts_choice = t.get_current_tree()
1819- if self.choice_or_tree_is_hidden(ts_choice):
1820+ if ts_choice.is_hidden():
1821+ attrid = t.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(iter))
1822+ dataid = t.connect("on-set-data", self.on_set_data, self.treestore.get_path(iter))
1823+ self.signals[t] = (attrid, dataid)
1824 continue
1825+
1826 if replace:
1827- child_iter = self.treestore.insert_before(iter, replacediter, [self.get_display_name(ts_choice), liststore, t, ts_choice, ""])
1828+ child_iter = self.treestore.insert_before(iter, replacediter, [ts_choice.get_display_name(), liststore, t, ts_choice])
1829 else:
1830- child_iter = self.treestore.append(iter, [self.get_display_name(ts_choice), liststore, t, ts_choice, ""])
1831+ child_iter = self.treestore.append(iter, [ts_choice.get_display_name(), liststore, t, ts_choice])
1832+
1833+ attrid = t.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(child_iter))
1834+ dataid = t.connect("on-set-data", self.on_set_data, self.treestore.get_path(child_iter))
1835+ self.signals[t] = (attrid, dataid)
1836+
1837 if recurse and t.active: self.set_treestore(child_iter, ts_choice.children, recurse)
1838
1839 if replace:
1840@@ -976,10 +961,7 @@
1841 foreground colour.
1842 """
1843
1844- liststore = self.treestore.get_value(iter, 1)
1845- choice_or_tree = self.treestore.get_value(iter, 2)
1846- active_tree = self.treestore.get_value(iter, 3)
1847- data = self.treestore.get_value(iter, 4)
1848+ liststore, choice_or_tree, active_tree = self.treestore.get(iter, 1, 2, 3)
1849
1850 # set the model for the cellcombo, where it gets the possible choices for the name
1851 cellCombo.set_property("model", liststore)
1852@@ -1007,9 +989,9 @@
1853 """
1854
1855 choice_or_tree = self.treestore.get_value(iter, 2)
1856- if choice_or_tree.__class__ is tree.Tree:
1857+ if isinstance(choice_or_tree, tree.Tree):
1858 cell.set_property("stock-id", None)
1859- elif choice_or_tree.__class__ is choice.Choice:
1860+ elif isinstance(choice_or_tree, choice.Choice):
1861 cell.set_property("stock-id", gtk.STOCK_GO_DOWN)
1862
1863 return
1864@@ -1042,132 +1024,6 @@
1865
1866 return
1867
1868- def on_treeview_row_collapsed(self, treeview, iter, path):
1869- """
1870- Called when a row in the LHS treeview is collapsed.
1871- """
1872-
1873- #self.treeview.get_column(0).queue_resize()
1874- #self.treeview.get_column(1).queue_resize()
1875-
1876- return
1877-
1878- def on_tooltip(self, widget, x, y, keyboard_mode, tooltip):
1879- y-=25 # It's hardcoded. Gtk doesn't offer a way to get at the column height without a lot more code.
1880- (tx, ty) = self.treeview.convert_bin_window_to_tree_coords(x, y)
1881- pathinfo = self.treeview.get_path_at_pos(x, y)
1882-
1883- if pathinfo is None:
1884- return False
1885-
1886- path = pathinfo[0]
1887- column = pathinfo[1]
1888-
1889- if path is None:
1890- return
1891-
1892- ctitle = column.get_title()
1893-
1894- iter = self.treestore.get_iter(path)
1895-
1896- # Get the tree or choice pointed to by the iterator.
1897- tree = self.treestore.get_value(iter, 2)
1898-
1899- if tree.__class__ is choice.Choice:
1900- tree = tree.get_current_tree()
1901-
1902- if ctitle == "Node":
1903- if tree.doc is None:
1904- text = "(No documentation)"
1905- else:
1906- text = self.render_whitespace(tree.doc)
1907- elif ctitle == "Data":
1908- comment_tree = self.get_comment(tree)
1909-
1910- if comment_tree is None:
1911- text = "(No comment)"
1912- else:
1913- text = comment_tree.data
1914- else:
1915- return False
1916-
1917- tooltip.set_text(text)
1918- return True
1919-
1920- def on_cell_edit(self, cellr, path, new_text):
1921- iter = self.treestore.get_iter(path)
1922- node = self.selected_node
1923-
1924- if new_text == "":
1925- return
1926-
1927- # mtw07 - Check whether the data is valid or not. If so, update the treeview
1928- # and internal model. GTK automatically clears the text field if we don't
1929- # add the new data to the treestore.
1930- (invalid, data) = node.valid_data(node.datatype, new_text)
1931-
1932- if invalid:
1933- try:
1934- name = node.datatype.__name__
1935- dialogs.error(self.main_window, "Invalid data type. A %s is required." % name)
1936- except:
1937- pass
1938- return
1939-
1940- self.treestore.set_value(iter, 4, new_text)
1941- node.data = new_text
1942-
1943- # Update the node data box.
1944- self.node_data.get_buffer().set_text(new_text)
1945-
1946- # Update the validation errors list.
1947- if self.scherror.errlist_is_open():
1948- if self.scherror.errlist_type == 0:
1949- self.scherror.on_validate_schematron()
1950- else:
1951- self.scherror.on_validate()
1952-
1953- return
1954-
1955- def on_cell_edit_start(self, cellrenderer, editable, path):
1956- iter = self.treestore.get_iter(path)
1957- node = self.selected_node
1958-
1959- # If the cell has (<type>) in it, clear it for the user to enter data.
1960- if node.datatype is not None and node.data is None:
1961- editable.set_text("")
1962-
1963- return True
1964-
1965- def on_treeview_clicked(self, treeview, event):
1966- """
1967- This routine is called every time the mouse is clicked on the treeview on the
1968- left-hand side. It processes the "buttons" gtk.STOCK_ADD and gtk.STOCK_REMOVE
1969- in the right-hand column, activating, adding and removing tree nodes as
1970- necessary.
1971- """
1972-
1973- if event.button != 1:
1974- return
1975-
1976- pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y))
1977-
1978- if pathinfo is None:
1979- return
1980-
1981- path = pathinfo[0]
1982- col = pathinfo[1]
1983-
1984- iter = self.treestore.get_iter(path)
1985- self.update_data_column(self.treestore, iter)
1986-
1987- if col is not self.imgcolumn:
1988- return
1989-
1990- self.toggle_tree(iter)
1991-
1992- return
1993-
1994 def toggle_tree(self, iter):
1995 """
1996 Toggles the state of part of the tree.
1997@@ -1285,7 +1141,10 @@
1998 self.expand_treestore(iter)
1999 iter = self.treestore.insert_after(
2000 parent=parent_iter, sibling=iter,
2001- row=[self.get_display_name(new_tree), liststore, new_tree, new_tree.get_current_tree(), ""])
2002+ row=[new_tree.get_display_name(), liststore, new_tree, new_tree.get_current_tree()])
2003+ attrid = new_tree.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(iter))
2004+ dataid = new_tree.connect("on-set-data", self.on_set_data, self.treestore.get_path(iter))
2005+ self.signals[new_tree] = (attrid, dataid)
2006 self.set_saved(False)
2007
2008 parent_tree.recompute_validity()
2009@@ -1301,10 +1160,13 @@
2010 self.options_tree_select_func_enabled = True
2011 return False
2012
2013- if not self.node_data_store():
2014+ if not self.data.store():
2015 return False
2016
2017- if isinstance(self.selected_node, MixedTree) and self.geometry_dim_tree is not None and self.selected_node.parent is self.geometry_dim_tree.parent and self.selected_node.data is not None:
2018+ if isinstance(self.selected_node, mixedtree.MixedTree) \
2019+ and self.geometry_dim_tree is not None \
2020+ and self.selected_node.parent is self.geometry_dim_tree.parent \
2021+ and self.selected_node.data is not None:
2022 self.geometry_dim_tree.set_data(self.selected_node.data)
2023
2024 return True
2025@@ -1327,15 +1189,29 @@
2026 return
2027
2028 def on_treeview_button_press(self, treeview, event):
2029- if event.button == 3:
2030- x = int(event.x)
2031- y = int(event.y)
2032- path = treeview.get_path_at_pos(x, y)[0]
2033- if path is not None:
2034- treeview.get_selection().select_path(path)
2035+ """
2036+ This routine is called every time the mouse is clicked on the treeview on the
2037+ left-hand side. It processes the "buttons" gtk.STOCK_ADD and gtk.STOCK_REMOVE
2038+ in the right-hand column, activating, adding and removing tree nodes as
2039+ necessary.
2040+ """
2041+ pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y))
2042+
2043+ if event.button == 1:
2044+
2045+ if pathinfo is not None:
2046+ path = pathinfo[0]
2047+ col = pathinfo[1]
2048+
2049+ if col is self.imgcolumn:
2050+ iter = self.treestore.get_iter(path)
2051+ self.toggle_tree(iter)
2052+
2053+ elif event.button == 3:
2054+ if pathinfo is not None:
2055+ treeview.get_selection().select_path(pathinfo[0])
2056 self.show_popup(None, event.button, event.time)
2057 return True
2058- return False
2059
2060 def popup_location(self, widget, user_data):
2061 column = self.treeview.get_column(0)
2062@@ -1358,28 +1234,22 @@
2063 Called when a row is selected. Update the options frame.
2064 """
2065
2066- path = self.get_selected_row(selection)
2067+ if isinstance(selection, str) or isinstance(selection, tuple):
2068+ path = selection
2069+ else:
2070+ path = self.get_selected_row(selection)
2071+
2072 if path is None:
2073 return
2074+
2075 self.selected_iter = iter = self.treestore.get_iter(path)
2076-
2077- choice_or_tree = self.treestore.get_value(iter, 2)
2078-
2079- active_tree = self.treestore.get_value(iter, 3)
2080+ choice_or_tree, active_tree = self.treestore.get(iter, 2, 3)
2081+
2082 debug.dprint(active_tree)
2083
2084 self.selected_node = self.get_painted_tree(iter)
2085 self.update_options_frame()
2086
2087- node = self.selected_node
2088-
2089- if isinstance(node, MixedTree):
2090- node = node.child
2091-
2092- # TODO: Handle tuple datatypes. Is it possible in a Gtk treeview column?
2093- if not isinstance(node.datatype, tuple):
2094- self.data_renderer.set_property("editable", not (node.not_editable()))
2095-
2096 name = self.get_spudpath(active_tree)
2097 self.statusbar.set_statusbar(name)
2098 self.current_spudpath = name
2099@@ -1477,18 +1347,6 @@
2100 else:
2101 return None
2102
2103- def update_data_column(self, model, itParent):
2104- """
2105- Update the data column in the treeview. Used when a user expands a row.
2106- """
2107-
2108- iter = model.iter_children(itParent)
2109-
2110- while iter is not None:
2111- self.set_cell_node(iter)
2112- iter = model.iter_next(iter)
2113-
2114-
2115 def on_activate_row(self, treeview, path, view_column):
2116 """
2117 Called when you double click or press Enter on a row.
2118@@ -1507,10 +1365,6 @@
2119 else:
2120 treeview.expand_row(path, False)
2121
2122- # Update the data column for the newly expanded row.
2123- (model, itParent) = self.treeview.get_selection().get_selected()
2124- self.update_data_column(model, itParent)
2125-
2126 return
2127
2128 def cellcombo_edited(self, cellrenderertext, path, new_text):
2129@@ -1561,53 +1415,6 @@
2130
2131 return
2132
2133- def paint_validity(self):
2134- """
2135- Walk up the parental line, repainting the colour for their validity
2136- appropriately. This is called when a validity-changing event occurs.
2137- Repaint the whole tree if there is no selection.
2138- """
2139-
2140- def paint_iter_validity(iter):
2141- while iter is not None:
2142- active_tree = self.treestore.get_value(iter, 3)
2143- if active_tree.valid:
2144- self.cellcombo.set_property("foreground", "black")
2145- else:
2146- self.cellcombo.set_property("foreground", "blue")
2147- iter = self.treestore.iter_next(iter)
2148-
2149- return
2150-
2151- selection = self.treeview.get_selection()
2152- path = self.get_selected_row(selection)
2153- if path is None:
2154- paint_iter_validity(self.treestore.get_iter_first())
2155- else:
2156- paint_iter_validity(self.treestore.get_iter(path))
2157-
2158- self.treeview.queue_draw()
2159-
2160- return
2161-
2162- def get_display_name(self, active_tree):
2163- """
2164- This is a fluidity hack, allowing the name displayed in the treeview on the
2165- left to be different to the element name. If it has an attribute name="xxx",
2166- element_tag (xxx) is displayed.
2167- """
2168-
2169- if active_tree.__class__ is tree.Tree:
2170- displayname = active_tree.name
2171- if "name" in active_tree.attrs.keys():
2172- attrname = active_tree.attrs["name"][1]
2173- if attrname is not None:
2174- displayname = displayname + " (" + attrname + ")"
2175- elif active_tree.__class__ is choice.Choice:
2176- displayname = self.get_display_name(active_tree.get_current_tree())
2177-
2178- return displayname
2179-
2180 def get_treeview_iter(self, selection):
2181 """
2182 Get a treeview iterator object, given a selection.
2183@@ -1619,19 +1426,19 @@
2184
2185 return self.treestore.get_iter(path)
2186
2187- def update_painted_name(self):
2188- """
2189- This updates the treestore (and the liststore for the gtk.CellRendererCombo)
2190- with a new name, when the name="xxx" attribute is changed.
2191- """
2192+ def on_set_data(self, node, data, path):
2193+ self.set_saved(False)
2194+ self.treeview.queue_draw()
2195+ #self.on_select_row(path)
2196
2197- iter = self.get_treeview_iter(self.treeview.get_selection())
2198- if iter is None:
2199+ def on_set_attr(self, node, attr, value, path):
2200+ if attr != "name":
2201 return
2202
2203+ iter = self.treestore.get_iter(path)
2204 liststore = self.treestore.get_value(iter, 1)
2205 active_tree = self.treestore.get_value(iter, 3)
2206- new_name = self.get_display_name(active_tree)
2207+ new_name = active_tree.get_display_name()
2208 self.treestore.set_value(iter, 0, new_name)
2209
2210 # find the liststore iter corresponding to the painted choice
2211@@ -1642,9 +1449,7 @@
2212 liststore.set_value(list_iter, 0, new_name)
2213 list_iter = liststore.iter_next(list_iter)
2214
2215- self.treeview.get_column(0).queue_resize()
2216-
2217- return
2218+ self.treeview.queue_resize()
2219
2220 def get_painted_tree(self, iter_or_tree, lock_geometry_dim = True):
2221 """
2222@@ -1662,25 +1467,7 @@
2223 else:
2224 active_tree = self.treestore.get_value(iter_or_tree, 3)
2225
2226- integers = [child for child in active_tree.children if child.name == "integer_value"]
2227- reals = [child for child in active_tree.children if child.name == "real_value"]
2228- logicals = [child for child in active_tree.children if child.name == "logical_value"]
2229- strings = [child for child in active_tree.children if child.name == "string_value"]
2230-
2231- child = None
2232- if len(integers) > 0:
2233- child = integers[0]
2234- if len(reals) > 0:
2235- child = reals[0]
2236- if len(logicals) > 0:
2237- child = logicals[0]
2238- if len(strings) > 0:
2239- child = strings[0]
2240-
2241- if child is None:
2242- painted_tree = active_tree
2243- else:
2244- painted_tree = MixedTree(active_tree, child)
2245+ painted_tree = active_tree.get_mixed_data()
2246
2247 if not isinstance(iter_or_tree, tree.Tree) and not self.treestore_iter_is_active(iter_or_tree):
2248 painted_tree = tree.Tree(painted_tree.name, painted_tree.schemaname, painted_tree.attrs, doc = painted_tree.doc)
2249@@ -1690,10 +1477,10 @@
2250 data_tree = tree.Tree(painted_tree.name, painted_tree.schemaname, datatype = "fixed")
2251 data_tree.data = painted_tree.data
2252 painted_tree = MixedTree(painted_tree, data_tree)
2253- elif isinstance(self.geometry_dim_tree, MixedTree) and active_tree is self.geometry_dim_tree.parent:
2254+ elif isinstance(self.geometry_dim_tree, mixedtree.MixedTree) and active_tree is self.geometry_dim_tree.parent:
2255 data_tree = tree.Tree(painted_tree.child.name, painted_tree.child.schemaname, datatype = "fixed")
2256 data_tree.data = painted_tree.data
2257- painted_tree = MixedTree(painted_tree, data_tree)
2258+ painted_tree = mixedtree.MixedTree(painted_tree, data_tree)
2259
2260 return painted_tree
2261
2262@@ -1724,20 +1511,19 @@
2263 Find the iter into the treestore corresponding to the geometry dimension, and
2264 perform checks to test that the geometry dimension node is valid.
2265 """
2266-
2267+
2268+ self.geometry_dim_tree = self.data.geometry_dim_tree = None
2269 # The tree must exist
2270 if self.tree is None:
2271- self.geometry_dim_tree = None
2272 return
2273
2274 # A geometry dimension element must exist
2275 iter = self.get_treestore_iter_from_xmlpath("/" + self.tree.name + self.data_paths["dim"])
2276 if iter is None:
2277- self.geometry_dim_tree = None
2278 return
2279
2280 painted_tree = self.get_painted_tree(iter, False)
2281- if isinstance(painted_tree, MixedTree):
2282+ if isinstance(painted_tree, mixedtree.MixedTree):
2283 # If the geometry dimension element has a hidden data element, it must
2284 # have datatype tuple or fixed
2285 if not isinstance(painted_tree.datatype, tuple) and painted_tree.datatype != "fixed":
2286@@ -1745,7 +1531,6 @@
2287 return
2288 elif painted_tree.datatype != "fixed":
2289 # Otherwise, only fixed datatype is permitted
2290- self.geometry_dim_tree = None
2291 return
2292
2293 # All parents of the geometry dimension element must have cardinality ""
2294@@ -1763,18 +1548,16 @@
2295 elif painted_tree.datatype == "fixed":
2296 possible_dims = [painted_tree.data]
2297 else:
2298- self.geometry_dim_tree = None
2299 return
2300 for opt in possible_dims:
2301 try:
2302 test = int(opt)
2303 assert test > 0
2304 except:
2305- self.geometry_dim_tree = None
2306 return
2307
2308 # A valid geometry dimension element has been located
2309- self.geometry_dim_tree = painted_tree
2310+ self.geometry_dim_tree = self.data.geometry_dim_tree = painted_tree
2311
2312 return
2313
2314@@ -1792,55 +1575,6 @@
2315
2316 return True
2317
2318- def choice_or_tree_is_hidden(self, choice_or_tree):
2319- """
2320- Tests whether the supplied choice or tree should be hidden from the LHS.
2321- """
2322-
2323- return self.choice_or_tree_is_comment(choice_or_tree) or choice_or_tree.name in ["integer_value", "real_value", "string_value", "logical_value"]
2324-
2325- def choice_or_tree_is_comment(self, choice_or_tree):
2326- """
2327- Test whether the given node is a comment node.
2328- """
2329-
2330- if not isinstance(choice_or_tree, tree.Tree):
2331- return False
2332-
2333- if not choice_or_tree.name == "comment":
2334- return False
2335-
2336- if not choice_or_tree.attrs == {}:
2337- return False
2338-
2339- if not choice_or_tree.children == []:
2340- return False
2341-
2342- if not choice_or_tree.datatype is str:
2343- return False
2344-
2345- if not choice_or_tree.cardinality == "?":
2346- return False
2347-
2348- return True
2349-
2350- def get_comment(self, choice_or_tree):
2351- """
2352- Return the first comment found as a child of the supplied node, or None if
2353- none found.
2354- """
2355-
2356- if choice_or_tree is None or isinstance(choice_or_tree, choice.Choice):
2357- return None
2358-
2359- for child in choice_or_tree.children:
2360- if self.choice_or_tree_is_comment(child):
2361- return child
2362-
2363- return None
2364-
2365- return
2366-
2367 def choice_or_tree_matches(self, text, choice_or_tree, recurse, search_active_subtrees = False):
2368 """
2369 See if the supplied node matches a given piece of text. If recurse is True,
2370@@ -1849,7 +1583,7 @@
2371 choice match.
2372 """
2373
2374- if self.choice_or_tree_is_hidden(choice_or_tree):
2375+ if choice_or_tree.is_hidden():
2376 return False
2377 elif isinstance(choice_or_tree, choice.Choice):
2378 if self.choice_or_tree_matches(text, choice_or_tree.get_current_tree(), False):
2379@@ -1870,7 +1604,7 @@
2380 text_re = re.compile(text)
2381 else:
2382 text_re = re.compile(text, re.IGNORECASE)
2383- comment = self.get_comment(choice_or_tree)
2384+ comment = choice_or_tree.get_comment()
2385 if comment is not None and comment.data is not None and text_re.search(comment.data) is not None:
2386 return True
2387 elif recurse:
2388@@ -1909,1279 +1643,58 @@
2389 return
2390
2391 ### RHS ###
2392-
2393- def render_whitespace(self, desc):
2394- ''' Render the line wrapping in desc as follows:
2395-
2396- * Newlines followed by 0-1 spaces are ignored.
2397- * Blank lines start new paragraphs.
2398- * Newlines followed by more than 1 space are honoured.
2399- '''
2400-
2401- prev_line_literal=False
2402- prev_line_new_para=False
2403- newdesc=""
2404-
2405- for line in desc.split("\n"):
2406-
2407- if (line[:1]==" "):
2408- # Literal line with leading blanks.
2409- newdesc=newdesc+"\n"+line
2410- prev_line_literal=True
2411- prev_line_new_para=False
2412- continue
2413-
2414- if (line.strip()==""):
2415- # New paragraph.
2416-
2417- # Collapse multiple new paragraphs into one, except when
2418- # following a literal line.
2419- if (prev_line_new_para and not prev_line_literal):
2420- continue
2421-
2422- newdesc=newdesc+"\n"
2423- prev_line_new_para=True
2424- continue
2425-
2426- if prev_line_literal:
2427- newdesc=newdesc+"\n"
2428- prev_line_literal=False
2429- prev_line_new_para=False
2430-
2431- if prev_line_new_para:
2432- newdesc=newdesc+" "
2433- prev_line_new_para=False
2434-
2435- # Default case
2436- newdesc=newdesc+line+" "
2437-
2438- return newdesc
2439-
2440- def link_bounds(self, text):
2441- """
2442- Return a list of tuples corresponding to the start and end points of links in
2443- the supplied string.
2444- """
2445-
2446- bounds = []
2447-
2448- text_split = text.lower().split("http://")
2449- if len(text_split) > 1:
2450- lbound = -7
2451- for i in range(len(text_split))[1:]:
2452- lbound += len(text_split[i - 1]) + 7
2453- ubound = lbound + len(text_split[i].split(" ")[0].split("\n")[0]) + 7
2454- while text[ubound - 1:ubound] in [".", ",", ":", ";", "\"", "'", ")", "]", "}"]:
2455- ubound -= 1
2456- bounds.append((lbound, ubound))
2457-
2458- return bounds
2459-
2460- def type_name(self, datatype):
2461- """
2462- Return a human readable version of datatype.
2463- """
2464-
2465- def familiar_type(type_as_printable):
2466- """
2467- Convert some type names to more familiar equivalents.
2468- """
2469-
2470- if type_as_printable == "decim":
2471- return "float"
2472- elif type_as_printable == "int":
2473- return "integer"
2474- elif type_as_printable == "str":
2475- return "string"
2476- else:
2477- return type_as_printable
2478-
2479- datatype_string = str(datatype)
2480-
2481- if datatype_string[:7] == "<type '" and datatype_string[len(datatype_string) - 2:] == "'>":
2482- value_type_split = datatype_string.split("'")
2483- return familiar_type(value_type_split[1])
2484-
2485- value_type_split1 = datatype_string.split(".")
2486- value_type_split2 = value_type_split1[len(value_type_split1) - 1].split(" ")
2487- if len(value_type_split2) == 1:
2488- return familiar_type(value_type_split2[0][0:len(value_type_split2[0]) - 6])
2489- else:
2490- return familiar_type(value_type_split2[0])
2491-
2492- def printable_type(self, datatype, bracket = True):
2493- """
2494- Create a string to be displayed in place of empty data / attributes.
2495- """
2496-
2497- if isinstance(datatype, plist.List):
2498- if (isinstance(datatype.cardinality, int) and datatype.cardinality == 1) or datatype.cardinality == "":
2499- type_as_printable = self.type_name(datatype.datatype).lower()
2500- else:
2501- type_as_printable = self.type_name(datatype).lower() + " of "
2502- list_type_as_printable = self.type_name(datatype.datatype).lower()
2503- if isinstance(datatype.cardinality, int):
2504- type_as_printable += str(datatype.cardinality) + " " + list_type_as_printable + "s"
2505- else:
2506- type_as_printable += list_type_as_printable + "s"
2507- else:
2508- type_as_printable = self.type_name(datatype).lower()
2509-
2510- if bracket:
2511- type_as_printable = "(" + type_as_printable + ")"
2512-
2513- return type_as_printable
2514-
2515- def init_options_frame(self):
2516- """
2517- Initialise the RHS.
2518- """
2519-
2520- # mtw07 - TODO: set the visibility of the options frame, depending on the value in the schema file.
2521- self.options_frame = self.gui.get_widget("optionsFrame")
2522-
2523- # Display the right hand side by default.
2524- self.options_frame.set_property("visible", True)
2525-
2526- self.node_desc = self.gui.get_widget("nodeDescription")
2527- self.node_desc.set_buffer(TextBufferMarkup.PangoBuffer())
2528- self.node_desc.connect("button-release-event", self.node_desc_mouse_button_release)
2529- self.node_desc.connect("motion-notify-event", self.node_desc_mouse_over)
2530-
2531- self.node_attrs = self.gui.get_widget("nodeAttributes")
2532- attrs_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
2533- self.node_attrs.set_model(attrs_model)
2534- self.node_attrs.connect("motion-notify-event", self.node_attrs_mouse_over)
2535- key_renderer = gtk.CellRendererText()
2536- key_renderer.set_property("editable", False)
2537- attrs_col1 = gtk.TreeViewColumn("Name", key_renderer, text = 0)
2538- attrs_col1.set_cell_data_func(key_renderer, self.node_attrs_key_data_func)
2539- attrs_col1.set_property("min-width", 75)
2540- attrs_val_entry_renderer = gtk.CellRendererText()
2541- attrs_val_entry_renderer.connect("edited", self.node_attrs_edited)
2542- attrs_val_entry_renderer.connect("editing-started", self.node_attrs_entry_edit_start)
2543- attrs_val_combo_renderer = gtk.CellRendererCombo()
2544- attrs_val_combo_renderer.set_property("text-column", 0)
2545- attrs_val_combo_renderer.connect("edited", self.node_attrs_selected)
2546- attrs_val_combo_renderer.connect("editing-started", self.node_attrs_combo_edit_start)
2547- attrs_col2 = gtk.TreeViewColumn("Value", attrs_val_entry_renderer, text = 1)
2548- attrs_col2.pack_start(attrs_val_combo_renderer)
2549- attrs_col2.set_attributes(attrs_val_combo_renderer, text = 1)
2550- attrs_col2.set_cell_data_func(attrs_val_entry_renderer, self.node_attrs_entry_data_func)
2551- attrs_col2.set_cell_data_func(attrs_val_combo_renderer, self.node_attrs_combo_data_func)
2552- attrs_col2.set_property("expand", True)
2553- attrs_col2.set_property("min-width", 75)
2554- attrs_icon_renderer = gtk.CellRendererPixbuf()
2555- attrs_col3 = gtk.TreeViewColumn("", attrs_icon_renderer)
2556- attrs_col3.set_cell_data_func(attrs_icon_renderer, self.node_attrs_icon_data_func)
2557- self.node_attrs.append_column(attrs_col1)
2558- self.node_attrs.append_column(attrs_col2)
2559- self.node_attrs.append_column(attrs_col3)
2560-
2561- self.node_data_frame = self.gui.get_widget("dataFrame")
2562-
2563- self.node_data_buttons_hbox = self.gui.get_widget("dataButtonsHBox")
2564-
2565- data_revert_button = self.gui.get_widget("dataRevertButton")
2566- data_revert_button.connect("clicked", self.node_data_revert)
2567-
2568- data_store_button = self.gui.get_widget("dataStoreButton")
2569- data_store_button.connect("clicked", self.node_data_store)
2570-
2571- self.node_comment = self.gui.get_widget("nodeComment")
2572- self.node_comment.get_buffer().create_tag("comment_buffer_tag")
2573- self.node_comment.connect("focus-in-event", self.node_comment_focus_in)
2574- self.node_comment.connect("expose-event", self.node_comment_expose)
2575-
2576- return
2577-
2578+
2579+ def add_custom_widgets(self):
2580+ """
2581+ Adds custom python widgets that aren't easily handeled by glade.
2582+ """
2583+
2584+ optionsFrame = self.gui.get_widget("optionsFrame")
2585+
2586+ vpane1 = gtk.VPaned()
2587+ vpane2 = gtk.VPaned()
2588+ vbox = gtk.VBox()
2589+
2590+ vpane1.pack2(vpane2, True, False)
2591+ vpane2.pack1(vbox, True, False)
2592+ optionsFrame.add(vpane1)
2593+
2594+ self.description = descriptionwidget.DescriptionWidget()
2595+ vpane1.pack1(self.description, True, False)
2596+
2597+ self.attributes = attributewidget.AttributeWidget()
2598+ vbox.pack_start(self.attributes, True, True)
2599+
2600+ databuttons = databuttonswidget.DataButtonsWidget()
2601+ vbox.pack_end(databuttons, False)
2602+
2603+ self.data = datawidget.DataWidget()
2604+ self.data.set_buttons(databuttons)
2605+ vbox.pack_end(self.data, True, True)
2606+
2607+ self.comment = commentwidget.CommentWidget()
2608+ vpane2.pack2(self.comment, True, False)
2609+
2610+ optionsFrame.show_all()
2611+ return
2612+
2613 def update_options_frame(self):
2614 """
2615 Update the RHS.
2616 """
2617-
2618- if self.selected_node is None:
2619- self.set_node_desc("<span foreground=\"grey\">No node selected</span>")
2620- elif self.selected_node.doc is None:
2621- self.set_node_desc("<span foreground=\"red\">No documentation</span>")
2622- else:
2623- self.set_node_desc(self.selected_node.doc)
2624-
2625- self.update_node_attrs()
2626-
2627- if self.selected_node is None or not self.selected_node.active:
2628- self.set_node_data_entry()
2629- elif self.node_data_is_tensor() and self.geometry_dim_tree.data is not None:
2630- self.set_node_data_tensor()
2631- elif isinstance(self.selected_node.datatype, tuple):
2632- self.set_node_data_combo()
2633- else:
2634- self.set_node_data_entry()
2635-
2636- self.update_node_comment()
2637+
2638+ self.description.update(self.selected_node)
2639+
2640+ self.attributes.update(self.selected_node)
2641+
2642+ self.data.update(self.selected_node)
2643+
2644+ self.comment.update(self.selected_node)
2645
2646 self.gui.get_widget("optionsFrame").queue_resize()
2647
2648 return
2649
2650- def node_desc_mouse_over(self, widget, event):
2651- """
2652- Called when the mouse moves over the node description widget. Sets the cursor
2653- to a hand if the mouse hovers over a link.
2654-
2655- Based on code from HyperTextDemo class in hypertext.py from PyGTK 2.12 demos
2656- """
2657-
2658- if self.selected_node is None or self.selected_node.doc is None:
2659- return
2660-
2661- buffer_pos = self.node_desc.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(event.x), int(event.y))
2662- char_offset = self.node_desc.get_iter_at_location(buffer_pos[0], buffer_pos[1]).get_offset()
2663-
2664- for bounds in self.node_desc_link_bounds:
2665- if char_offset >= bounds[0] and char_offset <= bounds[1]:
2666- self.node_desc.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
2667- return
2668-
2669- self.node_desc.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
2670-
2671- return
2672-
2673- def node_desc_mouse_button_release(self, widget, event):
2674- """
2675- Called when a mouse button is released over the node description widget.
2676- Launches a browser if the mouse release was over a link, the left mouse button
2677- was released and no text was selected.
2678-
2679- Based on code from HyperTextDemo class in hypertext.py from PyGTK 2.12 demos
2680- """
2681-
2682- if self.selected_node is None or self.selected_node.doc is None or event.button != 1:
2683- return
2684-
2685- selection_bounds = self.node_desc.get_buffer().get_selection_bounds()
2686- if selection_bounds:
2687- return
2688-
2689- buffer_pos = self.node_desc.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(event.x), int(event.y))
2690- char_offset = self.node_desc.get_iter_at_location(buffer_pos[0], buffer_pos[1]).get_offset()
2691-
2692- for bounds in self.node_desc_link_bounds:
2693- if char_offset >= bounds[0] and char_offset <= bounds[1]:
2694- webbrowser.open(self.selected_node.doc[bounds[0]:bounds[1]])
2695- return
2696-
2697- return
2698-
2699- def set_node_desc(self, desc):
2700- """
2701- Set the node description.
2702- """
2703-
2704- self.node_desc_link_bounds = self.link_bounds(desc)
2705-
2706- desc = self.render_whitespace(desc)
2707-
2708- if self.node_desc_link_bounds:
2709- new_desc = ""
2710- for i in range(len(self.node_desc_link_bounds)):
2711- if i == 0:
2712- new_desc += desc[:self.node_desc_link_bounds[i][0]]
2713- else:
2714- new_desc += desc[self.node_desc_link_bounds[i - 1][1]:self.node_desc_link_bounds[i][0]]
2715- new_desc += "<span foreground=\"blue\" underline=\"single\">" + desc[self.node_desc_link_bounds[i][0]:self.node_desc_link_bounds[i][1]] + "</span>"
2716- new_desc += desc[self.node_desc_link_bounds[len(self.node_desc_link_bounds) - 1][1]:]
2717- if self.node_desc_link_bounds[len(self.node_desc_link_bounds) - 1][1] == len(desc):
2718- new_desc += " "
2719- desc = new_desc
2720-
2721- self.node_desc.get_buffer().set_text(desc)
2722-
2723- return
2724-
2725- def update_node_attrs(self):
2726- """
2727- Update the RHS attributes widget.
2728- """
2729-
2730- self.node_attrs.get_model().clear()
2731-
2732- if self.selected_node is None:
2733- self.node_attrs.get_column(2).set_property("visible", False)
2734- self.node_attrs.get_column(0).queue_resize()
2735- self.node_attrs.get_column(1).queue_resize()
2736- elif len(self.selected_node.attrs.keys()) == 0:
2737- self.gui.get_widget("attributeFrame").set_property("visible", False)
2738- else:
2739- self.gui.get_widget("attributeFrame").set_property("visible", True)
2740- for key in self.selected_node.attrs.keys():
2741- iter = self.node_attrs.get_model().append()
2742- self.node_attrs.get_model().set_value(iter, 0, key)
2743- cell_model = gtk.ListStore(gobject.TYPE_STRING)
2744- self.node_attrs.get_model().set_value(iter, 2, cell_model)
2745- if isinstance(self.selected_node.attrs[key][0], tuple):
2746- if self.selected_node.attrs[key][1] is None:
2747- if isinstance(self.selected_node.attrs[key][0][0], tuple):
2748- self.node_attrs.get_model().set_value(iter, 1, "Select " + self.printable_type(self.selected_node.attrs[key][0][1]) + "...")
2749- else:
2750- self.node_attrs.get_model().set_value(iter, 1, "Select...")
2751- else:
2752- self.node_attrs.get_model().set_value(iter, 1, self.selected_node.attrs[key][1])
2753- if isinstance(self.selected_node.attrs[key][0][0], tuple):
2754- opts = self.selected_node.attrs[key][0][0]
2755- else:
2756- opts = self.selected_node.attrs[key][0]
2757- for opt in opts:
2758- cell_iter = cell_model.append()
2759- cell_model.set_value(cell_iter, 0, opt)
2760- self.node_attrs.get_column(2).set_property("visible", True)
2761- elif self.selected_node.attrs[key][0] is None:
2762- self.node_attrs.get_model().set_value(iter, 1, "No data")
2763- elif self.selected_node.attrs[key][1] is None:
2764- self.node_attrs.get_model().set_value(iter, 1, self.printable_type(self.selected_node.attrs[key][0]))
2765- else:
2766- self.node_attrs.get_model().set_value(iter, 1, self.selected_node.attrs[key][1])
2767- self.node_attrs.get_column(0).queue_resize()
2768- self.node_attrs.get_column(1).queue_resize()
2769- self.node_attrs.get_column(2).queue_resize()
2770-
2771- return
2772-
2773- def node_attrs_mouse_over(self, widget, event):
2774- """
2775- Called when the mouse moves over the node attributes widget. Sets the
2776- appropriate attribute widget tooltip.
2777- """
2778-
2779- path_info = self.node_attrs.get_path_at_pos(int(event.x), int(event.y))
2780- if path_info is None:
2781- try:
2782- self.node_attrs.set_tooltip_text("")
2783- self.node_attrs.set_property("has-tooltip", False)
2784- except:
2785- pass
2786- return
2787-
2788- path = path_info[0]
2789- col = path_info[1]
2790- if col is not self.node_attrs.get_column(1):
2791- try:
2792- self.node_attrs.set_tooltip_text("")
2793- self.node_attrs.set_property("has-tooltip", False)
2794- except:
2795- pass
2796- return
2797-
2798- iter = self.node_attrs.get_model().get_iter(path)
2799- iter_key = self.node_attrs.get_model().get_value(iter, 0)
2800-
2801- return
2802-
2803- def node_attrs_key_data_func(self, col, cell_renderer, model, iter):
2804- """
2805- Attribute name data function. Sets the cell renderer text colours.
2806- """
2807-
2808- iter_key = model.get_value(iter, 0)
2809-
2810- if not self.selected_node.active or self.selected_node.attrs[iter_key][0] is None or self.selected_node.attrs[iter_key][0] == "fixed":
2811- cell_renderer.set_property("foreground", "grey")
2812- elif self.selected_node.attrs[iter_key][1] is None:
2813- cell_renderer.set_property("foreground", "blue")
2814- else:
2815- cell_renderer.set_property("foreground", "black")
2816-
2817- return
2818-
2819- def node_attrs_entry_data_func(self, col, cell_renderer, model, iter):
2820- """
2821- Attribute text data function. Hides the renderer if a combo box is required,
2822- and sets colours and editability otherwise.
2823- """
2824-
2825- iter_key = model.get_value(iter, 0)
2826-
2827- if not self.selected_node.active or self.selected_node.attrs[iter_key][0] is None or self.selected_node.attrs[iter_key][0] == "fixed":
2828- cell_renderer.set_property("editable", False)
2829- cell_renderer.set_property("foreground", "grey")
2830- cell_renderer.set_property("visible", True)
2831- elif not isinstance(self.selected_node.attrs[iter_key][0], tuple):
2832- cell_renderer.set_property("editable", True)
2833- cell_renderer.set_property("visible", True)
2834- if self.selected_node.attrs[iter_key][1] is None:
2835- cell_renderer.set_property("foreground", "blue")
2836- else:
2837- cell_renderer.set_property("foreground", "black")
2838- else:
2839- cell_renderer.set_property("editable", False)
2840- cell_renderer.set_property("visible", False)
2841-
2842- return
2843-
2844- def node_attrs_combo_data_func(self, col, cell_renderer, model, iter):
2845- """
2846- Attribute combo box data function. Hides the renderer if a combo box is not
2847- required, and sets the combo box options otherwise. Adds an entry if required.
2848- """
2849-
2850- iter_key = model.get_value(iter, 0)
2851-
2852- if self.selected_node.active and isinstance(self.selected_node.attrs[iter_key][0], tuple):
2853- cell_renderer.set_property("editable", True)
2854- cell_renderer.set_property("visible", True)
2855- if isinstance(self.selected_node.attrs[iter_key][0][0], tuple):
2856- cell_renderer.set_property("has-entry", True)
2857- else:
2858- cell_renderer.set_property("has-entry", False)
2859- if self.selected_node.attrs[iter_key][1] is None:
2860- cell_renderer.set_property("foreground", "blue")
2861- else:
2862- cell_renderer.set_property("foreground", "black")
2863- else:
2864- cell_renderer.set_property("visible", False)
2865- cell_renderer.set_property("editable", False)
2866- cell_renderer.set_property("model", model.get_value(iter, 2))
2867-
2868- return
2869-
2870- def node_attrs_icon_data_func(self, col, cell_renderer, model, iter):
2871- """
2872- Attribute icon data function. Used to add downward pointing arrows for combo
2873- attributes, for consistency with the LHS.
2874- """
2875-
2876- iter_key = model.get_value(iter, 0)
2877-
2878- if self.selected_node.active and isinstance(self.selected_node.attrs[iter_key][0], tuple):
2879- cell_renderer.set_property("stock-id", gtk.STOCK_GO_DOWN)
2880- else:
2881- cell_renderer.set_property("stock-id", None)
2882-
2883- return
2884-
2885- def node_attrs_entry_edit_start(self, cell_renderer, editable, path):
2886- """
2887- Called when editing is started on an attribute text cell. Used to delete the
2888- printable_type placeholder.
2889- """
2890-
2891- iter = self.node_attrs.get_model().get_iter(path)
2892- iter_key = self.node_attrs.get_model().get_value(iter, 0)
2893-
2894- if self.selected_node.attrs[iter_key][1] is None:
2895- editable.set_text("")
2896-
2897- return
2898-
2899- def node_attrs_combo_edit_start(self, cell_renderer, editable, path):
2900- """
2901- Called when editing is started on an attribute combo cell. Used to delete the
2902- select placeholder for mixed entry / combo attributes.
2903- """
2904-
2905- iter = self.node_attrs.get_model().get_iter(path)
2906- iter_key = self.node_attrs.get_model().get_value(iter, 0)
2907-
2908- if isinstance(self.selected_node.attrs[iter_key][0][0], tuple) and self.selected_node.attrs[iter_key][1] is None:
2909- editable.child.set_text("")
2910-
2911- return
2912-
2913- def node_attrs_edited(self, cell_renderer, path, new_text):
2914- """
2915- Called when editing is finished on an attribute text cell. Updates data in the
2916- treestore.
2917- """
2918-
2919- iter = self.node_attrs.get_model().get_iter(path)
2920- iter_key = self.node_attrs.get_model().get_value(iter, 0)
2921-
2922- if self.selected_node.get_attr(iter_key) is None and new_text == "":
2923- return
2924-
2925- value_check = self.validity_check(new_text, self.selected_node.attrs[iter_key][0])
2926-
2927- if not value_check is None and not value_check == self.selected_node.attrs[iter_key][1]:
2928- if iter_key == "name" and not self.name_check(value_check):
2929- return
2930-
2931- self.node_attrs.get_model().set_value(iter, 1, value_check)
2932- self.selected_node.set_attr(iter_key, value_check)
2933- self.paint_validity()
2934- if iter_key == "name":
2935- self.update_painted_name()
2936- self.on_select_row()
2937- self.set_saved(False)
2938-
2939- return
2940-
2941- def node_attrs_selected(self, cell_renderer, path, new_text):
2942- """
2943- Called when an attribute combo box element is selected, or combo box entry
2944- element entry is edited. Updates data in the treestore.
2945- """
2946-
2947- iter = self.node_attrs.get_model().get_iter(path)
2948- iter_key = self.node_attrs.get_model().get_value(iter, 0)
2949-
2950- if new_text is None:
2951- return
2952-
2953- if isinstance(self.selected_node.attrs[iter_key][0][0], tuple) and not new_text in self.selected_node.attrs[iter_key][0][0]:
2954- if self.selected_node.get_attr(iter_key) is None and new_text == "":
2955- return
2956-
2957- new_text = self.validity_check(new_text, self.selected_node.attrs[iter_key][0][1])
2958- if iter_key == "name" and not self.name_check(new_text):
2959- return False
2960- if not new_text == self.selected_node.attrs[iter_key][1]:
2961- self.node_attrs.get_model().set_value(iter, 1, new_text)
2962- self.selected_node.set_attr(iter_key, new_text)
2963- self.paint_validity()
2964- if iter_key == "name":
2965- self.update_painted_name()
2966- self.set_saved(False)
2967-
2968- return
2969-
2970- def set_node_data_empty(self):
2971- """
2972- Empty the node data frame.
2973- """
2974-
2975- if len(self.node_data_frame.get_children()) > 1:
2976- if isinstance(self.node_data, gtk.TextView):
2977- self.node_data.handler_block_by_func(self.node_data_entry_focus_in)
2978- elif isinstance(self.node_data, gtk.ComboBox):
2979- self.node_data.handler_block_by_func(self.node_data_combo_focus_child)
2980- self.node_data_frame.remove(self.node_data_frame.child)
2981-
2982- self.node_data_interacted = False
2983-
2984- return
2985-
2986- def set_node_data_entry(self):
2987- """
2988- Create a text view for data entry in the node data frame.
2989- """
2990-
2991- self.set_node_data_empty()
2992-
2993- data_scrolled_window = gtk.ScrolledWindow()
2994- self.node_data_frame.add(data_scrolled_window)
2995- data_scrolled_window.show()
2996-
2997- try:
2998- import gtksourceview2
2999- buf = gtksourceview2.Buffer()
3000- lang_manager = gtksourceview2.LanguageManager()
3001- buf.set_highlight_matching_brackets(True)
3002- if self.node_data_is_python_code():
3003- python = lang_manager.get_language("python")
3004- buf.set_language(python)
3005- buf.set_highlight_syntax(True)
3006- self.node_data = gtksourceview2.View(buffer=buf)
3007- self.node_data.set_auto_indent(True)
3008- #self.node_data.set_highlight_current_line(True)
3009- self.node_data.set_insert_spaces_instead_of_tabs(True)
3010- self.node_data.set_tab_width(2)
3011- if self.node_data_is_python_code():
3012- self.node_data.set_show_line_numbers(True)
3013- font_desc = pango.FontDescription("monospace")
3014- if font_desc:
3015- self.node_data.modify_font(font_desc)
3016- except ImportError:
3017- self.node_data = gtk.TextView()
3018-
3019- data_scrolled_window.add(self.node_data)
3020- self.node_data.show()
3021-
3022- data_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
3023-
3024- self.node_data.set_pixels_above_lines(2)
3025- self.node_data.set_pixels_below_lines(2)
3026- self.node_data.set_wrap_mode(gtk.WRAP_WORD)
3027-
3028- self.node_data.connect("focus-in-event", self.node_data_entry_focus_in)
3029-
3030- data_frame_packing = self.node_data_frame.get_property("parent").query_child_packing(self.node_data_frame)
3031- self.node_data_frame.get_property("parent").set_child_packing(self.node_data_frame, True, data_frame_packing[1], data_frame_packing[2], data_frame_packing[3])
3032-
3033- self.node_data.get_buffer().create_tag("node_data_buffer_tag")
3034- text_tag = self.node_data.get_buffer().get_tag_table().lookup("node_data_buffer_tag")
3035- if self.selected_node is None:
3036- self.node_data.set_cursor_visible(False)
3037- self.node_data.set_editable(False)
3038- self.node_data_buttons_hbox.hide()
3039- self.node_data.get_buffer().set_text("")
3040- text_tag.set_property("foreground", "grey")
3041- elif not self.selected_node.active:
3042- self.node_data.set_cursor_visible(False)
3043- self.node_data.set_editable(False)
3044- self.node_data_buttons_hbox.hide()
3045- self.node_data.get_buffer().set_text("Inactive node")
3046- text_tag.set_property("foreground", "grey")
3047- elif self.selected_node.datatype is None:
3048- self.node_data.set_cursor_visible(False)
3049- self.node_data.set_editable(False)
3050- self.node_data_buttons_hbox.hide()
3051- self.node_data.get_buffer().set_text("No data")
3052- text_tag.set_property("foreground", "grey")
3053- elif self.node_data_is_tensor():
3054- self.node_data.set_cursor_visible(False)
3055- self.node_data.set_editable(False)
3056- self.node_data_buttons_hbox.hide()
3057- self.node_data.get_buffer().set_text("Dimension not set")
3058- text_tag.set_property("foreground", "grey")
3059- elif self.selected_node.data is None:
3060- self.node_data.set_cursor_visible(True)
3061- self.node_data.set_editable(True)
3062- self.node_data_buttons_hbox.show()
3063- self.node_data.get_buffer().set_text(self.printable_type(self.selected_node.datatype))
3064- text_tag.set_property("foreground", "blue")
3065- else:
3066- self.node_data.get_buffer().set_text(self.selected_node.data)
3067- if self.selected_node.datatype == "fixed":
3068- self.node_data.set_cursor_visible(False)
3069- self.node_data.set_editable(False)
3070- self.node_data_buttons_hbox.hide()
3071- text_tag.set_property("foreground", "grey")
3072- else:
3073- self.node_data.set_cursor_visible(True)
3074- self.node_data.set_editable(True)
3075- self.node_data_buttons_hbox.show()
3076- #text_tag.set_property("foreground", "black")
3077- buffer_bounds = self.node_data.get_buffer().get_bounds()
3078- self.node_data.get_buffer().apply_tag(text_tag, buffer_bounds[0], buffer_bounds[1])
3079-
3080- return
3081-
3082- def set_node_data_tensor(self):
3083- """
3084- Create a table container packed with appropriate widgets for tensor data entry
3085- in the node data frame.
3086- """
3087-
3088- self.set_node_data_empty()
3089-
3090- data_scrolled_window = gtk.ScrolledWindow()
3091- self.node_data_frame.add(data_scrolled_window)
3092- data_scrolled_window.show()
3093-
3094- data_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
3095-
3096- dim1, dim2 = self.node_data_tensor_shape()
3097- self.node_data = gtk.Table(dim1, dim2)
3098- data_scrolled_window.add_with_viewport(self.node_data)
3099- self.node_data.show()
3100-
3101- data_scrolled_window.child.set_property("shadow-type", gtk.SHADOW_NONE)
3102-
3103- data_frame_packing = self.node_data_frame.get_property("parent").query_child_packing(self.node_data_frame)
3104- self.node_data_frame.get_property("parent").set_child_packing(self.node_data_frame, True, data_frame_packing[1], data_frame_packing[2], data_frame_packing[3])
3105-
3106- self.node_data_buttons_hbox.show()
3107-
3108- is_symmetric = self.node_data_is_symmetric_tensor()
3109- for i in range(dim1):
3110- for j in range(dim2):
3111- entry = gtk.Entry()
3112- self.node_data.attach(entry, dim2 - j - 1, dim2 - j, dim1 - i - 1, dim1 - i)
3113- if not is_symmetric or i >= j:
3114- entry.show()
3115-
3116- entry.connect("focus-in-event", self.node_data_tensor_element_focus_in, dim2 - j - 1, dim1 - i - 1)
3117-
3118- if self.selected_node.data is None:
3119- entry.set_text(self.printable_type(self.selected_node.datatype.datatype))
3120- entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("blue"))
3121- else:
3122- entry.set_text(self.selected_node.data.split(" ")[(dim2 - j - 1) + (dim1 - i - 1) * dim2])
3123-
3124- self.node_data_interacted = [False for i in range(dim1 * dim2)]
3125-
3126- return
3127-
3128- def set_node_data_combo(self):
3129- """
3130- Create a combo box for node data selection in the node data frame. Add an
3131- entry if required.
3132- """
3133-
3134- self.set_node_data_empty()
3135-
3136- if isinstance(self.selected_node.datatype[0], tuple):
3137- self.node_data = gtk.combo_box_entry_new_text()
3138- else:
3139- self.node_data = gtk.combo_box_new_text()
3140- self.node_data_frame.add(self.node_data)
3141- self.node_data.show()
3142-
3143- self.node_data.connect("set-focus-child", self.node_data_combo_focus_child)
3144- self.node_data.connect("scroll-event", self.node_data_combo_scroll)
3145-
3146- data_frame_packing = self.node_data_frame.get_property("parent").query_child_packing(self.node_data_frame)
3147- self.node_data_frame.get_property("parent").set_child_packing(self.node_data_frame, False, data_frame_packing[1], data_frame_packing[2], data_frame_packing[3])
3148-
3149- if isinstance(self.selected_node.datatype[0], tuple):
3150- self.node_data_buttons_hbox.show()
3151- else:
3152- self.node_data_buttons_hbox.hide()
3153-
3154- if self.selected_node.data is None:
3155- if isinstance(self.selected_node.datatype[0], tuple):
3156- self.node_data.child.set_text("Select " + self.printable_type(self.selected_node.datatype[1]) + "...")
3157- else:
3158- self.node_data.append_text("Select...")
3159- self.node_data.set_active(0)
3160- self.node_data.child.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("blue"))
3161- self.node_data.child.modify_text(gtk.STATE_PRELIGHT, gtk.gdk.color_parse("blue"))
3162-
3163- if isinstance(self.selected_node.datatype[0], tuple):
3164- options = self.selected_node.datatype[0]
3165- else:
3166- options = self.selected_node.datatype
3167- for i in range(len(options)):
3168- opt = options[i]
3169- self.node_data.append_text(opt)
3170- if self.selected_node.data == opt:
3171- self.node_data.set_active(i)
3172-
3173- if isinstance(self.selected_node.datatype[0], tuple) and not self.selected_node.data is None and not self.selected_node.data in self.selected_node.datatype[0]:
3174- self.node_data.child.set_text(self.selected_node.data)
3175-
3176- self.node_data.connect("changed", self.node_data_combo_changed)
3177-
3178- return
3179-
3180- def node_data_entry_focus_in(self, widget, event):
3181- """
3182- Called when a text view data entry widget gains focus. Used to delete the
3183- printable_type placeholder.
3184- """
3185-
3186- if not self.selected_node is None and not self.selected_node.datatype is None and not self.node_data_is_tensor() and self.selected_node.data is None and not self.node_data_interacted:
3187- self.node_data.get_buffer().set_text("")
3188-
3189- self.node_data_interacted = True
3190-
3191- return
3192-
3193- def node_data_tensor_element_focus_in(self, widget, event, row, col):
3194- """
3195- Called when a tensor data entry widget gains focus. Used to delete the
3196- printable_type placeholder.
3197- """
3198-
3199- dim1, dim2 = self.node_data_tensor_shape()
3200- if not self.node_data_interacted[col + row * dim2]:
3201- self.node_data_interacted[col + row * dim2] = True
3202- if self.node_data_is_symmetric_tensor():
3203- self.node_data_interacted[row + col * dim1] = True
3204- if self.selected_node.data is None:
3205- widget.set_text("")
3206- widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
3207-
3208- return
3209-
3210- def node_data_combo_focus_child(self, container, widget):
3211- """
3212- Called when a data selection widget gains focus. Used to delete the select
3213- placeholder.
3214- """
3215-
3216- if not self.node_data_interacted:
3217- self.node_data_interacted = True
3218- if self.selected_node.data is None:
3219- self.node_data_interacted = True
3220- if isinstance(self.selected_node.datatype[0], tuple):
3221- self.node_data.handler_block_by_func(self.node_data_combo_changed)
3222- self.node_data.child.set_text("")
3223- self.node_data.handler_unblock_by_func(self.node_data_combo_changed)
3224- else:
3225- self.node_data.set_active(1)
3226- self.node_data.remove_text(0)
3227- self.node_data.child.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
3228- self.node_data.child.modify_text(gtk.STATE_PRELIGHT, gtk.gdk.color_parse("black"))
3229-
3230- return
3231-
3232- def node_data_combo_changed(self, combo_box):
3233- """
3234- Called when a data combo box element is selected. Updates data in the
3235- treestore.
3236- """
3237-
3238- if not isinstance(self.selected_node.datatype[0], tuple) or not self.node_data.child.get_property("has-focus"):
3239- self.selected_node.set_data(self.node_data.get_active_text())
3240- self.paint_validity()
3241- self.set_saved(False)
3242- self.node_data_interacted = False
3243- iter = self.get_treeview_iter(self.treeview.get_selection())
3244- self.treestore.set_value(iter, 4, self.node_data.get_active_text())
3245-
3246- return
3247-
3248- def node_data_combo_scroll(self, widget, event):
3249- """
3250- Called when the data combo box is scrolled with the mouse wheel. Removes the
3251- select placeholder and updates data in the treestore.
3252- """
3253-
3254- self.node_data_combo_focus_child(self.node_data_frame, self.node_data)
3255- if not isinstance(self.selected_node.datatype[0], tuple) or not self.selected_node.data is None:
3256- self.node_data_combo_changed(self.node_data)
3257-
3258- return
3259-
3260- def node_data_revert(self, button = None):
3261- """
3262- "Revert Data" button click signal handler. Reverts data in the node data frame.
3263- """
3264-
3265- if self.node_data_is_tensor() and not self.geometry_dim_tree.data is None:
3266- self.set_node_data_tensor()
3267- elif not self.selected_node is None and isinstance(self.selected_node.datatype, tuple):
3268- self.set_node_data_combo()
3269- else:
3270- self.set_node_data_entry()
3271-
3272- return
3273-
3274- def node_data_store(self, button = None):
3275- """
3276- "Store Data" button click signal handler. Stores data from the node data frame
3277- in the treestore.
3278- """
3279-
3280- if self.node_data_is_tensor() and not self.geometry_dim_tree.data is None:
3281- store_success = self.node_data_tensor_store()
3282- elif not self.selected_node is None and isinstance(self.selected_node.datatype, tuple):
3283- store_success = self.node_data_combo_store()
3284- else:
3285- store_success = self.node_data_entry_store()
3286-
3287- if self.scherror.errlist_is_open():
3288- if self.scherror.errlist_type == 0:
3289- self.scherror.on_validate_schematron()
3290- else:
3291- self.scherror.on_validate()
3292-
3293- return store_success
3294-
3295- def node_data_entry_store(self):
3296- """
3297- Attempt to store data read from a textview packed in the node data frame.
3298- """
3299-
3300- if self.selected_node is None or self.selected_node.datatype in ["fixed", None] or self.node_data_is_tensor():
3301- return True
3302-
3303- data_buffer_bounds = self.node_data.get_buffer().get_bounds()
3304- new_data = self.node_data.get_buffer().get_text(data_buffer_bounds[0], data_buffer_bounds[1])
3305-
3306- if new_data == "":
3307- return True
3308-
3309- if self.selected_node.data is None and not self.node_data_interacted:
3310- return True
3311- else:
3312- value_check = self.validity_check(new_data, self.selected_node.datatype)
3313- if value_check is None:
3314- return False
3315- elif not value_check == self.selected_node.data:
3316- self.selected_node.set_data(value_check)
3317- if isinstance(self.selected_node, MixedTree) and "shape" in self.selected_node.child.attrs.keys() and self.selected_node.child.attrs["shape"][0] is int and isinstance(self.selected_node.datatype, plist.List) and self.selected_node.datatype.cardinality == "+":
3318- self.selected_node.child.set_attr("shape", str(len(value_check.split(" "))))
3319- self.paint_validity()
3320-
3321- iter = self.selected_iter
3322- self.treestore.set_value(iter, 4, new_data)
3323- self.set_saved(False)
3324- self.node_data_interacted = False
3325-
3326- return True
3327-
3328- def node_data_combo_store(self):
3329- """
3330- Attempt to store data read from a combo box entry packed in the node data
3331- frame.
3332- """
3333-
3334- if not isinstance(self.selected_node.datatype[0], tuple):
3335- return True
3336-
3337- new_data = self.node_data.child.get_text()
3338-
3339- if self.selected_node.data is None and not self.node_data_interacted:
3340- return True
3341- elif not new_data in self.selected_node.datatype[0]:
3342- new_data = self.validity_check(new_data, self.selected_node.datatype[1])
3343- if new_data is None:
3344- return False
3345-
3346- if not new_data == self.selected_node.data:
3347- self.selected_node.set_data(new_data)
3348- self.paint_validity()
3349- self.set_saved(False)
3350- self.node_data_interacted = False
3351-
3352- return True
3353-
3354- def node_data_tensor_store(self):
3355- """
3356- Attempt to store data read from tensor data entry widgets packed in the node
3357- data frame.
3358- """
3359-
3360- dim1, dim2 = self.node_data_tensor_shape()
3361- is_symmetric = self.node_data_is_symmetric_tensor()
3362-
3363- if not True in self.node_data_interacted:
3364- return True
3365-
3366- entry_values = []
3367- for i in range(dim1):
3368- for j in range(dim2):
3369- if is_symmetric and i > j:
3370- entry_values.append(self.node_data.get_children()[i + j * dim1].get_text())
3371- else:
3372- entry_values.append(self.node_data.get_children()[j + i * dim2].get_text())
3373-
3374- changed = False
3375- for i in range(dim1):
3376- for j in range(dim2):
3377- if self.node_data_interacted[j + i * dim2] and not entry_values[j + i * dim2] == "" and (self.selected_node.data is None or not self.selected_node.data.split(" ")[j + i * dim2] == entry_values[j + i * dim2]):
3378- changed = True
3379- if not changed:
3380- return True
3381- elif (self.selected_node.data is None and False in self.node_data_interacted) or "" in entry_values:
3382- dialogs.error(self.main_window, "Invalid value entered")
3383- return False
3384-
3385- new_data = ""
3386- for i in range(dim1):
3387- for j in range(dim2):
3388- new_data += " " + entry_values[j + i * dim2]
3389-
3390- value_check = self.validity_check(new_data, self.selected_node.datatype)
3391- if value_check is None:
3392- return False
3393- elif not value_check == self.selected_node.data:
3394- self.selected_node.set_data(value_check)
3395-
3396- dim1, dim2 = self.node_data_tensor_shape()
3397- if int(self.selected_node.child.attrs["rank"][1]) == 1:
3398- self.selected_node.child.set_attr("shape", str(dim1))
3399- else:
3400- self.selected_node.child.set_attr("shape", str(dim1) + " " + str(dim2))
3401-
3402- self.paint_validity()
3403- self.set_saved(False)
3404- self.node_data_interacted = [False for i in range(dim1 * dim2)]
3405-
3406- return True
3407-
3408- def update_node_comment(self):
3409- """
3410- Update the comment widget.
3411- """
3412-
3413- if self.selected_node is None or not self.selected_node.active:
3414- self.node_comment.get_buffer().set_text("")
3415- self.node_comment.set_cursor_visible(False)
3416- self.node_comment.set_editable(False)
3417- try:
3418- self.node_comment.set_tooltip_text("")
3419- self.node_comment.set_property("has-tooltip", False)
3420- except:
3421- pass
3422-
3423- return
3424-
3425- comment_tree = self.get_comment(self.selected_node)
3426- text_tag = self.node_comment.get_buffer().get_tag_table().lookup("comment_buffer_tag")
3427- if comment_tree is None:
3428- self.node_comment.get_buffer().set_text("No comment")
3429- self.node_comment.set_cursor_visible(False)
3430- self.node_comment.set_editable(False)
3431- text_tag.set_property("foreground", "grey")
3432- try:
3433- self.node_comment.set_tooltip_text("")
3434- self.node_comment.set_property("has-tooltip", False)
3435- except:
3436- pass
3437- else:
3438- if comment_tree.data is None:
3439- self.node_comment.get_buffer().set_text("(string)")
3440- else:
3441- self.node_comment.get_buffer().set_text(comment_tree.data)
3442- if self.selected_node.active:
3443- self.node_comment.set_cursor_visible(True)
3444- self.node_comment.set_editable(True)
3445- text_tag.set_property("foreground", "black")
3446- else:
3447- self.node_comment.set_cursor_visible(False)
3448- self.node_comment.set_editable(False)
3449- text_tag.set_property("foreground", "grey")
3450-
3451- buffer_bounds = self.node_comment.get_buffer().get_bounds()
3452- self.node_comment.get_buffer().apply_tag(text_tag, buffer_bounds[0], buffer_bounds[1])
3453-
3454- self.node_comment_interacted = False
3455-
3456- return
3457-
3458- def node_comment_focus_in(self, widget, event):
3459- """
3460- Called when the comment widget gains focus. Removes the printable_type
3461- placeholder.
3462- """
3463-
3464- comment_tree = self.get_comment(self.selected_node)
3465- if not comment_tree is None and not self.node_comment_interacted:
3466- self.node_comment_interacted = True
3467- if comment_tree.data is None:
3468- self.node_comment.get_buffer().set_text("")
3469-
3470- return
3471-
3472- def node_comment_expose(self, widget, event):
3473- """
3474- Called when the comment widget is repainted. Stores the comment if required.
3475- """
3476-
3477- self.node_comment_store()
3478-
3479- return
3480-
3481- def node_comment_store(self):
3482- """
3483- Store data in the node comment.
3484- """
3485-
3486- comment_tree = self.get_comment(self.selected_node)
3487- if comment_tree is None or not self.node_comment_interacted:
3488- return
3489-
3490- data_buffer_bounds = self.node_comment.get_buffer().get_bounds()
3491- new_comment = self.node_comment.get_buffer().get_text(data_buffer_bounds[0], data_buffer_bounds[1])
3492-
3493- if not new_comment == comment_tree.data:
3494- if new_comment == "":
3495- comment_tree.data = None
3496- comment_tree.active = False
3497- else:
3498- comment_tree.set_data(new_comment)
3499- comment_tree.active = True
3500- self.set_saved(False)
3501-
3502- return
3503-
3504- def validity_check(self, val, val_type):
3505- """
3506- Check to see if the supplied data with supplied type can be stored in a
3507- tree.Tree.
3508- """
3509-
3510- (invalid, data) = self.selected_node.valid_data(val_type, val)
3511- if not invalid and isinstance(data, str) and not data == "":
3512- if not data == val and self.validity_check(data, val_type) is None:
3513- return None
3514- else:
3515- return data
3516- else:
3517- dialogs.error(self.main_window, "Invalid value entered")
3518- return None
3519-
3520- def name_check(self, val):
3521- """
3522- Check to see if the supplied data is a valid tree name.
3523- """
3524-
3525- valid_chars = "_:[]1234567890qwertyuioplkjhgfdsazxcvbnmMNBVCXZASDFGHJKLPOIUYTREWQ"
3526- for char in val:
3527- if not char in valid_chars:
3528- dialogs.error(self.main_window, "Invalid value entered")
3529- return False
3530-
3531- return True
3532-
3533- def node_data_is_python_code(self):
3534- """
3535- Perform a series of tests on the current tree.Tree / MixedTree, to determine if
3536- it is intended to be used to store python code data.
3537- """
3538-
3539- try:
3540- lang = self.selected_node.get_attr("language")
3541- if lang == "python":
3542- return True
3543- except:
3544- pass
3545-
3546- if not isinstance(self.selected_node, MixedTree):
3547- return False
3548-
3549- if self.selected_node.datatype is not str:
3550- return False
3551-
3552- if "type" in self.selected_node.child.attrs.keys():
3553- return self.selected_node.child.attrs["type"][1] == "python"
3554- else:
3555- return False
3556-
3557- def node_data_is_tensor(self):
3558- """
3559- Perform a series of tests on the current tree.Tree / MixedTree, to determine if
3560- it is intended to be used to store tensor or vector data.
3561- """
3562-
3563- # Check that a geometry is defined
3564- if self.geometry_dim_tree is None:
3565- return False
3566-
3567- # Check that this element has calculable and positive dimensions
3568- if isinstance(self.geometry_dim_tree.datatype, tuple):
3569- possible_dims = self.geometry_dim_tree.datatype
3570- else:
3571- possible_dims = [self.geometry_dim_tree.data]
3572- for opt in possible_dims:
3573- try:
3574- dim1, dim2 = self.node_data_tensor_shape(int(opt))
3575- assert dim1 > 0
3576- assert dim2 > 0
3577- except:
3578- return False
3579-
3580- # All tensor elements must be of MixedTree type
3581- if not isinstance(self.selected_node, MixedTree):
3582- return False
3583-
3584- # The element must have dim1, rank and shape attributes
3585- if not "dim1" in self.selected_node.child.attrs.keys() or not "rank" in self.selected_node.child.attrs.keys() or not "shape" in self.selected_node.child.attrs.keys():
3586- return False
3587- # The dim1 and rank attributes must be of fixed type
3588- if not self.selected_node.child.attrs["dim1"][0] == "fixed" or not self.selected_node.child.attrs["rank"][0] == "fixed":
3589- return False
3590-
3591- if "dim2" in self.selected_node.child.attrs.keys():
3592- # If a dim2 attribute is specified, it must be of fixed type and the rank must be 2
3593- # Also, the shape attribute must be a list of integers with cardinality equal to the rank
3594- if not self.selected_node.child.attrs["dim2"][0] == "fixed" or not self.selected_node.child.attrs["rank"][1] == "2" or not isinstance(self.selected_node.child.attrs["shape"][0], plist.List) or not self.selected_node.child.attrs["shape"][0].datatype is int or not str(self.selected_node.child.attrs["shape"][0].cardinality) == self.selected_node.child.attrs["rank"][1]:
3595- return False
3596- # Otherwise, the rank must be one and the shape an integer
3597- elif not self.selected_node.child.attrs["rank"][1] == "1" or not self.selected_node.child.attrs["shape"][0] is int:
3598- return False
3599-
3600- # The data for the element must be a list of one or more
3601- if not isinstance(self.selected_node.datatype, plist.List) or not self.selected_node.datatype.cardinality == "+":
3602- return False
3603-
3604- # If the shape has been set, check that it has a valid value
3605- if not self.selected_node.child.attrs["shape"][1] == None:
3606- if self.geometry_dim_tree.data is None:
3607- return False
3608-
3609- dim1, dim2 = self.node_data_tensor_shape()
3610- if "dim2" in self.selected_node.child.attrs.keys():
3611- if not self.selected_node.child.attrs["shape"][1] == str(dim1) + " " + str(dim2):
3612- return False
3613- elif not self.selected_node.child.attrs["shape"][1] == str(dim1):
3614- return False
3615-
3616- return True
3617-
3618- def node_data_tensor_shape(self, geometry_dim = None):
3619- """
3620- Read the tensor shape for tensor or vector data in the current MixedTree.
3621- """
3622-
3623- if geometry_dim == None:
3624- geometry_dim = int(self.geometry_dim_tree.data)
3625-
3626- dim1 = 1
3627- dim2 = 1
3628- if "dim1" in self.selected_node.child.attrs.keys():
3629- dim1 = int(eval(self.selected_node.child.attrs["dim1"][1], {"dim":geometry_dim}))
3630- if "dim2" in self.selected_node.child.attrs.keys():
3631- dim2 = int(eval(self.selected_node.child.attrs["dim2"][1], {"dim":geometry_dim}))
3632-
3633- return (dim1, dim2)
3634-
3635- def node_data_is_symmetric_tensor(self):
3636- """
3637- Read if the tensor data in the current MixedTree is symmetric.
3638- """
3639-
3640- dim1, dim2 = self.node_data_tensor_shape()
3641- if not dim1 == dim2:
3642- return False
3643-
3644- if "symmetric" in self.selected_node.child.attrs.keys() and self.selected_node.child.attrs["symmetric"][1] == "true":
3645- return True
3646- else:
3647- return False
3648-
3649-class MixedTree:
3650- def __init__(self, parent, child):
3651- """
3652- The .doc and .attrs comes from parent, and the .data comes from child. This
3653- is used to hide integer_value etc. from the left hand side, but for its data
3654- entry to show up on the right.
3655- """
3656-
3657- self.parent = parent
3658- self.child = child
3659-
3660- self.name = parent.name
3661- self.schemaname = parent.schemaname
3662- self.attrs = self.parent.attrs
3663- self.children = parent.children
3664- self.datatype = child.datatype
3665- self.data = child.data
3666- self.doc = parent.doc
3667- self.active = parent.active
3668-
3669- return
3670-
3671- def set_attr(self, attr, val):
3672- self.parent.set_attr(attr, val)
3673-
3674- return
3675-
3676- def get_attr(self, attr):
3677- return self.parent.get_attr(attr)
3678-
3679- def set_data(self, data):
3680- self.child.set_data(data)
3681- self.datatype = self.child.datatype
3682- self.data = self.child.data
3683-
3684- return
3685-
3686- def valid_data(self, datatype, data):
3687- return self.parent.valid_data(datatype, data)
3688-
3689- def matches(self, text, case_sensitive = False):
3690- old_parent_data = self.parent.data
3691- self.parent.data = None
3692- parent_matches = self.parent.matches(text, case_sensitive)
3693- self.parent.data = old_parent_data
3694-
3695- if parent_matches:
3696- return True
3697-
3698- if case_sensitive:
3699- text_re = re.compile(text)
3700- else:
3701- text_re = re.compile(text, re.IGNORECASE)
3702-
3703- if not self.child.data is None and not text_re.search(self.child.data) is None:
3704- return True
3705- else:
3706- return False
3707-
3708 class DiamondFindDialog:
3709 def __init__(self, parent, gladefile):
3710 self.parent = parent
3711
3712=== added file 'diamond/diamond/mixedtree.py'
3713--- diamond/diamond/mixedtree.py 1970-01-01 00:00:00 +0000
3714+++ diamond/diamond/mixedtree.py 2011-07-25 13:56:22 +0000
3715@@ -0,0 +1,204 @@
3716+#!/usr/bin/env python
3717+
3718+# This file is part of Diamond.
3719+#
3720+# Diamond is free software: you can redistribute it and/or modify
3721+# it under the terms of the GNU General Public License as published by
3722+# the Free Software Foundation, either version 3 of the License, or
3723+# (at your option) any later version.
3724+#
3725+# Diamond is distributed in the hope that it will be useful,
3726+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3727+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3728+# GNU General Public License for more details.
3729+#
3730+# You should have received a copy of the GNU General Public License
3731+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
3732+
3733+import plist
3734+
3735+class MixedTree:
3736+ def __init__(self, parent, child):
3737+ """
3738+ The .doc and .attrs comes from parent, and the .data comes from child. This
3739+ is used to hide integer_value etc. from the left hand side, but for its data
3740+ entry to show up on the right.
3741+ """
3742+
3743+ self.parent = parent
3744+ self.child = child
3745+
3746+ self.name = parent.name
3747+ self.schemaname = parent.schemaname
3748+ self.attrs = self.parent.attrs
3749+ self.children = parent.children
3750+ self.datatype = child.datatype
3751+ self.data = child.data
3752+ self.doc = parent.doc
3753+ self.active = parent.active
3754+
3755+ return
3756+
3757+ def set_attr(self, attr, val):
3758+ self.parent.set_attr(attr, val)
3759+
3760+ return
3761+
3762+ def get_attr(self, attr):
3763+ return self.parent.get_attr(attr)
3764+
3765+ def set_data(self, data):
3766+ self.child.set_data(data)
3767+ self.datatype = self.child.datatype
3768+ self.data = self.child.data
3769+
3770+ return
3771+
3772+ def valid_data(self, datatype, data):
3773+ return self.parent.valid_data(datatype, data)
3774+
3775+ def validity_check(self, datatype, data):
3776+ return self.parent.validity_check(datatype, data)
3777+
3778+ def matches(self, text, case_sensitive = False):
3779+ old_parent_data = self.parent.data
3780+ self.parent.data = None
3781+ parent_matches = self.parent.matches(text, case_sensitive)
3782+ self.parent.data = old_parent_data
3783+
3784+ if parent_matches:
3785+ return True
3786+
3787+ if case_sensitive:
3788+ text_re = re.compile(text)
3789+ else:
3790+ text_re = re.compile(text, re.IGNORECASE)
3791+
3792+ if not self.child.data is None and not text_re.search(self.child.data) is None:
3793+ return True
3794+ else:
3795+ return False
3796+
3797+ def is_comment(self):
3798+ return self.parent.is_comment()
3799+
3800+ def get_comment(self):
3801+ return self.parent.get_comment()
3802+
3803+ def is_tensor(self, geometry_dim_tree):
3804+ """
3805+ Perform a series of tests on the current MixedTree, to determine if
3806+ it is intended to be used to store tensor or vector data.
3807+ """
3808+
3809+ # Check that a geometry is defined
3810+ if geometry_dim_tree is None:
3811+ return False
3812+
3813+ # Check that this element has calculable and positive dimensions
3814+ if isinstance(geometry_dim_tree.datatype, tuple):
3815+ possible_dims = geometry_dim_tree.datatype
3816+ else:
3817+ possible_dims = [geometry_dim_tree.data]
3818+ for opt in possible_dims:
3819+ try:
3820+ dim1, dim2 = self.tensor_shape(int(opt))
3821+ assert dim1 > 0
3822+ assert dim2 > 0
3823+ except:
3824+ return False
3825+
3826+ # The element must have dim1, rank and shape attributes
3827+ if "dim1" not in self.child.attrs.keys() \
3828+ or "rank" not in self.child.attrs.keys() \
3829+ or "shape" not in self.child.attrs.keys():
3830+ return False
3831+
3832+ # The dim1 and rank attributes must be of fixed type
3833+ if self.child.attrs["dim1"][0] != "fixed" or self.child.attrs["rank"][0] != "fixed":
3834+ return False
3835+
3836+ if "dim2" in self.child.attrs.keys():
3837+ # If a dim2 attribute is specified, it must be of fixed type and the rank must be 2
3838+ # Also, the shape attribute must be a list of integers with cardinality equal to the rank
3839+ if self.child.attrs["dim2"][0] != "fixed" \
3840+ or self.child.attrs["rank"][1] != "2" \
3841+ or not isinstance(self.child.attrs["shape"][0], plist.List) \
3842+ or self.child.attrs["shape"][0].datatype is not int \
3843+ or str(self.child.attrs["shape"][0].cardinality) != self.child.attrs["rank"][1]:
3844+ return False
3845+
3846+ # Otherwise, the rank must be one and the shape an integer
3847+ elif self.child.attrs["rank"][1] != "1" or self.child.attrs["shape"][0] is not int:
3848+ return False
3849+
3850+ # The data for the element must be a list of one or more
3851+ if not isinstance(self.datatype, plist.List) or self.datatype.cardinality != "+":
3852+ return False
3853+
3854+ # If the shape has been set, check that it has a valid value
3855+ if self.child.attrs["shape"][1] != None:
3856+ if geometry_dim_tree.data is None:
3857+ return False
3858+
3859+ dim1, dim2 = self.tensor_shape()
3860+ if "dim2" in self.child.attrs.keys():
3861+ if self.child.attrs["shape"][1] != str(dim1) + " " + str(dim2):
3862+ return False
3863+ elif self.child.attrs["shape"][1] != str(dim1):
3864+ return False
3865+
3866+ return True
3867+
3868+ def tensor_shape(self, dimension):
3869+ """
3870+ Read the tensor shape for tensor or vector data in the current MixedTree.
3871+ """
3872+
3873+ if not isinstance(dimension, int):
3874+ dimension = int(dimension.data)
3875+
3876+ dim1 = 1
3877+ dim2 = 1
3878+ if "dim1" in self.child.attrs.keys():
3879+ dim1 = int(eval(self.child.attrs["dim1"][1], {"dim":dimension}))
3880+ if "dim2" in self.child.attrs.keys():
3881+ dim2 = int(eval(self.child.attrs["dim2"][1], {"dim":dimension}))
3882+
3883+ return (dim1, dim2)
3884+
3885+ def is_symmetric_tensor(self, geometry):
3886+ """
3887+ Read if the tensor data in the current MixedTree is symmetric.
3888+ """
3889+
3890+ dim1, dim2 = self.tensor_shape(geometry)
3891+
3892+ return dim1 == dim2 and "symmetric" in self.child.attrs.keys() and self.child.attrs["symmetric"][1] == "true"
3893+
3894+ def is_python_code(self):
3895+ """
3896+ Perform a series of tests on the current MixedTree, to determine if
3897+ it is intended to be used to store python code data.
3898+ """
3899+
3900+ try:
3901+ lang = self.get_attr("language")
3902+ if lang == "python":
3903+ return True
3904+ except:
3905+ pass
3906+
3907+ if self.datatype is not str:
3908+ return False
3909+
3910+ if "type" in self.child.attrs.keys():
3911+ return self.child.attrs["type"][1] == "python"
3912+
3913+ return False
3914+
3915+ def get_name_path(self, leaf = True):
3916+ return self.parent.get_name_path(leaf)
3917+
3918+ def is_sliceable(self):
3919+ return True
3920
3921=== modified file 'diamond/diamond/schema.py'
3922--- diamond/diamond/schema.py 2011-07-08 17:15:57 +0000
3923+++ diamond/diamond/schema.py 2011-07-25 13:56:22 +0000
3924@@ -160,7 +160,7 @@
3925 node = xpath[0]
3926
3927 node = self.to_tree(node)
3928-
3929+
3930 if eidtree is not None:
3931 if eidtree.parent is not None:
3932 eidtree.parent.children.append(node)
3933
3934=== added file 'diamond/diamond/sliceview.py'
3935--- diamond/diamond/sliceview.py 1970-01-01 00:00:00 +0000
3936+++ diamond/diamond/sliceview.py 2011-07-25 13:56:22 +0000
3937@@ -0,0 +1,117 @@
3938+#!/usr/bin/env python
3939+
3940+# This file is part of Diamond.
3941+#
3942+# Diamond is free software: you can redistribute it and/or modify
3943+# it under the terms of the GNU General Public License as published by
3944+# the Free Software Foundation, either version 3 of the License, or
3945+# (at your option) any later version.
3946+#
3947+# Diamond is distributed in the hope that it will be useful,
3948+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3949+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3950+# GNU General Public License for more details.
3951+#
3952+# You should have received a copy of the GNU General Public License
3953+# along with Diamond. If not, see <http://www.gnu.org/licenses/>.
3954+
3955+import gobject
3956+import gtk
3957+
3958+import attributewidget
3959+import databuttonswidget
3960+import datawidget
3961+import mixedtree
3962+
3963+class SliceView(gtk.Window):
3964+
3965+ __gsignals__ = { "on-store" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
3966+ "update-name" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())}
3967+
3968+ def __init__(self, parent):
3969+ gtk.Window.__init__(self)
3970+
3971+ self.set_default_size(800, 600)
3972+ self.set_title("Slice View")
3973+ self.set_modal(True)
3974+ self.set_transient_for(parent)
3975+
3976+ mainvbox = gtk.VBox()
3977+ self.vbox = gtk.VBox()
3978+
3979+ scrolledWindow = gtk.ScrolledWindow()
3980+ scrolledWindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
3981+ scrolledWindow.add_with_viewport(self.vbox)
3982+
3983+ self.databuttons = databuttonswidget.DataButtonsWidget()
3984+
3985+ self.statusbar = gtk.Statusbar()
3986+
3987+ mainvbox.pack_start(scrolledWindow, True)
3988+ mainvbox.pack_start(self.databuttons, False)
3989+ mainvbox.pack_start(self.statusbar, False)
3990+
3991+ self.add(mainvbox)
3992+ self.show_all()
3993+
3994+ def update(self, node, tree):
3995+ nodes = self.get_nodes(node, tree)
3996+ if not nodes:
3997+ self.destroy()
3998+
3999+ for n in nodes:
4000+ self.vbox.pack_start(self.control(n))
4001+
4002+ maxwidth = 0
4003+ for child in self.vbox.get_children():
4004+ width, height = child.label.get_size_request()
4005+ maxwidth = max(maxwidth, width)
4006+
4007+ for child in self.vbox.get_children():
4008+ child.label.set_size_request(maxwidth, -1)
4009+
4010+ self.check_resize()
4011+
4012+ def get_nodes(self, node, tree):
4013+ nodes = []
4014+
4015+ for child in tree.get_children():
4016+ if child.active:
4017+ if child.name == node.name and child.is_sliceable():
4018+ nodes.append(child.get_mixed_data())
4019+ nodes += self.get_nodes(node, child)
4020+
4021+ return nodes
4022+
4023+ def control(self, node):
4024+ hbox = gtk.HBox()
4025+
4026+ label = gtk.Label(node.get_name_path())
4027+ hbox.label = label
4028+
4029+ data = datawidget.DataWidget()
4030+ data.geometry_dim_tree = self.geometry_dim_tree
4031+ data.connect("on-store", self.on_store)
4032+ data.set_buttons(self.databuttons)
4033+ data.update(node)
4034+
4035+ attributes = attributewidget.AttributeWidget()
4036+ attributes.connect("on-store", self.on_store)
4037+ attributes.connect("update-name", self.update_name)
4038+ attributes.update(node)
4039+
4040+ hbox.pack_start(label)
4041+ hbox.pack_start(data)
4042+ hbox.pack_start(attributes)
4043+
4044+ hbox.show_all()
4045+
4046+ return hbox
4047+
4048+ def on_store(self, widget = None):
4049+ self.emit("on-store")
4050+
4051+ def update_name(self, widget = None):
4052+ self.emit("update-name")
4053+
4054+gobject.type_register(SliceView)
4055
4056=== modified file 'diamond/diamond/tree.py'
4057--- diamond/diamond/tree.py 2011-07-07 12:38:32 +0000
4058+++ diamond/diamond/tree.py 2011-07-25 13:56:22 +0000
4059@@ -1,4 +1,4 @@
4060-#!/usr/bin/env python
4061+#/usr/bin/env python
4062
4063 # This file is part of Diamond.
4064 #
4065@@ -25,18 +25,26 @@
4066 from lxml import etree
4067 import sys
4068
4069+import gobject
4070+
4071 import debug
4072 import choice
4073+import mixedtree
4074
4075-class Tree:
4076+class Tree(gobject.GObject):
4077 """This class maps pretty much 1-to-1 with an xml tree.
4078 It is used to represent the options in-core."""
4079
4080+ __gsignals__ = { "on-set-data" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
4081+ "on-set-attr" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, str))}
4082+
4083 def __init__(self, name="", schemaname="", attrs={}, children=None, cardinality='', datatype=None, doc=None):
4084+ gobject.GObject.__init__(self)
4085+
4086 # name: the element name in the options XML
4087 # e.g. "fluidity_options"
4088 self.name = name
4089-
4090+
4091 # schemaname: the label given to it in the Xvif parsing of the schema
4092 # this is necessary to walk the tree to see what possible valid
4093 # children this node could have
4094@@ -99,6 +107,7 @@
4095 raise Exception, "invalid data: (%s, %s)" % (datatype, val)
4096 self.attrs[attr] = (datatype, newdata)
4097 self.recompute_validity()
4098+ self.emit("on-set-attr", attr, val)
4099
4100 def get_attr(self, attr):
4101 """Get an attribute."""
4102@@ -111,6 +120,7 @@
4103 raise Exception, "invalid data: (%s, %s)" % (str(self.datatype), data)
4104 self.data = data
4105 self.recompute_validity()
4106+ self.emit("on-set-data", data)
4107
4108 def valid_data(self, datatype, data):
4109 if datatype is None:
4110@@ -146,19 +156,31 @@
4111
4112 return (True, data)
4113
4114+ def validity_check(self, datatype, data):
4115+ """
4116+ Check to see if the supplied data with supplied type can be stored in a
4117+ tree.Tree.
4118+ """
4119+
4120+ (invalid, new_data) = self.valid_data(datatype, data)
4121+ if not invalid and isinstance(new_data, str) and not new_data == "":
4122+ if new_data != data and self.validity_check(new_data, datatype) is None:
4123+ return None
4124+ else:
4125+ return new_data
4126+ else:
4127+ return None
4128+
4129 def copy(self):
4130 new_copy = Tree()
4131- for attr in ["attrs", "name", "schemaname", "doc", "cardinality", "datatype", "parent", "active", "valid"]:
4132+ for attr in ["attrs", "name", "schemaname", "doc", "cardinality", "datatype", "data", "active", "valid"]:
4133 setattr(new_copy, attr, copy.copy(getattr(self, attr)))
4134
4135- new_copy.data = self.data
4136- new_copy.children = copy.copy([])
4137+ new_copy.parent = self.parent
4138+ new_copy.children = []
4139
4140 return new_copy
4141
4142- def not_editable(self):
4143- return not self.active or self.datatype is None or self.datatype == "fixed"
4144-
4145 def recompute_validity(self):
4146
4147 new_valid = True
4148@@ -330,7 +352,7 @@
4149 for i in range(len(self.children)):
4150 if isinstance(self.children[i], Tree):
4151 self.children[i].print_recursively(indent + ">>")
4152- elif isinstance(self.children[i], Choice):
4153+ elif isinstance(self.children[i], choice.Choice):
4154 ref = self.children[i].get_current_tree()
4155 ref.print_recursively(indent + ">>")
4156 if i < len(self.children) - 1:
4157@@ -387,3 +409,135 @@
4158
4159 def choices(self):
4160 return [self]
4161+
4162+ def is_comment(self):
4163+ """
4164+ Test whether the given node is a comment node.
4165+ """
4166+
4167+ if not self.name == "comment":
4168+ return False
4169+
4170+ if not self.attrs == {}:
4171+ return False
4172+
4173+ if not self.children == []:
4174+ return False
4175+
4176+ if not self.datatype is str:
4177+ return False
4178+
4179+ if not self.cardinality == "?":
4180+ return False
4181+
4182+ return True
4183+
4184+ def get_comment(self):
4185+ """
4186+ Return the first comment found as a child of the supplied node, or None if
4187+ none found.
4188+ """
4189+
4190+ for child in self.children:
4191+ if child.is_comment():
4192+ return child
4193+
4194+ return None
4195+
4196+ def is_tensor(self, geometry_dim_tree):
4197+ return False
4198+
4199+ def is_python_code(self):
4200+ """
4201+ Perform a series of tests on the current Tree, to determine if
4202+ it is intended to be used to store python code data.
4203+ """
4204+
4205+ try:
4206+ lang = self.selected_node.get_attr("language")
4207+ if lang == "python":
4208+ return True
4209+ except:
4210+ pass
4211+
4212+ return False
4213+
4214+ def get_display_name(self):
4215+ """
4216+ This is a fluidity hack, allowing the name displayed in the treeview on the
4217+ left to be different to the element name. If it has an attribute name="xxx",
4218+ element_tag (xxx) is displayed.
4219+ """
4220+
4221+ name = self.get_name()
4222+
4223+ if name is None:
4224+ return self.name
4225+ else:
4226+ return self.name + " (" + name + ")"
4227+
4228+ def get_name(self):
4229+ if "name" in self.attrs:
4230+ name = self.attrs["name"][1]
4231+ return name
4232+
4233+ return None
4234+
4235+ def get_children(self):
4236+ return self.children
4237+
4238+ def get_choices(self):
4239+ return [self]
4240+
4241+ def is_hidden(self):
4242+ """
4243+ Tests whether the supplied tree should be hidden in view.
4244+ """
4245+ return self.is_comment() or self.name in ["integer_value", "real_value", "string_value", "logical_value"]
4246+
4247+ def get_name_path(self, leaf = True):
4248+ name = self.get_display_name() if leaf else self.get_name()
4249+
4250+ if self.parent is None:
4251+ return name
4252+ else:
4253+
4254+ pname = self.parent.get_name_path(False)
4255+
4256+ if name is None:
4257+ return pname
4258+ elif pname is None:
4259+ return name
4260+ else:
4261+ return pname + "/" + name
4262+
4263+ def get_mixed_data(self):
4264+ integers = [child for child in self.children if child.name == "integer_value"]
4265+ reals = [child for child in self.children if child.name == "real_value"]
4266+ logicals = [child for child in self.children if child.name == "logical_value"]
4267+ strings = [child for child in self.children if child.name == "string_value"]
4268+
4269+ child = None
4270+ if len(integers) > 0:
4271+ child = integers[0]
4272+ if len(reals) > 0:
4273+ child = reals[0]
4274+ if len(logicals) > 0:
4275+ child = logicals[0]
4276+ if len(strings) > 0:
4277+ child = strings[0]
4278+
4279+ if child is None:
4280+ return self
4281+ else:
4282+ return mixedtree.MixedTree(self, child)
4283+
4284+ def is_sliceable(self):
4285+ mixed = self.get_mixed_data()
4286+ if isinstance(mixed, mixedtree.MixedTree):
4287+ return True
4288+
4289+ return (self.datatype is not None and self.datatype != "fixed") or self.attrs
4290+
4291+gobject.type_register(Tree)
4292+
4293
4294=== modified file 'diamond/gui/gui.glade'
4295--- diamond/gui/gui.glade 2011-07-20 17:19:44 +0000
4296+++ diamond/gui/gui.glade 2011-07-25 13:56:22 +0000
4297@@ -290,225 +290,6 @@
4298 <property name="label_xalign">0</property>
4299 <property name="shadow_type">none</property>
4300 <child>
4301- <widget class="GtkAlignment" id="optionsFrameAlignment">
4302- <property name="visible">True</property>
4303- <property name="xalign">0</property>
4304- <property name="yalign">0</property>
4305- <property name="top_padding">6</property>
4306- <property name="left_padding">12</property>
4307- <child>
4308- <widget class="GtkVPaned" id="optionsFrameVPanes1">
4309- <property name="visible">True</property>
4310- <property name="can_focus">True</property>
4311- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4312- <child>
4313- <widget class="GtkFrame" id="descriptionFrame">
4314- <property name="visible">True</property>
4315- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4316- <property name="label_xalign">0</property>
4317- <property name="shadow_type">none</property>
4318- <child>
4319- <widget class="GtkScrolledWindow" id="descriptionFrameScrolledWindow">
4320- <property name="visible">True</property>
4321- <property name="can_focus">True</property>
4322- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4323- <property name="hscrollbar_policy">automatic</property>
4324- <property name="vscrollbar_policy">automatic</property>
4325- <child>
4326- <widget class="GtkTextView" id="nodeDescription">
4327- <property name="visible">True</property>
4328- <property name="can_focus">True</property>
4329- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4330- <property name="editable">False</property>
4331- <property name="wrap_mode">word</property>
4332- <property name="cursor_visible">False</property>
4333- </widget>
4334- </child>
4335- </widget>
4336- </child>
4337- <child>
4338- <widget class="GtkLabel" id="descriptionFrameLabel">
4339- <property name="visible">True</property>
4340- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4341- <property name="label" translatable="yes">&lt;b&gt;Description&lt;/b&gt;</property>
4342- <property name="use_markup">True</property>
4343- </widget>
4344- <packing>
4345- <property name="type">label_item</property>
4346- </packing>
4347- </child>
4348- </widget>
4349- <packing>
4350- <property name="resize">True</property>
4351- <property name="shrink">False</property>
4352- </packing>
4353- </child>
4354- <child>
4355- <widget class="GtkVPaned" id="optionsFrameVPanes2">
4356- <property name="visible">True</property>
4357- <property name="can_focus">True</property>
4358- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4359- <child>
4360- <widget class="GtkVBox" id="attributeDataVBox">
4361- <property name="visible">True</property>
4362- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4363- <child>
4364- <widget class="GtkFrame" id="attributeFrame">
4365- <property name="visible">True</property>
4366- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4367- <property name="label_xalign">0</property>
4368- <property name="shadow_type">none</property>
4369- <child>
4370- <widget class="GtkScrolledWindow" id="attributeFrameScrolledWindow">
4371- <property name="visible">True</property>
4372- <property name="can_focus">True</property>
4373- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4374- <property name="hscrollbar_policy">automatic</property>
4375- <property name="vscrollbar_policy">automatic</property>
4376- <child>
4377- <widget class="GtkTreeView" id="nodeAttributes">
4378- <property name="visible">True</property>
4379- <property name="can_focus">True</property>
4380- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4381- </widget>
4382- </child>
4383- </widget>
4384- </child>
4385- <child>
4386- <widget class="GtkLabel" id="attributeFrameLabel">
4387- <property name="visible">True</property>
4388- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4389- <property name="label" translatable="yes">&lt;b&gt;Attributes&lt;/b&gt;</property>
4390- <property name="use_markup">True</property>
4391- </widget>
4392- <packing>
4393- <property name="type">label_item</property>
4394- </packing>
4395- </child>
4396- </widget>
4397- <packing>
4398- <property name="expand">False</property>
4399- <property name="fill">False</property>
4400- <property name="padding">2</property>
4401- <property name="position">0</property>
4402- </packing>
4403- </child>
4404- <child>
4405- <widget class="GtkFrame" id="dataFrame">
4406- <property name="visible">True</property>
4407- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4408- <property name="label_xalign">0</property>
4409- <property name="shadow_type">none</property>
4410- <child>
4411- <widget class="GtkLabel" id="dataFrameLabel">
4412- <property name="visible">True</property>
4413- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4414- <property name="label" translatable="yes">&lt;b&gt;Data&lt;/b&gt;</property>
4415- <property name="use_markup">True</property>
4416- </widget>
4417- <packing>
4418- <property name="type">label_item</property>
4419- </packing>
4420- </child>
4421- </widget>
4422- <packing>
4423- <property name="padding">2</property>
4424- <property name="position">1</property>
4425- </packing>
4426- </child>
4427- <child>
4428- <widget class="GtkHBox" id="dataButtonsHBox">
4429- <property name="visible">True</property>
4430- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4431- <child>
4432- <widget class="GtkButton" id="dataRevertButton">
4433- <property name="label" translatable="yes">Revert Data</property>
4434- <property name="visible">True</property>
4435- <property name="can_focus">True</property>
4436- <property name="receives_default">True</property>
4437- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4438- </widget>
4439- <packing>
4440- <property name="position">0</property>
4441- </packing>
4442- </child>
4443- <child>
4444- <widget class="GtkButton" id="dataStoreButton">
4445- <property name="label" translatable="yes">Store Data</property>
4446- <property name="visible">True</property>
4447- <property name="can_focus">True</property>
4448- <property name="receives_default">True</property>
4449- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4450- </widget>
4451- <packing>
4452- <property name="position">1</property>
4453- </packing>
4454- </child>
4455- </widget>
4456- <packing>
4457- <property name="expand">False</property>
4458- <property name="position">2</property>
4459- </packing>
4460- </child>
4461- </widget>
4462- <packing>
4463- <property name="resize">True</property>
4464- <property name="shrink">False</property>
4465- </packing>
4466- </child>
4467- <child>
4468- <widget class="GtkFrame" id="commentFrame">
4469- <property name="visible">True</property>
4470- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4471- <property name="label_xalign">0</property>
4472- <property name="shadow_type">none</property>
4473- <child>
4474- <widget class="GtkScrolledWindow" id="commentFrameScrolledWindow">
4475- <property name="visible">True</property>
4476- <property name="can_focus">True</property>
4477- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4478- <property name="hscrollbar_policy">automatic</property>
4479- <property name="vscrollbar_policy">automatic</property>
4480- <child>
4481- <widget class="GtkTextView" id="nodeComment">
4482- <property name="visible">True</property>
4483- <property name="can_focus">True</property>
4484- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4485- <property name="editable">False</property>
4486- <property name="wrap_mode">word</property>
4487- <property name="cursor_visible">False</property>
4488- </widget>
4489- </child>
4490- </widget>
4491- </child>
4492- <child>
4493- <widget class="GtkLabel" id="commentFrameLabel">
4494- <property name="visible">True</property>
4495- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
4496- <property name="label" translatable="yes">&lt;b&gt;Comment&lt;/b&gt;</property>
4497- <property name="use_markup">True</property>
4498- </widget>
4499- <packing>
4500- <property name="type">label_item</property>
4501- </packing>
4502- </child>
4503- </widget>
4504- <packing>
4505- <property name="resize">False</property>
4506- <property name="shrink">False</property>
4507- </packing>
4508- </child>
4509- </widget>
4510- <packing>
4511- <property name="resize">True</property>
4512- <property name="shrink">False</property>
4513- </packing>
4514- </child>
4515- </widget>
4516- </child>
4517- </widget>
4518- </child>
4519- <child>
4520 <widget class="GtkLabel" id="optionsFrameLabel">
4521 <property name="visible">True</property>
4522 <property name="label" translatable="yes">&lt;b&gt;Option Properties&lt;/b&gt;</property>
4523@@ -706,7 +487,6 @@
4524 </widget>
4525 <widget class="GtkMenu" id="popupmenu">
4526 <property name="visible">True</property>
4527- <property name="ubuntu_local">True</property>
4528 <child>
4529 <widget class="GtkMenuItem" id="menuitemCopy">
4530 <property name="visible">True</property>
4531@@ -723,5 +503,13 @@
4532 <signal name="activate" handler="on_paste"/>
4533 </widget>
4534 </child>
4535+ <child>
4536+ <widget class="GtkMenuItem" id="menuitemSlice">
4537+ <property name="visible">True</property>
4538+ <property name="label" translatable="yes">Slice</property>
4539+ <property name="use_underline">True</property>
4540+ <signal name="activate" handler="on_slice"/>
4541+ </widget>
4542+ </child>
4543 </widget>
4544 </glade-interface>

Subscribers

People subscribed via source and target branches