TreeView: Add cursor, node collapse, reworked draw.
--- /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()
+
--- /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)
--- /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()
--- 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')
--- 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)
+