#!/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","",self.collapseNodeEvent) self.textWidget.tag_bind("expand","",self.expandNodeEvent) self.textWidget.tag_bind("node","",self.selectNodeEvent) self.textWidget.tag_bind("node","",self.chooseNodeEvent) self.textWidget.bind("",lambda e:"break") self.textWidget.bind("" , self.prevNodeEvent) self.textWidget.bind("", self.nextNodeEvent) self.textWidget.bind("" , self.collapseNodeEvent) self.textWidget.bind("", self.expandNodeEvent) self.textWidget.bind("",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