# XXX to do:
#-Popup menu
#-Support partial or total redisplay
#-Key bindings (instead of quick-n-dirty bindings on canvas ):
#-Up/down arrow keys to move focus around
#-Ditto for page up/down, home/end
#-Left/right arrows to expand/Collapse & move out/in
#-More Doc strings
#-Add icons for "file", "module", "class", "method"; better "Python" icon
#-Callback for selection ???
#-Multiple-item selection
#-Tooltips
#-Redo geometry without magic numbers
#-Keep track of object IDs to allow more careful cleaning
#-Optimize tree redraw after expand of subnode
Import OS
From tkinter import *
Import imp
From idlelib import zoomheight
From idlelib. confighandler import idleconf
Icondir = "icons"
# Look for icons subdirectory in the same directory as this module
Try:
_ Icondir = OS. Path. Join (OS. Path. dirname (_ file _), icondir)
Failed t nameerror:
_ Icondir = icondir
If OS. Path. isdir (_ icondir ):
Icondir = _ icondir
Elif not OS. Path. isdir (icondir ):
Raise runtimeerror ("can't find icon directory (% R)" % (icondir ,))
Def listicons (icondir = icondir ):
"Utility to display the available icons ."""
Root = TK ()
Import glob
List = glob. glob (OS. Path. Join (icondir, "*. GIF "))
List. Sort ()
Images = []
Row = column = 0
For file in list:
Name = OS. Path. splitext (OS. Path. basename (File) [0]
Image = photoimage (file = file, Master = root)
Images. append (image)
Label = label (root, image = image, BD = 1, relief = "Raised ")
Label. Grid (ROW = row, column = column)
Label = label (root, text = Name)
Label. Grid (ROW = row + 1, column = column)
Column = column + 1
If column> = 10:
Row = row + 2
Column = 0
Root. Images = Images
Class treenode:
Def _ init _ (self, canvas, parent, item ):
Self. Canvas = canvas
Self. Parent = parent
Self. Item = item
Self. State = 'collapsed'
Self. Selected = false
Self. Children = []
Self. x = self. Y = none
Self. iconimages ={}# cache of photoimage instances for icons
Def destroy (Self ):
For C in self. Children [:]:
Self. Children. Remove (c)
C. Destroy ()
Self. Parent = none
Def geticonimage (self, name ):
Try:
Return self. iconimages [name]
Failed t keyerror:
Pass
File, ext = OS. Path. splitext (name)
EXT = ext or ". GIF"
Fullname = OS. Path. Join (icondir, file + ext)
Image = photoimage (Master = self. Canvas, file = fullname)
Self. iconimages [name] = image
Return Image
Def select (self, event = none ):
If self. Selected:
Return
Self. deselectall ()
Self. Selected = true
Self. Canvas. Delete (self. image_id)
Self. drawicon ()
Self. drawtext ()
Def deselect (self, event = none ):
If not self. Selected:
Return
Self. Selected = false
Self. Canvas. Delete (self. image_id)
Self. drawicon ()
Self. drawtext ()
Def deselectall (Self ):
If self. Parent:
Self. Parent. deselectall ()
Else:
Self. deselecttree ()
Def deselecttree (Self ):
If self. Selected:
Self. deselect ()
For child in self. Children:
Child. deselecttree ()
Def flip (self, event = none ):
If self. State = 'expanded ':
Self. Collapse ()
Else:
Self. Expand ()
Self. item. ondoubleclick ()
Return "break"
Def expand (self, event = none ):
If not self. item. _ isexpandable ():
Return
If self. State! = 'Expanded ':
Self. State = 'expanded'
Self. Update ()
Self. View ()
Def collapse (self, event = none ):
If self. State! = 'Collapsed ':
Self. State = 'collapsed'
Self. Update ()
Def view (Self ):
Top = self. Y-2
Bottom = self. lastvisiblechild (). Y + 17
Height = bottom-top
Visible_top = self. Canvas. canvasy (0)
Visible_height = self. Canvas. winfo_height ()
Visible_bottom = self. Canvas. canvasy (visible_height)
If visible_top <= top and bottom <= visible_bottom:
Return
X0, y0, X1, Y1 = self. Canvas. _ getints (self. Canvas ['scrollregion'])
If top> = visible_top and height <= visible_height:
Fraction = Top + height-visible_height
Else:
Fraction = Top
Fraction = float (fraction)/Y1
Self. Canvas. yview_moveto (fraction)
Def lastvisiblechild (Self ):
If self. Children and self. State = 'expanded ':
Return self. Children [-1]. lastvisiblechild ()
Else:
Return self
Def Update (Self ):
If self. Parent:
Self. Parent. Update ()
Else:
Oldcursor = self. Canvas ['cursor ']
Self. Canvas ['cursor '] = "watch"
Self. Canvas. Update ()
Self. Canvas. Delete (All) # XXX cocould be more subtle
Self. Draw (7, 2)
X0, y0, X1, Y1 = self. Canvas. bBox (all)
Self. Canvas. Configure (scrollregion = (0, 0, X1, Y1 ))
Self. Canvas ['cursor '] = oldcursor
Def draw (self, x, y ):
# XXX This hard-codes too plugin geometry constants!
Self. X, self. Y = x, y
Self. drawicon ()
Self. drawtext ()
If self. State! = 'Expanded ':
Return y + 17
# Draw children
If not self. Children:
Sublist = self. item. _ getsublist ()
If not sublist:
# _ Isexpandable () was mistaken; that's allowed
Return y + 17
For item in sublist:
Child = self. _ class _ (self. Canvas, self, item)
Self. Children. append (child)
Cx = x + 20
Cy = Y + 17
Cylast = 0
For child in self. Children:
Cylast = cy
Self. Canvas. create_line (x + 9, Cy + 7, CX, Cy + 7, fill = "gray50 ")
Cy = Child. Draw (CX, CY)
If child. item. _ isexpandable ():
If child. State = 'expanded ':
Iconname = "minusnode"
Callback = Child. Collapse
Else:
Iconname = "plusnode"
Callback = Child. Expand
Image = self. geticonimage (iconname)
Id = self. Canvas. create_image (x + 9, cylast + 7, image = image)
# XXX This leaks bindings until canvas is deleted:
Self. Canvas. tag_bind (ID, "<1>", callback)
Self. Canvas. tag_bind (ID, "<double-1>", Lambda X: None)
Id = self. Canvas. create_line (x + 9, Y + 10, x + 9, cylast + 7,
# Stipple = "gray50", # XXX seems broken in TK 8.0.x
Fill = "gray50 ")
Self. Canvas. tag_lower (ID) # XXX. Lower (ID) before Python 1.5.2
Return cy
Def drawicon (Self ):
If self. Selected:
Imagename = (self. item. getselectediconname () or
Self. item. geticonname () or
"Openfolder ")
Else:
Imagename = self. item. geticonname () or "folder"
Image = self. geticonimage (imagename)
Id = self. Canvas. create_image (self. X, self. Y, anchor = "nw", image = image)
Self. image_id = ID
Self. Canvas. tag_bind (ID, "<1>", self. Select)
Self. Canvas. tag_bind (ID, "<double-1>", self. Flip)
Def drawtext (Self ):
Textx = self. x + 20-1
Texty = self. Y-1
Labeltext = self. item. getlabeltext ()
If labeltext:
Id = self. Canvas. create_text (textx, texty, anchor = "nw ",
TEXT = labeltext)
Self. Canvas. tag_bind (ID, "<1>", self. Select)
Self. Canvas. tag_bind (ID, "<double-1>", self. Flip)
X0, y0, X1, Y1 = self. Canvas. bBox (ID)
Textx = max (x1, 200) + 10
TEXT = self. item. gettext () or "<no text>"
Try:
Self. Entry
T attributeerror:
Pass
Else:
Self. edit_finish ()
Try:
Label = self. Label
T attributeerror:
# Padding carefully selected (on Windows) to match entry Widget:
Self. Label = label (self. Canvas, text = text, BD = 0, padx = 2, pady = 2)
Theme = idleconf. getoption ('main', 'Theme ', 'name ')
If self. Selected:
Self. Label. Configure (idleconf. gethighlight (theme, 'hilite '))
Else:
Self. Label. Configure (idleconf. gethighlight (theme, 'normal '))
Id = self. Canvas. create_window (textx, texty,
Anchor = "nw", window = self. Label)
Self. Label. BIND ("<1>", self. select_or_edit)
Self. Label. BIND ("<double-1>", self. Flip)
Self. text_id = ID
Def select_or_edit (self, event = none ):
If self. Selected and self. item. iseditable ():
Self. Edit (Event)
Else:
Self. Select (Event)
Def edit (self, event = none ):
Self. Entry = entry (self. Label, BD = 0, highlightthickness = 1, width = 0)
Self. Entry. insert (0, self. Label ['text'])
Self. Entry. selection_range (0, end)
Self. Entry. Pack (ipadx = 5)
Self. Entry. focus_set ()
Self. Entry. BIND ("<return>", self. edit_finish)
Self. Entry. BIND ("<escape>", self. edit_cancel)
Def edit_finish (self, event = none ):
Try:
Entry = self. Entry
Del self. Entry
T attributeerror:
Return
TEXT = entry. Get ()
Entry. Destroy ()
If text and text! = Self. item. gettext ():
Self. item. settext (text)
TEXT = self. item. gettext ()
Self. Label ['text'] = text
Self. drawtext ()
Self. Canvas. focus_set ()
Def edit_cancel (self, event = none ):
Try:
Entry = self. Entry
Del self. Entry
T attributeerror:
Return
Entry. Destroy ()
Self. drawtext ()
Self. Canvas. focus_set ()
Class treeitem:
"Abstract class representing tree items.
Methods shocould typically be overridden, otherwise a default action
Is used.
"""
Def _ init _ (Self ):
"Constructor. Do whatever you need to do ."""
Def gettext (Self ):
"" Return text string to display ."""
Def getlabeltext (Self ):
"Return label text string to display in front of text (if any )."""
Expandable = none
Def _ isexpandable (Self ):
"Do not override! Called by treenode ."""
If self. expandable is none:
Self. expandable = self. isexpandable ()
Return self. Expandable
Def isexpandable (Self ):
"" Return whether there are subitems ."""
Return 1
Def _ getsublist (Self ):
"Do not override! Called by treenode ."""
If not self. isexpandable ():
Return []
Sublist = self. getsublist ()
If not sublist:
Self. expandable = 0
Return sublist
Def iseditable (Self ):
"Return whether the item's text may be edited ."""
Def settext (self, text ):
"Change the item's text (if it is editable )."""
Def geticonname (Self ):
"Return name of icon to be displayed normally ."""
Def getselectediconname (Self ):
"Return name of icon to be displayed when selected ."""
Def getsublist (Self ):
"" Return list of items forming sublist ."""
Def ondoubleclick (Self ):
"Called on a double-click on the item ."""
# Example application
Class filetreeitem (treeitem ):
"Example treeitem subclass -- browse the file system ."""
Def _ init _ (self, PATH ):
Self. Path = path
Def gettext (Self ):
Return OS. Path. basename (self. Path) or self. Path
Def iseditable (Self ):
Return OS. Path. basename (self. Path )! = ""
Def settext (self, text ):
Newpath = OS. Path. dirname (self. Path)
Newpath = OS. Path. Join (newpath, text)
If OS. Path. dirname (newpath )! = OS. Path. dirname (self. Path ):
Return
Try:
OS. Rename (self. Path, newpath)
Self. Path = newpath
Failed t OS. Error:
Pass
Def geticonname (Self ):
If not self. isexpandable ():
Return "Python" # XXX wish there was a "file" icon
Def isexpandable (Self ):
Return OS. Path. isdir (self. Path)
Def getsublist (Self ):
Try:
Names = OS. listdir (self. Path)
Failed t OS. Error:
Return []
Names. Sort (Key = OS. Path. normcase)
Sublist = []
For name in names:
Item = filetreeitem (OS. Path. Join (self. Path, name ))
Sublist. append (item)
Return sublist
# A canvas widget with scroll bars and some useful bindings
Class scrolledcanvas:
Def _ init _ (self, Master, ** opts ):
If 'yscrollincrement 'not in opts:
Opts ['maid] = 17
Self. Master = Master
Self. Frame = frame (master)
Self. Frame. rowconfigure (0, Weight = 1)
Self. Frame. columnconfigure (0, Weight = 1)
Self. Canvas = canvas (self. Frame, ** opts)
Self. Canvas. Grid (ROW = 0, column = 0, sticky = "nsew ")
Self. vbar = scrollbar (self. Frame, name = "vbar ")
Self. vbar. Grid (ROW = 0, column = 1, sticky = "neuron ")
Self. hbar = scrollbar (self. Frame, name = "hbar", orient = "horizontal ")
Self. hbar. Grid (ROW = 1, column = 0, sticky = "EWS ")
Self. Canvas ['yscrollcommand'] = self. vbar. Set
Self. vbar ['command'] = self. Canvas. yview
Self. Canvas ['xscrollcommand'] = self. hbar. Set
Self. hbar ['command'] = self. Canvas. xview
Self. Canvas. BIND ("<key-Prior>", self. page_up)
Self. Canvas. BIND ("<key-Next>", self. page_down)
Self. Canvas. BIND ("<key-up>", self. unit_up)
Self. Canvas. BIND ("<key-down>", self. unit_down)
# If isinstance (master, toplevel) or isinstance (master, TK ):
Self. Canvas. BIND ("<alt-key-2>", self. zoom_height)
Self. Canvas. focus_set ()
Def page_up (self, event ):
Self. Canvas. yview_scroll (-1, "page ")
Return "break"
Def page_down (self, event ):
Self. Canvas. yview_scroll (1, "page ")
Return "break"
Def unit_up (self, event ):
Self. Canvas. yview_scroll (-1, "unit ")
Return "break"
Def unit_down (self, event ):
Self. Canvas. yview_scroll (1, "unit ")
Return "break"
Def zoom_height (self, event ):
Zoomheight. zoom_height (self. Master)
Return "break"
# Testing functions
Def test ():
From idlelib import pyshell
Root = toplevel (pyshell. Root)
Root. Configure (BD = 0, BG = "yellow ")
Root. focus_set ()
SC = scrolledcanvas (root, BG = "white", highlightthickness = 0, takefocus = 1)
SC. Frame. Pack (expand = 1, fill = "both ")
Item = filetreeitem ("C:/Windows/desktop ")
Node = treenode (SC. Canvas, none, item)
Node. Expand ()
Def Test2 ():
# Test w/o scrolling canvas
Root = TK ()
Root. Configure (BD = 0)
Canvas = canvas (root, BG = "white", highlightthickness = 0)
Canvas. Pack (expand = 1, fill = "both ")
Item = filetreeitem (OS. curdir)
Node = treenode (canvas, none, item)
Node. Update ()
Canvas. focus_set ()
# If _ name _ = '_ main __':
# Test ()
Import tkinter as TK
From idlelib import treewidget
If _ name _ = '_ main __':
Root = Tk. TK ()
Root. Configure (BD = 0)
Canvas = Tk. Canvas (root, BG = "white", highlightthickness = 0)
Canvas. Pack (expand = 1, fill = "both ")
Item = treewidget. filetreeitem ("/")
# Item = ['1', '2', '3']
Node = treewidget. treenode (canvas, none, item)
Node. Update ()
Canvas. focus_set ()
Root. mainloop ()
The following files must be included: