# Copyright (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""XmlTree module"""

__revision__ = "$Id: XmlTree.py,v 1.14 2002/01/15 15:51:45 syt Exp $"

import GDK
from gtk import *
from xml.dom.Event import EventListener, MutationEvent
from xml.dom.ext.Printer import utf8_to_code
try:
    from xml.xpath import Evaluate
except:
    from Ft.Xml.XPath import Evaluate
    

# On Windows, gtk requires UTF-8 strings
import sys
if sys.platform=='win32':
    def utf8_to_gtk(string):
        if string is not None:
            return string.encode('utf-8')
        else :
            return ''
else:
    def utf8_to_gtk (string):
        if string:
            try:
                return utf8_to_code(string,'ISO-8859-1')
            except:
                print 'error converting string',string
                return string
        else:
            return ''
    
_interestingEvents = [
    "DOMNodeInserted",
    "DOMNodeRemoved",
    "DOMAttrModified",
    "DOMCharacterDataModified"
    ]


class InfoGenerator:
    """Infogenerator"""
    
    def __init__(self,patterns=None):
        self.patterns= patterns or []
        self.pattern_change_listener=[]
    
    def append_patterns(self,patterns):
        self.patterns = self.patterns + patterns

    def generate_info_for_element(self,element_node):
        for (p,i) in self.patterns:
            if Evaluate(p,element_node):
                return Evaluate(i,element_node)
        return ''
        

class XmlTree(GtkCTree,EventListener) :
    """XmlTree"""

    def __init__(self):
        GtkCTree.__init__(self,3,1,['info','node name','node value'])
        self.set_expander_style(CTREE_EXPANDER_CIRCULAR)
        self.set_line_style(CTREE_LINES_DOTTED)
        self.set_column_auto_resize(0,1)
        self.set_column_auto_resize(1,1)
        self.connect('button-press-event',self.clicked)
        self.connect('key-press-event',self.key_pressed)
        self.connect('destroy',self.cleanup)
        self.filter = '.'
        self.document = None
        self.info_generator = None
        self.filtered_nodes = []
        self.expand_mode = 0
        
    def set_document(self,document):
        """sets the document displayed by the widget"""
        if self.document == document:
            return
        
        if self.document:
            for type in _interestingEvents:
                self.document.removeEventListener(type,self,0)
        self.document=document
        self.set_filter(self.filter)
        if document:
            for type in _interestingEvents:
                self.document.addEventListener(type,self,0)

    def set_info_generator(self,generator):
        self.info_generator = generator

    def set_filter(self,xpath):
        """sets the XPath that will serve as a filter on the document"""
        if self.document:
            print xpath, self.document
            f = Evaluate(xpath,self.document)        
            self.freeze()
            for n in self.filtered_nodes[:]:
                if n not in f:
                    self.removeTree(n)
                    self.filtered_nodes.remove(n)
            for n in f:
                print n
                if n not in self.filtered_nodes[:]:
                    try:
                        next_node = f[f.index(n)+1]
                        for s in self.root_nodes:
                            if self.node_get_row_data(s)==next_node:
                                sibling = s
                                break
                    except:
                        sibling = None
                    self.addTree(n,gtktree_sibling_node=sibling,
                                 expand = self.expand_mode)
            self.thaw()
            self.filtered_nodes = f
        self.filter = xpath

    def set_expand_mode(self,mode):
        self.expand_mode = mode
    
    #
    # Event Listener implementation
    #

    def handleEvent(self,evt):
        if evt.type == 'DOMNodeRemoved':
            self.removeTree(evt.target)
        elif evt.type == 'DOMNodeInserted':
            found = 0
            for b in self.base_nodes():
                tree_parent = self.find_by_row_data(b,evt.relatedNode)
                if tree_parent:
                    from xml.dom.ext import PrettyPrint
                    if evt.target.nextSibling:
                        tree_sibling = self.find_by_row_data(tree_parent,
                                                             evt.target.nextSibling)
                    else :
                        tree_sibling = None
                    #print 'DOMNodeInserted:',tree_parent,tree_sibling
                    self.addTree(evt.target,
                                 tree_parent,
                                 tree_sibling,
                                 expand = self.expand_mode)
                    found = 1
                    break
            if not found:
                self.set_filter(self.filter)
        elif evt.type == 'DOMAttrModified' :
            if evt.attrChange == evt.MODIFICATION:
                self.updateTree(evt.relatedNode)
            elif evt.attrChange == evt.REMOVAL:
                self.removeTree(evt.relatedNode)
            elif evt.attrChange == evt.ADDITION:
                for b in self.base_nodes():
                    tree_parent = self.find_by_row_data(b,evt.target)
                    if tree_parent:
                        if tree_parent.children:
                            tree_sibling = tree_parent.children[0]
                        else:
                            tree_sibling = None
                        self.addTree(evt.relatedNode,
                                     tree_parent,tree_sibling,
                                     expand = self.expand_mode)
                        break
        elif evt.type == 'DOMCharacterDataModified':
            for b in self.base_nodes():
                    treenode = self.find_by_row_data(b,evt.target)
                    if treenode:
                        self.node_set_text(treenode,2,evt.target.nodeValue)
    #
    # Miscelaneous internal methods
    #

    def cleanup(self,*args):
        self.set_document(None)

    def key_pressed(self,tree,event):
        if event.keyval==GDK.KP_Multiply:
            for sel in self.selection:
                self.expand_recursive(sel)
            self.emit_stop_by_name('key-press-event')
        elif event.keyval==GDK.KP_Divide:
            for sel in self.selection:
                self.collapse_recursive(sel)
            self.emit_stop_by_name('key-press-event')
                
    def clicked(self,tree,event):
        if event.button == 1 and \
           event.type == GDK._2BUTTON_PRESS and \
           self.get_selection_info(event.x,event.y):
            row,col = self.get_selection_info(event.x,event.y)
            text = self.get_text(row,2)
            if text:
                dlg = GtkDialog()
                gtktext = GtkText()
                gtktext.insert_text(text)
                scroll = GtkScrolledWindow()
                scroll.set_policy(POLICY_AUTOMATIC,POLICY_AUTOMATIC)
                scroll.add(gtktext)
                dlg.vbox.set_spacing(10)
                dlg.vbox.pack_start(scroll)
                butt = GtkButton("Dismiss")
                butt.set_flags(CAN_DEFAULT|HAS_DEFAULT)
                butt.connect_object('clicked',lambda d:d.destroy(),dlg)
                bbox=GtkHButtonBox()
                bbox.set_layout(BUTTONBOX_SPREAD)
                dlg.action_area.add(bbox)
                bbox.add(butt)
                dlg.set_position(WIN_POS_MOUSE)
                dlg.show_all()
                self.emit_stop_by_name('button-press-event')

            
    def removeTree(self,domtree):
        for b in self.base_nodes():
            treenode=self.find_by_row_data(b,domtree)
            if treenode:
                self.remove_node(treenode)
                break

    def addTree(self, node, gtktree_parent_node=None,gtktree_sibling_node=None,expand=0) :
        name=''
        if node.nodeType==node.ELEMENT_NODE and self.info_generator:
            name = self.info_generator.generate_info_for_element(node)
        elif node.nodeType == node.COMMENT_NODE :
            name = 'Comment'
            
        label=[utf8_to_gtk(name),utf8_to_gtk(node.nodeName),utf8_to_gtk(node.nodeValue)]
        item = self.insert_node(gtktree_parent_node,gtktree_sibling_node,label,
                                is_leaf=0)
        self.node_set_row_data(item,node)
        if node.nodeType!=node.ATTRIBUTE_NODE:
            if node.attributes or node.childNodes :
                for att in node.attributes or []:
                    self.addTree(att,item)
                for child in node.childNodes or []:
                    self.addTree(child,item)
        if expand:
            self.expand(item)

    def updateTree(self,domtree):
        for b in self.base_nodes():
            treenode=self.find_by_row_data(b,domtree)
            if treenode:
                parent = treenode.parent
                if parent: expanded = parent.expanded
                else: expanded = treenode.expanded
                sibling = treenode.sibling
                self.freeze()
                self.remove_node(treenode)
                self.addTree(domtree,parent,sibling,expanded)
                self.thaw()
                return
        self.set_filter(self.filter)
        
# test #########################################################################

if __name__=='__main__':
    from xml.dom.ext.reader import Sax2
    window=GtkWindow(WINDOW_TOPLEVEL,'test XmlTree')
    window.set_usize(300,300)
    window.connect('delete_event',lambda x,e:mainquit())
    domtree=Sax2.FromXml('<foo att="mlk" id="node1"><bar baz="dummy" name="my name">textnode</bar>another textnode</foo>')
    ig = InfoGenerator([('@id','string(@id)'),('@name','string(@name)')])
    tree=XmlTree()
    tree.set_info_generator(ig)
    window.add(tree)
    tree.set_document(domtree)
    window.show_all()
    mainloop()
