text: ########################################################################
  ##
  ## Author Michel Sanner  Copyright TSRI (C) 2006 
  ##
  ########################################################################
  #
  # $Header: /opt/cvs/python/packages/share1.5/mglutil/gui/BasicWidgets/Tk/trees/tree.py,v 1.59 2013/10/15 21:50:42 sanner Exp $
  #
  # $Id: tree.py,v 1.59 2013/10/15 21:50:42 sanner Exp $
  #
  """Efficient TreeWidget based on Tkinter and Pmw
  
  The speed comes from only drawing visibles parts of the tree.
  """
  
  import os, sys, types, Pmw
  from PIL import Image, ImageTk
  from weakref import ref
  from Pmw import ScrolledCanvas
  from Tkinter import Tk, PhotoImage, Label, ALL, Menu, IntVar
  from mglutil.util.packageFilePath import findFilePath
  from mglutil.gui import widgetsOnBackWindowsCanGrabFocus
  from mglutil.events import Event, EventHandler
  from mglutil.util.callback import CallbackFunction
  
  ## TODO
  ## - add size subdirectory to IconManager
  ## - cannot select 2 ranges, might want to add drag selection
  
  ##
  ## BUGS
  ##
  
  class IconsManager:
      """The IconsManager object simplifies creating PhotoImage icons from
  images in the directory.  The directory can be set by specifying a path,
  a path within a Python package, or a Pyton package.
  Images are loaded upon requests and a reference to the icon is saved to
  avoid its garbage collection."""
  
  
      def __init__(self, path=[], packageName=''):
          """Create an IconManager object.
  obj <- IconManager(path='', packageName='')
  If packageName is specified, the path is assumed to be relative to the location
  of the package."""
          if packageName:
              mod = __import__(packageName)
              components = packageName.split('.')
              for comp in components[1:]:
                  mod = getattr(mod, comp)
              packagePath = os.path.abspath(mod.__path__[0])
          else:
              packagePath=''
              
          self.directory = os.path.join( packagePath, os.path.join(*path))
          
          assert os.path.exists(self.directory)
          assert os.path.isdir(self.directory)
          self.icons = {}
  
  
      def get(self, iconName, master=None, directory=None):
          """ return an Tk PhotoImage for a given icon name and saves a reference
  """
          if directory is None:
              directory = self.directory
          icon = self.icons.get(iconName, None)
          if icon is None:
              filename = os.path.join(directory, iconName)
              image = Image.open(filename)
              icon = ImageTk.PhotoImage(master=master, image=image)
              self.icons[iconName] = icon
          return icon
  
  
  
  class KeySelectable:
      """Adds the ability to use keystrokes to quickly select items in a list.
  root has to be a widget supporting .bind .after
  """
      
      def __init__(self, KeyRootTk):
          self.KeyRootTk = KeyRootTk
          self.afterID = None
          self.matchString = ''
          self.lastMatchString = ''
  	KeyRootTk.bind('<KeyPress>', self.key_cb)
  	KeyRootTk.bind('<KeyRelease>', self.keyUp_cb)
          self.isControl = False
          self.isShift = False
          self.isAlt = False
          self.ctrlModCallback = None
          self.shiftModCallback = None
          self.altModCallback = None
          
  
      def timeOut(self, event=None):
          """resets self.matchCharIndex to 0, called after a short period of
          time if no new character has been typed"""
          #print 'timeout'
          self.lastMatchString = self.matchString
          self.matchString = ''
          self.matchCharIndex = 1
          self.afterID = None
          
  
      def keyUp_cb(self, event=None):
          if event.keysym=='Control_L' or event.keysym=='Control_R':
              self.isControl = False
          elif event.keysym=='Shift_L' or event.keysym=='Shift_R':
              self.isShift = False
          elif event.keysym=='Alt_L' or event.keysym=='Alt_R':
              self.isAlt = False
  
          
      def key_cb(self, event=None):
          # use key strokes to select entry in listbox
          # strokes placed within 500 miliseconds are concatenated
          #print self.matchCharIndex, '|', self.matchString, '|', event.keysym
          if event.keysym=='Control_L' or event.keysym=='Control_R':
              self.isControl = True
              return
          elif event.keysym=='Shift_L' or event.keysym=='Shift_R':
              self.isShift = True
              return
          elif event.keysym=='Alt_L' or event.keysym=='Alt_R':
              self.isAlt = True
              return
  
          if self.isControl:
              if self.ctrlModCallback:
                  self.ctrlModCallback(event)
              return
          elif self.isShift:
              if self.shiftModCallback:
                  self.shiftModCallback(event)
              return
          elif self.isAlt:
              if self.altModCallback:
                  self.altModCallback(event)
              return
              
          if event.keysym=='Return':
              str = self.lastMatchString
          else:
              str = self.matchString + event.keysym
          #print str
          item = self.match(str)
          if item:
              self.selectItem(item)
              if self.afterID is not None:
                  self.KeyRootTk.after_cancel(self.afterID)
              self.afterID = self.KeyRootTk.after(1000, self.timeOut)
              self.matchString = str
  
      # SUBCLASS THIS
      def match(self, name):
          """has to return None if no match or an object that matches"""
          print 'jumping to ', name
          return None
  
  
      def selectItem(self, item):
          """do what has to be done to show what matches the typed string"""
          print 'selecting item', item
  
  
  class SelectEvent(Event):
      def __init__(self, object):
          Event.__init__(self)
          self.object = object
  
  class DeselectEvent(Event):
      def __init__(self, object):
          Event.__init__(self)
          self.object = object
  
  class ClearSelectionEvent(Event):
      def __init__(self, object):
          Event.__init__(self)
          self.object = object
  
  
  class Tree(ScrolledCanvas, KeySelectable, EventHandler):
      """Tree widget for Tk and Pmw"""
  
  
      def __init__(self, master, root, iconsManager=None, selectionMode='single',
                   idleRedraw=True, nodeHeight=15, headerHeight=0,
                   balloon=None, **kw):
          """Tree( master, root, idleRedraw=True, kw = {}, **opts)
  - Master can be a Tk frame in which the tree is displayed
  - root has to be a Node object
  - iconsManageer has to be an instance of an IconManager object or None.
  If None is passed, an IconManager with the default icons directory will be
  created.  If an IconManager is passed we expect to find ...
  - selection mode cane be 'single' or 'multiple'. In multiple mode, the shift
  modifier is use to select/deselect ranges (ranges can only be defined on
  sibling nodes) and the Control modifier is used to add/remove to the current
  selection.  Node are selected by click withthe left mouse button on the node's
  label or icon.
  - Set idleRedraw to False to disable background drawing.
  - nodeHeight is the heigh of a node int he Tree in pixels
  - kw can contain any keywork allowable for a Pmw.ScrolledCanvas
  """
          assert isinstance(root, Node)
          assert selectionMode in ['single', 'multiple']
  
          EventHandler.__init__(self)
  
          self.master = master
          
          self.selectionMode = selectionMode
          self.lastPickedNode = None
          self.balloon = balloon
          
          self.idleRedraw  = idleRedraw  # when set to true redraw operation
                                         # occur when CPU is idle
          
          if iconsManager is None:
              iconsManager = IconsManager(
                  ['gui','BasicWidgets','Tk','trees','Icons'], 'mglutil')
  
          assert isinstance(iconsManager, IconsManager)
          self.iconsManager = iconsManager
          fs = self.fontSize = 10
          self.font = "Arial %d"%fs
          
          # cache the icons use to expand and collapse since we will
          # use them for each node
          mod = __import__('mglutil')
          packagePath = mod.__path__[0]
          path = ['gui','BasicWidgets','Tk','trees','Icons']
          directory = os.path.join( packagePath, os.path.join(*path))
          #self.collapsedIcon  = self.iconsManager.get("1rightarrow.png",
          #                                            directory=directory)
          #self.expandedIcon = self.iconsManager.get("1downarrow.png",
          #                                          directory=directory)
          self.collapsedIcon  = self.iconsManager.get("plus.png",
                                                      directory=directory)
          self.expandedIcon = self.iconsManager.get("minus.png",
                                                    directory=directory)
          self.iconHalfWidth = self.collapsedIcon.width()/2 
          self.selectedNodes = []
          self.selectionHistory = [] # used to undo selection operations
          self.nodeHeight = nodeHeight
          self.headerHeight = headerHeight
          
          self.firstVisibleNodeLineNum = 0 # height of the first visible node
                                  # i.e. how many nodes drown above, 0 for root
  
          self.firstVisibleNode = root # first node visible in window
          
          self.displayedNodes = [] # list of nodes having a graphical represenation
          self.pending_draw = None
          self.suspendRedraw = False
  
          self.root = root
          root.currenty = 0
          self.maxNodeNum = 0
          
          root.tree = ref(self)
  
          root.childNumber = 0
          self.nbLines = 1 # height of the tree (i.e. how many lines in canvas)
  
          # build the GUI
          if not kw.has_key('vscrollmode'):
              kw['vscrollmode'] ='dynamic'
          if not kw.has_key('hscrollmode'):
              kw['hscrollmode'] ='dynamic'
          if not kw.has_key('hull_width'):
              kw['hull_width'] = 550
          if not kw.has_key('hull_height'):
              kw['hull_height'] = 100
          if not kw.has_key('background') and not kw.has_key('bg') and \
                 not kw.has_key('canvas_bg'):
              kw['canvas_bg']='white'
          if not kw.has_key('usehullsize'):
              kw['usehullsize'] = 1
              
          self.nbLinesPerPage = (kw['hull_height']-self.headerHeight) / nodeHeight
  
          if not kw.has_key('yscrollincrement'):
              kw['canvas_yscrollincrement'] = nodeHeight
  
          kw['horizscrollbar_command'] = self.xview
          kw['vertscrollbar_command'] = self.yview
          kw['borderframe'] = 5
          kw['canvas_highlightthickness'] = 0
          
          #kw['canvasmargin'] = 10
          #kw['hscrollmode'] = 'none'
          
          apply( ScrolledCanvas.__init__, (self, master), kw)
  
          canvas = self.canvas = self.component('canvas')
          canvas.master.configure(borderwidth=0)
          
          KeySelectable.__init__(self, canvas)
          self.ctrlModCallback = self.handleControlKey
  
          canvas.bind("<Configure>", self.configure_cb)
          canvas.bind("<Key-Prior>", self.pageUp)
          canvas.bind("<Key-Next>", self.pageDown)
          canvas.bind("<Key-Up>", self.lineUp)
          canvas.bind("<Key-Down>", self.lineDown)
          if os.name == 'nt': #sys.platform == 'win32':
              canvas.bind("<MouseWheel>", self.lineUpDown)
          else:
              canvas.bind("<Button-4>", self.lineUp)
              canvas.bind("<Button-5>", self.lineDown)
          canvas.bind("<Enter>", self.enter_cb)
  
          self.isControl = False
          self.isShift = False
          self.isAlt = False
  
          if widgetsOnBackWindowsCanGrabFocus is False:
              lActiveWindow = canvas.focus_get()
              if    lActiveWindow is not None \
                and ( lActiveWindow.winfo_toplevel() != canvas.winfo_toplevel() ):
                  return
  
          canvas.focus_set()
  
  
      def redrawHeader(self, *args):
          pass
      
      def yview(self, *args):
          # dragging scroll bar triggers('moveto', '0.024242424242424242')
          # using scrolbar arrows triggers ('scroll', '1', 'units')
          # clicking in scroll bar background ('scroll', '-1', 'pages')
          #
          ## callback for vertscrollbar_command
          # args can be ('scroll', number, type) where type is page or unit
          # or ('moveto', '0.2854982')
          #print 'YVIEW', args
          if args[0] == "scroll":
              # ('scroll', '1', 'pages') when click on back of bar
              # ('scroll', '1', 'units') when click on bar arrow
              #print 'SCROLL', args
              if args[1]=='1' and self.displayedNodes[-1].nextNode() is None:
                      return
              self.yview_scroll(args[1], args[2])
          else:
              # ('moveto', '0.350939') when dragging bar
              percent = float(args[1])
              line = min( self.nbLines-self.nbLinesPerPage,
                          int((self.nbLines-1) * float(percent)))
              if line > self.firstVisibleNodeLineNum:
                  if self.displayedNodes[-1].nextNode() is None:
                      return
              if self.firstVisibleNodeLineNum != line:
                  v = self.scrollView(line - self.firstVisibleNodeLineNum)
                  if v:
                      # compute percentage of motion
                      total = self.nbLines+(self.headerHeight/float(self.nodeHeight))
                      percent = self.firstVisibleNodeLineNum/total
                      ScrolledCanvas.yview_moveto(self, percent)
                      self.redraw()
          
              #self.redrawHeader(args)
  
      def pageUp(self, event):
          # triggered by page up on keyboard
          self.yview_scroll(-1, "pages")
          return "break"
  
  
      def pageDown(self, event):
          # triggered by page down on keyboard
          self.yview_scroll(1, "pages")
          return "break"
  
  
      def lineUp(self, event):
          # triggered by up arrow on keyboard
          # print 'LINE UP'
          self.yview_scroll(-1, "units")
          return "break"
  
  
      def lineDown(self, event):
          # triggered by down arrow on keyboard
          #print 'LINE DOWN'
          self.yview_scroll(1, "units")
          return "break"
  
  
      def lineUpDown(self, event):
          #print 'LINE UPDOWN'
          if event.delta < 0:
              return self.lineDown(event)
          else:
              return self.lineUp(event)
  
  
      def yview_scroll(self, *args):
          # mouse wheel or arrow keys up and down args = (1, 'units'), (-1, 'units')
          # page up and down keys (1, 'pages')(13, 'units')
          #                       (-1, 'pages')(-13, 'units')
          # end key args = "jumping to  End"
          # home key args = "jumping to  Home"
          height = self.winfo_height() - self.headerHeight
          #print 'yview_scroll', args
  
          #height = int(self.canvas['height']) - self.headerHeight
          #print 'GGGG', height, self.nbLines, self.nodeHeight, self.nbLines * self.nodeHeight
          if self.nbLines * self.nodeHeight <= height:
              return
  
          if args[1] == "pages":
              # FIXME we scroll the whole thing one page
              linesPerPage = int(args[0]) * (height / self.nodeHeight)
              self.yview_scroll(linesPerPage, "units")
          else: # it is a unit scroll of args[0] units
              if args[0]=='1' and self.displayedNodes[-1].nextNode() is None:
                  return
              line = min(self.nbLines-self.nbLinesPerPage,
                         self.firstVisibleNodeLineNum+int(args[0]))
              v = self.scrollView(line - self.firstVisibleNodeLineNum)
              if v:
                  ScrolledCanvas.yview_scroll(self, *args)
                  self.redraw()
  
          #self.redrawHeader(args)
  
  
      def scrollView(self, deltalines):
          """move the visible part of the tree up of down by deltalines lines
          """
          #print 'SCROLL VIEW', deltalines
          if deltalines > 0:
              # compute the index of the line for which the bottom of the tree
              # is drawn at the bottom of the visible window
              last = self.nbLines - self.nbLinesPerPage + 1
              # clamp deltalines so we do no go too far down
              deltalines = min(last-self.firstVisibleNodeLineNum+1, deltalines)
              #print 'GUGU', last, self.firstVisibleNodeLineNum, deltalines
              j = 0
              for i in range(deltalines):
                  node = self.firstVisibleNode.nextNode()
                  if node is None:
                      break
                  self.firstVisibleNodeLineNum += 1
                  self.firstVisibleNode = node
                  j+=1
              return j
          else:
              j = 0
              for i in range(-deltalines):
                  node = self.firstVisibleNode.previousNode()
                  if node is None:
                      break
                  self.firstVisibleNodeLineNum -= 1
                  self.firstVisibleNode = node
                  j+=1
              return j
          
  
      def showNode(self, node):
          self.firstVisibleNodeLineNum = node.childNumber
          self.firstVisibleNode = node
          self.redraw()
  
          
      def enter_cb(self, event=None):
          if widgetsOnBackWindowsCanGrabFocus is False:
              lActiveWindow = self.focus_get()
              if    lActiveWindow is not None \
                and ( lActiveWindow.winfo_toplevel() != self.winfo_toplevel() ):
                  return
  
          self.canvas.focus_set()
  
  
      def configure_cb(self, event=None):
          nl = (self.winfo_height()-self.headerHeight) / self.nodeHeight
          self.nbLinesPerPage = nl
          last = self.nbLines - nl
          if self.firstVisibleNodeLineNum > last:
              self.scrollView(last - self.firstVisibleNodeLineNum)
          self.redraw()
  
  
      def destroy(self):
          if self.root:
              self.root.destroy()
          ScrolledCanvas.destroy(self)
  
      ##
      ##  Drawing
      ##
      def undisplay(self):
          for node in self.displayedNodes:
              node.deleteNodeIcons()
  
      def reparentNodes(self, tree):
          n = self.root
          while n:
              n.tree = ref(tree)
              n = n.nextNode()
  
      def redraw(self, force=False):
          """post a redraw or actually redraw depending on self.idleRedraaw
  """
          if self.suspendRedraw: return
          if self.root:
              if self.idleRedraw:
                  if self.pending_draw:
                      self.after_cancel(self.pending_draw)
                  cb = CallbackFunction(self.reallyRedraw, force=force)
                  self.pending_draw = self.after(10, cb)
              else:
                  self.reallyRedraw(force)
  
  
      def reallyRedraw(self, force=False):
          """actually redraw
          if force is True we for redrawing each line
          else we move existing lines
          """
          self.pending_draw = None
          canvas = self.canvas
  
          if force:
              # destroy representation of visible nodes
              for node in self.displayedNodes:
                  #print 'destroying', node.object.name
                  node.deleteNodeIcons()
          else:
              # make a copy of previousely displayed nodes
              prevDisplayNodes = {}.fromkeys(self.displayedNodes[:])
  
          # build list of visible nodes
          nodeHeight = self.nodeHeight
          nodes = []
          node = self.firstVisibleNode
          for i in range(self.nbLinesPerPage):
              if not force:
                  if prevDisplayNodes.has_key(node):
                      del prevDisplayNodes[node]
              nodes.append(node)
              node = node.nextNode()
              if node is None:
                  break
  
          # destroy representation of previously visible nodes
          # that are not longer visible
          if not force:
              for node in prevDisplayNodes.keys():
                  #print 'destroying', node.object.name
                  node.deleteNodeIcons()
  
  ##  this approach does not destoy some canvas items of node that move
  ## for instance after an expand
  
  ##         # destroy representation of nodes no longer visible
  ##         for node in self.displayedNodes:
  ##             if nodesd.has_key(node):
  ##                 print 'deleteIcon', node.object
  ##                 node.deleteNodeIcons()
  
          # Draw visible nodes
          ypos = self.firstVisibleNodeLineNum * nodeHeight + self.headerHeight
          for node in nodes:
              if node.labelTkid is None or node.currenty is None:
                  node.redraw(ypos)
                  #print 'DRAWING', node.object.name, ypos, node.currenty, node.uniqID
              else:
                  dy = ypos - node.currenty
                  #print 'MOVING', node.object.name, dy, self.firstVisibleNodeLineNum, ypos, node.currenty
                  if dy:
                      canvas.move(node.nodeTkTag, 0, dy)
                      node.currenty += dy
                      
              #if node.currenty is None:
              #    node.currenty = 0
              #print 'redraw', node.needsRedraw, node.object.name
              #node.redraw(ypos)
              ypos += nodeHeight
          
          self.displayedNodes = nodes
        
          # Update the scroll-bar size
          if self.root:
              canvas = self.canvas
              x1, y1, x2, y2 = canvas.bbox(ALL)
              canvas.configure( scrollregion=(
                  0, 0, x2, self.nbLines*nodeHeight+self.headerHeight))
  
  
      def updateTreeHeight(self):
          """ """
          self.nbLines = self.root.countSubtreeLines()
  
  
      def clearSelection(self, history=True):
          """Deselect all selected Nodes."""
          canvas = self.canvas
          if history:
              self.selectionHistory.append(self.selectedNodes[:])
          selectedNodes = self.selectedNodes[:]
          for node in selectedNodes:
              node.deselect(history=False)
  
  
      def undoSelect(self):
          """ """
          if len(self.selectionHistory):
              self.clearSelection(history=False)
              self.selectedNodes = self.selectionHistory.pop()
              for n in self.selectedNodes:
                  n.select(only=False, history=False)
  
  
      def handleControlKey(self, event):
          """ """
          if event.keysym in ['z', 'Z']:
              self.undoSelect()
  
          elif event.keysym in ['l', 'L']:
              self.redraw()
  
  ##     def clearTree(self):
  ##         if self.pending_draw:
  ##             self.after_cancel(self.pending_draw)
  ##             self.pending_draw = None
        
  ##         if self.root:
  ##             self.root.destroy()
  ##             for n in self.displayedNodes:
  ##                 n.deleteNodeIcons()
  
  
  ##     def refresh(self):
  ##         """Refresf the tree. Notice that it is speeder to update only some Nodes (with Node.update() or Node.updatetree() and then tree.redraw()), if you know which Nodes have changed."""
  ##         if self.root:
  ##             self.root.refresh()
  ##             self.redraw()
  
  
  
  class Node:
      """Base class for Nodes in a Tree"""
  
      def __init__(self, object, parent):
          """Create a Node.
  object is the Python object represented by this node in the tree.
  Object is expected to provide an interable sequence in its .children attribute.
  Parent can be either the parent's Node, or the tree (for the root Node).
  """
          self.object = object
          self.isExpanded = False
          self.hasExpandIcon = True
          self.isSelected = False
          self.hasBeenExpanded = False
          self.children = []
          self.childNumber = 0 # index of this node in the list of children of its parent
          
          self.iconWidth = 0
          self.font = None
          
          self.currenty = None     # y value on canvas when node is visible
                                   # if None the node is not drawn
          self.needsRedraw = False
          
          self.parent = parent
          if parent is None:
              #print 'FOFOFOFOFOFOFOFOFO', object
              self.generation = 0   # how many ancestors
              self.tree = None      # weakref to tree
              self.uniqID = 0
              self.balloon = None
          else:
              self.generation = parent.generation + 1
              self.tree = parent.tree
              self.tree().maxNodeNum += 1
              self.balloon = self.tree().balloon
              
              # canvas ids
              self.uniqID = self.tree().maxNodeNum
  
          self.nodeTkTag = 'node_'+str(self.uniqID)
          self.labelTkid = None  # canvas id of node name
          self.iconTkid = None  # canvas id if node's icon
          self.expandIconTkid = None # canvas id of expand/collapse icon
          self.selectionBoxId = None
          self.canvasIDs = [] # list of other ids
          
      ##
      ## to be overridden
      ##
      def isExpandable(self):
          """Returns true if this node has children"""
          return 0
  
  
      def getChildren(self):
          """
          return children for object associated with this node.
          By default we return object.children. Override this method to
          selectively show children
          """
          return self.object.children
  
      
      def createChildrenNodes(self):
          """Create node for all children of self.object"""
          children = []
          for child in self.getChildren():
              if hasattr(child, 'treeNodeClass'):
                  klass = child.treeNodeClass
              else:
                  klass = self.__class__
              children.append(klass(child, self))
          return children
  
  
      ##
      ##  recursive traversals
      ##
      def countSubtreeLines(self):
          """Recusrsively traverse the tree and count the number of lines needed
  to draw this subtree on a canvas"""
          nbLines = 1
          if self.isExpanded:
              for child in self.children:
                  nbLines = nbLines + child.countSubtreeLines()
          return nbLines
  
  
      def lastVisibleChild(self):
          """Return the last visible child of this node"""
          if self.hasBeenExpanded and self.isExpanded:
              return self.children[-1].lastVisibleChild()
          else:
              return self
  
  
      def findNextNode(self, node):
          """recursively traverse the tree to find the first node visible
  below self"""
          # if this is not the last child, return the next one
          if node.childNumber + 1 < len(self.children):
              return self.children[node.childNumber + 1]
  
          if self.parent: # we need to walk the tree to find te next node
              return self.parent.findNextNode(self)
          else: # root node
              return None
  
  
      ##
      ## tree navigation methods
      ##
      def previousNode(self):
          """return the node right above this one in the tree"""
          if self.parent is None: # root node
              return None
          if self.childNumber==0: # node is first child, return parent
              return self.parent
          else: # return last visible child of sibling before self
              return self.parent.children[self.childNumber-1].lastVisibleChild()
  
  
      def nextNode(self):
          """return the node right below this one in the tree"""
          if self.isExpanded and self.children:
              return self.children[0]
          else:
              if self.parent:
                  return self.parent.findNextNode(self)
              else:
                  return None # Root Node
  
  
      ##
      ##  expanding and collapsing the tree
      ##
      def expand(self, event = None):
          """Expand this node and show its children"""
          if self.isExpanded:
              return
  
          tree = self.tree()
          if not self.hasBeenExpanded:
              self.children = self.createChildrenNodes()
              for i,c in enumerate(self.children):
                  c.childNumber = i
                  tree.objectToNode[c.object] = c
              self.hasBeenExpanded = True
  
          if len(self.children):
              self.needsRedraw = True
              self.isExpanded = True
              tree.updateTreeHeight()
              self.deleteNodeIcons()
              tree.redraw()
  
  
      def collapse(self, event = None):
          """Collapse this node, i.e hid its children"""
          if not self.isExpanded:
              return
          
          self.isExpanded = False
          self.needsRedraw = True
          tree = self.tree()
          tree.updateTreeHeight()
          self.deleteNodeIcons()
          tree.redraw()
  
  
      def doubleLabel1(self, event=None):
          """Callback for double click on label with button 1"""
          self.toggleExpansion(event)
  
  
      def toggleExpansion(self, event=None):
          """Toggles expanded/collapsed state of this node"""
          if self.isExpanded:
              self.collapse()
          else:
              self.expand()
  
  
      ##
      ##  Drawing
      ##
      def getIcon(self):
          """return node's icons"""
          iconsManager = self.tree().iconsManager
          if hasattr(self.object, 'treeIconName'):
              icon = iconsManager.get(self.object.treeIconName,
                                      self.tree().master)
          elif self.isExpandable():
              if self.isExpanded:
                  icon = iconsManager.get("expandedIcon.pgm")
              else:
                  icon = iconsManager.get("collapsedIcon.pgm")
          else:
              icon = iconsManager.get("python.pgm")
  
          if icon:
              self.iconWidth = icon.width()
          else:
              self.iconWidth = 0
          return icon
  
  
      def drawExpandCollapseIcon(self, x, y):
          """Draw the icon used to expand and collapse nodes"""
          tree = self.tree()
          canvas = tree.canvas
  
          self.nodeStartX = x
  
          # delete old icon
          if self.expandIconTkid:
              canvas.delete(self.expandIconTkid)
  
          # draw new one
          if self.isExpandable() and self.hasExpandIcon:
              if self.isExpanded:
                  icon = tree.expandedIcon
              else:
                  icon = tree.collapsedIcon
  
              tkid = self.expandIconTkid = canvas.create_image(
                  x + tree.iconHalfWidth, y, image=icon, tags=(self.nodeTkTag,))
              canvas.tag_bind(tkid, "<Button-1>", self.toggleExpansion)
              iconWidth = icon.width()
              return iconWidth+tree.iconHalfWidth
          else:
              iconWidth = 0
              self.expandIconTkid = None
              return 0
  
  
      def drawNodeIcon(self, x, y):
          """Draw the node's icon"""
          tree = self.tree()
          canvas = tree.canvas
  
          if self.iconTkid:
              canvas.delete(self.iconTkid)
  
          icon = self.getIcon()
          if icon:
              self.iconTkid = canvas.create_image(
                  x, y, image=icon, anchor="nw",
                  tags=(self.nodeTkTag,))
              canvas.tag_bind(self.iconTkid, "<Double-1>", self.toggleExpansion)
              canvas.tag_bind(self.iconTkid, "<Shift-Button-1>",
                              self.selectRange_cb)
              canvas.tag_bind(self.iconTkid, "<Control-Button-1>",
                              self.modifySelection_cb)
              canvas.tag_bind(self.iconTkid, "<Button-1>", self.toggleSelection)
              if os.name != 'nt': #sys.platform != 'win32':
                  canvas.tag_bind(self.iconTkid, "<Button-4>", self.tree().lineUp)
                  canvas.tag_bind(self.iconTkid, "<Button-5>", self.tree().lineDown)
          else:
              self.iconTkid = None
  
          return self.iconWidth
  
      def fitText(self, x, y, tree, canvas, text, font=None):
          # added so that NodeWithButton can change label to fit tree width
          return text
          
  
      def drawNodeLabel(self, x, y):
          """Draw the node's label"""
          tree = self.tree()
          canvas = tree.canvas
  
          origtext = self.object.name
          if not origtext:
              self.needsRedraw = True
              return 0
  
          if self.font:
              font = self.font
          else:
              font = self.tree().font
  
          text = self.fitText(x, y, tree, canvas, origtext, font)
          if self.labelTkid:
              curText = canvas.itemconfig(self.labelTkid)['text'][4]
          else:
              curText = ''
          if curText==text:
              canvas.coords(self.labelTkid, x, y)
              return 0
          
          if self.labelTkid:
              canvas.delete(self.labelTkid)
  
          ## if len(text) < len(origtext):
          ##     balloon = Pmw.Balloon(canvas)
          ## else:
          ##     balloon = None
  
          labelTkid = canvas.create_text(
              x, y, anchor='nw', text=text, font=font, tags=(self.nodeTkTag,))
  
          canvas.tag_bind(labelTkid, "<Button-1>", self.toggleSelection)
          canvas.tag_bind(labelTkid, "<Shift-Button-1>", self.selectRange_cb)
          canvas.tag_bind(labelTkid, "<Control-Button-1>", self.modifySelection_cb)
          canvas.tag_bind(labelTkid, "<Button-3>", self.button3OnLabel)
          canvas.tag_bind(labelTkid, "<Double-Button-1>", self.doubleLabel1)
          canvas.tag_bind(labelTkid, "<Motion>", tree.move_cb)
          if os.name == 'nt': #sys.platform == 'win32':
              #this does not work:
              #canvas.tag_bind(labelTkid, "<MouseWheel>", tree.lineUpDown)
              canvas.tag_bind(labelTkid, "<Button-4>", tree.lineUpDown)
          else:
              canvas.tag_bind(labelTkid, "<Button-4>", tree.lineUp)
              canvas.tag_bind(labelTkid, "<Button-5>", tree.lineDown)
          self.labelTkid = labelTkid
  
          if self.balloon:
              self.balloon.tagbind(canvas, labelTkid, origtext)
              
          bb = canvas.bbox(self.labelTkid)
          return bb[2]-bb[0]
  
  
      def drawNodeCustomization(self, x, y):
          """Draw additional things on the canvas"""
          return 0
    
  
      def nodeRedraw(self):
          # this functions performs a redraw of a node in the same place
          ypos = self.currenty
          self.deleteNodeIcons()
          self.redraw(ypos)
  
          
      def redraw(self, y):
          """Redraw this node at position y on the canvas"""
          # this function actually draws the node
          
          #print 'AAAA Redraw', self.object.name
          #if self.currenty is None:
          #    return
  
          tree = self.tree()
          if y != self.currenty or self.needsRedraw:
              x = 2 + max(0, self.generation) * tree.nodeHeight
              if not hasattr(self, 'nodeTkTag'):
                  self.nodeTkTag = 'node_'+str(self.uniqID)
              x += self.drawExpandCollapseIcon(x, y+tree.iconHalfWidth)
              x += self.drawNodeIcon(x, y)
              x += self.drawNodeLabel(x, y)
              x += self.drawNodeCustomization(x, y)
              self.currenty = y
              if self.isSelected:
                  if self.selectionBoxId:
                      tree.canvas.delete(self.selectionBoxId)
                  self.drawSelectionBox()
          self.needsRedraw = False
  
  
      def deleteNodeIcons(self):
          """Delect canvas items representing this node"""
  
          canvas = self.tree().canvas
          canvas.delete(self.nodeTkTag)
          self.expandIconTkid = None
          self.labelTkid = None
          self.selectionBoxId = None
              
          ## if self.expandIconTkid:
          ##     canvas.delete(self.expandIconTkid)
          ##     self.expandIconTkid = None
  
          ## if self.labelTkid:
          ##     canvas.delete(self.labelTkid)
          ##     self.labelTkid = None
  
          ## if self.selectionBoxId:
          ##     canvas.delete(self.selectionBoxId)
          ##     self.selectionBoxId = None
          ##     self.labelTkid = None
  
          #for id in self.canvasIDs:
          #    canvas.delete(id)
          self.canvasIDs = []
  
          self.currenty = None
  
  
      def destroy(self):
          for child in self.children:
              child.destroy()
  
  
      ##
      ## selection
      ##
      def drawSelectionBox(self):
          """draw a yellow box"""
          tree = self.tree()
          canvas = tree.canvas
          w = tree.winfo_width()
          y = self.currenty
          id = canvas.create_rectangle(0, y-2, w, y+20, outline='yellow',
                                       fill='yellow', tags=(self.nodeTkTag,)) 
          canvas.lower(id)
          self.selectionBoxId = id
  
  
      def button3OnLabel(self, event=None):
          # override in subclass
          return
  
      
      def toggleSelection(self, event=None, only=True):
          tree = self.tree()
          tree.lastPickedNode = self
          if self.isSelected:
              self.deselect()
          else:
              self.select(only=only)
  
  
      def select(self, only=True, history=True):
          if self.isSelected:
              return
  
          tree = self.tree()
          if history:
              tree.selectionHistory.append(tree.selectedNodes[:])
          if only:
              tree.clearSelection(history=False)
          self.isSelected = True
          tree.selectedNodes.append(self)
          if self in tree.displayedNodes:
              self.drawSelectionBox()
          tree.dispatchEvent( SelectEvent(self))
  
  
      def deselect(self, history=True):
          if not self.isSelected:
              return
          tree = self.tree()
          if history:
              tree.selectionHistory.append(tree.selectedNodes[:])
          tree.selectedNodes.remove(self)
          if self in tree.displayedNodes:
              tree.canvas.delete(self.selectionBoxId)
  
          self.isSelected = False
          self.selectionBoxId = None
          tree.dispatchEvent( DeselectEvent(self))
  
  
      def selectRange_cb(self, event=None):
          tree = self.tree()
          if tree.selectionMode=='single':
              return
  
          if len(tree.selectedNodes)==0:
              return
  
          last = tree.lastPickedNode
          all = self.parent.children
          try:
              i1 = all.index(self)
              i2 = all.index(last)
          except ValueError:
              raise ValueError, "range only work over siblings"
  
          if i1 > i2:
              tmp=i1; i1=i2; i2=tmp
  
          tree.selectionHistory.append(tree.selectedNodes[:])
  
          #print last, i1, i2+1
          for node in all[i1:i2+1]:
              if node==last:
                  continue
              if node.isSelected:
                  node.deselect(history=False)
              else:
                  node.select(only=False, history=False)
  
      
      def modifySelection_cb(self, event=None):
          tree = self.tree()
          tree.lastPickedNode = self
          if tree.selectionMode=='single':
              return
          self.toggleSelection(event=event, only=False)
  
  
      def undoSelect(self, event=None):
          tree.lastPickedNode = None
          self.tree().undoSelect()
  
  
      def refresh(self):
          self.needsRedraw = True
          self.tree().canvas.itemconfig(self.labelTkid, text=unicode(self))
  
  
      def refreshSubTree(self):
          self.refresh()
          for child in self.children:
              child.refreshSubTree()
          self.tree().redraw()
          
  
      def refreshChildren(self, redraw=True):
          ## bizarre
          if self.hasBeenExpanded:
              if self.isExpanded:
                  tree = self.tree()
                  # save current children
                  oldchildren = self.children
                  # delete all icons for current children
                  for child in self.children:
                      child.deleteNodeIcons()
                  # create Nodes for new children while saving existing ones
                  oldChildObj = {}
                  for c in oldchildren: # dict of obj:existing nodes
                      oldChildObj[c.object]= c
                  i = 0
                  newchildren = [] # build the list and create new children
                  for childobj in self.getChildren():
                      try:
                          c = oldChildObj[childobj]
                      except KeyError:
                          if hasattr(childobj, 'treeNodeClass'):
                              klass = childobj.treeNodeClass
                          elif  hasattr(childobj, 'molIteratorNodeClass'):
                              klass = childobj.molIteratorNodeClass
                          else:
                              klass = self.__class__
                          c = klass(childobj, self)
                          tree.objectToNode[c.object] = c
  
                      newchildren.append(c)
                      c.childNumber = i
                      i = i + 1
                  self.children = newchildren
  
                  if len(self.children)==0:
                      self.isExpanded = 0 # Cannot be expanded if no children
                  if redraw:
                      self.tree().redraw()
              else:
                  for child in self.children:
                      child.destroy()
                  self.children = []
                  self.hasBeenExpanded = False
                  if redraw and self.currenty:
                      self.redraw(self.currenty)
          else:
              if redraw and self.currenty:
                  self.redraw(self.currenty)
  
  
  ##     def selectTree(self):
  ##         if not self.isSelected: self.select()
  ##          for child in self.children:
  ##              child.selectTree()
  
  
  ##     def deselectTree(self):
  ##         self.deselect()
  ##         for child in self.children:
  ##             child.deselectTree()
  
  ## #root = Tk()
  ## #iconsmanager = IconsManager('Icons/32x32/', 'Pmv')
  ## #ico = iconmanager.get('ss.png', root)
  
  if __name__=='__main__':
  
      from mglutil.util.callback import CallbackFunction
  
      from MolKit.molecule import Atom, Molecule
      from MolKit.protein import Residue, Chain
  
      class ObjectTree(Tree):
          """Each node in the tree has an object associated in the node's .object
      attribute.  The objects are expected to have a .parent and a .children
      attribute describing the hierarchy."""
  
          def __init__(self, master, root, iconsManager=None, idleRedraw=True,
                       nodeHeight=15, **kw):
              Tree.__init__(self, master, root, iconsManager=None, idleRedraw=True,
                            nodeHeight=nodeHeight, **kw)
              self.objectToNode = {}  # key is object, values if Node instance
              self.objectToNode[root.object] = root
  
              self.nbCol = 10
              self.menuEntries = []
              for i in range(self.nbCol):
                  self.menuEntries.append([])
              self.menuEntries[0] = [
                  'displayLines', 'display S&B', 'display CPK',
                  'display second. struct.', 'display molecular surface',
                  'display second and S&B'
                  'undisplayLines', 'undisplay S&B', 'undisplay CPK',
                  'undisplay second. struct.', 'undisplay molecular surface',
                  'undisplay second and S&B'
                  ]
              self.menuEntries[1] = [
                  'label Atoms', 'label residues', 'label chains', 'label molecules'
                  ]
              self.menuEntries[2] = [
                  'color by atom types', 'color by molecule', 'color by chains',
                  'color by residue (RASMOL)', 'color by residue (SHAPELY)',
                  'color by DG', 'color by instance', 'color by second. struct.'
                  ]
  
          def createNodeForObject(self, object):
              """given an object if the corresponding Node is not yet created
      for its creation by expanding all its ancestors"""
              try:
                  return self.objectToNode[object]
              except KeyError:
                  p = object.parent
                  ancestors = [p]
                  while p and not self.objectToNode.get(p, None):
                      ancestors.append(p)
                      p = p.parent
                  ancestors.append(p)
                  ancestors.reverse()
                  for p in ancestors:
                      self.objectToNode[p].expand()
                  return self.objectToNode[object]
  
  
      class ObjectNode(Node):
          """ """
  
          def __repr__(self):
              return self.object.name
  
          def getIcon(self):
              """return node's icons"""
              iconsManager = self.tree().iconsManager
              object = self.object
              
              if isinstance(object, Atom):
                  icon = iconsManager.get("atom.png", self.tree().master)
              elif isinstance(object, Residue):
                  icon = iconsManager.get("residue.png", self.tree().master)
              elif isinstance(object, Chain):
                  icon = iconsManager.get("chain.png", self.tree().master)
              elif isinstance(object, Molecule):
                  icon = iconsManager.get("ms.png", self.tree().master)
              else:
                  icon = None
                  
              if icon:
                  self.iconWidth = icon.width()
              else:
                  self.iconWidth = 0
              return icon
  
  
  
          def isExpandable(self):
              """Returns true if this node has children"""
              return len(self.getChildren())
  
  
          def createChildrenNodes(self):
              """Create node for all children of self.object"""
              children = []
              for child in self.getChildren():
                  children.append(ObjectNode(child, self))
              return children
  
  
          def drawNodeCustomization(self, x, y):
              """Add things to the rigth side of the tree
      """
              tree = self.tree()
              canvas = tree.canvas
              level = 2
              col = ['gray75', 'red', 'cyan', 'green', 'yellow']
  
              nbButtons = 10
      ##         if not hasattr(self, 'chkbt'):
      ##             self.chkbtVar = []
      ##             self.chkbt = []
      ##             for i in range(nbButtons):
      ##                 v = IntVar()
      ##                 self.chkbtVar.append(v)
      ##                 cb = CallbackFunction(self.buttonClick, i )
      ##                 button = Checkbutton(
      ##                     canvas, variable=v, command=cb, padx=0, pady=0,
      ##                     background=col[level-1], anchor='nw')
      ##                 self.chkbt.append(button)
  
              x = 150
              fill = ['white', 'green']
              if not hasattr(self, 'chkbt'):
                  nbButtons = tree.nbCol
                  self.chkbt = [0]*nbButtons
                  self.chkbtid = [None]*nbButtons
  
                  # create a pull down menu and allocate variables for each column
                  self.menu = Menu(canvas, title='Choose', tearoff=False)
                  self.menuVars = []  # list of [column][menu entries] IntVar
                  for i, entries in enumerate(tree.menuEntries):
                      l = []
                      for j in range(len(entries)):
                          l.append(IntVar())
                      self.menuVars.append(l) # a list for each column
  
              for i, val in enumerate(self.chkbt):
                  xo = x+i*35
                  cid = canvas.create_oval(xo, y, xo+15,y+15, fill=fill[val])
                  cb = CallbackFunction(self.buttonClick, i )
                  canvas.tag_bind(cid, "<Button-1>", cb)
                  cb = CallbackFunction(self.shiftButtonClick, i )
                  canvas.tag_bind(cid, "<Shift-Button-1>", cb)
                  self.chkbtid[i] = cid
                  self.canvasIDs.append(cid)
      ##             button = self.chkbt[i]
      ##             cid = canvas.create_window( x+i*35, y, window=button,
      ##                                         width=20, height=15)
      ##             self.canvasIDs.append(cid)
  
                  # add secondary structure glyph
                  molFrag = self.object
                  if isinstance(molFrag, Residue):
                      if hasattr(molFrag, 'secondarystructure'):
                          ssname = molFrag.secondarystructure.name
                          if ssname[:6]=='Strand': color = '#FFF700'
                          elif ssname[:4]=='Coil': color = 'grey45'
                          elif ssname[:5]=='Helix': color = '#FF198C'
                          elif ssname[:4]=='Turn': color = 'blue'
  
                          cid = canvas.create_rectangle(
                              130, y-10, 140, y+10, outline=color,fill=color)
                          self.canvasIDs.append(cid)
  
                          cid = canvas.create_text(
                              152, y, text=ssname, anchor='nw',
                              font=self.tree.font)
                          self.canvasIDs.append(cid)
              return x+i*35
      ##             func = tree.buttonValFunc[i]
      ##             if func:
      ##                 func(self)
  
          def menu_cb(self, column, what, menuEntryIndex, event=None):
              print 'FFFF', column, what, menuEntryIndex
  
  
          def buttonClick(self, column, event=None):
              # get called for each checkbutton
              tree = self.tree()
  
              if self.chkbt[column]==0:
                  tree.canvas.itemconfigure(self.chkbtid[column], fill='green')
                  self.chkbt[column] = 1
              else:
                  tree.canvas.itemconfigure(self.chkbtid[column], fill='white')
                  self.chkbt[column] = 0
  
              for i,v in enumerate(self.menuVars[column]):
                  if v.get():
                      print 'DDD', tree.menuEntries[column][i], self.chkbt[column]
  
  
          def shiftButtonClick(self, column, event=None):
              # get called for each checkbutton
              tree = self.tree()
              menu = self.menu
  
              if menu.index(0)==0: # there is something in the menu, remove it
                  menu.delete(0, 100)
  
              for i, menuEntry in enumerate(tree.menuEntries[column]):
                  v = self.menuVars[column][i]
                  cb = CallbackFunction(self.menu_cb, column, menuEntry, i)
                  menu.add_checkbutton(label=menuEntry, variable=v, command=cb)
              menu.post(event.x_root, event.y_root)
  
      ##         if self.chkbt[column]==0:
      ##             tree.canvas.itemconfigure(self.chkbtid[column], fill='green')
      ##             self.chkbt[column] = 1
      ##         else:
      ##             tree.canvas.itemconfigure(self.chkbtid[column], fill='white')
      ##             self.chkbt[column] = 0
  
      ##   def getIcon(self):
      ##       """return node's icons"""
      ##       return None
  
      from MolKit import Read
      from MolKit.molecule import MolecularSystem
      syst = MolecularSystem ('world')
      mols = Read('../dev23/1crn.pdb')
      syst.adopt(mols[0])
      
      #mols = Read('../dev23/2plv.pdb')
      #syst.adopt(mols[0])
      #mols = Read('../dev23/1gav.pdb')
  
      root = Tk()
      rootnode = ObjectNode(syst, None)
      tree = ObjectTree(root, rootnode, selectionMode='multiple')
      tree.pack(expand=1, fill="both")
  