tktree.py   [plain text]


# Twisted, the Framework of Your Internet
# Copyright (C) 2001 Matthew W. Lefkowitz
# 
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
# 
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""
What I want it to look like:

+- One
| \- Two
| |- Three
| |- Four
| +- Five
| | \- Six
| |- Seven
+- Eight
| \- Nine
"""

import os
from Tkinter import *

class Node:
    def __init__(self):
        """
        Do whatever you want here.
        """
        self.item=None
    def getName(self):
        """
        Return the name of this node in the tree.
        """
        pass
    def isExpandable(self):
        """
        Return true if this node is expandable.
        """
        return len(self.getSubNodes())>0
    def getSubNodes(self):
        """
        Return the sub nodes of this node.
        """
        return []
    def gotDoubleClick(self):
        """
        Called when we are double clicked.
        """
        pass
    def updateMe(self):
        """
        Call me when something about me changes, so that my representation
        changes.
        """
        if self.item:
            self.item.update()

class FileNode(Node):
    def __init__(self,name):
        Node.__init__(self)
        self.name=name
    def getName(self):
        return os.path.basename(self.name)
    def isExpandable(self):
        return os.path.isdir(self.name)
    def getSubNodes(self):
        names=map(lambda x,n=self.name:os.path.join(n,x),os.listdir(self.name))
        return map(FileNode,names)

class TreeItem:
    def __init__(self,widget,parent,node):
        self.widget=widget
        self.node=node
        node.item=self
        if self.node.isExpandable():
            self.expand=0
        else:
            self.expand=None
        self.parent=parent
        if parent:
            self.level=self.parent.level+1
        else:
            self.level=0
        self.first=0 # gets set in Tree.expand()
        self.subitems=[]
    def __del__(self):
        del self.node
        del self.widget
    def __repr__(self):
        return "<Item for Node %s at level %s>"%(self.node.getName(),self.level)
    def render(self):
        """
        Override in a subclass.
        """
        raise NotImplementedError
    def update(self):
        self.widget.update(self)

class ListboxTreeItem(TreeItem):
    def render(self):
        start=self.level*"|    "
        if self.expand==None and not self.first:
            start=start+"|"
        elif self.expand==0:
            start=start+"L"
        elif self.expand==1:
            start=start+"+"
        else:
            start=start+"\\"
        r=[start+"- "+self.node.getName()]
        if self.expand:
            for i in self.subitems:
                r.extend(i.render())
        return r

class ListboxTree:
    def __init__(self,parent=None,**options):
        self.box=apply(Listbox,[parent],options)
        self.box.bind("<Double-1>",self.flip)
        self.roots=[]
        self.items=[]
    def pack(self,*args,**kw):
        """
        for packing.
        """
        apply(self.box.pack,args,kw)
    def grid(self,*args,**kw):
        """
        for gridding.
        """
        apply(self.box.grid,args,kw)
    def yview(self,*args,**kw):
        """
        for scrolling.
        """
        apply(self.box.yview,args,kw)
    def addRoot(self,node):
        r=ListboxTreeItem(self,None,node)
        self.roots.append(r)
        self.items.append(r)
        self.box.insert(END,r.render()[0])
        return r
    def curselection(self):
        c=self.box.curselection()
        if not c: return
        return self.items[int(c[0])]
    def flip(self,*foo):
        if not self.box.curselection(): return
        item=self.items[int(self.box.curselection()[0])]
        if item.expand==None: return
        if not item.expand:
            self.expand(item)
        else:
            self.close(item)
        item.node.gotDoubleClick()
    def expand(self,item):
        if item.expand or item.expand==None: return
        item.expand=1
        item.subitems=map(lambda x,i=item,s=self:ListboxTreeItem(s,i,x),item.node.getSubNodes())
        if item.subitems:
            item.subitems[0].first=1
        i=self.items.index(item)
        self.items,after=self.items[:i+1],self.items[i+1:]
        self.items=self.items+item.subitems+after
        c=self.items.index(item)
        self.box.delete(c)
        r=item.render()
        for i in r:
            self.box.insert(c,i)
            c=c+1
    def close(self,item):
        if not item.expand: return
        item.expand=0
        length=len(item.subitems)
        for i in item.subitems:
            self.close(i)
        c=self.items.index(item)
        del self.items[c+1:c+1+length]
        for i in range(length+1):
            self.box.delete(c)
        self.box.insert(c,item.render()[0])
    def remove(self,item):
        if item.expand:
            self.close(item)
        c=self.items.index(item)
        del self.items[c]
        if item.parent:
            item.parent.subitems.remove(item)
        self.box.delete(c)
    def update(self,item):
        if item.expand==None:
            c=self.items.index(item)
            self.box.delete(c)
            self.box.insert(c,item.render()[0])
        elif item.expand:
            self.close(item)
            self.expand(item)

if __name__=="__main__":
    tk=Tk()
    s=Scrollbar()
    t=ListboxTree(tk,yscrollcommand=s.set)
    t.pack(side=LEFT,fill=BOTH)
    s.config(command=t.yview)
    s.pack(side=RIGHT,fill=Y)
    t.addRoot(FileNode("C:/"))
    #mainloop()