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

Proposed by Fraser Waters
Status: Merged
Approved by: Patrick Farrell
Approved revision: 458
Merged at revision: 425
Proposed branch: lp:~spud/spud/group-view
Merge into: lp:spud
Diff against target: 581 lines (+191/-93)
4 files modified
diamond/diamond/choice.py (+6/-0)
diamond/diamond/interface.py (+150/-89)
diamond/diamond/tree.py (+4/-1)
diamond/gui/gui.glade (+31/-3)
To merge this branch: bzr merge lp:~spud/spud/group-view
Reviewer Review Type Date Requested Status
Patrick Farrell Approve
Review via email: mp+69313@code.launchpad.net

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

Description of the change

Allows nodes to be grouped together in the LHS hiding all other nodes.

To post a comment you must log in.
Revision history for this message
Patrick Farrell (pefarrell) wrote :

Fraser and I went through several debugging iterations, and I've read the code. Time to push it out to the users for more thorough testing, I think ...

review: Approve

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-25 13:41:47 +0000
3+++ diamond/diamond/choice.py 2011-07-26 16:19:41 +0000
4@@ -196,5 +196,11 @@
5
6 def is_sliceable(self):
7 return self.get_current_tree().is_sliceable()
8+
9+ def __str__(self):
10+ """
11+ Returns the display name of the selected tree.
12+ """
13+ return self.get_display_name()
14
15 gobject.type_register(Choice)
16
17=== modified file 'diamond/diamond/interface.py'
18--- diamond/diamond/interface.py 2011-07-25 13:51:34 +0000
19+++ diamond/diamond/interface.py 2011-07-26 16:19:41 +0000
20@@ -90,7 +90,7 @@
21 treeview: the LHS tree widget
22
23 Important routines:
24- cellcombo_edited: called when a choice is selected on the left-hand pane
25+ cellcombo_changed: called when a choice is selected on the left-hand pane
26 init_treemodel: set up the treemodel and treeview
27 on_treeview_clicked: when a row is clicked, process the consequences (e.g. activate inactive instance)
28 set_treestore: stuff the treestore with a given tree.Tree
29@@ -139,7 +139,10 @@
30 "on_copy_spud_path": self.on_copy_spud_path,
31 "on_copy": self.on_copy,
32 "on_paste": self.on_paste,
33- "on_slice": self.on_slice}
34+ "on_slice": self.on_slice,
35+ "on_group": self.on_group,
36+ "on_ungroup": self.on_ungroup}
37+
38 self.gui.signal_autoconnect(signals)
39
40 self.main_window = self.gui.get_widget("mainWindow")
41@@ -157,6 +160,7 @@
42 self.suffix = suffix
43
44 self.selected_node = None
45+ self.selected_iter = None
46 self.update_options_frame()
47
48 self.file_path = os.getcwd()
49@@ -668,7 +672,7 @@
50 ios = StringIO.StringIO(clipboard.wait_for_text())
51
52 if self.selected_iter is not None:
53- node = self.treestore.get_value(self.selected_iter, 3)
54+ node = self.treestore.get_value(self.selected_iter, 1)
55
56 if node != None:
57
58@@ -729,6 +733,80 @@
59 def _slice_destroy(self, widget):
60 self.on_select_row()
61
62+
63+ groupmode = False
64+
65+ def on_group(self, widget = None):
66+ """
67+ Clears the treeview and then fills it with nodes
68+ with the same type as the selected node.
69+ """
70+
71+ if self.selected_node == self.tree or not self.selected_iter:
72+ self.statusbar.set_statusbar("Cannot group on this element.")
73+ return #Group on the entire tree... ie don't group or nothing selected
74+
75+ self.gui.get_widget("menuitemUngroup").show()
76+ self.gui.get_widget("popupmenuitemUngroup").show()
77+
78+ self.groupmode = True
79+ node, tree = self.treestore.get(self.selected_iter, 0, 1)
80+
81+ self.treeview.freeze_child_notify()
82+ self.treeview.set_model(None)
83+
84+ def get_nodes(node, tree):
85+ nodes = []
86+
87+ if isinstance(tree, choice.Choice):
88+ child = tree.get_current_tree()
89+ if child.name == node.name:
90+ nodes.append(tree)
91+ nodes += get_nodes(node, child)
92+ else:
93+ for child in tree.get_children():
94+ if child.name == node.name:
95+ nodes.append(child)
96+ nodes += get_nodes(node, child)
97+
98+ return nodes
99+
100+ self.set_treestore(None, get_nodes(tree, self.tree), True)
101+
102+ self.treeview.set_model(self.treestore)
103+ self.treeview.thaw_child_notify()
104+
105+ path = self.get_treestore_path_from_node(node)
106+ self.treeview.get_selection().select_path(path)
107+
108+ return
109+
110+ def on_ungroup(self, widget = None):
111+ """
112+ Restores the treeview to normal.
113+ """
114+
115+ self.gui.get_widget("menuitemUngroup").hide()
116+ self.gui.get_widget("popupmenuitemUngroup").hide()
117+
118+ self.groupmode = False
119+ node = self.treestore.get_value(self.selected_iter, 0)
120+
121+ self.treeview.freeze_child_notify()
122+ self.treeview.set_model(None)
123+
124+ self.set_treestore(None, [self.tree], True)
125+
126+ self.treeview.set_model(self.treestore)
127+ self.treeview.thaw_child_notify()
128+
129+ path = self.get_treestore_path_from_node(node)
130+ self.treeview.expand_to_path(path)
131+ self.treeview.scroll_to_cell(path)
132+ self.treeview.get_selection().select_path(path)
133+
134+ return
135+
136 ## LHS ###
137
138 def init_datatree(self):
139@@ -773,16 +851,14 @@
140 self.treeview.get_selection().set_select_function(self.options_tree_select_func)
141 self.options_tree_select_func_enabled = True
142
143- model = gtk.ListStore(str, str, gobject.TYPE_PYOBJECT)
144 self.cellcombo = cellCombo = gtk.CellRendererCombo()
145- cellCombo.set_property("model", model)
146 cellCombo.set_property("text-column", 0)
147 cellCombo.set_property("editable", True)
148 cellCombo.set_property("has-entry", False)
149- cellCombo.connect("edited", self.cellcombo_edited)
150+ cellCombo.connect("changed", self.cellcombo_changed)
151
152 # Node column
153- column = gtk.TreeViewColumn("Node", cellCombo, text=0)
154+ column = gtk.TreeViewColumn("Node", cellCombo)
155 column.set_property("expand", True)
156 column.set_resizable(True)
157 column.set_cell_data_func(cellCombo, self.set_combobox_liststore)
158@@ -800,12 +876,11 @@
159 imgcolumn.set_cell_data_func(cellPicture, self.set_cellpicture_cardinality)
160 optionsTree.append_column(imgcolumn)
161
162- # 0: display name,
163- # 1: gtk.ListStore containing the display names of possible choices
164- # 2: pointer to node in self.tree -- a choice or a tree
165- # 3: pointer to currently active tree
166+ # 0: pointer to node in self.tree -- a choice or a tree
167+ # 1: pointer to currently active tree
168+ # 2: gtk.ListStore containing the display names of possible choices
169
170- self.treestore = gtk.TreeStore(str, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)
171+ self.treestore = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)
172 self.treeview.set_model(self.treestore)
173 self.treeview.set_enable_search(False)
174
175@@ -820,8 +895,7 @@
176 liststore = gtk.ListStore(str, gobject.TYPE_PYOBJECT)
177
178 for t in choice_or_tree.get_choices():
179- name = t.get_display_name()
180- liststore.append([name, t])
181+ liststore.append([str(t), t])
182
183 return liststore
184
185@@ -851,9 +925,9 @@
186 liststore = self.create_liststore(t)
187
188 if replace:
189- child_iter = self.treestore.insert_before(iter, replacediter, [t.get_display_name(), liststore, t, t])
190+ child_iter = self.treestore.insert_before(iter, replacediter, [t, t, liststore])
191 else:
192- child_iter = self.treestore.append(iter, [t.get_display_name(), liststore, t, t])
193+ child_iter = self.treestore.append(iter, [t, t, liststore])
194
195 attrid = t.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(child_iter))
196 dataid = t.connect("on-set-data", self.on_set_data, self.treestore.get_path(child_iter))
197@@ -871,9 +945,9 @@
198 continue
199
200 if replace:
201- child_iter = self.treestore.insert_before(iter, replacediter, [ts_choice.get_display_name(), liststore, t, ts_choice])
202+ child_iter = self.treestore.insert_before(iter, replacediter, [t, ts_choice, liststore])
203 else:
204- child_iter = self.treestore.append(iter, [ts_choice.get_display_name(), liststore, t, ts_choice])
205+ child_iter = self.treestore.append(iter, [t, ts_choice, liststore])
206
207 attrid = t.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(child_iter))
208 dataid = t.connect("on-set-data", self.on_set_data, self.treestore.get_path(child_iter))
209@@ -916,7 +990,7 @@
210 self.set_treestore(iter, [])
211 return
212
213- choice_or_tree, active_tree = self.treestore.get(iter, 2, 3)
214+ choice_or_tree, active_tree = self.treestore.get(iter, 0, 1)
215 if active_tree.active is False or choice_or_tree.active is False:
216 return
217
218@@ -927,7 +1001,7 @@
219 child_iter = self.treestore.iter_children(iter)
220 while child_iter is not None:
221 # fix for recursive schemata!
222- child_active_tree = self.treestore.get_value(child_iter, 3)
223+ child_active_tree = self.treestore.get_value(child_iter, 1)
224 if child_active_tree.schemaname == active_tree.schemaname:
225 debug.deprint("Warning: recursive schema elements not supported: %s" % active_tree.name)
226 child_iter = self.treestore.iter_next(child_iter)
227@@ -961,15 +1035,20 @@
228 foreground colour.
229 """
230
231- liststore, choice_or_tree, active_tree = self.treestore.get(iter, 1, 2, 3)
232-
233- # set the model for the cellcombo, where it gets the possible choices for the name
234+ choice_or_tree, active_tree, liststore = self.treestore.get(iter, 0, 1, 2)
235+
236+ if self.groupmode and self.treestore.iter_parent(iter) is None:
237+ cellCombo.set_property("text", choice_or_tree.get_name_path())
238+ else:
239+ cellCombo.set_property("text", str(choice_or_tree))
240+
241+
242 cellCombo.set_property("model", liststore)
243
244 # set the properties: colour, etc.
245- if choice_or_tree.__class__ is tree.Tree:
246+ if isinstance(choice_or_tree, tree.Tree):
247 cellCombo.set_property("editable", False)
248- elif choice_or_tree.__class__ is choice.Choice:
249+ elif isinstance(choice_or_tree, choice.Choice):
250 cellCombo.set_property("editable", True)
251
252 if self.treestore_iter_is_active(iter):
253@@ -987,8 +1066,8 @@
254 This hook function sets up the other gtk.CellRendererPixbuf, the one that gives
255 the clue to the user whether this is a choice or not.
256 """
257-
258- choice_or_tree = self.treestore.get_value(iter, 2)
259+
260+ choice_or_tree = self.treestore.get_value(iter, 0)
261 if isinstance(choice_or_tree, tree.Tree):
262 cell.set_property("stock-id", None)
263 elif isinstance(choice_or_tree, choice.Choice):
264@@ -1003,7 +1082,7 @@
265 something can be added or removed or has to be there.
266 """
267
268- choice_or_tree = self.treestore.get_value(iter, 2)
269+ choice_or_tree = self.treestore.get_value(iter, 0)
270 if choice_or_tree.cardinality == "":
271 cell.set_property("stock-id", None)
272 elif choice_or_tree.cardinality == "?" or choice_or_tree.cardinality == "*":
273@@ -1029,7 +1108,7 @@
274 Toggles the state of part of the tree.
275 """
276
277- choice_or_tree = self.treestore.get_value(iter, 2)
278+ choice_or_tree = self.treestore.get_value(iter, 0)
279
280 if choice_or_tree.active:
281 self.collapse_tree(iter)
282@@ -1045,13 +1124,8 @@
283 Collapses part of the tree.
284 """
285
286- choice_or_tree, = self.treestore.get(iter, 2)
287- parent_iter = self.treestore.iter_parent(iter)
288-
289- if parent_iter == None:
290- parent_tree = None
291- else:
292- parent_tree = self.treestore.get_value(parent_iter, 3)
293+ choice_or_tree, = self.treestore.get(iter, 0)
294+ parent_tree = choice_or_tree.parent
295
296 if not choice_or_tree.active:
297 return
298@@ -1088,16 +1162,11 @@
299 return
300
301 def delete_tree(self, iter):
302- choice_or_tree, = self.treestore.get(iter, 2)
303- parent_iter = self.treestore.iter_parent(iter)
304+ choice_or_tree, = self.treestore.get(iter, 0)
305+ parent_tree = choice_or_tree.parent
306 isSelected = self.treeview.get_selection().iter_is_selected(iter)
307 sibling = self.treestore.iter_next(iter)
308
309- if parent_iter == None:
310- parent_tree = None
311- else:
312- parent_tree = self.treestore.get_value(parent_iter, 3)
313-
314 confirm = dialogs.prompt(self.main_window, "Are you sure you want to delete this node?")
315 if confirm == gtk.RESPONSE_YES:
316 parent_tree.delete_child_by_ref(choice_or_tree)
317@@ -1114,13 +1183,8 @@
318 Expands part of the tree.
319 """
320
321- choice_or_tree, active_tree = self.treestore.get(iter, 2, 3)
322- parent_iter = self.treestore.iter_parent(iter)
323-
324- if parent_iter == None:
325- parent_tree = None
326- else:
327- parent_tree = self.treestore.get_value(parent_iter, 3)
328+ choice_or_tree, active_tree = self.treestore.get(iter, 0, 1)
329+ parent_tree = choice_or_tree.parent
330
331 if choice_or_tree.active:
332 return
333@@ -1140,8 +1204,7 @@
334 liststore = self.create_liststore(new_tree)
335 self.expand_treestore(iter)
336 iter = self.treestore.insert_after(
337- parent=parent_iter, sibling=iter,
338- row=[new_tree.get_display_name(), liststore, new_tree, new_tree.get_current_tree()])
339+ None, iter, [new_tree, new_tree.get_current_tree(), liststore])
340 attrid = new_tree.connect("on-set-attr", self.on_set_attr, self.treestore.get_path(iter))
341 dataid = new_tree.connect("on-set-data", self.on_set_data, self.treestore.get_path(iter))
342 self.signals[new_tree] = (attrid, dataid)
343@@ -1243,7 +1306,7 @@
344 return
345
346 self.selected_iter = iter = self.treestore.get_iter(path)
347- choice_or_tree, active_tree = self.treestore.get(iter, 2, 3)
348+ choice_or_tree, active_tree = self.treestore.get(iter, 0, 1)
349
350 debug.dprint(active_tree)
351
352@@ -1367,26 +1430,18 @@
353
354 return
355
356- def cellcombo_edited(self, cellrenderertext, path, new_text):
357+ def cellcombo_changed(self, combo, tree_path, combo_iter):
358 """
359 This is called when a cellcombo on the left-hand treeview is edited,
360 i.e. the user chooses between more than one possible choice.
361 """
362
363- iter = self.treestore.get_iter(path)
364- self.treestore.set(iter, 0, new_text)
365- choice = self.treestore.get_value(iter, 2)
366+ tree_iter = self.treestore.get_iter(tree_path)
367+ choice = self.treestore.get_value(tree_iter, 0)
368
369 # get the ref to the new active choice
370- liststore = self.treestore.get_value(iter, 1)
371- list_iter = liststore.get_iter_first()
372- ref = None
373- while list_iter is not None:
374- list_text = liststore.get_value(list_iter, 0)
375- if list_text == new_text:
376- ref = liststore.get_value(list_iter, 1)
377- break
378- list_iter = liststore.iter_next(list_iter)
379+ liststore = self.treestore.get_value(tree_iter, 2)
380+ ref = liststore.get_value(combo_iter, 1)
381
382 # record the choice in the datatree
383 choice.set_active_choice_by_ref(ref)
384@@ -1394,7 +1449,7 @@
385
386 name = self.get_spudpath(new_active_tree)
387 self.statusbar.set_statusbar(name)
388- self.treestore.set(iter, 3, new_active_tree)
389+ self.treestore.set(tree_iter, 1, new_active_tree)
390 self.current_spudpath = name
391 xpath = self.get_xpath(new_active_tree)
392 self.current_xpath = xpath
393@@ -1405,12 +1460,12 @@
394 if plugin.matches(xpath):
395 self.add_plugin_button(plugin)
396
397- self.remove_children(iter)
398- self.expand_treestore(iter)
399- self.treeview.expand_row(path, False)
400+ self.remove_children(tree_iter)
401+ self.expand_treestore(tree_iter)
402+ self.treeview.expand_row(tree_path, False)
403
404 self.set_saved(False)
405- self.selected_node = self.get_painted_tree(iter)
406+ self.selected_node = self.get_painted_tree(tree_iter)
407 self.update_options_frame()
408
409 return
410@@ -1435,20 +1490,7 @@
411 if attr != "name":
412 return
413
414- iter = self.treestore.get_iter(path)
415- liststore = self.treestore.get_value(iter, 1)
416- active_tree = self.treestore.get_value(iter, 3)
417- new_name = active_tree.get_display_name()
418- self.treestore.set_value(iter, 0, new_name)
419-
420- # find the liststore iter corresponding to the painted choice
421- list_iter = liststore.get_iter_first()
422- while list_iter is not None:
423- liststore_tree = liststore.get_value(list_iter, 1)
424- if liststore_tree is active_tree:
425- liststore.set_value(list_iter, 0, new_name)
426- list_iter = liststore.iter_next(list_iter)
427-
428+ self.treeview.queue_draw()
429 self.treeview.queue_resize()
430
431 def get_painted_tree(self, iter_or_tree, lock_geometry_dim = True):
432@@ -1465,7 +1507,7 @@
433 if isinstance(iter_or_tree, tree.Tree):
434 active_tree = iter_or_tree
435 else:
436- active_tree = self.treestore.get_value(iter_or_tree, 3)
437+ active_tree = self.treestore.get_value(iter_or_tree, 1)
438
439 painted_tree = active_tree.get_mixed_data()
440
441@@ -1505,7 +1547,26 @@
442 return None
443
444 return iter
445+
446+ def get_treestore_path_from_node(self, node):
447+ """
448+ Look for the path for the given node.
449+ """
450
451+ def search(iter, node, indent=""):
452+ while iter:
453+ if self.treestore.get_value(iter, 0) is node:
454+ return iter
455+ else:
456+ child = search(self.treestore.iter_children(iter), node, indent + " ")
457+ if child: return child
458+
459+ iter = self.treestore.iter_next(iter)
460+ return iter
461+
462+ iter = search(self.treestore.get_iter_first(), node)
463+ return self.treestore.get_path(iter) if iter else None
464+
465 def set_geometry_dim_tree(self):
466 """
467 Find the iter into the treestore corresponding to the geometry dimension, and
468@@ -1567,8 +1628,8 @@
469 """
470
471 while iter is not None:
472- choice_or_tree = self.treestore.get_value(iter, 2)
473- active_tree = self.treestore.get_value(iter, 3)
474+ choice_or_tree = self.treestore.get_value(iter, 0)
475+ active_tree = self.treestore.get_value(iter, 1)
476 if not choice_or_tree.active or not active_tree.active:
477 return False
478 iter = self.treestore.iter_parent(iter)
479@@ -1629,7 +1690,7 @@
480 iter = self.treestore.get_iter_first()
481 if iter is None:
482 yield None
483- choice_or_tree = self.treestore.get_value(iter, 2)
484+ choice_or_tree = self.treestore.get_value(iter, 0)
485
486 if self.choice_or_tree_matches(text, choice_or_tree, isinstance(choice_or_tree, choice.Choice)):
487 yield iter
488
489=== modified file 'diamond/diamond/tree.py'
490--- diamond/diamond/tree.py 2011-07-25 13:41:47 +0000
491+++ diamond/diamond/tree.py 2011-07-26 16:19:41 +0000
492@@ -301,7 +301,7 @@
493 def unpickle(self, pick):
494 return pickle.loads(bz2.decompress(base64.b64decode(pick)))
495
496- def __str__(self):
497+ def print_str(self):
498 s = "name: %s at %s\n" % (self.name, hex(id(self)))
499 s = s + "schemaname: %s\n" % self.schemaname
500 s = s + "attrs: %s\n" % self.attrs
501@@ -538,6 +538,9 @@
502 return True
503
504 return (self.datatype is not None and self.datatype != "fixed") or self.attrs
505+
506+ def __str__(self):
507+ return self.get_display_name()
508
509 gobject.type_register(Tree)
510
511
512=== modified file 'diamond/gui/gui.glade'
513--- diamond/gui/gui.glade 2011-07-22 15:46:25 +0000
514+++ diamond/gui/gui.glade 2011-07-26 16:19:41 +0000
515@@ -132,6 +132,20 @@
516 <accelerator key="V" signal="activate" modifiers="GDK_CONTROL_MASK"/>
517 </widget>
518 </child>
519+ <child>
520+ <widget class="GtkMenuItem" id="menuitemGroup">
521+ <property name="visible">True</property>
522+ <property name="label" translatable="yes">Group</property>
523+ <signal name="activate" handler="on_group"/>
524+ </widget>
525+ </child>
526+ <child>
527+ <widget class="GtkMenuItem" id="menuitemUngroup">
528+ <property name="visible">False</property>
529+ <property name="label" translatable="yes">Ungroup</property>
530+ <signal name="activate" handler="on_ungroup"/>
531+ </widget>
532+ </child>
533 </widget>
534 </child>
535 </widget>
536@@ -488,7 +502,7 @@
537 <widget class="GtkMenu" id="popupmenu">
538 <property name="visible">True</property>
539 <child>
540- <widget class="GtkMenuItem" id="menuitemCopy">
541+ <widget class="GtkMenuItem" id="popupmenuitemCopy">
542 <property name="visible">True</property>
543 <property name="label" translatable="yes">Copy</property>
544 <property name="use_underline">True</property>
545@@ -496,7 +510,7 @@
546 </widget>
547 </child>
548 <child>
549- <widget class="GtkMenuItem" id="menuitemPaste">
550+ <widget class="GtkMenuItem" id="popupmenuitemPaste">
551 <property name="visible">True</property>
552 <property name="label" translatable="yes">Paste</property>
553 <property name="use_underline">True</property>
554@@ -504,12 +518,26 @@
555 </widget>
556 </child>
557 <child>
558- <widget class="GtkMenuItem" id="menuitemSlice">
559+ <widget class="GtkMenuItem" id="popupmenuitemSlice">
560 <property name="visible">True</property>
561 <property name="label" translatable="yes">Slice</property>
562 <property name="use_underline">True</property>
563 <signal name="activate" handler="on_slice"/>
564 </widget>
565 </child>
566+ <child>
567+ <widget class="GtkMenuItem" id="popupmenuitemGroup">
568+ <property name="visible">True</property>
569+ <property name="label" translatable="yes">Group</property>
570+ <signal name="activate" handler="on_group"/>
571+ </widget>
572+ </child>
573+ <child>
574+ <widget class="GtkMenuItem" id="popupmenuitemUngroup">
575+ <property name="visible">False</property>
576+ <property name="label" translatable="yes">Ungroup</property>
577+ <signal name="activate" handler="on_ungroup"/>
578+ </widget>
579+ </child>
580 </widget>
581 </glade-interface>

Subscribers

People subscribed via source and target branches