# HG changeset patch # User Radek Brich # Date 1313617432 -7200 # Node ID 762513aacc87aa4c78b3ad13c54d1e4c104e6210 # Parent ec1d47e6fe09f20a1d99224e7c02df914dd132f3 TreeView: Add cursor, node collapse, reworked draw. diff -r ec1d47e6fe09 -r 762513aacc87 example_treeview.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/example_treeview.py Wed Aug 17 23:43:52 2011 +0200 @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import locale +locale.setlocale(locale.LC_ALL, '') + +from tuikit import * + + +class MyApplication(Application): + def __init__(self): + Application.__init__(self) + self.top.connect('keypress', self.globalkeypress) + + model = TreeModel() + model.add('/', ['a', 'b']) + model.add('/a', ['c', 'd']) + model.add((0,1), ['e', 'f']) + model.add('/0/1/0', 'g') + model.add('/a/d/f', 'h') + + view = TreeView(model) + view.collapse('/a/d') + self.top.add(view) + + vert = VerticalLayout() + self.top.layout(vert) + + def globalkeypress(self, keyname, char): + if keyname == 'escape': + self.terminate() + + +if __name__ == '__main__': + app = MyApplication() + app.start() + diff -r ec1d47e6fe09 -r 762513aacc87 tests/gridlayout_rowspan.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/gridlayout_rowspan.py Wed Aug 17 23:43:52 2011 +0200 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from tuikit import * + +# +---+---+---+ +# | 0 | 1 | +# +---+---+ + +# | 2 | 3 | | +# +---+ + + +# | 4 | | | +# +---+---+---+ +# +# 0 - colspan = 2 +# 1 - rowspan = 3 +# 3 - rowspan = 2 + +if __name__ == '__main__': + cont = Container() + + w = [None] * 6 + for i in range(6): + w[i] = Widget() + cont.add(w[i]) + + w[0].layouthints['colspan'] = 2 + + w[1].sizemin = (2,2) + w[3].sizemin = (6,1) + + grid = GridLayout(3) + grid.container = cont + + print('* _fillgrid') + grid._fillgrid() + + print('span:') + for row in grid._grid: + for col in row: + print('[%s,%d,%d]' % ( + col['widget'].__class__.__name__[0], + col['colspan'], + col['rowspan']), end=' ') + print() + + print('* _computesizes') + grid._computesizes() + + print('sizemin:') + for row in grid._grid: + for col in row: + w = col['widget'] + if w is None: + print('[0,0]', end=' ') + else: + print('[%d,%d]' % w.sizemin, end=' ') + print() + + print('colminw:') + print(grid._colminw) + + print('rowminh:') + print(grid._rowminh) diff -r ec1d47e6fe09 -r 762513aacc87 tests/test_treeview.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_treeview.py Wed Aug 17 23:43:52 2011 +0200 @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +from tuikit.treeview import * +import unittest + +class TestTreeView(unittest.TestCase): + def test_treemodel(self): + '''Build tree model, iterate through the tree, test result.''' + # build tree model + # root + # ├ a + # │ ├ c + # │ └ d + # │ ├ e + # │ │ └ g + # │ └ f + # │ └ h + # └ b + model = TreeModel() + model.add('/', ['a', 'b']) + model.add('/a', ['c', 'd']) + model.add((0,1), ['e', 'f']) + model.add('/0/1/0', 'g') + model.add('/a/d/f', 'h') + + res = '' + for l, i, c, n in model: + res += str(l) + str(i) + str(c) + n.name + + self.assertEqual(res, '112a212c222d312e411g322f411h122b') + + +if __name__ == '__main__': + unittest.main() diff -r ec1d47e6fe09 -r 762513aacc87 tuikit/application.py --- a/tuikit/application.py Wed Aug 17 21:11:47 2011 +0200 +++ b/tuikit/application.py Wed Aug 17 23:43:52 2011 +0200 @@ -121,6 +121,7 @@ def applytheme(self): screen = self.screen screen.setcolor('normal', 'white on black') + screen.setcolor('active', 'black on cyan') screen.setcolor('window:normal', 'white on blue') screen.setcolor('window:controls', 'white on blue, bold') screen.setcolor('window:controls-active', 'cyan on blue, bold') diff -r ec1d47e6fe09 -r 762513aacc87 tuikit/treeview.py --- a/tuikit/treeview.py Wed Aug 17 21:11:47 2011 +0200 +++ b/tuikit/treeview.py Wed Aug 17 23:43:52 2011 +0200 @@ -4,16 +4,18 @@ from .widget import Widget class TreeIter: - def __init__(self, model): - self.model = model - self._node = self.model.root + def __init__(self, root, collapsed=[]): + self._node = root self._index = 0 self._stack = [] + self._collapsed = collapsed def __next__(self): node = None while node is None: try: + if self._node in self._collapsed: + raise IndexError() node = self._node[self._index] if node is None: raise Exception('Bad node: None') @@ -23,8 +25,8 @@ else: raise StopIteration - name = node.name level = len(self._stack) + 1 + index = self._index + 1 count = len(self._node) self._index += 1 @@ -33,13 +35,18 @@ self._node = node self._index = 0 - return (level, count, name) + return (level, index, count, node) class TreeNode(list): - def __init__(self, name=''): + def __init__(self, parent=None, name=''): list.__init__(self) + self.parent = parent self.name = name + + def __eq__(self, other): + # do not compare by list content + return self is other class TreeModel(EventSource): @@ -49,9 +56,9 @@ self.root = TreeNode() def __iter__(self): - return TreeIter(self) + return TreeIter(self.root) - def add(self, path, names): + def find(self, path): if isinstance(path, str): path = path.split('/') # strip empty strings from both ends @@ -75,21 +82,38 @@ item = int(item) node = node[item] + return node + + def add(self, path, names): + node = self.find(path) + if isinstance(names, str): names = (names,) for name in names: - node.append(TreeNode(name)) + node.append(TreeNode(node, name)) self.emit('change') class TreeView(Widget): - def __init__(self, width=20, height=20, model=None): + def __init__(self, model=None, width=20, height=20): Widget.__init__(self, width, height) + + # cursor + self.cnode = None + + # model self._model = None self.setmodel(model) + + self.collapsed = [] + self.connect('draw', self.on_draw) + self.connect('keypress', self.on_keypress) + + def __iter__(self): + return TreeIter(self._model.root, self.collapsed) def getmodel(self): '''TreeModel in use by this TreeView.''' @@ -101,27 +125,118 @@ self._model = value if self._model: self._model.connect('change', self.redraw) + try: + self.cnode = self._model.root[0] + except IndexError: + pass model = property(getmodel, setmodel) + def collapse(self, path, collapse=True): + node = self._model.find(path) + self.collapse_node(node, collapse) + + def collapse_node(self, node, collapse=True): + if collapse: + if not node in self.collapsed and len(node) > 0: + self.collapsed.append(node) + else: + try: + self.collapsed.remove(node) + except ValueError: + pass + def on_draw(self, screen, x, y): screen.pushcolor('normal') - self.draw_children(self._model.root, screen, x, y) - - screen.popcolor() - - #screen.VLINE - #screen.LTEE - #screen.LLCORNER - + lines = 0 # bit array, bit 0 - draw vertical line on first column, etc. + for level, index, count, node in self: + # prepare string with vertical lines where they should be + head = [] + for l in range(level-1): + if lines & (1 << l): + head.append(screen.VLINE + ' ') + else: + head.append(' ') + # add vertical line if needed + if index < count: + head.append(screen.LTEE) + lines |= 1 << level-1 + else: + head.append(screen.LLCORNER) + lines &= ~(1 << level-1) + # draw lines and name + head = ''.join(head) + if node in self.collapsed: + sep = '+' + else: + sep = ' ' + screen.puts(x, y, head + sep + node.name) + if node is self.cnode: + screen.pushcolor('active') + screen.puts(x + len(head), y, sep + node.name + ' ') + screen.popcolor() + + y += 1 - def draw_children(self, parent, screen, x, y): - orig_y = y - for node in parent: - screen.puts(x, y, '- ' + node.name) - y += 1 - if len(node): - y += self.draw_children(node, screen, x + 2, y) - return y - orig_y + screen.popcolor() + + def on_keypress(self, keyname, char): + if keyname: + if keyname == 'up': self.move_up() + if keyname == 'down': self.move_down() + if keyname == 'left': self.move_left() + if keyname == 'right': self.move_right() + + self.redraw() + + def prev_node(self, node): + # previous sibling + parent = node.parent + i = parent.index(node) + if i > 0: + node = parent[i-1] + while node not in self.collapsed and len(node) > 0: + node = node[-1] + return node + else: + if parent.parent is None: + return None + return parent + def next_node(self, node): + if node in self.collapsed or len(node) == 0: + # next sibling + parent = node.parent + while parent is not None: + i = parent.index(node) + try: + return parent[i+1] + except IndexError: + node = parent + parent = node.parent + return None + else: + # first child + return node[0] + + def move_up(self): + prev = self.prev_node(self.cnode) + if prev is not None: + self.cnode = prev + return True + return False + + def move_down(self): + next = self.next_node(self.cnode) + if next is not None: + self.cnode = next + return True + return False + + def move_left(self): + self.collapse_node(self.cnode, True) + + def move_right(self): + self.collapse_node(self.cnode, False) +