Merge lp:~spud/spud/copy-paste-fix into lp:spud

Proposed by Fraser Waters
Status: Superseded
Proposed branch: lp:~spud/spud/copy-paste-fix
Merge into: lp:spud
Diff against target: 482 lines (+86/-108)
4 files modified
diamond/diamond/choice.py (+23/-47)
diamond/diamond/interface.py (+41/-29)
diamond/diamond/schema.py (+16/-21)
diamond/diamond/tree.py (+6/-11)
To merge this branch: bzr merge lp:~spud/spud/copy-paste-fix
Reviewer Review Type Date Requested Status
Fraser Waters Needs Resubmitting
Cian Wilson Needs Information
Patrick Farrell Pending
Review via email: mp+69465@code.launchpad.net

This proposal supersedes a proposal from 2011-07-18.

This proposal has been superseded by a proposal from 2011-07-28.

Description of the change

Fix for bug #811200 and #812348. on_paste was not handling MixedTree's in the same way as on_copy. on_paste was not handling choices correctly.

To post a comment you must log in.
Revision history for this message
Cian Wilson (cwilson) wrote : Posted in a previous version of this proposal

With that branch I'm still getting the same error:

  File "/usr/lib/python2.7/dist-packages/diamond/interface.py", line 695, in on_paste
    newnode = self.s.read(ios, node)
  File "/usr/lib/python2.7/dist-packages/diamond/schema.py", line 532, in read
    datatree = self.valid_node(root)
  File "/usr/lib/python2.7/dist-packages/diamond/schema.py", line 167, in valid_node
    eidtree.parent.children.remove(eidtree)
ValueError: list.remove(x): x not in list

and behaviour as reported in bug #811200 (i.e. saving the file immediately afterwards saves a file that's different from the one you're seeing and you also don't get a copied branch of the tree).

Can anyone else reproduce this or have I mangled my paths somewhere? (I packaged the branch and installed it so shouldn't be mixing versions of diamond.)

review: Needs Information
Revision history for this message
Patrick Farrell (pefarrell) wrote : Posted in a previous version of this proposal

I attempted to replicate #811200 with this branch.

I don't get Cian's terminal error; I think that's been fixed, and I suspect it's something to do with running the wrong version of diamond. Cian: when you check out a branch, just do

cd branch
./configure
python diamond/bin/diamond [args]

and it will automatically use the right code.

But I get a different bug. It doesn't give any exception, but when I save the file I get

<?xml version='1.0' encoding='utf-8'?>
<dummy_options>
  <system name="Dummy">
    <nonlinear_solver name="Simple">
      <type name="SNES">
        <python name="Jacobian" rank="1">
          <string_value lines="20" type="python">some python code here</string_value>
        </python>
      </type>
    </nonlinear_solver>
    <nonlinear_solver>
      <type name="SNES">
        <python name="Jacobian" rank="1">
          <string_value type="python" lines="20"/>
        </python>
      </type>
    </nonlinear_solver>
  </system>
</dummy_options>

Note that the second nonlinear solver has a) lost its name, and b) lost its python code. It *looks* fine in the live diamond window, but if you save it it gets lost. (If you open it up again, you'll get all sorts of lost element errors.)

Revision history for this message
Cian Wilson (cwilson) wrote : Posted in a previous version of this proposal

> cd branch
> ./configure
> python diamond/bin/diamond [args]

I get the same error (terminal output and all) running the command this way:

cwilson@uisce:copy-paste-fix$ python diamond/bin/diamond -s ~/temp/schema/dummy.rng ~/temp/schema/dummy.dml

Just to confirm it's the branch I'm running:
cwilson@uisce:copy-paste-fix$ bzr info
Repository tree (format: 2a)
Location:
  shared repository: /home/cwilson/spud/bzr-repo
  repository branch: .

Related branches:
  parent branch: bzr+ssh://bazaar.launchpad.net/~spud/spud/copy-paste-fix/

> <?xml version='1.0' encoding='utf-8'?>
> <dummy_options>
> <system name="Dummy">
> <nonlinear_solver name="Simple">
> <type name="SNES">
> <python name="Jacobian" rank="1">
> <string_value lines="20" type="python">some python code
> here</string_value>
> </python>
> </type>
> </nonlinear_solver>
> <nonlinear_solver>
> <type name="SNES">
> <python name="Jacobian" rank="1">
> <string_value type="python" lines="20"/>
> </python>
> </type>
> </nonlinear_solver>
> </system>
> </dummy_options>
>
> Note that the second nonlinear solver has a) lost its name, and b) lost its
> python code. It *looks* fine in the live diamond window, but if you save it it
> gets lost. (If you open it up again, you'll get all sorts of lost element
> errors.)

With the exception of looking fine in the diamond window before I close it (i.e. it doesn't look like it's copied to me and diamond shows elements as blue still) that sounds like the same behaviour I see on reopening the file.

Revision history for this message
Patrick Farrell (pefarrell) wrote : Posted in a previous version of this proposal

Also: I seem to get different behaviour depending on whether I CTRL+C/CTRL+V, or right-click copy, right-click paste. When I right-click copy, I get the following error in the terminal:

Traceback (most recent call last):
  File "/data/pfarrell/src/spud/copy-paste-fix/diamond/bin/../diamond/interface.py", line 658, in on_copy
    if widget is not self.treeview and gobject.signal_lookup("copy-clipboard", widget):
TypeError: type must be instantiable or an interface

The XML appears correct regardless of which approach is taken.

When I paste onto the blank nonlinear_solver, it doesn't have the right cardinality: it doesn't have the '-' on the right-hand side of the treeview to deactivate it.

Revision history for this message
Cian Wilson (cwilson) wrote : Posted in a previous version of this proposal

The above was done by first adding a new nonlinear_solver element then running CTRL+C on the old copy nonlinear_solver::Simple then moving to the new element and pressing CTRL+V.

If instead I use the right-click feature I get:

Traceback (most recent call last):
  File "/home/cwilson/spud/bzr-repo/copy-paste-fix/diamond/bin/../diamond/interface.py", line 658, in on_copy
    if widget is not self.treeview and gobject.signal_lookup("copy-clipboard", widget):
TypeError: type must be instantiable or an interface

on copy and:

Traceback (most recent call last):
  File "/home/cwilson/spud/bzr-repo/copy-paste-fix/diamond/bin/../diamond/interface.py", line 681, in on_paste
    if widget is not self.treeview and gobject.signal_lookup("paste-clipboard", widget):
TypeError: type must be instantiable or an interface

on paste.

Revision history for this message
Fraser Waters (fraser-waters08) wrote : Posted in a previous version of this proposal

"TypeError: type must be instantiable or an interface"

I found the cause of that, it's fixed in my local version.

The rest I'm not sure, I'll look into it more it's probably some confusion between Choices, Trees and MixedTrees. Really need to standardize them at some point.

Revision history for this message
Cian Wilson (cwilson) wrote :

Repeating the steps (and using the schema and options file) in bug #811200 (i.e. activating a new nonlinear_solver element, copying nonlinear_solver::Simple and trying to paste it into the new active element - using Ctrl+C and Ctrl+V) I get the error:

  File "/usr/lib/python2.7/dist-packages/diamond/interface.py", line 684, in on_paste
    newnode = self.s.read(ios, node)
  File "/usr/lib/python2.7/dist-packages/diamond/schema.py", line 526, in read
    datatree = self.valid_node(root)
  File "/usr/lib/python2.7/dist-packages/diamond/schema.py", line 156, in valid_node
    xpath = self.tree.xpath(eid)
  File "lxml.etree.pyx", line 2029, in lxml.etree._ElementTree.xpath (src/lxml/lxml.etree.c:45934)
  File "xpath.pxi", line 363, in lxml.etree.XPathDocumentEvaluator.__call__ (src/lxml/lxml.etree.c:114234)
  File "apihelpers.pxi", line 1364, in lxml.etree._utf8 (src/lxml/lxml.etree.c:22190)
TypeError: Argument must be bytes or unicode, got 'Choice'

Nothing appears in the pasted to element.

If I then delete the node that I just failed to copy information to and save the file, close it and reopen it, it reappears as I last saw (and saved) it so that part of the bug appears fixed.

I get the same error using right-click copy and paste and if the element being pasted to is active or not.

Can anyone reproduce this with this branch? I've built packages from this branch and overwritten my previously installed spud so am reasonably confident I'm not getting paths muddled.

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

Cian: yes, I can reproduce your behaviour. Thanks again for your thorough testing.

We know that the way choices are handled needs to be changed (there's another bug I didn't bother writing up), so Fraser will start on that soon.

Revision history for this message
Fraser Waters (fraser-waters08) wrote :

Yes just to confirm. Choice nodes aren't being handled correctly at the moment. I'm working on it. Trying to paste on a choice should always fail at the moment. If your getting bugs with normal nodes keep reporting them, because they SHOULD work.

review: Needs Fixing
Revision history for this message
Fraser Waters (fraser-waters08) wrote :

Choice nodes should be handled correctly now.

review: Needs Resubmitting

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'diamond/diamond/choice.py'
2--- diamond/diamond/choice.py 2011-07-26 14:04:07 +0000
3+++ diamond/diamond/choice.py 2011-07-28 13:56:32 +0000
4@@ -31,23 +31,23 @@
5 __gsignals__ = { "on-set-data" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
6 "on-set-attr" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, str))}
7
8- def __init__(self, l, cardinality=''):
9+ def __init__(self, choices, schemaname="", cardinality=''):
10 gobject.GObject.__init__(self)
11
12- self.l = l
13- if l == []:
14+ self.choices = choices
15+ if choices == []:
16 raise Exception
17 self.index = 0
18 name = ""
19- for choice in l:
20+ for choice in choices:
21 assert choice.__class__ is tree.Tree
22- name = name + choice.name + ":"
23 choice.connect("on-set-data", self._on_set_data)
24 choice.connect("on-set-attr", self._on_set_attr)
25
26- name = name[:-1]
27+ name = ":".join(choice.name for choice in choices)
28+
29 self.name = name
30- self.schemaname = name
31+ self.schemaname = schemaname
32 self.cardinality = cardinality
33 self.parent = None
34 self.set_default_active()
35@@ -67,21 +67,21 @@
36 self.index = i
37
38 def find_tree(self, name):
39- for t in self.l:
40+ for t in self.choices:
41 if t.name == name:
42 return t
43
44 debug.deprint("self.name == %s" % self.name, 0)
45- for choice in self.l:
46+ for choice in self.choices:
47 debug.deprint("choice.name == %s" % choice.name, 0)
48 raise Exception, "No such choice name: %s" % name
49
50 def set_active_choice_by_name(self, name):
51 matched = False
52- for t in self.l:
53+ for t in self.choices:
54 if t.name == name.strip():
55 matched = True
56- self.index = self.l.index(t)
57+ self.index = self.choices.index(t)
58
59 if not matched:
60 raise Exception, "no such name %s found" % name
61@@ -89,11 +89,11 @@
62 self.recompute_validity()
63
64 def set_active_choice_by_ref(self, ref):
65- self.index = self.l.index(ref)
66+ self.index = self.choices.index(ref)
67 self.recompute_validity()
68
69 def get_current_tree(self):
70- return self.l[self.index]
71+ return self.choices[self.index]
72
73 def add_children(self, schema):
74 return self.get_current_tree().add_children(schema)
75@@ -106,7 +106,7 @@
76
77 def copy(self):
78 new_choices = []
79- for choice in self.l:
80+ for choice in self.choices:
81 new_choices.append(choice.copy())
82
83 new_choice = Choice(new_choices)
84@@ -114,37 +114,23 @@
85 setattr(new_choice, attr, copy.copy(getattr(self, attr)))
86
87 new_choice.set_parent(self.parent)
88- for choice in new_choice.l:
89+ for choice in new_choice.choices:
90 choice.children = copy.copy([])
91
92 return new_choice
93
94 def get_possible_names(self):
95- return [x.name for x in self.l]
96+ return [x.name for x in self.choices]
97
98 def set_parent(self, parent):
99 self.parent = parent
100- for choice in self.l:
101+ for choice in self.choices:
102 choice.parent = parent
103
104 def write_core(self, parent):
105- l = self.l
106- for i in range(len(l)):
107- if self.index == i:
108- l[i].write_core(parent)
109-# else:
110-# root=etree.Element(parent.tag)
111-# l[i].write_core(root)
112-# comment_buffer = StringIO.StringIO(etree.tostring(root))
113-# comment_text = ("DIAMOND MAGIC COMMENT (neglected choice subtree %s):\n" % l[i].schemaname)
114-# comment_text = comment_text + base64.b64encode(bz2.compress(comment_buffer.getvalue()))
115-# parent.append(etree.Comment(unicode(comment_text)))
116-
117+ self.choices[self.index].write_core(parent)
118 return parent
119
120- def choices(self):
121- return self.l
122-
123 def is_comment(self):
124 return False
125
126@@ -167,7 +153,7 @@
127 return [self.get_current_tree()]
128
129 def get_choices(self):
130- return self.l
131+ return self.choices
132
133 def is_hidden(self):
134 """
135@@ -176,20 +162,7 @@
136 return False
137
138 def get_name_path(self, leaf = True):
139- name = self.get_display_name() if leaf else self.get_name()
140-
141- if self.parent is None:
142- return name
143- else:
144-
145- pname = self.parent.get_name_path(False)
146-
147- if name is None:
148- return pname
149- elif pname is None:
150- return name
151- else:
152- return pname + "/" + name
153+ return self.get_current_tree().get_name_path(leaf)
154
155 def get_mixed_data(self):
156 return self
157@@ -203,4 +176,7 @@
158 """
159 return self.get_display_name()
160
161+ def __repr__(self):
162+ return self.get_name_path() + "[" + self.name + "]"
163+
164 gobject.type_register(Choice)
165
166=== modified file 'diamond/diamond/interface.py'
167--- diamond/diamond/interface.py 2011-07-28 12:03:24 +0000
168+++ diamond/diamond/interface.py 2011-07-28 13:56:32 +0000
169@@ -283,9 +283,6 @@
170 # if we have a relative path, make it absolute
171 filename = os.path.abspath(filename)
172
173- if filename == self.filename:
174- return
175-
176 try:
177 os.stat(filename)
178 except OSError:
179@@ -638,12 +635,19 @@
180 else:
181 return self._get_focus_widget(focus)
182
183+ def _handle_clipboard(self, widget, signal):
184+ if isinstance(widget, gtk.MenuItem):
185+ return False
186+ else:
187+ widget = self._get_focus_widget(self.main_window)
188+ if widget is not self.treeview and gobject.signal_lookup(signal + "-clipboard", widget):
189+ widget.emit(signal + "-clipboard")
190+ return True
191+ return False
192+
193 def on_copy(self, widget=None):
194- if not isinstance(widget, gtk.MenuItem):
195- widget = self._get_focus_widget(self.main_window)
196- if widget is not self.treeview and gobject.signal_lookup("copy-clipboard", widget):
197- widget.emit("copy-clipboard")
198- return
199+ if self._handle_clipboard(widget, "copy"):
200+ return
201
202 if isinstance(self.selected_node, mixedtree.MixedTree):
203 node = self.selected_node.parent
204@@ -662,28 +666,39 @@
205 return
206
207 def on_paste(self, widget=None):
208- if not isinstance(widget, gtk.MenuItem):
209- widget = self._get_focus_widget(self.main_window)
210- if widget is not self.treeview and gobject.signal_lookup("paste-clipboard", widget):
211- widget.emit("paste-clipboard")
212- return
213+ if self._handle_clipboard(widget, "paste"):
214+ return
215
216 clipboard = gtk.clipboard_get()
217 ios = StringIO.StringIO(clipboard.wait_for_text())
218
219 if self.selected_iter is not None:
220- node = self.treestore.get_value(self.selected_iter, 1)
221+ node = self.treestore.get_value(self.selected_iter, 0)
222
223 if node != None:
224+
225+ expand = not node.active
226+ if expand:
227+ self.expand_tree(self.selected_iter)
228
229 newnode = self.s.read(ios, node)
230
231 if newnode is None:
232+ if expand:
233+ self.collapse_tree(self.selected_iter, False)
234 self.statusbar.set_statusbar("Trying to paste invalid XML.")
235 return
236
237- if not node.active:
238- self.expand_tree(self.selected_iter)
239+ if node.parent is not None:
240+ newnode.set_parent(node.parent)
241+ children = node.parent.get_children()
242+ children.insert(children.index(node), newnode)
243+ children.remove(node)
244+
245+ self.set_treestore(self.selected_iter, [newnode], True, True)
246+
247+ newnode.recompute_validity()
248+ self.treeview.queue_draw()
249
250 # Extract and display validation errors
251 lost_eles, added_eles, lost_attrs, added_attrs = self.s.read_errors()
252@@ -710,12 +725,6 @@
253 dialogs.long_message(self.main_window, msg)
254
255 self.set_saved(False)
256-
257- self.treeview.freeze_child_notify()
258- iter = self.set_treestore(self.selected_iter, [newnode], True, True)
259- self.treeview.thaw_child_notify()
260-
261- self.treeview.get_selection().select_iter(iter)
262
263 return
264
265@@ -907,7 +916,7 @@
266 if replace:
267 replacediter = iter
268 iter = self.treestore.iter_parent(replacediter)
269- else:
270+ else:
271 self.remove_children(iter)
272
273 for t in new_tree:
274@@ -1119,7 +1128,7 @@
275
276 return
277
278- def collapse_tree(self, iter):
279+ def collapse_tree(self, iter, confirm = True):
280 """
281 Collapses part of the tree.
282 """
283@@ -1147,7 +1156,7 @@
284 self.set_saved(False)
285 self.remove_children(iter)
286 else:
287- self.delete_tree(iter)
288+ self.delete_tree(iter, confirm)
289
290 elif choice_or_tree.cardinality == "+":
291 count = parent_tree.count_children_by_schemaname(choice_or_tree.schemaname)
292@@ -1155,20 +1164,23 @@
293 # do nothing
294 return
295 else: # count > 2
296- self.delete_tree(iter)
297+ self.delete_tree(iter, confirm)
298
299 parent_tree.recompute_validity()
300 self.treeview.queue_draw()
301 return
302
303- def delete_tree(self, iter):
304+ def delete_tree(self, iter, confirm):
305 choice_or_tree, = self.treestore.get(iter, 0)
306 parent_tree = choice_or_tree.parent
307 isSelected = self.treeview.get_selection().iter_is_selected(iter)
308 sibling = self.treestore.iter_next(iter)
309
310- confirm = dialogs.prompt(self.main_window, "Are you sure you want to delete this node?")
311- if confirm == gtk.RESPONSE_YES:
312+ if confirm:
313+ response = dialogs.prompt(self.main_window, "Are you sure you want to delete this node?")
314+
315+ # not A or B == A implies B
316+ if not confirm or response == gtk.RESPONSE_YES:
317 parent_tree.delete_child_by_ref(choice_or_tree)
318 self.remove_children(iter)
319 self.treestore.remove(iter)
320
321=== modified file 'diamond/diamond/schema.py'
322--- diamond/diamond/schema.py 2011-07-18 11:49:27 +0000
323+++ diamond/diamond/schema.py 2011-07-28 13:56:32 +0000
324@@ -142,10 +142,10 @@
325 return results
326
327 def valid_node(self, eid):
328- if isinstance(eid, tree.Tree):
329+ if isinstance(eid, tree.Tree) or isinstance(eid, choice.Choice):
330 eidtree = eid
331 eid = eid.schemaname
332-
333+
334 if eid == ":start":
335 try:
336 node = self.tree.xpath('/t:grammar/t:start', namespaces={'t': 'http://relaxng.org/ns/structure/1.0'})[0]
337@@ -162,12 +162,8 @@
338 node = self.to_tree(node)
339
340 if eidtree is not None:
341- if eidtree.parent is not None:
342- eidtree.parent.children.append(node)
343- eidtree.parent.children.remove(eidtree)
344- node.set_parent(eidtree.parent)
345- node.attrs = eidtree.attrs
346 node.cardinality = eidtree.cardinality
347+ node.parent = eidtree.parent
348
349 return node
350
351@@ -176,7 +172,7 @@
352 f = self.callbacks[tag]
353 facts = {}
354 x = f(element, facts)
355- return x
356+ return x
357
358 #############################################
359 # Beginning of schema processing functions. #
360@@ -389,20 +385,19 @@
361 if "schemaname" in facts:
362 return
363
364+ facts['schemaname'] = self.tree.getpath(element)
365+
366 r = []
367 children = self.choice_children(self.element_children(element))
368
369 # bloody simplified RNG
370 if len(children) == 2:
371 empty = [x for x in children if self.tag(x) == "empty"]
372- nonempty = [x for x in children if self.tag(x) != "empty"]
373- if len(empty) > 0:
374+ if empty:
375+ nonempty = [x for x in children if self.tag(x) != "empty"]
376 tag = self.tag(nonempty[0])
377- if tag == "oneOrMore":
378- return self.cb_oneormore(element, facts)
379- else:
380- f = self.callbacks[tag]
381- return f(element, facts)
382+ f = self.callbacks[tag]
383+ return f(element, facts)
384
385 for child in children:
386 newfacts = {}
387@@ -533,7 +528,7 @@
388
389 xmlnode = doc.getroot()
390 self.xml_read_merge(datatree, xmlnode)
391- self.xml_read_core(datatree, xmlnode, doc)
392+ self.xml_read_core(datatree.get_current_tree(), xmlnode, doc)
393
394 if len(self.lost_eles) != 0:
395 debug.deprint("WARNING: Lost XML elements:\n" + str(self.lost_eles))
396@@ -564,7 +559,7 @@
397 xmlname = xmlnode.get("name")
398 have_found = False
399
400- possibles = [tree_choice for tree_choice in datatree.choices() if tree_choice.name == xmlnode.tag]
401+ possibles = [tree_choice for tree_choice in datatree.get_choices() if tree_choice.name == xmlnode.tag]
402 # first loop over the fixed-value names
403 for tree_choice in possibles:
404 if "name" not in tree_choice.attrs:
405@@ -698,7 +693,7 @@
406
407 for schemachild in priority_queue:
408 if schemachild.cardinality in ['', '?']:
409- for curtree in schemachild.choices():
410+ for curtree in schemachild.get_choices():
411 name = curtree.name
412
413 have_fixed_name = False
414@@ -725,7 +720,7 @@
415 xmls[schemachild.schemaname] = copy.deepcopy([])
416 elif schemachild.cardinality in ['*', '+']:
417 xmls[schemachild.schemaname] = copy.deepcopy([])
418- for curtree in schemachild.choices():
419+ for curtree in schemachild.get_choices():
420 name = curtree.name
421
422 have_fixed_name = False
423@@ -803,7 +798,7 @@
424 bins[schemachild.schemaname].append(child)
425
426 # search for neglected choices
427- if schemachild.__class__ is choice.Choice and schemachild.cardinality in ['', '?']:
428+ if isinstance(schemachild, choice.Choice) and schemachild.cardinality in ['', '?']:
429 for child in bins[schemachild.schemaname]:
430
431 # Does the child have a valid XML node attached?
432@@ -811,7 +806,7 @@
433 if child.xmlnode is None: continue
434
435 current_choice = child.get_current_tree()
436- for tree_choice in child.l:
437+ for tree_choice in child.get_choices():
438 if tree_choice is current_choice: continue
439
440 return bins
441
442=== modified file 'diamond/diamond/tree.py'
443--- diamond/diamond/tree.py 2011-07-26 14:04:07 +0000
444+++ diamond/diamond/tree.py 2011-07-28 13:56:32 +0000
445@@ -266,7 +266,7 @@
446
447 sub_tree=etree.Element(self.name)
448
449- for key in self.attrs.keys():
450+ for key in self.attrs:
451 val = self.attrs[key]
452 output_val = val[1]
453 if output_val is not None:
454@@ -275,17 +275,9 @@
455 for child in self.children:
456 if child.active is True:
457 child.write_core(sub_tree)
458-# else:
459-# if child.cardinality == '?':
460-# root=etree.Element(self.name)
461-# child.write_core(root)
462-# comment_buffer = StringIO.StringIO(etree.tostring(root))
463-# comment_text = ("DIAMOND MAGIC COMMENT (inactive optional subtree %s):\n" % child.schemaname)
464-# comment_text = comment_text + base64.b64encode(bz2.compress(comment_buffer.getvalue()))
465-# sub_tree.append(etree.Comment(unicode(comment_text)))
466
467 if self.data is not None:
468- sub_tree.text=(unicode(self.data))
469+ sub_tree.text = unicode(self.data)
470
471 if parent is not None:
472 parent.append(sub_tree)
473@@ -541,6 +533,9 @@
474
475 def __str__(self):
476 return self.get_display_name()
477-
478+
479+ def __repr__(self):
480+ return self.get_name_path()
481+
482 gobject.type_register(Tree)
483

Subscribers

People subscribed via source and target branches