5 # This file provides a generic hierarchical tree browser widget.
7 # AUTHOR: Steve Kinneberg <skinneberg@mvista.com>,
8 # MontaVista Software, Inc. <source@mvista.com>
10 # Copyright 2001 MontaVista Software Inc.
12 # This program is free software; you can redistribute it and/or modify it
13 # under the terms of the GNU General Public License as published by the
14 # Free Software Foundation; either version 2 of the License, or (at your
15 # option) any later version.
17 # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
18 # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
20 # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 # You should have received a copy of the GNU General Public License along
29 # with this program; if not, write to the Free Software Foundation, Inc.,
30 # 675 Mass Ave, Cambridge, MA 02139, USA.
41 # List of branch names
44 # Map from branch name to branch info
45 # branch Either _LeafNode or _BranchNode widget of the branch
46 # nodetype Either 'TreeNode' or 'LeafNode'
49 def addbranch(self, branchName = None, **kw):
50 kw['indent'] = self['indent']
51 return apply(self._insertnode,
52 ('tree', branchName, len(self._nodeNames),
56 def addleaf(self, leafName = None, **kw):
57 return apply(self._insertnode,
58 ('leaf', leafName, len(self._nodeNames),
62 def insertbranch(self, branchName = None, before = 0, **kw):
63 kw['indent'] = self['indent']
64 return apply(self._insertnode,
65 ('tree', branchName, before, self._treeRoot),
68 def insertleaf(self, leafName = None, before = 0, **kw):
69 return apply(self._insertnode,
70 ('leaf', leafName, before, self._treeRoot),
73 def _insertnode(self, type, nodeName, before, treeRoot, **kw):
74 if 'selectbackground' not in kw.keys():
75 kw['selectbackground'] = self['selectbackground']
77 if 'selectforeground' not in kw.keys():
78 kw['selectforeground'] = self['selectforeground']
80 if 'background' not in kw.keys():
81 kw['background'] = self['background']
83 if 'foreground' not in kw.keys():
84 kw['foreground'] = self['foreground']
87 nodeName = self._nodeName + ".%d" % (len(self._nodeNames) + 1)
89 if self._nodeAttrs.has_key(nodeName):
90 msg = 'Node "%s" already exists.' % nodeName
93 # Do this early to catch bad <before> spec before creating any items.
94 beforeIndex = self.index(before, 1)
97 last = (beforeIndex == len(self._nodeNames))
98 if last and len(self._nodeNames) > 0:
99 # set the previous node to not last
100 self._nodeAttrs[self._nodeNames[-1]]['branch']._setlast(0)
103 node = apply(self.createcomponent, ('branch%d'%len(self._nodeNames),
112 attributes['nodetype'] = 'TreeNode'
114 node = apply(self.createcomponent, ('leaf%d'%len(self._nodeNames),
123 attributes['nodetype'] = 'LeafNode'
125 if len(self._nodeNames) == beforeIndex:
126 node.pack(anchor='w')
128 bname = self._nodeNames[beforeIndex]
129 battrs = self._nodeAttrs[bname]
130 node.pack(anchor='w', before=battrs['branch'])
132 attributes['branch'] = node
134 self._nodeAttrs[nodeName] = attributes
135 self._nodeNames.insert(beforeIndex, nodeName)
139 def delete(self, *nodes):
140 curSel = self._treeRoot.curselection()[0]
142 index = self.index(node)
143 name = self._nodeNames.pop(index)
144 dnode = self._nodeAttrs[name]['branch']
145 del self._nodeAttrs[name]
147 self._treeRoot._unhightlightnode(dnode)
152 for node in len(self._nodeNames):
154 Pmw.MegaWidget.destroy(self)
156 def index(self, index, forInsert = 0):
157 if isinstance(index, _LeafNode):
158 index = index._nodeName
159 listLength = len(self._nodeNames)
160 if type(index) == types.IntType:
161 if forInsert and index <= listLength:
163 elif not forInsert and index < listLength:
166 raise ValueError, 'index "%s" is out of range' % index
167 elif type(index) == types.StringType:
168 if index in self._nodeNames:
169 return self._nodeNames.index(index)
170 raise ValueError, 'bad branch or leaf name: %s' % index
171 elif index is Pmw.END:
175 return listLength - 1
177 raise ValueError, 'TreeNode has no branches'
178 #elif index is Pmw.SELECT:
179 # if listLength == 0:
180 # raise ValueError, 'TreeNode has no branches'
181 # return self._pageNames.index(self.getcurselection())
183 validValues = 'a name, a number, Pmw.END, Pmw.SELECT, or a reference to a TreeBrowser Leaf or Branch'
185 'bad index "%s": must be %s' % (index, validValues)
187 def getnodenames(self):
188 return self._nodeNames
190 def getnode(self, node):
191 nodeName = self._nodeNames[self.index(node)]
192 return self._nodeAttrs[nodeName]['branch']
195 class _LeafNode(Pmw.MegaWidget):
197 def __init__(self, parent, nodeName, treeRoot, parentnode, last = 1, **kw):
198 colors = Pmw.Color.getdefaultpalette(parent)
200 self._nodeName = nodeName
201 self._treeRoot = treeRoot
202 self._parentNode = parentnode
205 # Define the megawidget options.
206 INITOPT = Pmw.INITOPT
208 ('selectbackground', colors['selectBackground'], INITOPT),
209 ('selectforeground', colors['selectForeground'], INITOPT),
210 ('background', colors['background'], INITOPT),
211 ('foreground', colors['foreground'], INITOPT),
212 ('selectcommand', None, None),
213 ('deselectcommand', None, None),
214 ('labelpos', 'e', INITOPT),
215 ('labelmargin', 0, INITOPT),
216 ('label', None, None),
218 self.defineoptions(kw, optiondefs)
220 # Initialise the base class (after defining the options).
221 Pmw.MegaWidget.__init__(self, parent)
223 # Create the components
224 interior = self._hull
227 labelpos = self['labelpos']
229 if self['label'] == None:
230 self._labelWidget = self.createcomponent('labelwidget',
234 #background = self['background'],
235 #foreground = self['foreground'],
238 self._labelWidget = self.createcomponent('labelwidget',
242 label_background = self['background'],
243 label_foreground = self['foreground'],
245 labelmargin = self['labelmargin'],
246 label_text = self['label'],
248 self._labelWidget.component('label').bind('<ButtonRelease-1>',
251 self._labelWidget.grid(column = 1, row = 0, sticky = 'w')
253 self._labelWidget.update()
255 self._labelheight = self._labelWidget.winfo_height()
257 self._lineCanvas = self.createcomponent('linecanvas',
261 width = self._labelheight,
262 height = self._labelheight,
264 self._lineCanvas.grid( column = 0, row = 0, sticky = 'news')
265 self._lineCanvas.update()
267 cw = int(self._lineCanvas['width'])
268 ch = int(self._lineCanvas['height'])
270 self._lineCanvas.create_line(cw/2, ch/2, cw, ch/2, tag='hline')
272 self._lineCanvas.create_line(cw/2, 0, cw/2, ch/2, tag='vline')
274 self._lineCanvas.create_line(cw/2, 0, cw/2, ch, tag='vline')
276 # Check keywords and initialise options.
277 self.initialiseoptions()
281 return self._labelWidget.interior()
287 return self._nodeName
292 def _selectevent(self, event):
295 def _highlight(self):
296 self._treeRoot._highlightnode(self)
297 #self._subHull.configure(background = self._selectbg, relief = 'raised')
298 if self['label'] != None:
299 self._labelWidget.configure(label_background = self['selectbackground'])
300 self._labelWidget.configure(label_foreground = self['selectforeground'])
301 #self._viewButton.configure(background = self._selectbg)
302 cmd = self['selectcommand']
306 def _unhighlight(self):
307 #self._subHull.configure(background = self._bg, relief = 'flat')
308 if self['label'] != None:
309 self._labelWidget.configure(label_background = self['background'])
310 self._labelWidget.configure(label_foreground = self['foreground'])
311 #self._viewButton.configure(background = self._bg)
312 cmd = self['deselectcommand']
316 def _setlast(self, last):
319 cw = int(self._lineCanvas['width'])
320 ch = int(self._lineCanvas['height'])
323 self._lineCanvas.create_line(cw/2, 0, cw/2, ch/2, tag='vline')
325 self._lineCanvas.create_line(cw/2, 0, cw/2, ch, tag='vline')
328 class _BranchNode(_LeafNode, _Branching): #Pmw.MegaWidget):
330 def __init__(self, parent, nodeName, treeRoot, parentnode, last = 1, **kw):
331 # Define the megawidget options.
332 INITOPT = Pmw.INITOPT
334 ('view', 'collapsed', None),
335 ('expandcommand', None, None),
336 ('collapsecommand', None, None),
337 ('indent', 0, INITOPT)
339 self.defineoptions(kw, optiondefs)
341 # Initialise the base class (after defining the options).
342 apply(_LeafNode.__init__,
343 (self, parent, nodeName, treeRoot, parentnode, last),
345 _Branching.__init__(self)
347 # Create the components
348 interior = self._hull
350 # Create the expand/collapse button
351 self._viewButton = self.createcomponent('viewbutton', (), None,
354 background = self['background'],
355 width = self._labelheight - 4,
356 height = self._labelheight - 4,
360 self._viewButton.grid(column = 0, row = 0, sticky='se')
361 self._viewButton.bind('<ButtonPress-1>', self._showbuttonpress)
362 self._viewButton.bind('<ButtonRelease-1>', self._toggleview)
364 # The label widget is already created by the base class, however
365 # we do need to make some slight modifications.
366 if self['label'] != None:
367 self._labelWidget.component('label').bind('<Double-1>',
369 self._labelWidget.grid(column=1, row=0, columnspan = 3, sticky='sw')
371 # A line canvas is already created for us, we just need to make
372 # some slight modifications
373 self._lineCanvas.delete('hline')
374 self._lineCanvas.grid_forget()
377 # Set the minsize of column 1 to control additional branch frame indentation
378 self.grid_columnconfigure(1, minsize = self['indent'])
380 # Create the branch frame that will contain all the branch/leaf nodes
381 self._branchFrame = self.createcomponent('frame', (), None,
382 Tkinter.Frame, (interior,),
386 self.grid_columnconfigure(2,minsize=0, weight=1)
387 #self.grid_rowconfigure(0,minsize=0)
389 if(self['view'] == 'expanded'):
390 Pmw.drawarrow(self._viewButton,
393 self._branchFrame.grid(column = 2, row = 1, sticky='nw')
395 self._branchFrame.update()
396 bh = self._branchFrame.winfo_height()
397 self._lineCanvas.configure(height = bh)
398 self._lineCanvas.grid(column = 0, row = 1, sticky='news')
399 cw = int(self._lineCanvas['width'])
400 ch = int(self._lineCanvas['height'])
401 #self._lineCanvas.create_line(cw/2, 1, cw/2, ch, tag = 'vline')
402 self._lineCanvas.coords('vline', cw/2, 1, cw/2, ch)
404 Pmw.drawarrow(self._viewButton,
407 self._viewButton.configure(relief = 'raised')
410 # Check keywords and initialise options.
411 self.initialiseoptions()
415 def _showbuttonpress(self, event):
416 self._viewButton.configure(relief = 'sunken')
418 def _toggleview(self, event):
419 self._viewButton.configure(relief = 'sunken')
421 if(self['view'] == 'expanded'):
425 self._viewButton.configure(relief = 'raised')
427 def expandtree(self):
428 if(self['view'] == 'collapsed'):
429 cmd = self['expandcommand']
432 self['view'] = 'expanded'
433 Pmw.drawarrow(self._viewButton,
436 self._branchFrame.grid(column = 2, row = 1, sticky='nw')
439 self._branchFrame.update()
440 bh = self._branchFrame.winfo_height()
441 self._lineCanvas.configure(height = bh)
442 self._lineCanvas.grid(column = 0, row = 1, sticky='news')
443 cw = int(self._lineCanvas['width'])
444 ch = int(self._lineCanvas['height'])
445 #self._lineCanvas.create_line( cw/2, 1, cw/2, ch, tag = 'vline')
446 self._lineCanvas.coords('vline', cw/2, 1, cw/2, ch)
447 self._parentNode._sizechange()
449 def collapsetree(self):
450 if(self['view'] == 'expanded'):
451 cmd = self['collapsecommand']
454 self['view'] = 'collapsed'
455 Pmw.drawarrow(self._viewButton,
458 self._branchFrame.grid_forget()
460 #self._lineCanvas.delete('vline')
461 self._lineCanvas.grid_forget()
462 self._parentNode._sizechange()
464 def _setlast(self, last):
466 if self['view'] == 'expanded':
467 self._branchFrame.update()
468 bh = self._branchFrame.winfo_height()
469 self._lineCanvas.configure(height = bh)
470 cw = int(self._lineCanvas['width'])
471 ch = int(self._lineCanvas['height'])
472 self._lineCanvas.delete('vline')
474 self._lineCanvas.create_line(cw/2, 1, cw/2, ch, tag='vline')
477 def _sizechange(self):
478 if not self._last and self['view'] == 'expanded':
479 self._branchFrame.update()
480 bh = self._branchFrame.winfo_height()
481 self._lineCanvas.configure(height = bh)
482 if self._lineCanvas.coords('vline')[3] < bh:
483 cw = int(self._lineCanvas['width'])
484 ch = int(self._lineCanvas['height'])
485 #self._lineCanvas.delete('vline')
486 #self._lineCanvas.create_line(cw/2, 1, cw/2, ch, tag='vline')
487 self._lineCanvas.coords('vline', cw/2, 1, cw/2, ch)
488 self._parentNode._sizechange()
490 class TreeBrowser(Pmw.MegaWidget, _Branching):
492 def __init__(self, parent = None, nodeName = '0', **kw):
493 colors = Pmw.Color.getdefaultpalette(parent)
495 # Define the megawidget options.
496 INITOPT = Pmw.INITOPT
498 ('indent', 0, INITOPT),
499 ('selectbackground', colors['selectBackground'], INITOPT),
500 ('selectforeground', colors['selectForeground'], INITOPT),
501 ('background', colors['background'], INITOPT),
502 ('foreground', colors['foreground'], INITOPT),
503 #('selectrelief', 'raised', INITOPT),
506 self.defineoptions(kw, optiondefs)
508 # Initialise the base class (after defining the options).
509 Pmw.MegaWidget.__init__(self, parent)
510 _Branching.__init__(self)
513 # Create the components
514 interior = self._hull
516 browserFrame = self.createcomponent('frame', (), None,
521 browserFrame.pack(expand = 1, fill='both')
523 self._branchFrame = browserFrame.interior()
525 self._highlightedNode = None
526 self._treeRoot = self
527 self._nodeName = nodeName
528 # Check keywords and initialise options.
529 self.initialiseoptions()
531 def _highlightnode(self, newNode):
532 if self._highlightedNode != newNode:
533 if self._highlightedNode != None:
534 self._highlightedNode._unhighlight()
535 self._highlightedNode = newNode
537 def _unhighlightnode(self):
538 if self._highlightedNode != None:
539 self._highlightedNode._unhighlight()
540 self._highlightedNode = None
542 def curselection(self):
544 if self._highlightedNode != None:
545 retVal = (self._highlightedNode,
546 self._highlightedNode._nodeName,
547 self._highlightedNode['label'])
551 return self._nodeName
553 # The top-level TreeBrowser widget only shows nodes in an expanded view
554 # but still provides collapsetree() and expandtree() methods so that users
555 # don't have to special case the top-level node
557 def collapsetree(self):
560 def expandtree(self):
563 def _sizechange(self):
566 if __name__ == '__main__':
568 rootWin = Tkinter.Tk()
572 rootWin.title('TreeBrowser Demo')
574 # Create the hierarchical tree browser widget
575 treeBrowser = TreeBrowser(rootWin,
576 #selectbackground = "darkgreen",
577 #selectforeground = 'lightgreen',
578 #background = 'green',
583 def printselected(node):
584 selection = treeBrowser.curselection()
585 if selection != None:
586 print "Selected node name:", selection[1], " label:", selection[2]
589 def printdeselected(node):
590 selection = treeBrowser.curselection()
591 if selection != None:
592 print "Deselected node name:", selection[1], " label:", selection[2]
594 def printexpanded(node):
595 print "Expanded node name:", node.getname(), " label:", node.getlabel()
597 def printcollapsed(node):
598 print "Collapsed node name:", node.getname(), " label:", node.getlabel()
603 # Add a tree node to the top level
604 treeNodeLevel1 = treeBrowser.addbranch(label = 'TreeNode %d'%i,
605 selectcommand = printselected,
606 deselectcommand = printdeselected,
607 expandcommand = printexpanded,
608 collapsecommand = printcollapsed,
611 # Add a tree node to the second level
612 treeNodeLevel2 = treeNodeLevel1.addbranch(label = 'TreeNode %d.%d'%(i,j),
613 #selectforeground = 'yellow',
614 selectcommand = printselected,
615 deselectcommand = printdeselected,
616 expandcommand = printexpanded,
617 collapsecommand = printcollapsed,
619 if i == 0 and j == 1:
620 dynamicTreeRootNode = treeNodeLevel1
621 dynamicTreePosNode = treeNodeLevel2
623 for item in range((i+1)*(j+1)):
624 # Add a leaf node to the third level
625 leaf = treeNodeLevel2.addleaf(label = "Item %c"%(item+65),
626 #selectbackground = 'blue',
627 selectcommand = printselected,
628 deselectcommand = printdeselected)
629 for item in range(i+1):
630 # Add a leaf node to the top level
631 leaf = treeNodeLevel1.addleaf(label = "Item %c"%(item+65),
632 selectcommand = printselected,
633 deselectcommand = printdeselected)
636 treeNodeLevel1 = treeBrowser.addbranch(label = 'Check Button Label',
637 selectcommand = printselected,
638 deselectcommand = printdeselected,
639 expandcommand = printexpanded,
640 collapsecommand = printcollapsed,
642 checkButton = Tkinter.Checkbutton(treeNodeLevel1.interior(),
643 text = 'Da Check Button',
645 command = treeNodeLevel1.select)
648 treeNodeLevel1.addleaf(label = 'Labeled Leaf',
649 selectcommand = printselected,
650 deselectcommand = printdeselected)
651 leaf = treeNodeLevel1.addleaf(label = 'Labeled Leaf w/ Checkbutton',
652 selectcommand = printselected,
653 deselectcommand = printdeselected)
654 checkButton = Tkinter.Checkbutton(leaf.interior(),
655 text = 'Da Check Button',
657 command = leaf.select)
661 treeNodeLevel1 = treeBrowser.addbranch(selectcommand = printselected,
662 deselectcommand = printdeselected,
663 expandcommand = printexpanded,
664 collapsecommand = printcollapsed,
666 checkButton = Tkinter.Checkbutton(treeNodeLevel1.interior(),
667 text = 'Check Button with no label',
669 command = treeNodeLevel1.select)
672 treeNodeLevel1 = treeBrowser.addbranch(label = 'Label',
673 selectcommand = printselected,
674 deselectcommand = printdeselected,
675 expandcommand = printexpanded,
676 collapsecommand = printcollapsed,
679 # setup dynamic tree node insertion and removal
682 self.dyn = Tkinter.IntVar()
685 self.dLeaf = treeBrowser.addleaf(selectcommand = self.dynSelected,
686 deselectcommand = self.dynDeselected)
688 self.dCheckButton = Tkinter.Checkbutton(self.dLeaf.interior(),
689 text = 'Enable Dynamic Tree',
691 command = self.ChkBtnHandler)
692 self.dCheckButton.pack()
695 def dynSelected(self, node):
696 self.dCheckButton.configure(background = self.dLeaf.configure('selectbackground')[4])
699 def dynDeselected(self, node):
700 self.dCheckButton.configure(background = self.dLeaf.configure('background')[4])
701 printdeselected(node)
703 def ChkBtnHandler(self):
705 if self.dyn.get() == 1:
706 self.dtree = dynamicTreeRootNode.insertbranch(label = 'Dynamic Tree Node',
707 selectcommand = printselected,
708 deselectcommand = printdeselected,
709 expandcommand = printexpanded,
710 collapsecommand = printcollapsed,
711 before = dynamicTreePosNode)
712 self.dtree.addleaf(label = 'Dynamic Leaf 1',
713 selectcommand = printselected,
714 deselectcommand = printdeselected)
715 self.dtree.addleaf(label = 'Dynamic Leaf 2',
716 selectcommand = printselected,
717 deselectcommand = printdeselected)
719 if self.dtree != None:
720 dynamicTreeRootNode.delete(self.dtree)
727 treeBrowser.pack(expand = 1, fill='both')
729 exitButton = Tkinter.Button(rootWin, text="Quit", command=rootWin.quit)