TreeNavigator.py :  » GUI » PmwContribD » PmwContribD-r2_0_2 » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » GUI » PmwContribD 
PmwContribD » PmwContribD r2_0_2 » TreeNavigator.py
#!/usr/bin/env python
#
# $Id: TreeNavigator.py,v 1.3 2001/11/03 11:05:22 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
#                         All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

"""A Pmw style widget for navigating the contents of a tree data structure.

"""

__rcs_info__ = {
    #
    #  Creation Information
    #
    'module_name'  : '$RCSfile: TreeNavigator.py,v $',
    'rcs_id'       : '$Id: TreeNavigator.py,v 1.3 2001/11/03 11:05:22 doughellmann Exp $',
    'creator'      : 'Doug Hellmann <doug@hellfly.net>',
    'project'      : 'PmwContribD',
    'created'      : 'Sat, 05-May-2001 13:39:44 EDT',

    #
    #  Current Information
    #
    'author'       : '$Author: doughellmann $',
    'version'      : '$Revision: 1.3 $',
    'date'         : '$Date: 2001/11/03 11:05:22 $',
}

#
# Import system modules
#
import Tkinter, Canvas
import Pmw
import sys, os, string, math


#
# Import Local modules
#
import NavigableTree


#
# Module
#


class TreeNavigator(Pmw.LabeledWidget):
    """A Pmw style widget for navigating the contents of a tree data structure.
    
    The ideas for the implementation of this widget are based on the 
    Hierarchical browser discussed in _Effective Tcl/Tk Programming_ 
    by Mark Harrison and Michael McLennan.
    
    """
    
    root_mark_name = 'root:start'
    
    def __init__(self, parent=None, **kw):
        """Create a TreeNavigator widget.

        Create a new instance with our parent widget and the treedata
        to be displayed.

        Options

          'activebackground' -- Background color for active nodes.

          'autoexpand' -- Boolean indicating whether or not to
          atuomatically expand nodes in the tree.

          'command' -- Callback to be executed when nodes are
          selected.

          'doubleclickcommand' -- Callback to be executed when nodes
          are double clicked.

          'entercommand' -- Callback to be executed when pointer
          enters node.

          'iconheight' -- Height to use for all icons.

          'iconwidth' -- Width to use for all icons.

          'indent' -- Amount of space to indent each child node level.

          'ipadx' -- Internal padding (X).

          'ipady' -- Internal padding (Y).

          'leavecommand' -- Callback to be executed when mouse pointer
          leaves the node.

          'selectedbackground' -- Background color to use to show a
          node when it was selected by the user.

          'treedata' -- The NavigableTree instance to be displayed.
        
        """
        
        optiondefs = (
            ('activebackground', 'lightslateblue', Pmw.INITOPT),
            ('autoexpand',       None, Pmw.INITOPT),
            ('command',          None, Pmw.INITOPT),
            ('doubleclickcommand', None, Pmw.INITOPT),
            ('entercommand',     None, Pmw.INITOPT),
            ('iconheight',       20, Pmw.INITOPT),
            ('iconwidth',        30, Pmw.INITOPT),
            ('indent',           20, Pmw.INITOPT),
            ('ipadx',             0, Pmw.INITOPT),
            ('ipady',             0, Pmw.INITOPT),
            ('leavecommand',     None, Pmw.INITOPT),
            ('selectedbackground', 'CadetBlue', Pmw.INITOPT),
            ('treedata',         None, self._reset_data),
            )
        self.defineoptions(kw, optiondefs)

        # Initialize the base class
        Pmw.LabeledWidget.__init__(self, parent=parent)
        
        self.selected = None
        self._reset_data()

        # Create our interface
        self._createInterior()

        # Check for initialization options
        # for this class
        self.initialiseoptions(TreeNavigator)
        return
        
    def is_selected(self, node):
        "Determine if the given node is currently selected."
        if self.selected == node:
           return 1
        return 0
        
    def _reset_data(self):
        "Update the tree data and redraw."
        # Store parameters
        self.treedata = self['treedata']
        self.treelookup = {}
        self.iconlookup = {}
        self.canvaslookup = {}
        self.select_node(None)
        try:
            #print 'deleting current tree data'
            self.tree.delete( '0.0', 'end' )
        except AttributeError:
            # 'tree' component hasn't been initialized.
            pass
        else:
            # insert base data
            #print 'inserting new data (%s) into tree' % self.treedata
            self.insert_node(self.treedata, insert_expanded=self['autoexpand'])
        return
        
    def getcurselection(self):
        'Returns the current selection of the TreeNavigator.'
        return self.selected
        
    def deselect_node(self, node):
        "Remove selection from a node."
        self.highlight_node(node, color=self.tree.component('text')['background'])      
        return
    
    def setcurselection(self, new_selection):
        "Set the current selection."
        if self.selected:
            self.deselect_node(self.selected)
        self.selected = new_selection
        return
        
    def reset_selection(self, parent):
        "Reset the selection."
        sel = self.getcurselection()
        if not sel:
            return None
        if len(parent.tag_name) < len(sel.tag_name):
            # the current selection could possibly be
            # a child of the parent node that is being
            # collapsed
            if sel.tag_name[:len(parent.tag_name)] == parent.tag_name:
                # it is
                self.select_node(parent)
            else:
                # it is not
                pass
        else:
            # if the parent name is longer
            # than the current selection, it can't
            # be the parent of the current selection
            pass
        return
        
    def build_mark_name(self, levels=()):
        """Create a name for the mark representing the position of the given levels.
        
        Given a tuple of level indeces, create a name of the form
        'root-1-1-...' to be used as the unique marker name for the
        node.
        
        """
        mn = 'root'
        for level in levels:
            mn = mn + '-%d' % level
        return mn
        
    def highlight_node(self, node, color='white'):
        """Draw the node in our highlight color.
        """
        #print 'highlighting %s' % node_name
        self.tree.component('text').tag_configure(node.tag_name, background=color)
        return
        
    def insert_node(self, node, levels=(), counter=1, insert_expanded=0):
        """Insert a node in the tree.
        
        Given a node, the level tuple indicating where it goes, and the counter
        indicating its sibling order, insert a representation of the node at
        the appropriate place.  The node's parent or older sibling (smaller counter)
        needs to be in place already.
        
        If insert_expanded is true, the children of the node are also inserted.
        """
        if not node: return None
        # Get some names and other local variables      
        mark_name = self.build_mark_name( levels + (counter,) )
        highlight_tag_name = '%s:highlight' % mark_name
        node_end = '%s:end' % mark_name
        
        # compute the level of indention
        indent = '\t' * len(levels)
        
        # mark this node as closed
        node.navigator_state = 'closed'

        # Fix the insertion point        
        self.tree.mark_set('pos', '%s:start' % mark_name)
        
        # Create the canvas to hold the icon for the node
        canvas = Tkinter.Canvas(self.tree.component('text'),
                                width=self['iconwidth'],
                                height=self['iconheight'],
                                background=self.tree.component('text')['background'],
                                bd=0,
                                #outline=self.tree.component('text')['background'],
                                )
        self.canvaslookup[mark_name] = canvas
        
        # Insert the indent text
        self.tree.insert('pos', indent, highlight_tag_name)
        
        # Insert the icon (start with inserting the canvas in our text widget, then
        # draw the icon)
        self.tree.window_create('pos', window=canvas, padx=0, pady=0)
        if insert_expanded and node.has_children():
            icon_state='open'
        else:
            icon_state='closed'
        icon = node.create_icon(
            canvas,
            command=lambda ignore, event, x=node, s=self: s.select_node_icon(x),
            state=icon_state)
        self.iconlookup[mark_name] = icon
        icon.bind('<Enter>', lambda event, x=node, s=self: s.enter_node(x))
        icon.bind('<Leave>', lambda event, x=node, s=self: s.leave_node(x))
        
        # Insert the node text
        self.tree.insert('pos', ' %s ' % node, mark_name)
        self.tree.insert('pos', '\n', highlight_tag_name)
        
        # Store the node in a quick-lookup for later
        self.treelookup[mark_name] = node
        node.tag_name = mark_name
        
        # Bind events to highlight this node when we
        # enter it with the mouse or select it when
        # we click on it
        self.tree.tag_bind(highlight_tag_name,
                           '<Enter>',
                           lambda event, x=node, s=self: s.enter_node(x))
        self.tree.tag_bind(highlight_tag_name,
                           '<Leave>',
                           lambda event, x=node, s=self: s.leave_node(x))
        self.tree.tag_bind(mark_name,
                           '<Enter>',
                           lambda event, x=node, s=self: s.enter_node(x))
        self.tree.tag_bind(mark_name,
                           '<Leave>',
                           lambda event, x=node, s=self: s.leave_node(x))
        self.tree.tag_bind(mark_name,
                           '<ButtonPress-1>',
                           lambda event, x=node, s=self: s.select_node(x))
        
        # Add a new mark to indicate the beginning of the interior of this node
        interior = '%s:interior' % mark_name
        self.tree.mark_set(interior, 'pos')
        self.tree.mark_gravity(interior, 'left')

        # Insert some debugging information        
        #self.tree.insert('pos', '%s{ %s\n' % (indent, mark_name))
        
        # Add a new mark to indicate the beginning of the subnode
        subnode = '%s-1' % mark_name
        subnode_start = '%s:start' % subnode
        self.tree.mark_set(subnode_start, 'pos')
        self.tree.mark_gravity(subnode_start, 'left')
        
        # Add a new mark to indicate the end of this node
        self.tree.mark_set(node_end, 'pos')
        
        self.tree.mark_gravity(node_end, 'left')
        self.tree.insert('pos', ' ')
        self.tree.mark_gravity(node_end, 'right')
        self.tree.insert('pos', ' ')
                
        # Insert some debugging information 
        #self.tree.insert('pos', '%s%s }\n' % (indent, mark_name))

        # Add a new mark to indicate the beginning of our next sibling        
        sibling_start = '%s:start' % self.build_mark_name(levels + (counter+1,))
        self.tree.mark_set(sibling_start, 'pos')
        self.tree.mark_gravity(sibling_start, 'left')
        
        # Add our children nodes
        if insert_expanded and node.has_children():
            self.expand_node(node, insert_expanded=1)
        return
    
        
    def enter_node(self, node):
        """Called when the mouse enters the node area.
        """
        #print 'entering: %s' % node_name
        self.highlight_node(node, color=self['activebackground'])
        if self['entercommand']:
            self['entercommand'](node)
        return
    
    def leave_node(self, node):
        """Called when the mouse leaves the node area.
        """
        #print 'leaving: %s' % node.name
        if self.is_selected(node):
            self.highlight_node(node, color=self['selectedbackground'])
        else:
            self.highlight_node(node, color=self.tree.component('text')['background'])
                
        if self['leavecommand']:
            self['leavecommand'](node)
        return
            
    def select_node_icon(self, node):
        "Update the icon for the node because it is selected."
        self.expand_node(node)
        #self.select_node(node)
        return
        
    def select_node(self, node):
        """Called when a node is selected by clicking on the text.
        """
        self.setcurselection(node)
        if node:
            #print 'selected %s' % node.name
            self.highlight_node(node, color=self['selectedbackground'])
        if self['command']:
                self['command'](node)
        return
    
        
    def expand_node(self, node, insert_expanded=0):
        """Either expand or collapse the node.
        """
        # Figure out where we are
        #print 'Node is a ', type(node), node.__class__.__name__
        #print dir(node)
        name_parts = string.split(node.tag_name, '-')
        levels = tuple(map(string.atoi, name_parts[1:]))
        
        # Handle the change in state
        if node.navigator_state == 'closed':
            # Open a closed node
            child_counter = 1
            node.navigator_state = 'open'
            for child in node.children():
                self.insert_node(child, levels=levels, counter=child_counter, 
                                insert_expanded=insert_expanded)
                child_counter = child_counter + 1
        elif node.navigator_state == 'open':
            # Close an opened node
            node.navigator_state = 'closed'
            mark_name = node.tag_name
            start_mark = '%s:interior' % mark_name
            end_mark = '%s:end' % mark_name
            self.tree.delete( start_mark, end_mark )
            self.reset_selection(node)
        else:
            # Future states ?
            pass
        return
        
    def _createInterior(self):
        """Create the interior components of the widget.
        """
        interior = self.interior()

        self.tree = self.createcomponent('tree', (), None,
                                        Pmw.ScrolledText,
                                        (interior,),
                                        )
        self.tree.component('text').configure(bg='white',
                                                takefocus=0,
                                                wrap='none',
                                                #cursor='center_ptr',
                                                cursor='left_ptr',
                                                )
        
        # Remove the Text class bindings from the text widget
        tags = list(self.tree.component('text').bindtags())
        tags.remove('Text')
        self.tree.component('text').bindtags(tags)
        
        # Set the tab stops for the text widget
        tabs = ''
        for i in range(1, 20):
           tabs = '%s %d' % (tabs, i*self['indent'])
        self.tree.component('text').configure(tabs=tabs)
        
        # Initialize the contents of the tree
        self.tree.delete('1.0', 'end')
        
        self.tree.mark_set(self.root_mark_name, '1.0')
        self.tree.mark_gravity(self.root_mark_name, 'left')
        
        root1name = 'root-1:start'
        self.tree.mark_set(root1name, '1.0')
        self.tree.mark_gravity(root1name, 'left')
        
        # insert base data
        #self.insert_node(self.treedata, insert_expanded=self['autoexpand'])
        
        self.tree.pack(side=Tkinter.TOP,
                        expand=Tkinter.YES,
                        fill=Tkinter.BOTH,
                        )
        return
    

if __name__ == '__main__':
    import GuiAppD

    class TNTest(GuiAppD.GuiAppD):
        
        appname = 'Test the TreeNavigator widget'
        usebuttonbox=1
        
        def createInterface(self):
            data = NavigableTree.create_from_tuples(
                ('data', 'nodedata', [ ('sub1', 'sub1 data',
                                              [ ('sub11', 'sub11 data', []) 
                                              ]
                                       ),
                                       ('sub2', 'sub2 data',
                                              [ ('sub21', 'sub21 data',
                                                        [ ('sub211', 'sub211 data', []) 
                                                        ]
                                                ),
                                                ('sub22', 'sub22 data', [] ),
                                                ('sub23', 'sub23 data', [] ),
                                              ]
                                       ),
                                       ('sub3', 'sub3 data', []),
                                     ]
                            )
               )
            self.navigator = TreeNavigator(self.interior(), treedata=data, 
                                        labelpos = 'n', 
                                        label_text='TreeNavigator',
                                        autoexpand=1,
                                        command=self.update_label)
            self.navigator.pack(side=Tkinter.TOP,
                          expand=Tkinter.YES,
                          fill=Tkinter.BOTH)
                          
            #self.buttonAdd('Change data', 'Change tree data to a different tree', command=self.do_it)
            
        def do_it(self):
            self.navigator.configure(treedata=NavigableTree.create_from_tuples(
                                                ('root', 'rootdata', [ ('datanode', 'this is the data', []),
                                                                       ('done', 'this is the last node', []),
                                                                     ]
                                                )
                                         )
                                         )
            
        def update_label(self, node):
            if node:
                pathstr = ''
                for n in node.getpath():
                    if pathstr:
                        pathstr='%s->%s' % (pathstr, n)
                    else:
                        pathstr = '%s' % n
                self.navigator.configure(label_text='Selected: %s (%s)' % (node,pathstr))
                #print 'updating label for %s' % node
            else:
                try:
                    self.navigator.configure(label_text='No item selected')
                except AttributeError:
                    # 'navigator' component not completely constructed
                    pass
        
    TNTest().run()
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.