#!/usr/bin/env python
################################################################################
#
#       This file is part of Gato (Graph Animation Toolbox) 
#
#	file:   GatoFile.py
#	author: Achim Gaedke (achim.gaedke@zpr.uni-koeln.de)
#
#       Copyright (C) 1998-2005, Alexander Schliep, Winfried Hochstaettler and 
#       Copyright 1998-2001 ZAIK/ZPR, Universitaet zu Koeln
#                                   
#       Contact: schliep@molgen.mpg.de, wh@zpr.uni-koeln.de             
#       Information: http://gato.sf.net
#
#       This library is free software; you can redistribute it and/or
#       modify it under the terms of the GNU Library General Public
#       License as published by the Free Software Foundation; either
#       version 2 of the License, or (at your option) any later version.
#
#       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
#       Library General Public License for more details.
#
#       You should have received a copy of the GNU Library General Public
#       License along with this library; if not, write to the Free
#       Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#       This file is version $Revision: 1.7 $ 
#                       from $Date: 2005/02/22 11:13:20 $
#             last change by $Author: schliep $.
#
################################################################################

import Tkinter

class dom_structure_widget(Tkinter.Frame):
    """
    this widget displays the structure of a dom tree
    """
    
    class nodeDisplayProperties:
        """
        holds all properties for node
        """
        def __init__(self):
            self.expanded=0
            self.fg_color=""
            self.bg_color=""
            self.tag=""
            
    def __init__(self,master,
                 dom,
                 report_function=None,
                 cnf={},
                 **config):
        """
        creates the dom tree widget
        """
        
        # open a text widget
        my_defaults={"highlightthickness":"0"}
        my_defaults.update(config)
        my_defaults.update(cnf)
        if my_defaults.has_key("font"): del my_defaults["font"] 
        text_defaults={'bg':'white','cursor':'top_left_arrow',
                       'selectbackground':"white", 'selectborderwidth':'0',
                       "insertwidth":"0", "exportselection":"0",
                       "highlightthickness":"0"}
        text_defaults.update(config)
        text_defaults.update(cnf)
        Tkinter.Frame.__init__(self,master,cnf=my_defaults)
        self.textWidget=Tkinter.Text(self,cnf=text_defaults)
        self.textWidget.pack(side=Tkinter.LEFT,fill=Tkinter.BOTH,expand=1)
        self.scroll=Tkinter.Scrollbar(self,command=self.textWidget.yview)
        self.textWidget.configure(yscrollcommand=self.scroll.set)
        self.scroll.pack(side=Tkinter.LEFT,fill=Tkinter.Y,expand=1)
        self.dom=dom
        self.selectedNode=None
        self.report_function=report_function
        self.indent_step="    "
        self.expand_sign="+"
        self.collapse_sign="-"
        
        #parameters for default isVisible function
        self.mergeText      = 0 # if contiguous text nodes should be merged
        self.textTypes      = [xml.dom.Node.TEXT_NODE, xml.dom.Node.CDATA_SECTION_NODE]
        self.ignorableTypes = self.textTypes # ignore text by default
        
        # without normal state no key events
        self.textWidget.configure(state=Tkinter.NORMAL)
        # start with root element
        self.textWidget.mark_set(Tkinter.INSERT,Tkinter.END)
        self.textWidget.mark_gravity(Tkinter.INSERT,Tkinter.RIGHT)
        self.createNodeEntries("root",dom,"",Tkinter.INSERT,-1)
        
        # enable node selection
        self.selectedTag=None
        self.textWidget.tag_bind("collapse","<Button-1>",self.collapseNodeEvent)
        self.textWidget.tag_bind("expand","<Button-1>",self.expandNodeEvent)
        self.textWidget.tag_bind("node","<Button-1>",self.selectNodeEvent)
        self.textWidget.tag_bind("node","<Double-Button-1>",self.chooseNodeEvent)
        self.textWidget.bind("<Key>",lambda e:"break")
        self.textWidget.bind("<Up>"  , self.prevNodeEvent)
        self.textWidget.bind("<Down>", self.nextNodeEvent)
        self.textWidget.bind("<Left>"  , self.collapseNodeEvent)
        self.textWidget.bind("<Right>", self.expandNodeEvent)
        self.textWidget.bind("<Return>",self.chooseNodeEvent)
        
    def isVisible(self, node):
        """
        default function for visibility of nodes
        """
        # simple case: ignore all types of this case
        if node.nodeType in self.ignorableTypes:
            return 0
            
            # merge subsequent text nodes and display only first.
        if self.mergeText and node.nodeType in self.textTypes:
            lastNode=node.previousSibling
            if lastNode:
                return lastNode.nodeType in self.textTypes
            else:
                return 1
                
        return 1
        
    def nameNode(self, node):
        return xmlDecode(node.nodeName)
        
    def setNodeColor(self, node, fg="", bg=""):
        """
        sets the color of a node
        """
        if not node.__dict__.has_key("DisplayProperties"):
            node.DisplayProperties=dom_structure_widget.nodeDisplayProperties()
        if fg:
            node.DisplayProperties.fg_color=fg
        if bg:
            node.DisplayProperties.bg_color=bg
        if node.DisplayProperties.tag:
            self.textWidget.tag_config("nodeName-"+node.DisplayProperties.tag,
                                       foreground=node.DisplayProperties.fg_color,
                                       background=node.DisplayProperties.bg_color)
            
    def createNodeEntries(self,subtag,subtree,indentation,mark_name,depth=-1):
        """
        recursive creation of all node entries
        """
        if depth==0: return
        i=0 # counter of nodes to identifiy the label
        if not subtree.__dict__.has_key("DisplayProperties"):
            subtree.DisplayProperties=dom_structure_widget.nodeDisplayProperties()
        subtree.DisplayProperties.expanded=1
        
        for node in subtree.childNodes:
        
            # select nodes that are displayed
            if not self.isVisible(node):
                # do not display it
                i+=1
                continue
                
            this_subtag="%s-%d"%(subtag,i)
            # node will be displayed, so add property tag
            if not node.__dict__.has_key("DisplayProperties"):
                node.DisplayProperties=dom_structure_widget.nodeDisplayProperties()
            node.DisplayProperties.tag=this_subtag
            self.textWidget.tag_config("nodeName-"+this_subtag,
                                       foreground=node.DisplayProperties.fg_color,
                                       background=node.DisplayProperties.bg_color)
            
            # has this node visible children ? -> is it expandable ?
            if node.hasChildNodes() and filter(self.isVisible,node.childNodes):
                # yes, it has children
            
                # decide if node is expanded or not
                if depth==1:
                    node.DisplayProperties.expanded=0
                elif depth>1:
                    node.DisplayProperties.expanded=1
                    # otherwise, do it as done last time
                    
                    # indent
                self.textWidget.insert(mark_name, indentation, this_subtag)
                # draw node
                if node.DisplayProperties.expanded:
                    self.textWidget.insert(mark_name,self.collapse_sign,(this_subtag,"collapse"))
                else:
                    self.textWidget.insert(mark_name,self.expand_sign,(this_subtag,"expand"))
                    # write name
                self.textWidget.insert(mark_name,
                                       self.nameNode(node),
                                       (this_subtag,"node","nodeName-"+this_subtag))
                # write end of line
                self.textWidget.insert(mark_name, "\n", this_subtag)
                if node.DisplayProperties.expanded and depth!=1:
                    # one more level to write
                    self.createNodeEntries(this_subtag,
                                           node,
                                           indentation+self.indent_step,
                                           mark_name,
                                           depth-1)
            else:
                # no, no children
                # write indentation and no collapse/expand handle
                self.textWidget.insert(mark_name, indentation+" ", this_subtag)
                # now name
                self.textWidget.insert(mark_name,
                            self.nameNode(node),
                            (this_subtag,"node","nodeName-"+this_subtag))
                # now end of line
                self.textWidget.insert(mark_name, "\n", this_subtag)
            i+=1
            
    def collapseNodeEvent(self,event):
        """
        find tag, that denotes the subtree and pass to collapse_tag
        """
        if event.type=="4":
            # collapse sign hit by mouse
            my_tags=self.textWidget.tag_names("@%d,%d"%(event.x,event.y))
            tree_tags=filter(lambda t:t[:4]=='root',my_tags)
            if len(tree_tags)!=1:
                print "not good: found ",tree_tags,"to collapse!"
                return "break"
            self.collapse_tag(tree_tags[0])
            return "break"
        elif event.type=="2":
            # left arrow key, ToDo
            return "break"
        else:
            return
            
    def expandNodeEvent(self,event):
        """
        find tag, that denotes the subtree and pass to collapse_tag
        """
        if event.type=="4":
            # collapse sign hit by mouse
            my_tags=self.textWidget.tag_names("@%d,%d"%(event.x,event.y))
            tree_tag=filter(lambda t:t[:4]=='root',my_tags)
            if len(tree_tag)!=1:
                print "not good: found ",tree_tag," to expand!"
                return "break"
            self.expand_tag(tree_tag[0])
            return "break"
        elif event.type=="2":
            # right arrow key, ToDo
            return "break"
        else:
            return
            
    def selectNodeEvent(self,event):
        """
        clicked once on the node
        """
        my_tags=self.textWidget.tag_names("@%d,%d"%(event.x,event.y))
        tree_tags=filter(lambda t:t[:4]=='root',my_tags)
        if len(tree_tags)!=1:
            print "not good: found ",tree_tags," to select!"
        subtree=self.subtree_from_tag(tree_tags[0],self.dom)
        # mark the node
        self.selectNode(tree_tags[0])
        return "break"
        
    def nextNodeEvent(self,event):
        """
        event for down key
        """
        if self.selectedNode is None: return "break"
        (start,end)=self.textWidget.tag_ranges(self.selectedNode.DisplayProperties.tag)
        result=self.textWidget.tag_nextrange("node",end)
        if len(result)==0: return "break"
        my_tags=self.textWidget.tag_names(result[0])
        tree_tags=filter(lambda t:t[:4]=='root',my_tags)
        if len(tree_tags)!=1:
            print "not good: found ",tree_tags," to select!"
            # mark the node
        self.selectNode(tree_tags[0])
        return "break"
        
    def prevNodeEvent(self,event):
        """
        event for up key
        """
        if self.selectedNode is None: return "break"
        (start,end)=self.textWidget.tag_ranges(self.selectedNode.DisplayProperties.tag)
        result=self.textWidget.tag_prevrange("node",start)
        if len(result)==0: return "break"
        my_tags=self.textWidget.tag_names(result[0])
        tree_tags=filter(lambda t:t[:4]=='root',my_tags)
        if len(tree_tags)!=1:
            print "not good: found ",tree_tags," to select!"
            # mark the node
        self.selectNode(tree_tags[0])
        return "break"
        
    def selectNode(self,tree_tag):
        """
        mark node as selected
        the node is specified by the tree tag
        """
        if self.selectedNode:
            self.setNodeColor(self.selectedNode,bg="white")
            # find node element for this line
            
        subtree=self.subtree_from_tag(tree_tag,self.dom)
        self.setNodeColor(subtree,bg="green")
        self.selectedNode=subtree
        
        if self.report_function and subtree:
            self.report_function("selectedNode",subtree)
            
            
    def chooseNodeEvent(self,event):
        """
        choose node: i.e. report to report function
        """
        subtree=None
        if event.type=="4":
            # selection by mouse
            my_tags=self.textWidget.tag_names("@%d,%d"%(event.x,event.y))
            tree_tags=filter(lambda t:t[:4]=='root',my_tags)
            if len(tree_tags)!=1:
                print "not good: found ",tree_tags," to select!"
                # select this node
            self.selectNode(tree_tags[0])
            subtree=self.subtree_from_tag(tree_tags[0],self.dom)
        elif event.type=="2":
            # selection by key
            if self.selectedNode is None:
                return "break"
            subtree=self.selectedNode
        else:
            # unknown
            return "break"
        if self.report_function and subtree:
            self.report_function("chosenNode",subtree)
        return "break"
        
    def collapse_tag(self,tag):
        """
        collapses the tree under the given tag
        """
        subtree=self.subtree_from_tag(tag,self.dom)
        subtree.DisplayProperties.expanded=0
        all_tags=self.textWidget.tag_names()
        # determine tags to delete
        tags_to_delete=filter(lambda s,t=tag+"-",l=len(tag)+1:s[:l]==t,all_tags)
        
        # handle selected node, that may become invisible
        if self.selectedNode is not None and self.selectedNode.DisplayProperties.tag in tags_to_delete:
            self.selectNode(tag)
            
        for t in tags_to_delete:
            self.textWidget.delete(t+".first",t+".last")
            self.textWidget.tag_delete(t)
            
            # change collapse symbol to expand symbol
        symbol_index=self.textWidget.tag_nextrange('collapse',tag+".first",tag+".last")
        self.textWidget.delete(symbol_index[0],symbol_index[1])
        self.textWidget.insert(symbol_index[0],self.expand_sign,('expand',tag))
        
    def expand_tag(self,tag):
        """
        expand the tree under the tag
        """
        subtree=self.subtree_from_tag(tag,self.dom)
        start_index=self.textWidget.index(tag+".last")
        self.textWidget.mark_set(Tkinter.INSERT,start_index)
        
        # change expand symbol to collapse symbol
        symbol_index=self.textWidget.tag_nextrange('expand',tag+".first",tag+".last")
        symbol_index=self.textWidget.tag_nextrange('expand',tag+".first",tag+".last")
        self.textWidget.delete(symbol_index[0],symbol_index[1])
        self.textWidget.insert(symbol_index[0],self.collapse_sign,('collapse',tag))
        
        # determine indentation
        indentation=self.textWidget.get(tag+".first",symbol_index[0])
        self.createNodeEntries(tag,
                               subtree,
                               indentation+self.indent_step,
                               Tkinter.INSERT,
                               -1)
        
    def subtree_from_tag(self,tag,dom):
        """
        parse tag and traverse tree
        """
        subtree=dom
        for token in tag.split('-'):
            if token=='root':
                subtree=dom
            else:
                index_nr=int(token)
                subtree=subtree.childNodes[index_nr]
        return subtree
        


syntax highlighted by Code2HTML, v. 0.9.1