[py-svn] r10293 - in py/branch/py-collect: bin test/tkinter test/tkinter/icons
jan at codespeak.net
jan at codespeak.net
Mon Apr 4 17:32:41 CEST 2005
Author: jan
Date: Mon Apr 4 17:32:41 2005
New Revision: 10293
Added:
py/branch/py-collect/bin/py.test.tkinter (contents, props changed)
py/branch/py-collect/test/tkinter/
py/branch/py-collect/test/tkinter/__init__.py
py/branch/py-collect/test/tkinter/gui.py
py/branch/py-collect/test/tkinter/guidriver.py
py/branch/py-collect/test/tkinter/icons/
py/branch/py-collect/test/tkinter/icons/gray.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green_aqua.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green_aqua.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green_s.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green_s.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green_s_aqua.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/green_s_aqua.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red_aqua.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red_aqua.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red_c.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red_c.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red_c_aqua.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/red_c_aqua.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/yellow.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/yellow.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/yellow_aqua.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/yellow_aqua.png (contents, props changed)
py/branch/py-collect/test/tkinter/icons/yellow_s.gif (contents, props changed)
py/branch/py-collect/test/tkinter/icons/yellow_s.png (contents, props changed)
py/branch/py-collect/test/tkinter/repository.py
py/branch/py-collect/test/tkinter/test_exceptionfailure.py
py/branch/py-collect/test/tkinter/test_guidriver.py
py/branch/py-collect/test/tkinter/test_repository.py
py/branch/py-collect/test/tkinter/test_utils.py
py/branch/py-collect/test/tkinter/tkgui.py
py/branch/py-collect/test/tkinter/tktree.py
py/branch/py-collect/test/tkinter/utils.py
Log:
First checkin of a Tkinter frontend for py.test.
Start with py.test.tkinter.
Added: py/branch/py-collect/bin/py.test.tkinter
==============================================================================
--- (empty file)
+++ py/branch/py-collect/bin/py.test.tkinter Mon Apr 4 17:32:41 2005
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+from _findpy import py
+from py.__impl__.test.tkinter import tkgui
+tkgui.main()
Added: py/branch/py-collect/test/tkinter/__init__.py
==============================================================================
Added: py/branch/py-collect/test/tkinter/gui.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/gui.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,180 @@
+from tktree import Tree, Node
+from utils import TestReport, Status, Null
+from Tkinter import PhotoImage
+import py
+Item = py.test.Item
+Collector = py.test.Collector
+
+
+class ResultTree(Tree):
+
+ def __init__(self, *args, **kwargs):
+ kwdict = {'dist_x': 19, 'dist_y': 19,
+ 'selection_callback': Null(),
+ 'double_click_callback': Null()}
+ kwdict.update(kwargs)
+ self.set_selection_callback(kwdict['selection_callback'])
+ self.set_double_click_callback(kwdict['double_click_callback'])
+ del kwdict['selection_callback']
+ del kwdict['double_click_callback']
+ self.setup_icons()
+
+ Tree.__init__(self, *args, **kwdict)
+ self.save_view()
+ self.save_selection()
+ self.bind('<4>', self.prev)
+ self.bind('<5>', self.next)
+
+ def setup_icons(self):
+ iconpath = py.magic.autopath().dirpath('icons')
+ self.icon_green = PhotoImage(file = str(iconpath.join('green.gif')))
+ self.icon_red = PhotoImage(file = str(iconpath.join('red.gif')))
+ self.icon_yellow = PhotoImage(file = str(iconpath.join('yellow.gif')))
+ self.icon_gray = PhotoImage(file = str(iconpath.join('gray.gif')))
+
+ self.icon_map = {str(Status.NotExecuted()): self.icon_gray,
+ str(Status.Passed()): self.icon_green,
+ str(Status.Failed()): self.icon_red,
+ str(Status.Skipped()): self.icon_yellow,
+ str(Status.ExceptionFailure()): self.icon_red}
+
+ def get_icon(self, status):
+ status = str(status)
+ return self.icon_map[status]
+
+ def add_node(self, *args, **kwargs):
+ report = kwargs.setdefault('report', TestReport())
+ icon = self.get_icon(report.status)
+ kwdict = {'collapsed_icon': icon, 'expanded_icon': icon}
+ del kwargs['report']
+ kwdict.update(kwargs)
+ Tree.add_node(self, *args, **kwdict)
+
+ def set_selection_callback(self, callback):
+ self._selection_callback = callback
+
+ def set_double_click_callback(self, callback):
+ self._double_click_callback = callback
+
+ def double_click_callback(self, node = None):
+ self._double_click_callback(self.pos)
+
+ def save_view(self):
+ self.saved_view = self.get_expanded_state()
+
+ def save_selection(self):
+ self.saved_selection_id = self.cursor_node(None).full_id()
+
+ def restore_view(self):
+ self.apply_expanded_state(self.saved_view)
+
+ def restore_selection(self):
+ if self.find_full_id(self.saved_selection_id):
+ self.move_cursor(self.find_full_id(self.saved_selection_id))
+
+ def next(self, event = None):
+ Tree.next(self, event)
+ self.save_selection()
+
+ def prev(self, event = None):
+ Tree.prev(self, event)
+ self.save_selection()
+
+
+ def move_cursor(self, node):
+ Tree.move_cursor(self, node)
+ self._selection_callback(node)
+
+ def listnodes(self, startnode = None):
+ if startnode is None:
+ startnode = self.root
+ ret = [startnode]
+ for child in startnode.children():
+ ret.append(child)
+ ret.extend(self.listnodes(child))
+ return ret
+
+
+ def get_expanded_state(self, key = None):
+ "returns [node.full_id() for all_nodes if node.expanded() == True"
+ if key is None:
+ key = self.root.full_id()
+ if not self.find_full_id(key) or not self.find_full_id(key).expanded():
+ return []
+ startnode = self.find_full_id(key)
+ return [node.full_id() for node in self.listnodes(startnode) if node.expanded()]
+
+ def apply_expanded_state(self, id_list):
+ ids = [item for item in id_list]
+ ids.sort()
+ for id in ids:
+ node = self.find_full_id(id)
+ if node is None or not node.expandable():
+ continue
+ node.expand()
+ pass
+
+ def get_parent(self, node_id):
+ node = self.find_full_id(node_id)
+ if node:
+ return node.parent()
+ return self.find_full_id(node_id[:-1])
+
+ def update_root(self, report):
+ self.root.set_collapsed_icon(self.get_icon(report.status))
+ self.root.set_expanded_icon(self.get_icon(report.status))
+ self.root.set_label(report.label)
+
+ def update_node(self, node_id):
+ if node_id == self.root.full_id() or self.get_parent(node_id) is None:
+ parent = self.root
+ else:
+ parent = self.get_parent(node_id)
+
+ if not parent.expandable():
+ self.update_node(parent.full_id())
+ return
+
+ if parent.expanded():
+ parent.toggle_state()
+ parent.toggle_state()
+ self.restore_view()
+ self.restore_selection()
+
+
+
+class ResultNode(Node):
+
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+ Node.__init__(self, *args, **kwargs)
+ self.widget.tag_bind(self.symbol, '<Button-1>',
+ self.PVT_click_select, add='+')
+ self.widget.tag_bind(self.label, '<Button-1>',
+ self.PVT_click_select, add='+')
+ self.widget.tag_bind(self.symbol, '<Double-Button-1>',
+ self.PVT_double_click, add='+')
+ self.widget.tag_bind(self.label, '<Double-Button-1>',
+ self.PVT_double_click, add='+')
+
+
+
+ def get_node_args(self):
+ return self.args, self.kwargs
+
+ def PVT_click(self, event):
+ Node.PVT_click(self, event)
+ self.widget.save_selection()
+ self.widget.save_view()
+
+ def PVT_click_select(self, event):
+ # not interested in dnd, so just do a select
+ self.widget.move_cursor(self)
+ self.widget.save_selection()
+
+ def PVT_double_click(self, event):
+ self.widget.double_click_callback(self)
+
+
+
Added: py/branch/py-collect/test/tkinter/guidriver.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/guidriver.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,100 @@
+
+import py
+from utils import Status, TestReport, Null
+from py.__impl__.test.drive import Exit, exit, SimpleOutErrCapture
+
+import pprint
+
+class GuiDriver(py.test.Driver):
+
+ def __init__(self, option = None, channel = None):
+ super(GuiDriver, self).__init__(option)
+ self.channel = channel
+ self.reportlist = []
+
+ def info(self, **kwargs):
+ return
+ print '-' * 60
+ #print 'reportlist:', self.reportlist
+ for key, value in kwargs.items():
+ print key,':', pprint.pformat(value)
+ if isinstance(kwargs.get('res'), (py.test.Item.Passed,
+ py.test.Item.Skipped,
+ py.test.Item.Failed)):
+ print '!' *80
+ print 'py.test.Collector.Outcome found'
+ print '!' *80
+ print '-' * 60
+
+ def header(self, colitems):
+ super(GuiDriver, self).header(colitems)
+ report = TestReport()
+ report.settime()
+ self.reportlist = [report]
+ self.sendreport(report)
+
+ def footer(self, colitems):
+ super(GuiDriver, self).footer(colitems)
+ report = self.reportlist.pop()
+ report.settime()
+ self.sendreport(report)
+ self.channel.send(None)
+
+ def start(self, colitem):
+ super(GuiDriver, self).start(colitem)
+ report = TestReport()
+ report.start(colitem)
+ self.reportlist.append(report)
+ self.sendreport(report)
+
+
+ def finish(self, colitem, res):
+ super(GuiDriver, self).finish(colitem, res)
+ report = self.reportlist.pop()
+ report.finish(colitem, res, self.option)
+ self.reportlist[-1].status.update(report.status)
+ self.sendreport(report)
+ #py.std.time.sleep(0.5)
+
+ def sendreport(self, report):
+ self.channel.send(report.toChannel())
+
+ def warning(self, msg):
+ pass
+
+ # hack! Driver.runone should be patched
+ def runone(self, colitem):
+ if self.shouldclose():
+ raise SystemExit, "received external close signal"
+
+ capture = None
+ if not self.option.nocapture and isinstance(colitem, py.test.Item):
+ capture = SimpleOutErrCapture()
+ res = None
+ try:
+ self.start(colitem)
+ try:
+ try:
+ res = self.runinner(colitem)
+ except (KeyboardInterrupt, Exit):
+ res = None
+ raise
+ except colitem.Outcome, res:
+ res.excinfo = py.code.ExceptionInfo()
+ except:
+ excinfo = py.code.ExceptionInfo()
+ res = colitem.Failed(excinfo=excinfo)
+ else:
+ assert (res is None or
+ isinstance(res, (list, colitem.Outcome)))
+ finally:
+ if capture is not None:
+ out, err = capture.reset()
+ try:
+ res.out, res.err = out, err
+ except AttributeError:
+ pass
+ self.finish(colitem, res)
+ finally:
+ pass
+
Added: py/branch/py-collect/test/tkinter/icons/gray.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green_aqua.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green_aqua.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green_s.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green_s.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green_s_aqua.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/green_s_aqua.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red_aqua.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red_aqua.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red_c.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red_c.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red_c_aqua.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/red_c_aqua.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/yellow.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/yellow.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/yellow_aqua.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/yellow_aqua.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/yellow_s.gif
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/icons/yellow_s.png
==============================================================================
Binary file. No diff available.
Added: py/branch/py-collect/test/tkinter/repository.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/repository.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,170 @@
+import py
+Item = py.test.Item
+Collector = py.test.Collector
+
+
+
+import copy
+import time
+
+from utils import Null
+from itertools import izip, count
+
+import UserDict
+
+## import logging
+## log = logging
+## def initlog():
+## global log
+## remote = logging.FileHandler('./remote.log')
+## remote.setLevel(logging.DEBUG)
+## formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
+## remote.setFormatter(formatter)
+## log = logging.getLogger('remote')
+## log.addHandler(remote)
+
+
+
+class Repository(object):
+ nothing = object()
+
+ def __init__(self):
+ self.root = self.newnode()
+
+ def newnode(self):
+ return [self.nothing, OrderedDictMemo()]
+
+ def copy(self):
+ newrepos = Repository()
+ newrepos.root = copy.deepcopy(self.root)
+ return newrepos
+
+ def add(self, key, value):
+ node = self.root
+ for k in key:
+ node = node[1].setdefault(k, self.newnode())
+ node[0] = value
+
+ def find_tuple(self, key=[]):
+ node = self.root
+ for k in key:
+ node = node[1][k]
+ return node
+
+ def find(self, key=[]):
+ return self.find_tuple(key)[0]
+
+ def haskey(self, key):
+ try:
+ value = self.find(key)
+ except KeyError:
+ return False
+ return True
+
+ def haskeyandvalue(self, key):
+ if self.haskey(key):
+ value = self.find(key)
+ return value is not self.nothing
+ return False
+
+ def find_children(self, key=[]):
+ if self.haskey(key):
+ node = self.find_tuple(key)
+ return [list(key) + [childname] for childname in node[1].keys()]
+ return []
+
+ def keys(self, startkey=[]):
+ ret = []
+ for key in self.find_children(startkey):
+ ret.append(key)
+ ret.extend(self.keys(key))
+ return ret
+
+ def removestalekeys(self, key):
+ if self.find_children(key) == [] and not self.haskeyandvalue(key):
+ if len(key) > 0:
+ parent = self.find_tuple(key[:-1])
+ del parent[1][key[-1]]
+ self.removestalekeys(key[:-1])
+
+
+ def delete(self, key):
+ if self.haskeyandvalue(key):
+ node = self.find_tuple(key)
+ node[0] = self.newnode()[0]
+ self.removestalekeys(key)
+
+ def delete_all(self, key):
+ if self.haskeyandvalue(key):
+ node = self.find_tuple(key)
+ node[0], node[1] = self.newnode()[0], self.newnode()[1]
+ self.removestalekeys(key)
+
+ def values(self, startkey=[]):
+ return [self.find(key) for key in self.keys(startkey)]
+
+ def items(self, startkey=[]):
+ return [(key, self.find(key)) for key in self.keys(startkey)]
+
+
+class OrderedDict(UserDict.DictMixin):
+
+ def __init__(self, *args, **kwargs):
+ self._dict = dict(*args, **kwargs)
+ self._keys = self._dict.keys()
+
+ def __getitem__(self, key):
+ return self._dict.__getitem__(key)
+
+ def __setitem__(self, key, value):
+ self._dict.__setitem__(key, value)
+ try:
+ self._keys.remove(key)
+ except ValueError:
+ pass
+ self._keys.append(key)
+
+ def __delitem__(self, key):
+ self._dict.__delitem__(key)
+ self._keys.remove(key)
+
+ def keys(self):
+ return self._keys[:]
+
+ def copy(self):
+ new = OrderedDict()
+ for key, value in self.iteritems():
+ new[key] = value
+ return new
+
+class OrderedDictMemo(UserDict.DictMixin):
+
+ def __init__(self, *args, **kwargs):
+ self._dict = dict(*args, **kwargs)
+ self._keys = self._dict.keys()
+
+ def __getitem__(self, key):
+ return self._dict.__getitem__(key)
+
+ def __setitem__(self, key, value):
+ self._dict.__setitem__(key, value)
+ if key not in self._keys:
+ self._keys.append(key)
+
+ def __delitem__(self, key):
+ self._dict.__delitem__(key)
+
+ def keys(self):
+ return [key for key in self._keys if key in self._dict]
+
+ def copy(self):
+ new = OrderedDict()
+ for key, value in self.iteritems():
+ new[key] = value
+ return new
+
+
+
+
+
+
Added: py/branch/py-collect/test/tkinter/test_exceptionfailure.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/test_exceptionfailure.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,7 @@
+
+import time
+
+import py
+
+def test_exceptionfailure():
+ py.test.raises(TypeError, 'time.time()')
Added: py/branch/py-collect/test/tkinter/test_guidriver.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/test_guidriver.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,63 @@
+
+import py
+import guidriver
+GuiDriver = guidriver.GuiDriver
+
+from utils import Status, TestReport, Null
+
+class TestGuiDriver:
+
+ class ChannelMock:
+
+ def __init__(self, receinvelist = []):
+ self.reset(receinvelist)
+
+ def reset(self, receivelist = []):
+ self.receivelist = receivelist
+ self.sendlist = []
+
+ def send(self, obj):
+ self.sendlist.append(obj)
+
+ def receive(self):
+ return self.receivelist.pop(0)
+
+ def setup_method(self, method):
+ self.channel = self.ChannelMock()
+ self.driver = GuiDriver(Null(), self.channel)
+ self.collitems = [Null(), Null()]
+
+ def test_header_sends_report_with_id_root(self):
+ self.driver.header(self.collitems)
+
+ assert self.channel.sendlist != []
+ report = TestReport.fromChannel(self.channel.sendlist[0])
+ assert report.status == Status.NotExecuted()
+ assert report.id == 'Root'
+ assert report.label == 'Root'
+
+ def test_footer_sends_report_and_None(self):
+ self.driver.header(self.collitems)
+ self.driver.footer(self.collitems)
+
+ assert self.channel.sendlist != []
+ assert self.channel.sendlist[-1] is None
+ report = TestReport.fromChannel(self.channel.sendlist[-2])
+ assert report.status == Status.NotExecuted()
+ assert report.id == 'Root'
+
+## def test_status_is_passed_to_root(self):
+## self.driver.header(self.collitems)
+## self.driver.start(self.collitems[0])
+## self.driver.finish(self.collitems[0], py.test.Collector.Failed())
+## self.driver.footer(self.collitems)
+
+## assert self.channel.sendlist[-1] is None
+## assert self.channel.sendlist.pop() is None
+
+## report = TestReport.fromChannel(self.channel.sendlist[-1])
+## assert report.name == 'Root'
+## assert report.status == Status.Failed()
+
+
+
Added: py/branch/py-collect/test/tkinter/test_repository.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/test_repository.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,170 @@
+
+from repository import Repository, OrderedDict, OrderedDictMemo
+import py
+Item = py.test.Item
+
+
+import itertools
+
+
+class TestRepository:
+
+ def setup_method(self, method):
+ self.rep = Repository()
+
+ def test_add_find_single_value(self):
+ key = ['key']
+ value = 'value'
+ self.rep.add(key, value)
+ assert self.rep.find(key) == value
+
+ def test_add_works_like_update(self):
+ key = 'k e y'.split()
+ value = 'value'
+ value2 = 'value2'
+ self.rep.add(key, value)
+ self.rep.add(key, value2)
+ assert self.rep.find(key) == value2
+
+ def test_haskeyandvalue(self):
+ key = 'first_middle_last'
+ value = 'value'
+ self.rep.add(key, value)
+ assert self.rep.haskeyandvalue(key)
+ assert not self.rep.haskeyandvalue('first')
+ for index in range(1, len(key[0])):
+ assert not self.rep.haskeyandvalue(key[0:index])
+
+ def test_add_find_subkey(self):
+ key = ('key', 'subkey')
+ value = 'subvalue'
+ self.rep.add(key, value)
+ self.rep.add((key[0],), 'value')
+ assert self.rep.find(key) == value
+
+ def test_find_raises_KeyError(self):
+ py.test.raises(KeyError, self.rep.find, 'nothing')
+
+ def test_haskey(self):
+ self.rep.add('key', 'value')
+ assert self.rep.haskey('key') == True
+ assert self.rep.haskey('katja') == False
+ assert self.rep.haskey('ke') == True
+
+ def test_find_children_empyt_repository(self):
+ assert self.rep.find_children() == []
+
+ def test_find_children(self):
+ self.rep.add(['c'], 'childvalue')
+ self.rep.add('c a'.split(), 'a')
+ self.rep.add('c b'.split(), 'b')
+ assert self.rep.find_children(['c']) == [ ['c','a'], ['c','b']]
+ assert self.rep.find_children() == [['c']]
+
+ def test_find_children_with_tuple_key(self):
+ key = tuple('k e y'.split())
+ value = 'value'
+ self.rep.add(key, value)
+ assert self.rep.find_children([]) == [['k']]
+ assert self.rep.find_children(('k', 'e')) == [['k', 'e', 'y']]
+
+ def test_keys(self):
+ keys = [ 'a b c'.split(), 'a b'.split(), ['a']]
+ for key in keys:
+ self.rep.add(key, 'value')
+ assert len(keys) == len(self.rep.keys())
+ for key in self.rep.keys():
+ assert key in keys
+ for key in keys:
+ assert key in self.rep.keys()
+
+ def test_delete_simple(self):
+ key = 'k'
+ value = 'value'
+ self.rep.add(key, value)
+ self.rep.delete(key)
+ assert self.rep.haskeyandvalue(key) == False
+
+
+ def test_removestallkeys_remove_all(self):
+ key = 'k e y'.split()
+ value = 'value'
+ self.rep.add(key, value)
+ node = self.rep.find_tuple(key)
+ node[0] = self.rep.newnode()[0]
+ self.rep.removestalekeys(key)
+ assert self.rep.keys() == []
+
+ def test_removestallkeys_dont_remove_parent(self):
+ key = 'k e y'.split()
+ key2 = 'k e y 2'.split()
+ value = 'value'
+ self.rep.add(key, value)
+ self.rep.add(key2, self.rep.newnode()[0])
+ self.rep.removestalekeys(key2)
+ assert self.rep.haskey(key2) == False
+ assert self.rep.haskeyandvalue(key)
+
+ def test_removestallkeys_works_with_parameter_root(self):
+ self.rep.removestalekeys([])
+
+ def test_copy(self):
+ key = 'k e y'.split()
+ key2 = 'k e y 2'.split()
+ value = 'value'
+ self.rep.add(key, value)
+ self.rep.add(key2, value)
+ newrep = self.rep.copy()
+ assert newrep.root is not self.rep.root
+ assert newrep.find(key) == self.rep.find(key)
+
+
+
+class TestOrderedDict:
+
+ def setup_method(self, method):
+ self.dict = OrderedDict()
+
+ def test_add(self):
+ self.dict['key'] = 'value'
+ assert 'key' in self.dict
+
+ def test_order(self):
+ keys = range(3)
+ for k in keys:
+ self.dict[k] = str(k)
+ assert keys == self.dict.keys()
+
+class TestOrderedDictMemo(TestOrderedDict):
+
+ def setup_method(self, method):
+ self.dict = OrderedDictMemo()
+
+ def test_insert(self):
+ self.dict['key1'] = 1
+ self.dict['key2'] = 2
+ del self.dict['key1']
+ self.dict['key1'] = 1
+ assert self.dict.keys() == ['key1', 'key2']
+
+
+class Test_Failing:
+ """ will fail!!"""
+
+ def test_fail(self):
+ print 'recorded stdout'
+ m = dict(((1,2),(3,4)))
+ l = (1,m)
+ assert False == l
+
+ def test_calling_fail(self):
+ py.test.fail('Try to fail')
+
+ def test_skip(self):
+ print 'recorded stdout'
+ py.test.skip('Try to skip this test')
+
+ def test_skip_on_error(self):
+ py.test.skip_on_error(''.index, 'hello')
+
+
Added: py/branch/py-collect/test/tkinter/test_utils.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/test_utils.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,118 @@
+
+import utils
+from utils import Status, TestReport, OutBuffer
+import py
+Item = py.test.Item
+
+class TestStatus:
+
+ def test_init_with_None(self):
+ status = Status(None)
+ assert status == status.NotExecuted()
+
+ def test_str(self):
+ status = Status(Item.Passed())
+ assert status == status.Passed()
+
+ status = Status(Item.Failed())
+ assert status == status.Failed()
+
+ status = Status(Item.Skipped())
+ assert status == status.Skipped()
+
+ status = Status(Item.ExceptionFailure())
+ assert status == status.ExceptionFailure()
+
+
+ def test_init_with_bad_name(self):
+ status = Status('nothing')
+ assert status == Status.NotExecuted()
+
+ def test_init_with_good_name(self):
+ def check_str(obj, expected):
+ assert str(obj) == expected
+
+ for name in Status.ordered_list:
+ yield check_str, Status(name), name
+
+ def test_update(self):
+ failed = Status.Failed()
+ passed = Status.Passed()
+ failed.update(passed)
+ assert failed == Status.Failed()
+
+ passed.update(failed)
+ assert passed == Status.Failed()
+ assert passed == failed
+
+ def test_eq_(self):
+ passed = Status.Passed()
+ assert passed == passed
+ assert passed == Status.Passed()
+
+ failed = Status.Failed()
+ assert failed != passed
+
+
+class TestTestReport:
+
+ def setup_method(self, method):
+ self.path = py.path.local()
+ self.collector = py.test.Directory(self.path)
+ self.testresult = TestReport()
+
+ def test_start(self):
+ self.testresult.start(self.collector)
+
+ assert self.testresult.full_id == self.collector.listnames()
+ assert self.testresult.time != 0
+ assert self.testresult.status == Status.NotExecuted()
+
+ def test_finish(self):
+ self.testresult.start(self.collector)
+
+ py.std.time.sleep(1.1)
+
+ self.testresult.finish(self.collector, None)
+ assert self.testresult.time > 1
+ assert self.testresult.status == Status.NotExecuted()
+
+## def test_finish_failed(self):
+## self.testresult.start(self.collector)
+
+## self.testresult.finish(self.collector, py.test.Collector.Failed())
+## assert self.testresult.status == Status.Failed()
+
+
+
+ def test_toChannel_fromChannel(self):
+ assert isinstance(self.testresult.toChannel()['status'], str)
+ result = TestReport.fromChannel(self.testresult.toChannel())
+ assert isinstance(result.status, Status)
+
+ def test_copy(self):
+ result2 = self.testresult.copy()
+ assert self.testresult.status == Status.NotExecuted()
+ for key in TestReport.template.keys():
+ assert getattr(result2, key) == getattr(self.testresult, key)
+
+ self.testresult.status = Status.Failed()
+ assert result2.status != self.testresult.status
+
+
+class Test_OutBuffer:
+
+ def setup_method(self, method):
+ self.out = OutBuffer()
+
+ def test_line(self):
+ oneline = 'oneline'
+ self.out.line(oneline)
+ assert self.out.getoutput() == oneline + '\n'
+
+ def test_write(self):
+ item = 'item'
+ self.out.write(item)
+ assert self.out.getoutput() == item
+
+
Added: py/branch/py-collect/test/tkinter/tkgui.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/tkgui.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,366 @@
+import Tkinter
+tk = Tkinter
+import ScrolledText
+import time
+import threading
+import Queue
+import sys
+import os
+import re
+import sha
+
+import py
+from py.test import Driver
+Item = py.test.Item
+Collector = py.test.Collector
+
+import repository
+import utils
+from utils import TestReport, Status, Null
+import gui
+
+
+class TkMain:
+ def __init__(self, parent, queue, endcommand, startcommand, repositorycommand, manager = Null()):
+ self.parent = parent
+ self.queue = queue
+ self.endcommand = endcommand
+ self.startcommand = startcommand
+ self.repositorycommand = repositorycommand
+ self.manager = manager
+ # GUI
+ self.text = Null()
+ self.createwidgets()
+ start = Tkinter.Button(parent, text='Start', command=startcommand)
+ start.pack(side = tk.TOP)
+ done = Tkinter.Button(parent, text='Done', command=endcommand)
+ done.pack(side = tk.TOP)
+
+
+ def createwidgets(self):
+ self.mainframe = tk.Frame(self.parent)
+ self.treeframe = tk.Frame(self.mainframe)
+ self.tree = gui.ResultTree(master=self.treeframe, root_id='Root',
+ get_contents_callback=self.create_new_nodes,
+ width=400, node_class = gui.ResultNode,
+ selection_callback=self.selection_callback,
+ double_click_callback = self.double_click_callback)
+ sb=tk.Scrollbar(self.treeframe)
+ sb.pack(side=tk.RIGHT, fill = tk.Y)
+ self.tree.configure(yscrollcommand=sb.set)
+ sb.configure(command=self.tree.yview)
+
+ sb=tk.Scrollbar(self.treeframe, orient=tk.HORIZONTAL)
+ sb.pack(side=tk.BOTTOM, fill = tk.X)
+ self.tree.configure(xscrollcommand=sb.set)
+ sb.configure(command=self.tree.xview)
+
+ self.tree.pack(side = tk.LEFT, expand=tk.YES, fill = tk.BOTH)
+ self.treeframe.pack(side = tk.TOP, expand = tk.YES, fill = tk.BOTH)
+
+
+ self.text = ScrolledText.ScrolledText(self.mainframe)
+ self.text.pack(side = tk.TOP, expand=tk.YES, fill = tk.BOTH)
+ self.mainframe.pack(side = tk.LEFT, expand = tk.YES, fill = tk.BOTH)
+
+ def double_click_callback(self, node):
+ self.startcommand(rerun_ids=[node.full_id()])
+
+ def selection_callback(self, node):
+ # check if node exists
+ if not self.repositorycommand().haskey(node.full_id()):
+ return
+ report = self.repositorycommand().find(node.full_id())
+ self.text['state'] = tk.NORMAL
+ self.text.delete(1.0, tk.END)
+ self.text.tag_config('sel', relief=tk.FLAT)
+ self.text.insert(tk.END, report.error_report)
+ #if len(report.error_report.splitlines()) < 20:
+ # self.text.config(height=len(report.error_report.splitlines()) + 5)
+ #self.text.yview_pickplace(tk.END)
+ self.attacheditorhotspots(self.text)
+ self.text['state'] = tk.DISABLED
+ pass
+
+ def attacheditorhotspots(self, text):
+ # Attach clickable regions to a Text widget.
+ filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""")
+ skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""")
+ lines = text.get('1.0', tk.END).splitlines(1)
+ if not lines:
+ return
+ tagname = ''
+ start, end = 0,0
+ for index, line in enumerate(lines):
+ match = filelink.search(line)
+ if match is None:
+ match = skippedlink.search(line)
+ if match is None:
+ continue
+ file, line = match.group(1, 2)
+ start, end = match.span()
+ tagname = "ref%d" % index
+ text.tag_add(tagname,
+ "%d.%d" % (index + 1, start),
+ "%d.%d" % (index + 1, end))
+ text.tag_bind(tagname, "<Enter>",
+ lambda e, n=tagname:
+ e.widget.tag_config(n, underline=1))
+ text.tag_bind(tagname, "<Leave>",
+ lambda e, n=tagname:
+ e.widget.tag_config(n, underline=0))
+ text.tag_bind(tagname, "<Button-1>",
+ lambda e, self=self, f=file, l=line:
+ self.launch_editor(f, l))
+
+ def launch_editor(self, file, line):
+ editor = (py.std.os.environ.get('PYUNIT_EDITOR', None) or
+ py.std.os.environ.get('EDITOR_REMOTE', None) or
+ os.environ.get('EDITOR', None) or "emacsclient --no-wait ")
+ #"emacsclient --no-wait ")
+ if editor:
+ print "%s +%s %s" % (editor, line, file)
+ py.process.cmdexec('%s +%s %s' % (editor, line, file))
+
+
+ def create_new_nodes(self, node):
+ repos = self.repositorycommand()
+ id = node.full_id()
+ for child in repos.find_children(id):
+ if not repos.haskeyandvalue(child):
+ continue
+ report = repos.find(child)
+ name = report.label
+ folder = 0
+ if repos.find_children(child):
+ folder = 1
+ self.tree.add_node(name=name, id=report.id, flag=folder, report = report)
+
+ def display_action(self, action):
+ self.parent.title(str(action))
+
+ def process_messages(self, manager):
+ """
+ Handle all the messages currently in the queue (if any).
+ """
+ while self.queue.qsize():
+ try:
+ full_id = self.queue.get(0)
+ if full_id is None:
+ root_status = self.repositorycommand().find(self.tree.root.full_id()).status
+ self.display_action(root_status)
+ if root_status != Status.Passed():
+ self.parent.bell()
+ py.std.time.sleep(0.2)
+ self.parent.bell()
+ else:
+ self.tree.update_node(full_id)
+ self.display_action('running: ' + str(full_id[-1]) )
+ except Queue.Empty:
+ pass
+ self.tree.update_root(self.repositorycommand().find(self.tree.root.full_id()))
+
+
+class Manager:
+ """
+ Launch the main part of the GUI and the worker thread. periodicCall and
+ endApplication could reside in the GUI part, but putting them here
+ means that you have all the thread controls in a single place.
+ """
+ def __init__(self, parent):
+ """
+ Start the GUI and the asynchronous threads. We are in the main
+ (original) thread of the application, which will later be used by
+ the GUI.
+ """
+ self.parent = parent
+
+ self.reset_repository()
+ self.args = py.std.sys.argv[1:]
+ self.paths = py.test.config.init(self.args)[1]
+ self.testfilewatcher = utils.TestFileWatcher(*self.paths)
+ # Create the queue
+ self.guiqueue = Queue.Queue()
+ self.reporterqueue = Queue.Queue()
+
+ self.should_stop = False
+
+ # Set up the GUI part
+ self.gui = TkMain(parent, self.guiqueue, self.endApplication, self.start_tests, self.getrepository, manager = self)
+
+ # Set up the thread to do asynchronous I/O
+ self.thread = Null()
+ # Start the periodic call in the GUI to check if the queue contains
+ # anything
+ self.check_messages()
+
+ def reset_repository(self, ids = []):
+ if not ids:
+ self.repository = self.get_new_repository()
+ return
+ for id in ids:
+ self.repository.delete_all(id)
+ for key, value in self.get_new_repository().items():
+ if not self.repository.haskey(key):
+ self.repository.add(key, value)
+
+
+ def get_new_repository(self):
+ new_repository = repository.Repository()
+ template = TestReport()
+ new_repository.add(['Root'], template.copy())
+ for name in [str(x) for x in (Status.Failed(),
+ Status.Skipped(),
+ Status.ExceptionFailure())]:
+ new_repository.add(['Root', name], template.copy(id = name, label= name))
+ return new_repository
+
+
+ def thread_is_alive(self):
+ return self.thread.isAlive() == True
+ running = property(thread_is_alive)
+
+
+ def getrepository(self):
+ return self.repository
+
+
+ def check_messages(self):
+ """
+ Check every 200 ms if there is something new in the queue.
+ """
+ while self.reporterqueue.qsize():
+ try:
+ report_fromChannel = self.reporterqueue.get(0)
+ if report_fromChannel is None:
+ self.guiqueue.put(None)
+ else:
+ report = TestReport.fromChannel(report_fromChannel)
+ root_id = TestReport().full_id
+ # keep root up-to-date
+ self.repository.find(root_id).status.update(report.status)
+ if report.full_id == root_id:
+ # don't overwrite overall status on partial run
+ report.status = Status(self.repository.find(root_id).status)
+ else:
+ # hack: prefix Root for all Tests, except Root
+ report.full_id = ['Root'] + report.full_id
+ id = report.full_id
+ self.repository.add(id, report)
+ added_name = self.add_status(report)
+ if added_name is not None:
+ self.guiqueue.put(added_name)
+ self.guiqueue.put(id)
+ except Queue.Empty:
+ pass
+
+ self.gui.process_messages(self)
+ if self.should_stop:
+ # This is the brutal stop of the system. You may want to do
+ # some cleanup before actually shutting it down.
+ import sys
+ sys.exit(1)
+ if not self.running and self.testfilewatcher.changed():
+ self.start_tests()
+ self.parent.after(200, self.check_messages)
+
+ def add_status(self, newreport):
+ report = newreport.copy()
+ status = report.status
+ if report.error_report:
+ folder = ('Root', str(report.status))
+ # unique id, for failing tests with the same name
+ report.id = report.id + str(report.full_id)
+ id = folder + (report.id,)
+ self.repository.find(folder).status.update(report.status)
+ self.repository.add(id, report)
+ return id
+
+ def endApplication(self):
+ self.should_stop = True
+ #print 'System is shutting down now!'
+ #print 'join thread %s' % self.thread
+ self.thread.join()
+ #print 'Thread is down'
+
+
+ def get_restart_params_list(self, id):
+ repos = self.getrepository()
+ if repos.find(id).restart_params is not None:
+ return [repos.find(id).restart_params]
+ params = []
+ for child_id in repos.find_children(id):
+ params.extend(self.get_restart_params_list(child_id))
+ return params
+
+ def start_tests(self, filenames = [], rerun_ids = []):
+ if self.running:
+ return
+ rerun_cols = []
+ for id in rerun_ids:
+ rerun_cols.extend(self.get_restart_params_list(id))
+
+ self.reset_repository(rerun_ids)
+ if filenames == []:
+ filenames = [str(p) for p in self.paths]
+ kwargs = {'rerun_cols': rerun_cols}
+ if rerun_cols == []:
+ kwargs['rerun_cols'] = [(filename,[]) for filename in filenames]
+
+ self.thread = threading.Thread(target=self.run_tests_threaded,
+ kwargs = kwargs)
+ self.thread.start()
+
+
+ def run_tests_threaded(self, rerun_cols = None):
+ gw = py.execnet.PopenGateway(sys.executable)
+ try:
+ channel = gw.remote_exec("""
+ import py
+ from py.__impl__.test.tkinter.guidriver import GuiDriver
+ from py.__impl__.test.tkinter import repository
+ import os
+ import time
+ from py.__impl__.test.run import FailureCollector
+
+ rerun_cols, args = channel.receive()
+ col = FailureCollector(rerun_cols)
+ # hack!!
+ frontenddriver, paths = py.test.config.init(args)
+ driver = GuiDriver(option = frontenddriver.option, channel = channel)
+ driver.run(col)
+ """)
+ channel.send((rerun_cols, self.args))
+ self.receive_all_data(channel)
+ self.channel_waitclose(channel)
+ finally:
+ channel.close()
+ channel.gateway.exit()
+
+ def receive_all_data(self, channel):
+ while True:
+ data = channel.receive()
+ self.reporterqueue.put(data)
+ if data is None:
+ break
+
+
+ def channel_waitclose(self, channel):
+ while 1:
+ try:
+ channel.waitclose(0.1)
+ except (IOError, py.error.Error):
+ continue
+ break
+
+def main():
+ root = Tkinter.Tk()
+
+ client = Manager(root)
+ root.protocol('WM_DELETE_WINDOW', client.endApplication)
+ root.mainloop()
+
+
+if __name__ == '__main__':
+ main()
+
Added: py/branch/py-collect/test/tkinter/tktree.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/tktree.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,911 @@
+# Highly optimized Tkinter tree control
+# by Charles E. "Gene" Cash <email gcash at cfl.rr.com>
+#
+# This is documented more fully on my homepage at
+# http://home.cfl.rr.com/genecash/ and if it's not there, look in the Vaults
+# of Parnassus at http://www.vex.net/parnassus/ which I promise to keep
+# updated.
+#
+# Thanks to Laurent Claustre <claustre at esrf.fr> for sending lots of helpful
+# bug reports.
+#
+# This copyright license is intended to be similar to the FreeBSD license.
+#
+# Copyright 1998 Gene Cash All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# This means you may do anything you want with this code, except claim you
+# wrote it. Also, if it breaks you get to keep both pieces.
+#
+# 02-DEC-98 Started writing code.
+# 22-NOV-99 Changed garbage collection to a better algorithm.
+# 28-AUG-01 Added logic to deal with exceptions in user callbacks.
+# 02-SEP-01 Fixed hang when closing last node.
+# 07-SEP-01 Added binding tracking so nodes got garbage-collected.
+# Also fixed subclass call to initialize Canvas to properly deal
+# with variable arguments and keyword arguments.
+# 11-SEP-01 Bugfix for unbinding code.
+# 13-OCT-01 Added delete & insert methods for nodes (by email request).
+# LOTS of code cleanup.
+# Changed leading double underscores to PVT nomenclature.
+# Added ability to pass Node subclass to Tree constructor.
+# Removed after_callback since subclassing Node is better idea.
+# 15-OCT-01 Finally added drag'n'drop support. It consisted of a simple
+# change to the Node PVT_click method, and addition of logic like
+# the example in Tkdnd.py. It took 3 days to grok the Tkdnd
+# example and 2 hours to make the code changes. Plus another 1/2
+# day to get a working where() function.
+# 16-OCT-01 Incorporated fixes to delete() and dnd_commit() bugs by
+# Laurent Claustre <claustre at esrf.fr>.
+# 17-OCT-01 Added find_full_id() and cursor_node() methods.
+# 18-OCT-01 Fixes to delete() on root during collapse and with
+# drag-in-progress flag by Laurent Claustre <claustre at esrf.fr>.
+# 10-FEB-02 Fix to prev_visible() by Nicolas Pascal <pascal at esrf.fr>.
+# Fixes which made insert_before()/insert_after() actually work.
+# Also added expand/collapse indicators like Internet Explorer
+# as requested by Nicolas.
+# 11-FEB-02 Another fix to prev_visible(). It works this time. Honest.
+# 31-MAY-02 Added documentation strings so the new PYthon 2.2 help function
+# is a little more useful.
+# 19-AUG-02 Minor fix to eliminate crash in "treedemo-icons.py" caused by
+# referencing expand/collapse indicators when lines are turned off.
+# 15-OCT-02 Used new idiom for calling Canvas superclass.
+# 18-NOV-02 Fixed bug discovered by Amanjit Gill <amanjit.gill at gmx.de>, where
+# I didn't pass "master" properly to the Canvas superclass. Sigh.
+# One step forward, one step back.
+
+import Tkdnd
+from Tkinter import *
+
+#------------------------------------------------------------------------------
+def report_callback_exception():
+ """report exception on sys.stderr."""
+ import traceback
+ import sys
+
+ sys.stderr.write("Exception in Tree control callback\n")
+ traceback.print_exc()
+
+#------------------------------------------------------------------------------
+class Struct:
+ """Helper object for add_node() method"""
+ def __init__(self):
+ pass
+
+#------------------------------------------------------------------------------
+class Node:
+ """Tree helper class that's instantiated for each element in the tree. It
+ has several useful attributes:
+ parent_node - immediate parent node
+ id - id assigned at creation
+ expanded_icon - image displayed when folder is expanded to display
+ children
+ collapsed_icon - image displayed when node is not a folder or folder is
+ collapsed.
+ parent_widget - reference to tree widget that contains node.
+ expandable_flag - is true when node is a folder that may be expanded or
+ collapsed.
+ expanded_flag - true to indicate node is currently expanded.
+ h_line - canvas line to left of node image.
+ v_line - canvas line below node image that connects children.
+ indic - expand/collapse canvas image.
+ label - canvas text label
+ symbol - current canvas image
+
+ Please note that methods prefixed PVT_* are not meant to be used by
+ client programs."""
+
+ def __init__(self, parent_node, id, collapsed_icon, x, y,
+ parent_widget=None, expanded_icon=None, label=None,
+ expandable_flag=0):
+ """Create node and initialize it. This also displays the node at the
+ given position on the canvas, and binds mouseclicks."""
+ # immediate parent node
+ self.parent_node=parent_node
+ # internal name used to manipulate things
+ self.id=id
+ # bitmaps to be displayed
+ self.expanded_icon=expanded_icon
+ self.collapsed_icon=collapsed_icon
+ # tree widget we belong to
+ if parent_widget:
+ self.widget=parent_widget
+ else:
+ self.widget=parent_node.widget
+ # for speed
+ sw=self.widget
+ # our list of child nodes
+ self.child_nodes=[]
+ # flag that node can be expanded
+ self.expandable_flag=expandable_flag
+ self.expanded_flag=0
+ # add line
+ if parent_node and sw.line_flag:
+ self.h_line=sw.create_line(x, y, x-sw.dist_x, y)
+ else:
+ self.h_line=None
+ self.v_line=None
+ # draw approprate image
+ self.symbol=sw.create_image(x, y, image=self.collapsed_icon)
+ # add expand/collapse indicator
+ self.indic=None
+ if expandable_flag and sw.line_flag and sw.plus_icon and sw.minus_icon:
+ self.indic=sw.create_image(x-sw.dist_x, y, image=sw.plus_icon)
+ # add label
+ self.label=sw.create_text(x+sw.text_offset, y, text=label, anchor='w')
+ # single-click to expand/collapse
+ if self.indic:
+ sw.tag_bind(self.indic, '<1>', self.PVT_click)
+ else:
+ sw.tag_bind(self.symbol, '<1>', self.PVT_click)
+ ## Modification: symbol always clickable
+ #sw.tag_bind(self.symbol, '<1>', self.PVT_click)
+
+ # for drag'n'drop target detection
+ sw.tag_bind(self.symbol, '<Any-Enter>', self.PVT_enter)
+ sw.tag_bind(self.label, '<Any-Enter>', self.PVT_enter)
+
+ # for testing (gotta make sure nodes get properly GC'ed)
+ #def __del__(self):
+ # print self.full_id(), 'deleted'
+
+ # ----- PUBLIC METHODS -----
+ def set_collapsed_icon(self, icon):
+ """Set node's collapsed image"""
+ self.collapsed_icon=icon
+ if not self.expanded_flag:
+ self.widget.itemconfig(self.symbol, image=icon)
+
+ def set_expanded_icon(self, icon):
+ """Set node's expanded image"""
+ self.expanded_icon=icon
+ if self.expanded_flag:
+ self.widget.itemconfig(self.symbol, image=icon)
+
+ def parent(self):
+ """Return node's parent node"""
+ return self.parent_node
+
+ def prev_sib(self):
+ """Return node's previous sibling (the child immediately above it)"""
+ i=self.parent_node.child_nodes.index(self)-1
+ if i >= 0:
+ return self.parent_node.child_nodes[i]
+ else:
+ return None
+
+ def next_sib(self):
+ """Return node's next sibling (the child immediately below it)"""
+ i=self.parent_node.child_nodes.index(self)+1
+ if i < len(self.parent_node.child_nodes):
+ return self.parent_node.child_nodes[i]
+ else:
+ return None
+
+ def next_visible(self):
+ """Return next lower visible node"""
+ n=self
+ if n.child_nodes:
+ # if you can go right, do so
+ return n.child_nodes[0]
+ while n.parent_node:
+ # move to next sibling
+ i=n.parent_node.child_nodes.index(n)+1
+ if i < len(n.parent_node.child_nodes):
+ return n.parent_node.child_nodes[i]
+ # if no siblings, move to parent's sibling
+ n=n.parent_node
+ # we're at bottom
+ return self
+
+ def prev_visible(self):
+ """Return next higher visible node"""
+ n=self
+ if n.parent_node:
+ i=n.parent_node.child_nodes.index(n)-1
+ if i < 0:
+ return n.parent_node
+ else:
+ j=n.parent_node.child_nodes[i]
+ return j.PVT_last()
+ else:
+ return n
+
+ def children(self):
+ """Return list of node's children"""
+ return self.child_nodes[:]
+
+ def get_label(self):
+ """Return string containing text of current label"""
+ return self.widget.itemcget(self.label, 'text')
+
+ def set_label(self, label):
+ """Set current text label"""
+ self.widget.itemconfig(self.label, text=label)
+
+ def expanded(self):
+ """Returns true if node is currently expanded, false otherwise"""
+ return self.expanded_flag
+
+ def expandable(self):
+ """Returns true if node can be expanded (i.e. if it's a folder)"""
+ return self.expandable_flag
+
+ def full_id(self):
+ """Return list of IDs of all parents and node ID"""
+ if self.parent_node:
+ return self.parent_node.full_id()+(self.id,)
+ else:
+ return (self.id,)
+
+ def expand(self):
+ """Expand node if possible"""
+ if not self.expanded_flag:
+ self.PVT_set_state(1)
+
+ def collapse(self):
+ """Collapse node if possible"""
+ if self.expanded_flag:
+ self.PVT_set_state(0)
+
+ def delete(self, me_too=1):
+ """Delete node from tree. ("me_too" is a hack not to be used by
+ external code, please!)"""
+ sw=self.widget
+ if not self.parent_node and me_too:
+ # can't delete the root node
+ raise ValueError, "can't delete root node"
+ self.PVT_delete_subtree()
+ # move everything up so that distance to next subnode is correct
+ n=self.next_visible()
+ x1, y1=sw.coords(self.symbol)
+ x2, y2=sw.coords(n.symbol)
+ if me_too:
+ dist=y2-y1
+ else:
+ dist=y2-y1-sw.dist_y
+ self.PVT_tag_move(-dist)
+ n=self
+ if me_too:
+ if sw.pos == self:
+ # move cursor if it points to current node
+ sw.move_cursor(self.parent_node)
+ self.PVT_unbind_all()
+ sw.delete(self.symbol)
+ sw.delete(self.label)
+ sw.delete(self.h_line)
+ sw.delete(self.v_line)
+ sw.delete(self.indic)
+ self.parent_node.child_nodes.remove(self)
+ # break circular ref now, so parent may be GC'ed later
+ n=self.parent_node
+ self.parent_node=None
+ n.PVT_cleanup_lines()
+ n.PVT_update_scrollregion()
+
+ def insert_before(self, nodes):
+ """Insert list of nodes as siblings before this node. Call parent
+ node's add_node() function to generate the list of nodes."""
+ i=self.parent_node.child_nodes.index(self)
+ self.parent_node.PVT_insert(nodes, i, self.prev_visible())
+
+ def insert_after(self, nodes):
+ """Insert list of nodes as siblings after this node. Call parent
+ node's add_node() function to generate the list of nodes."""
+ i=self.parent_node.child_nodes.index(self)+1
+ self.parent_node.PVT_insert(nodes, i, self.PVT_last())
+
+ def insert_children(self, nodes):
+ """Insert list of nodes as children of this node. Call node's
+ add_node() function to generate the list of nodes."""
+ self.PVT_insert(nodes, 0, self)
+
+ def toggle_state(self):
+ """Toggle node's state between expanded and collapsed, if possible"""
+ if self.expandable_flag:
+ if self.expanded_flag:
+ self.PVT_set_state(0)
+ else:
+ self.PVT_set_state(1)
+
+ # ----- functions for drag'n'drop support -----
+ def PVT_enter(self, event):
+ """detect mouse hover for drag'n'drop"""
+ self.widget.target=self
+
+ def dnd_end(self, target, event):
+ """Notification that dnd processing has been ended. It DOES NOT imply
+ that we've been dropped somewhere useful, we could have just been
+ dropped into deep space and nothing happened to any data structures,
+ or it could have been just a plain mouse-click w/o any dragging."""
+ if not self.widget.drag:
+ # if there's been no dragging, it was just a mouse click
+ self.widget.move_cursor(self)
+ self.toggle_state()
+ self.widget.drag=0
+
+ # ----- PRIVATE METHODS (prefixed with "PVT_") -----
+ # these methods are subject to change, so please try not to use them
+ def PVT_last(self):
+ """Return bottom-most node in subtree"""
+ n=self
+ while n.child_nodes:
+ n=n.child_nodes[-1]
+ return n
+
+ def PVT_find(self, search):
+ """Used by searching functions"""
+ if self.id != search[0]:
+ # this actually only goes tilt if root doesn't match
+ return None
+ if len(search) == 1:
+ return self
+ # get list of children IDs
+ i=map(lambda x: x.id, self.child_nodes)
+ # if there is a child that matches, search it
+ try:
+ return self.child_nodes[i.index(search[1])].PVT_find(search[1:])
+ except:
+ return None
+
+ def PVT_insert(self, nodes, pos, below):
+ """Create and insert new children. "nodes" is list previously created
+ via calls to add_list(). "pos" is index in the list of children where
+ the new nodes are inserted. "below" is node which new children should
+ appear immediately below."""
+ if not self.expandable_flag:
+ raise TypeError, 'not an expandable node'
+ # for speed
+ sw=self.widget
+ # expand and insert children
+ children=[]
+ self.expanded_flag=1
+ sw.itemconfig(self.symbol, image=self.expanded_icon)
+ if sw.minus_icon and sw.line_flag:
+ sw.itemconfig(self.indic, image=sw.minus_icon)
+ if len(nodes):
+ # move stuff to make room
+ below.PVT_tag_move(sw.dist_y*len(nodes))
+ # get position of first new child
+ xp, dummy=sw.coords(self.symbol)
+ dummy, yp=sw.coords(below.symbol)
+ xp=xp+sw.dist_x
+ yp=yp+sw.dist_y
+ # create vertical line
+ if sw.line_flag and not self.v_line:
+ self.v_line=sw.create_line(
+ xp, yp,
+ xp, yp+sw.dist_y*len(nodes))
+ sw.tag_lower(self.v_line, self.symbol)
+ n=sw.node_class
+ for i in nodes:
+ # add new subnodes, they'll draw themselves
+ # this is a very expensive call
+ children.append(
+ n(parent_node=self, expandable_flag=i.flag, label=i.name,
+ id=i.id, collapsed_icon=i.collapsed_icon,
+ expanded_icon=i.expanded_icon, x=xp, y=yp))
+ yp=yp+sw.dist_y
+ self.child_nodes[pos:pos]=children
+ self.PVT_cleanup_lines()
+ self.PVT_update_scrollregion()
+ sw.move_cursor(sw.pos)
+
+ def PVT_set_state(self, state):
+ """Common code forexpanding/collapsing folders. It's not re-entrant,
+ and there are certain cases in which we can be called again before
+ we're done, so we use a mutex."""
+ while self.widget.spinlock:
+ pass
+ self.widget.spinlock=1
+ # expand & draw our subtrees
+ if state:
+ self.child_nodes=[]
+ self.widget.new_nodes=[]
+ if self.widget.get_contents_callback:
+ # this callback needs to make multiple calls to add_node()
+ try:
+ self.widget.get_contents_callback(self)
+ except:
+ report_callback_exception()
+ self.PVT_insert(self.widget.new_nodes, 0, self)
+ # collapse and delete subtrees
+ else:
+ self.expanded_flag=0
+ self.widget.itemconfig(self.symbol, image=self.collapsed_icon)
+ if self.indic:
+ self.widget.itemconfig(self.indic, image=self.widget.plus_icon)
+ self.delete(0)
+ # release mutex
+ self.widget.spinlock=0
+
+ def PVT_cleanup_lines(self):
+ """Resize connecting lines"""
+ if self.widget.line_flag:
+ n=self
+ while n:
+ if n.child_nodes:
+ x1, y1=self.widget.coords(n.symbol)
+ x2, y2=self.widget.coords(n.child_nodes[-1].symbol)
+ self.widget.coords(n.v_line, x1, y1, x1, y2)
+ n=n.parent_node
+
+ def PVT_update_scrollregion(self):
+ """Update scroll region for new size"""
+ x1, y1, x2, y2=self.widget.bbox('all')
+ self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5))
+
+ def PVT_delete_subtree(self):
+ """Recursively delete subtree & clean up cyclic references to make
+ garbage collection happy"""
+ sw=self.widget
+ sw.delete(self.v_line)
+ self.v_line=None
+ for i in self.child_nodes:
+ # delete node's subtree, if any
+ i.PVT_delete_subtree()
+ i.PVT_unbind_all()
+ # delete widgets from canvas
+ sw.delete(i.symbol)
+ sw.delete(i.label)
+ sw.delete(i.h_line)
+ sw.delete(i.v_line)
+ sw.delete(i.indic)
+ # break circular reference
+ i.parent_node=None
+ # move cursor if it's in deleted subtree
+ if sw.pos in self.child_nodes:
+ sw.move_cursor(self)
+ # now subnodes will be properly garbage collected
+ self.child_nodes=[]
+
+ def PVT_unbind_all(self):
+ """Unbind callbacks so node gets garbage-collected. This wasn't easy
+ to figure out the proper way to do this. See also tag_bind() for the
+ Tree widget itself."""
+ for j in (self.symbol, self.label, self.indic, self.h_line,
+ self.v_line):
+ for k in self.widget.bindings.get(j, ()):
+ self.widget.tag_unbind(j, k[0], k[1])
+
+ def PVT_tag_move(self, dist):
+ """Move everything below current icon, to make room for subtree using
+ the Disney magic of item tags. This is the secret of making
+ everything as fast as it is."""
+ # mark everything below current node as movable
+ bbox1=self.widget.bbox(self.widget.root.symbol, self.label)
+ bbox2=self.widget.bbox('all')
+ self.widget.dtag('move')
+ self.widget.addtag('move', 'overlapping',
+ bbox2[0], bbox1[3], bbox2[2], bbox2[3])
+ # untag cursor & node so they don't get moved too
+ self.widget.dtag(self.widget.cursor_box, 'move')
+ self.widget.dtag(self.symbol, 'move')
+ self.widget.dtag(self.label, 'move')
+ # now do the move of all the tagged objects
+ self.widget.move('move', 0, dist)
+
+ def PVT_click(self, event):
+ """Handle mouse clicks by kicking off possible drag'n'drop
+ processing"""
+ if self.widget.drop_callback:
+ if Tkdnd.dnd_start(self, event):
+ x1, y1, x2, y2=self.widget.bbox(self.symbol)
+ self.x_off=(x1-x2)/2
+ self.y_off=(y1-y2)/2
+ else:
+ # no callback, don't bother with drag'n'drop
+ self.widget.drag=0
+ self.dnd_end(None, None)
+
+#------------------------------------------------------------------------------
+class Tree(Canvas):
+ # do we have enough possible arguments?!?!?!
+ def __init__(self, master, root_id, root_label='',
+ get_contents_callback=None, dist_x=15, dist_y=15,
+ text_offset=10, line_flag=1, expanded_icon=None,
+ collapsed_icon=None, regular_icon=None, plus_icon=None,
+ minus_icon=None, node_class=Node, drop_callback=None,
+ *args, **kw_args):
+ # pass args to superclass (new idiom from Python 2.2)
+ Canvas.__init__(self, master, *args, **kw_args)
+
+ # this allows to subclass Node and pass our class in
+ self.node_class=node_class
+ # keep track of node bindings
+ self.bindings={}
+ # cheap mutex spinlock
+ self.spinlock=0
+ # flag to see if there's been any d&d dragging
+ self.drag=0
+ # default images (BASE64-encoded GIF files)
+ if expanded_icon == None:
+ self.expanded_icon=PhotoImage(
+ data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
+ 'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \
+ 'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7')
+ else:
+ self.expanded_icon=expanded_icon
+ if collapsed_icon == None:
+ self.collapsed_icon=PhotoImage(
+ data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
+ 'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \
+ 'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==')
+ else:
+ self.collapsed_icon=collapsed_icon
+ if regular_icon == None:
+ self.regular_icon=PhotoImage(
+ data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \
+ 'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \
+ 'wbzuJrIHgw1WgAAOw==')
+ else:
+ self.regular_icon=regular_icon
+ if plus_icon == None:
+ self.plus_icon=PhotoImage(
+ data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \
+ 'FIyPoiu2sJyCyoF7W3hxz850CFIA\nADs=')
+ else:
+ self.plus_icon=plus_icon
+ if minus_icon == None:
+ self.minus_icon=PhotoImage(
+ data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \
+ 'EYyPoivG614LAlg7ZZbxoR8UADs=')
+ else:
+ self.minus_icon=minus_icon
+ # horizontal distance that subtrees are indented
+ self.dist_x=dist_x
+ # vertical distance between rows
+ self.dist_y=dist_y
+ # how far to offset text label
+ self.text_offset=text_offset
+ # flag controlling connecting line display
+ self.line_flag=line_flag
+ # called just before subtree expand/collapse
+ self.get_contents_callback=get_contents_callback
+ # called after drag'n'drop
+ self.drop_callback=drop_callback
+ # create root node to get the ball rolling
+ self.root=node_class(parent_node=None, label=root_label,
+ id=root_id, expandable_flag=1,
+ collapsed_icon=self.collapsed_icon,
+ expanded_icon=self.expanded_icon,
+ x=dist_x, y=dist_y, parent_widget=self)
+ # configure for scrollbar(s)
+ x1, y1, x2, y2=self.bbox('all')
+ self.configure(scrollregion=(x1, y1, x2+5, y2+5))
+ # add a cursor
+ self.cursor_box=self.create_rectangle(0, 0, 0, 0)
+ self.move_cursor(self.root)
+ # make it easy to point to control
+ self.bind('<Enter>', self.PVT_mousefocus)
+ # totally arbitrary yet hopefully intuitive default keybindings
+ # stole 'em from ones used by microsoft tree control
+ # page-up/page-down
+ self.bind('<Next>', self.pagedown)
+ self.bind('<Prior>', self.pageup)
+ # arrow-up/arrow-down
+ self.bind('<Down>', self.next)
+ self.bind('<Up>', self.prev)
+ # arrow-left/arrow-right
+ self.bind('<Left>', self.ascend)
+ # (hold this down and you expand the entire tree)
+ self.bind('<Right>', self.descend)
+ # home/end
+ self.bind('<Home>', self.first)
+ self.bind('<End>', self.last)
+ # space bar
+ self.bind('<Key-space>', self.toggle)
+
+ # ----- PRIVATE METHODS (prefixed with "PVT_") -----
+ # these methods are subject to change, so please try not to use them
+ def PVT_mousefocus(self, event):
+ """Soak up event argument when moused-over"""
+ self.focus_set()
+
+ # ----- PUBLIC METHODS -----
+ def tag_bind(self, tag, seq, *args, **kw_args):
+ """Keep track of callback bindings so we can delete them later. I
+ shouldn't have to do this!!!!"""
+ # pass args to superclass
+ func_id=apply(Canvas.tag_bind, (self, tag, seq)+args, kw_args)
+ # save references
+ self.bindings[tag]=self.bindings.get(tag, [])+[(seq, func_id)]
+
+ def add_list(self, list=None, name=None, id=None, flag=0,
+ expanded_icon=None, collapsed_icon=None):
+ """Add node construction info to list"""
+ n=Struct()
+ n.name=name
+ n.id=id
+ n.flag=flag
+ if collapsed_icon:
+ n.collapsed_icon=collapsed_icon
+ else:
+ if flag:
+ # it's expandable, use closed folder icon
+ n.collapsed_icon=self.collapsed_icon
+ else:
+ # it's not expandable, use regular file icon
+ n.collapsed_icon=self.regular_icon
+ if flag:
+ if expanded_icon:
+ n.expanded_icon=expanded_icon
+ else:
+ n.expanded_icon=self.expanded_icon
+ else:
+ # not expandable, don't need an icon
+ n.expanded_icon=None
+ if list == None:
+ list=[]
+ list.append(n)
+ return list
+
+ def add_node(self, name=None, id=None, flag=0, expanded_icon=None,
+ collapsed_icon=None):
+ """Add a node during get_contents_callback()"""
+ self.add_list(self.new_nodes, name, id, flag, expanded_icon,
+ collapsed_icon)
+
+ def find_full_id(self, search):
+ """Search for a node"""
+ return self.root.PVT_find(search)
+
+ def cursor_node(self, search):
+ """Return node under cursor"""
+ return self.pos
+
+ def see(self, *items):
+ """Scroll (in a series of nudges) so items are visible"""
+ x1, y1, x2, y2=apply(self.bbox, items)
+ while x2 > self.canvasx(0)+self.winfo_width():
+ old=self.canvasx(0)
+ self.xview('scroll', 1, 'units')
+ # avoid endless loop if we can't scroll
+ if old == self.canvasx(0):
+ break
+ while y2 > self.canvasy(0)+self.winfo_height():
+ old=self.canvasy(0)
+ self.yview('scroll', 1, 'units')
+ if old == self.canvasy(0):
+ break
+ # done in this order to ensure upper-left of object is visible
+ while x1 < self.canvasx(0):
+ old=self.canvasx(0)
+ self.xview('scroll', -1, 'units')
+ if old == self.canvasx(0):
+ break
+ while y1 < self.canvasy(0):
+ old=self.canvasy(0)
+ self.yview('scroll', -1, 'units')
+ if old == self.canvasy(0):
+ break
+
+ def move_cursor(self, node):
+ """Move cursor to node"""
+ self.pos=node
+ x1, y1, x2, y2=self.bbox(node.symbol, node.label)
+ self.coords(self.cursor_box, x1-1, y1-1, x2+1, y2+1)
+ self.see(node.symbol, node.label)
+
+ def toggle(self, event=None):
+ """Expand/collapse subtree"""
+ self.pos.toggle_state()
+
+ def next(self, event=None):
+ """Move to next lower visible node"""
+ self.move_cursor(self.pos.next_visible())
+
+ def prev(self, event=None):
+ """Move to next higher visible node"""
+ self.move_cursor(self.pos.prev_visible())
+
+ def ascend(self, event=None):
+ """Move to immediate parent"""
+ if self.pos.parent_node:
+ # move to parent
+ self.move_cursor(self.pos.parent_node)
+
+ def descend(self, event=None):
+ """Move right, expanding as we go"""
+ if self.pos.expandable_flag:
+ self.pos.expand()
+ if self.pos.child_nodes:
+ # move to first subnode
+ self.move_cursor(self.pos.child_nodes[0])
+ return
+ # if no subnodes, move to next sibling
+ self.next()
+
+ def first(self, event=None):
+ """Go to root node"""
+ # move to root node
+ self.move_cursor(self.root)
+
+ def last(self, event=None):
+ """Go to last visible node"""
+ # move to bottom-most node
+ self.move_cursor(self.root.PVT_last())
+
+ def pageup(self, event=None):
+ """Previous page"""
+ n=self.pos
+ j=self.winfo_height()/self.dist_y
+ for i in range(j-3):
+ n=n.prev_visible()
+ self.yview('scroll', -1, 'pages')
+ self.move_cursor(n)
+
+ def pagedown(self, event=None):
+ """Next page"""
+ n=self.pos
+ j=self.winfo_height()/self.dist_y
+ for i in range(j-3):
+ n=n.next_visible()
+ self.yview('scroll', 1, 'pages')
+ self.move_cursor(n)
+
+ # ----- functions for drag'n'drop support -----
+ def where(self, event):
+ """Determine drag location in canvas coordinates. event.x & event.y
+ don't seem to be what we want."""
+ # where the corner of the canvas is relative to the screen:
+ x_org=self.winfo_rootx()
+ y_org=self.winfo_rooty()
+ # where the pointer is relative to the canvas widget,
+ # including scrolling
+ x=self.canvasx(event.x_root-x_org)
+ y=self.canvasy(event.y_root-y_org)
+ return x, y
+
+ def dnd_accept(self, source, event):
+ """Accept dnd messages, i.e. we're a legit drop target, and we do
+ implement d&d functions."""
+ self.target=None
+ return self
+
+ def dnd_enter(self, source, event):
+ """Get ready to drag or drag has entered widget (create drag
+ object)"""
+ # this flag lets us know there's been drag motion
+ self.drag=1
+ x, y=self.where(event)
+ x1, y1, x2, y2=source.widget.bbox(source.symbol, source.label)
+ dx, dy=x2-x1, y2-y1
+ # create dragging icon
+ if source.expanded_flag:
+ self.dnd_symbol=self.create_image(x, y,
+ image=source.expanded_icon)
+ else:
+ self.dnd_symbol=self.create_image(x, y,
+ image=source.collapsed_icon)
+ self.dnd_label=self.create_text(x+self.text_offset, y,
+ text=source.get_label(),
+ justify='left',
+ anchor='w')
+
+ def dnd_motion(self, source, event):
+ """Move drag icon"""
+ self.drag=1
+ x, y=self.where(event)
+ x1, y1, x2, y2=self.bbox(self.dnd_symbol, self.dnd_label)
+ self.move(self.dnd_symbol, x-x1+source.x_off, y-y1+source.y_off)
+ self.move(self.dnd_label, x-x1+source.x_off, y-y1+source.y_off)
+
+ def dnd_leave(self, source, event):
+ """Finish dragging or drag has left widget (destroy drag object)"""
+ self.delete(self.dnd_symbol)
+ self.delete(self.dnd_label)
+
+ def dnd_commit(self, source, event):
+ """Object has been dropped here"""
+ # call our own dnd_leave() to clean up
+ self.dnd_leave(source, event)
+ # process pending events to detect target node
+ # update_idletasks() doesn't do the trick if source & target are
+ # on different widgets
+ self.update()
+ if not self.target:
+ # no target node
+ return
+ # we must update data structures based on the drop
+ if self.drop_callback:
+ try:
+ # called with dragged node and target node
+ # this is where a file manager would move the actual file
+ # it must also move the nodes around as it wishes
+ self.drop_callback(source, self.target)
+ except:
+ report_callback_exception()
+
+#------------------------------------------------------------------------------
+# the good 'ol test/demo code
+if __name__ == '__main__':
+ import os
+ import sys
+
+ # default routine to get contents of subtree
+ # supply this for a different type of app
+ # argument is the node object being expanded
+ # should call add_node()
+ def get_contents(node):
+ path=apply(os.path.join, node.full_id())
+ for filename in os.listdir(path):
+ full=os.path.join(path, filename)
+ name=filename
+ folder=0
+ if os.path.isdir(full):
+ # it's a directory
+ folder=1
+ elif not os.path.isfile(full):
+ # but it's not a file
+ name=name+' (special)'
+ if os.path.islink(full):
+ # it's a link
+ name=name+' (link to '+os.readlink(full)+')'
+ node.widget.add_node(name=name, id=filename, flag=folder)
+
+ root=Tk()
+ root.title(os.path.basename(sys.argv[0]))
+ tree=os.sep
+ if sys.platform == 'win32':
+ # we could call the root "My Computer" and mess with get_contents()
+ # to return "A:", "B:", "C:", ... etc. as it's children, but that
+ # would just be terminally cute and I'd have to shoot myself
+ tree='C:'+os.sep
+
+ # create the control
+ t=Tree(master=root,
+ root_id=tree,
+ root_label=tree,
+ get_contents_callback=get_contents,
+ width=300)
+ t.grid(row=0, column=0, sticky='nsew')
+
+ # make expandable
+ root.grid_rowconfigure(0, weight=1)
+ root.grid_columnconfigure(0, weight=1)
+
+ # add scrollbars
+ sb=Scrollbar(root)
+ sb.grid(row=0, column=1, sticky='ns')
+ t.configure(yscrollcommand=sb.set)
+ sb.configure(command=t.yview)
+
+ sb=Scrollbar(root, orient=HORIZONTAL)
+ sb.grid(row=1, column=0, sticky='ew')
+ t.configure(xscrollcommand=sb.set)
+ sb.configure(command=t.xview)
+
+ # must get focus so keys work for demo
+ t.focus_set()
+
+ # we could do without this, but it's nice and friendly to have
+ Button(root, text='Quit', command=root.quit).grid(row=2, column=0,
+ columnspan=2)
+
+ # expand out the root
+ t.root.expand()
+
+ root.mainloop()
Added: py/branch/py-collect/test/tkinter/utils.py
==============================================================================
--- (empty file)
+++ py/branch/py-collect/test/tkinter/utils.py Mon Apr 4 17:32:41 2005
@@ -0,0 +1,247 @@
+import types, string, sys
+import py
+from py.__impl__.test.report.text import out
+from py.__impl__.test.terminal import TerminalDriver
+
+class Null:
+ """ Null objects always and reliably "do nothing." """
+
+ def __init__(self, *args, **kwargs): pass
+ def __call__(self, *args, **kwargs): return self
+ def __repr__(self): return "Null()"
+ def __str__(self): return repr(self) + ' with id:' + str(id(self))
+ def __nonzero__(self): return 0
+
+ def __getattr__(self, name): return self
+ def __setattr__(self, name, value): return self
+ def __delattr__(self, name): return self
+
+
+_NotExecuted = 'NotExecuted'
+_Passed = 'Passed'
+_Failed = 'Failed'
+_Skipped = 'Skipped'
+_ExceptionFailure = 'ExceptionFailure'
+
+class Status(object):
+
+ @classmethod
+ def NotExecuted(cls):
+ return cls('NotExecuted')
+ @classmethod
+ def Passed(cls):
+ return cls('Passed')
+ @classmethod
+ def Failed(cls):
+ return cls('Failed')
+ @classmethod
+ def Skipped(cls):
+ return cls('Skipped')
+ @classmethod
+ def ExceptionFailure(cls):
+ return cls(_ExceptionFailure)
+
+ ordered_list = [_NotExecuted,
+ _Passed,
+ _Skipped,
+ _Failed,
+ _ExceptionFailure]
+
+ namemap = {
+ py.test.Item.Passed: _Passed,
+ py.test.Item.Skipped: _Skipped,
+ py.test.Item.Failed: _Failed,
+ py.test.Item.ExceptionFailure: _ExceptionFailure }
+
+ def __init__(self, outcome_or_name = ''):
+ self.str = _NotExecuted
+ if isinstance(outcome_or_name, py.test.Collector.Outcome):
+ # hack
+ if isinstance(outcome_or_name, py.test.Item.ExceptionFailure):
+ self.str = self.namemap[py.test.Item.ExceptionFailure]
+ else:
+ for restype, name in self.namemap.items():
+ if isinstance(outcome_or_name, restype):
+ self.str = name
+ else:
+ if str(outcome_or_name) in self.ordered_list:
+ self.str = str(outcome_or_name)
+
+ def __repr__(self):
+ return 'Status("%s")' % self.str
+
+ def __str__(self):
+ return self.str
+
+ def update(self, status):
+ name_int_map = dict(
+ py.std.itertools.izip(self.ordered_list,
+ py.std.itertools.count()))
+ self.str = self.ordered_list[max([name_int_map[i] for i in (str(status), self.str)])]
+
+ def __eq__(self, other):
+ return self.str == other.str
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+class OutBuffer(out.Out):
+
+ def __init__(self, fullwidth = 80 -1):
+ self.output = []
+ self.fullwidth = fullwidth
+
+ def line(self, s= ''):
+ self.output.append(str(s) + '\n')
+
+ def write(self, s):
+ self.output.append(str(s))
+
+ def getoutput(self):
+ return ''.join(self.output)
+
+ def rewrite(self, s=''):
+ self.write(s)
+
+
+
+
+class TestReport(object):
+
+ template = {'time' : 0,
+ 'label': 'Root',
+ 'id': 'Root',
+ 'full_id': ['Root'],
+ 'status': Status.NotExecuted(),
+ 'report': 'NoReport',
+ 'error_report': '',
+ 'finished': False,
+ 'restart_params': None, # ('',('',))
+ }
+ @classmethod
+ def fromChannel(cls, kwdict):
+ if 'status' in kwdict:
+ kwdict['status'] = Status(kwdict['status'])
+ return cls(**kwdict)
+
+
+ def __init__(self, **kwargs):
+ # copy status -> deepcopy
+ kwdict = py.std.copy.deepcopy(self.template)
+ kwdict.update(kwargs)
+ for key, value in kwdict.iteritems():
+ setattr(self, key, value)
+
+ def start(self, collector):
+ self.full_id = collector.listnames()
+ self.id = collector.name
+ if collector.getpathlineno(): # save for Null() in test_utils.py
+ fspath, lineno = collector.getpathlineno()
+ if lineno != sys.maxint:
+ str_append = ' [%s:%s]' % (fspath.basename, lineno)
+ else:
+ str_append = ' [%s]' % fspath.basename
+ self.label = collector.name + str_append
+
+ self.settime()
+ self.restart_params = (str(collector.listchain()[0].fspath),
+ collector.listnames())
+ self.status = Status.NotExecuted()
+
+ def finish(self, collector, res, option = Null()):
+ self.settime()
+ if collector.getpathlineno(): # save for Null() in test_utils.py
+ fspath, lineno = collector.getpathlineno()
+ if lineno != sys.maxint:
+ str_append = ' [%s:%s] %0.2fsecs' % (fspath.basename, lineno, self.time)
+ else:
+ str_append = ' [%s] %0.2fsecs' % (fspath.basename, self.time)
+ self.label = collector.name + str_append
+ if res:
+ if Status(res) in (Status.Failed(), Status.ExceptionFailure()):
+ self.error_report = self.report_failed(option, collector, res)
+ elif Status(res) == Status.Skipped():
+ self.error_report = self.report_skipped(option, collector, res)
+ self.status.update(Status(res))
+ self.finished = True
+
+ def report_failed(self, option, item, res):
+ #XXX hack abuse of TerminalDriver
+ terminal = TerminalDriver(option)
+ out = OutBuffer()
+ terminal.out = out
+ terminal.repr_failure(item, res)
+ return out.getoutput()
+
+ def report_skipped(self,option, item, res):
+ texts = {}
+ tbindex = getattr(res, 'tbindex', -1)
+ raisingtb = res.excinfo.traceback[tbindex]
+ fn = raisingtb.frame.code.path
+ lineno = raisingtb.lineno
+ d = texts.setdefault(res.excinfo.exconly(), {})
+ d[(fn,lineno)] = res
+ out = OutBuffer()
+ out.sep('_', 'reasons for skipped tests')
+ for text, dict in texts.items():
+ for (fn, lineno), res in dict.items():
+ out.line('Skipped in %s:%d' %(fn,lineno))
+ out.line("reason: %s" % text)
+
+ return out.getoutput()
+
+ def settime(self):
+ self.time = py.std.time.time() - self.time
+
+ def toChannel(self):
+ ret = self.template.copy()
+ for key in ret.keys():
+ ret[key] = getattr(self, key, self.template[key])
+ ret['status'] = str(ret['status'])
+ return ret
+
+ def __str__(self):
+ return str(self.toChannel())
+
+ def __repr__(self):
+ return str(self)
+
+ def copy(self, **kwargs):
+ channel_dict = self.toChannel()
+ channel_dict.update(kwargs)
+ return TestReport.fromChannel(channel_dict)
+
+class TestFileWatcher:
+
+ def __init__(self, *paths):
+ self.paths = [py.path.local(path) for path in paths]
+ self.watchdict = dict()
+
+ def file_information(self, path):
+ try:
+ return path.stat().st_ctime
+ except:
+ return None
+
+ def check_files(self):
+ fil = py.path.checker(fnmatch='*.py')
+ rec = py.path.checker(dotfile=0)
+
+ files = []
+ for path in self.paths:
+ if path.check(file=1):
+ files.append(path)
+ else:
+ files.extend(path.visit(fil, rec))
+ newdict = dict(zip(files, [self.file_information(p) for p in files]))
+ files_deleted = [f for f in self.watchdict.keys() if not newdict.has_key(f)]
+ files_new = [f for f in newdict.keys() if not self.watchdict.has_key(f)]
+ files_changed = [f for f in newdict.keys() if self.watchdict.has_key(f) and newdict[f]!= self.watchdict[f]]
+ files_changed = files_new + files_changed
+
+ self.watchdict = newdict
+ return files_changed, files_deleted
+
+ def changed(self):
+ changed, deleted = self.check_files()
+ return changed != [] or deleted != []
More information about the pytest-commit
mailing list