15 |
15 |
16 This is useful for displaying the tree. |
16 This is useful for displaying the tree. |
17 |
17 |
18 """ |
18 """ |
19 |
19 |
20 def __init__(self, root, collapsed=[]): |
20 def __init__(self, root, collapsed_nodes=[]): |
21 self._node = root |
21 self._node = root |
22 self._index = 0 |
22 self._index = 0 |
23 self._stack = [] |
23 self._stack = [] |
24 self._collapsed = collapsed |
24 self._collapsed_nodes = collapsed_nodes |
25 |
25 |
26 def __next__(self): |
26 def __next__(self): |
27 node = None |
27 node = None |
28 while node is None: |
28 while node is None: |
29 try: |
29 try: |
30 if self._node in self._collapsed: |
30 if self._node in self._collapsed_nodes: |
31 raise IndexError() |
31 raise IndexError() |
32 node = self._node.children[self._index] |
32 node = self._node.children[self._index] |
33 if node is None: |
33 if node is None: |
34 raise Exception('Bad node: None') |
34 raise Exception('Bad node: None') |
35 except IndexError: |
35 except IndexError: |
175 class TreeView(Widget): |
175 class TreeView(Widget): |
176 """Tree view displays data from tree model.""" |
176 """Tree view displays data from tree model.""" |
177 |
177 |
178 def __init__(self, model=None, width=20, height=20): |
178 def __init__(self, model=None, width=20, height=20): |
179 Widget.__init__(self, width, height) |
179 Widget.__init__(self, width, height) |
180 |
|
181 self.allow_focus = True |
180 self.allow_focus = True |
182 |
|
183 # cursor |
|
184 self.cnode = None |
|
185 |
181 |
186 # model |
182 # model |
187 self._model = None |
183 self._model = None |
188 |
184 |
189 self.collapsed = [] |
185 # cursor |
|
186 self._cursor_node = None |
|
187 |
|
188 self.collapsed_nodes = [] |
190 |
189 |
191 self.add_events( |
190 self.add_events( |
192 'expand', TreeEvent, # node expanded, event carries the affected node |
191 'expand', TreeEvent, # node expanded, event carries the affected node |
193 'collapse', TreeEvent) # node collapsed, event carries the affected node |
192 'collapse', TreeEvent) # node collapsed, event carries the affected node |
194 |
193 |
195 if model: |
194 if model: |
196 self.model = model |
195 self.model = model |
197 |
196 |
198 def __iter__(self): |
197 def __iter__(self): |
199 return TreeIter(self._model.root, self.collapsed) |
198 return TreeIter(self._model.root, self.collapsed_nodes) |
200 |
199 |
201 @property |
200 @property |
202 def model(self): |
201 def model(self): |
203 """TreeModel in use by this TreeView.""" |
202 """TreeModel in use by this TreeView.""" |
204 return self._model |
203 return self._model |
209 self._model.disconnect('node_added', self.on_model_node_added) |
208 self._model.disconnect('node_added', self.on_model_node_added) |
210 self._model = value |
209 self._model = value |
211 if self._model: |
210 if self._model: |
212 self._model.connect('node_added', self.on_model_node_added) |
211 self._model.connect('node_added', self.on_model_node_added) |
213 try: |
212 try: |
214 self.cnode = self._model.root.children[0] |
213 self.cursor_node = self._model.root.children[0] |
215 except IndexError: |
214 except IndexError: |
216 pass |
215 pass |
217 self._update_sizereq() |
216 self._update_sizereq() |
218 |
217 |
219 def on_model_node_added(self, ev): |
218 def on_model_node_added(self, ev): |
220 if self.cnode is None: |
219 if self.cursor_node is None: |
221 self.cnode = ev.node |
220 self.cursor_node = ev.node |
222 self._update_sizereq() |
221 self._update_sizereq() |
223 self.redraw() |
222 self.redraw() |
|
223 |
|
224 @property |
|
225 def cursor_node(self): |
|
226 return self._cursor_node |
|
227 |
|
228 @cursor_node.setter |
|
229 def cursor_node(self, value): |
|
230 self._cursor_node = value |
|
231 self._update_spot() |
224 |
232 |
225 def collapse(self, path, collapse=True): |
233 def collapse(self, path, collapse=True): |
226 node = self._model.find(path) |
234 node = self._model.find(path) |
227 self.collapse_node(node, collapse) |
235 self.collapse_node(node, collapse) |
228 |
236 |
229 def collapse_node(self, node, collapse=True): |
237 def collapse_node(self, node, collapse=True): |
230 if collapse: |
238 if collapse: |
231 if not node in self.collapsed and len(node.children) > 0: |
239 if not node in self.collapsed_nodes and len(node.children) > 0: |
232 self.collapsed.append(node) |
240 self.collapsed_nodes.append(node) |
233 self.emit('collapse', node) |
241 self.emit('collapse', node) |
234 else: |
242 else: |
235 try: |
243 try: |
236 self.collapsed.remove(node) |
244 self.collapsed_nodes.remove(node) |
237 self.emit('expand', node) |
245 self.emit('expand', node) |
238 except ValueError: |
246 except ValueError: |
239 pass |
247 pass |
240 self._update_sizereq() |
248 self._update_sizereq() |
241 |
249 |
260 else: |
268 else: |
261 head.append(ev.driver.unigraph.LLCORNER) |
269 head.append(ev.driver.unigraph.LLCORNER) |
262 lines &= ~(1 << level-1) |
270 lines &= ~(1 << level-1) |
263 # draw lines and titles |
271 # draw lines and titles |
264 head = ''.join(head) |
272 head = ''.join(head) |
265 if node in self.collapsed: |
273 if node in self.collapsed_nodes: |
266 sep = '+' |
274 sep = '+' |
267 else: |
275 else: |
268 sep = ' ' |
276 sep = ' ' |
269 ev.driver.puts(ev.x, y, head + sep + str(node)) |
277 ev.driver.puts(ev.x, y, head + sep + str(node)) |
270 if node is self.cnode: |
278 if node is self.cursor_node: |
271 ev.driver.pushcolor('active') |
279 ev.driver.pushcolor('active') |
272 ev.driver.puts(ev.x + len(head), y, sep + str(node) + ' ') |
280 ev.driver.puts(ev.x + len(head), y, sep + str(node) + ' ') |
273 ev.driver.popcolor() |
281 ev.driver.popcolor() |
274 y += 1 |
282 y += 1 |
275 |
283 |
289 # previous sibling |
297 # previous sibling |
290 parent = node.parent |
298 parent = node.parent |
291 i = parent.children.index(node) |
299 i = parent.children.index(node) |
292 if i > 0: |
300 if i > 0: |
293 node = parent.children[i-1] |
301 node = parent.children[i-1] |
294 while node not in self.collapsed and len(node.children) > 0: |
302 while node not in self.collapsed_nodes and len(node.children) > 0: |
295 node = node.children[-1] |
303 node = node.children[-1] |
296 return node |
304 return node |
297 else: |
305 else: |
298 if parent.parent is None: |
306 if parent.parent is None: |
299 return None |
307 return None |
300 return parent |
308 return parent |
301 |
309 |
302 def next_node(self, node): |
310 def next_node(self, node): |
303 if node in self.collapsed or len(node.children) == 0: |
311 if node in self.collapsed_nodes or len(node.children) == 0: |
304 # next sibling |
312 # next sibling |
305 parent = node.parent |
313 parent = node.parent |
306 while parent is not None: |
314 while parent is not None: |
307 i = parent.children.index(node) |
315 i = parent.children.index(node) |
308 try: |
316 try: |
313 return None |
321 return None |
314 else: |
322 else: |
315 return node.children[0] |
323 return node.children[0] |
316 |
324 |
317 def move_up(self): |
325 def move_up(self): |
318 prev = self.prev_node(self.cnode) |
326 prev = self.prev_node(self.cursor_node) |
319 if prev is not None: |
327 if prev is not None: |
320 self.cnode = prev |
328 self.cursor_node = prev |
321 return True |
329 return True |
322 return False |
330 return False |
323 |
331 |
324 def move_down(self): |
332 def move_down(self): |
325 node = self.next_node(self.cnode) |
333 node = self.next_node(self.cursor_node) |
326 if node is not None: |
334 if node is not None: |
327 self.cnode = node |
335 self.cursor_node = node |
328 return True |
336 return True |
329 return False |
337 return False |
330 |
338 |
331 def move_left(self): |
339 def move_left(self): |
332 self.collapse_node(self.cnode, True) |
340 self.collapse_node(self.cursor_node, True) |
333 |
341 |
334 def move_right(self): |
342 def move_right(self): |
335 self.collapse_node(self.cnode, False) |
343 self.collapse_node(self.cursor_node, False) |
336 |
344 |
337 def _update_sizereq(self): |
345 def _update_sizereq(self): |
338 height = 0 |
346 height = 0 |
339 for num, _ in enumerate(self, start=1): |
347 for num, _ in enumerate(self, start=1): |
340 height = num |
348 height = num |
341 self.sizereq.h = height |
349 self.sizereq.h = height |
342 |
350 |
|
351 def _update_spot(self): |
|
352 """Update spot to current position of cursor node.""" |
|
353 for num, (_level, _index, _count, node) in enumerate(self): |
|
354 if node is self.cursor_node: |
|
355 self._spot.x = _level * 2 |
|
356 self._spot.y = num |
|
357 self.emit('spotmove') |
|
358 |