Przeglądaj źródła

*** empty log message ***

Mark Mine 25 lat temu
rodzic
commit
8dbea4d196

+ 28 - 437
direct/src/leveleditor/LevelEditor.py

@@ -4,6 +4,7 @@ from OnscreenText import *
 from whrandom import *
 from whrandom import *
 from Tkinter import *
 from Tkinter import *
 from DirectGeometry import *
 from DirectGeometry import *
+from SceneGraphExplorer import *
 import Pmw
 import Pmw
 import Dial
 import Dial
 import Floater
 import Floater
@@ -111,7 +112,7 @@ class LevelEditor(NodePath, PandaObject):
 
 
 	base.cam.node().setNear(5.0)
 	base.cam.node().setNear(5.0)
 	base.cam.node().setFar(10000)
 	base.cam.node().setFar(10000)
-	self.direct.camera.setPos(0,0,10)
+	self.direct.camera.setPos(0,-10,10)
 
 
 	# Default is to use the toontown central color palette
 	# Default is to use the toontown central color palette
 	self.editToontownCentral()
 	self.editToontownCentral()
@@ -374,6 +375,7 @@ class LevelEditor(NodePath, PandaObject):
 	self.ignore('createNewLevelGroup')
 	self.ignore('createNewLevelGroup')
         self.ignore('setNodePathName')
         self.ignore('setNodePathName')
         self.ignore('manipulateObjectCleanup')
         self.ignore('manipulateObjectCleanup')
+        self.ignore('SGESelectNodePath')
 	self.ignore('showAll')
 	self.ignore('showAll')
 	self.ignore('p')
 	self.ignore('p')
 	self.disableManipulation()
 	self.disableManipulation()
@@ -453,6 +455,7 @@ class LevelEditor(NodePath, PandaObject):
 	self.accept('createNewLevelGroup', self.createNewLevelGroup)
 	self.accept('createNewLevelGroup', self.createNewLevelGroup)
 	self.accept('setNodePathName', self.setNodePathName)
 	self.accept('setNodePathName', self.setNodePathName)
         self.accept('manipulateObjectCleanup', self.updateSelectedPose)
         self.accept('manipulateObjectCleanup', self.updateSelectedPose)
+        self.accept('SGESelectNodePath', self.selectNodePath)
 	self.accept('showAll', self.showAll)
 	self.accept('showAll', self.showAll)
 	self.accept('p',self.plantSelectedNodePath)
 	self.accept('p',self.plantSelectedNodePath)
 	self.enableManipulation()
 	self.enableManipulation()
@@ -3216,6 +3219,7 @@ class LevelEditorPanel(Pmw.MegaToplevel):
         # suitBuildingsPage = notebook.add('Suit Buildings')
         # suitBuildingsPage = notebook.add('Suit Buildings')
         propsPage = notebook.add('Props')
         propsPage = notebook.add('Props')
         colorPage = notebook.add('Set Color')
         colorPage = notebook.add('Set Color')
+        sceneGraphPage = notebook.add('SceneGraph')
 
 
         self.addStreetButton = Button(
         self.addStreetButton = Button(
             streetsPage,
             streetsPage,
@@ -3228,6 +3232,8 @@ class LevelEditorPanel(Pmw.MegaToplevel):
             listheight = 200,
             listheight = 200,
             labelpos = W,
             labelpos = W,
             label_text = 'Street type:',
             label_text = 'Street type:',
+            label_width = 12,
+            label_anchor = W,
             entry_width = 24,
             entry_width = 24,
             selectioncommand = self.setStreetModuleType,
             selectioncommand = self.setStreetModuleType,
             scrolledlist_items = map(lambda s: s[7:],
             scrolledlist_items = map(lambda s: s[7:],
@@ -3236,7 +3242,7 @@ class LevelEditorPanel(Pmw.MegaToplevel):
             )
             )
         self.streetModuleType = levelEditor.getCatalogCode('street',0)
         self.streetModuleType = levelEditor.getCatalogCode('street',0)
         self.streetSelector.selectitem(self.streetModuleType[7:])
         self.streetSelector.selectitem(self.streetModuleType[7:])
-        self.streetSelector.pack(expand = 1, fill = 'x')
+        self.streetSelector.pack(expand = 1, fill = 'both')
 
 
         self.addToonBuildingButton = Button(
         self.addToonBuildingButton = Button(
             toonBuildingsPage,
             toonBuildingsPage,
@@ -3248,7 +3254,9 @@ class LevelEditorPanel(Pmw.MegaToplevel):
             dropdown = 0,
             dropdown = 0,
             listheight = 200,
             listheight = 200,
             labelpos = W,
             labelpos = W,
-            label_text = 'Toon building type:',
+            label_width = 12,
+            label_anchor = W,
+            label_text = 'Toon bldg type:',
             entry_width = 24,
             entry_width = 24,
             selectioncommand = self.setFlatBuildingType,
             selectioncommand = self.setFlatBuildingType,
             scrolledlist_items = ('random20', 'random30',
             scrolledlist_items = ('random20', 'random30',
@@ -3258,13 +3266,13 @@ class LevelEditorPanel(Pmw.MegaToplevel):
             )
             )
         self.toonBuildingType = 'random20'
         self.toonBuildingType = 'random20'
         self.toonBuildingSelector.selectitem(self.toonBuildingType)
         self.toonBuildingSelector.selectitem(self.toonBuildingType)
-        self.toonBuildingSelector.pack(expand = 0)
+        self.toonBuildingSelector.pack(expand = 1, fill = 'both')
         
         
         self.toonBuildingWidthScale = EntryScale.EntryScale(
         self.toonBuildingWidthScale = EntryScale.EntryScale(
             toonBuildingsPage, min = 1.0, max = 30.0,
             toonBuildingsPage, min = 1.0, max = 30.0,
             resolution = 0.01, text = 'Wall Width',
             resolution = 0.01, text = 'Wall Width',
             command = self.updateSelectedWallWidth)
             command = self.updateSelectedWallWidth)
-        self.toonBuildingWidthScale.pack(side = TOP, fill = 'x')
+        self.toonBuildingWidthScale.pack(fill = 'x')
         
         
         self.addLandmarkBuildingButton = Button(
         self.addLandmarkBuildingButton = Button(
             landmarkBuildingsPage,
             landmarkBuildingsPage,
@@ -3276,6 +3284,8 @@ class LevelEditorPanel(Pmw.MegaToplevel):
             dropdown = 0,
             dropdown = 0,
             listheight = 200,
             listheight = 200,
             labelpos = W,
             labelpos = W,
+            label_width = 12,
+            label_anchor = W,
             label_text = 'Landmark Building type:',
             label_text = 'Landmark Building type:',
             entry_width = 24,
             entry_width = 24,
             selectioncommand = self.setLandmarkType,
             selectioncommand = self.setLandmarkType,
@@ -3287,7 +3297,7 @@ class LevelEditorPanel(Pmw.MegaToplevel):
             'toon_landmark',0)
             'toon_landmark',0)
         self.landmarkBuildingSelector.selectitem(
         self.landmarkBuildingSelector.selectitem(
             levelEditor.getCatalogCode('toon_landmark',0)[14:])
             levelEditor.getCatalogCode('toon_landmark',0)[14:])
-        self.landmarkBuildingSelector.pack(side = 'left', expand = 0)
+        self.landmarkBuildingSelector.pack(expand = 1, fill = 'both')
 
 
         self.addPropsButton = Button(
         self.addPropsButton = Button(
             propsPage,
             propsPage,
@@ -3299,6 +3309,8 @@ class LevelEditorPanel(Pmw.MegaToplevel):
             dropdown = 0,
             dropdown = 0,
             listheight = 200,
             listheight = 200,
             labelpos = W,
             labelpos = W,
+            label_width = 12,
+            label_anchor = W,
             label_text = 'Prop type:',
             label_text = 'Prop type:',
             entry_width = 24,
             entry_width = 24,
             selectioncommand = self.setPropType,
             selectioncommand = self.setPropType,
@@ -3308,7 +3320,7 @@ class LevelEditorPanel(Pmw.MegaToplevel):
         self.propType = levelEditor.getCatalogCode('prop',0)
         self.propType = levelEditor.getCatalogCode('prop',0)
         self.propSelector.selectitem(
         self.propSelector.selectitem(
             levelEditor.getCatalogCode('prop',0)[5:])
             levelEditor.getCatalogCode('prop',0)[5:])
-        self.propSelector.pack(side = 'left', expand = 0)
+        self.propSelector.pack(expand = 1, fill = 'both')
         # Compact down notebook
         # Compact down notebook
         notebook.setnaturalsize()
         notebook.setnaturalsize()
 
 
@@ -3336,7 +3348,7 @@ class LevelEditorPanel(Pmw.MegaToplevel):
                                       variable = self.fHprSnap,
                                       variable = self.fHprSnap,
                                       command = self.toggleHprSnap)
                                       command = self.toggleHprSnap)
         self.hprSnapButton.pack(side = 'left', fill = 'x')
         self.hprSnapButton.pack(side = 'left', fill = 'x')
-        buttonFrame.pack(fill = 'both')
+        buttonFrame.pack(expand = 1, fill = 'x')
 
 
         buttonFrame2 = Frame(hull)
         buttonFrame2 = Frame(hull)
         self.groupButton = Button(
         self.groupButton = Button(
@@ -3356,12 +3368,17 @@ class LevelEditorPanel(Pmw.MegaToplevel):
                                       variable = self.fMapViz,
                                       variable = self.fMapViz,
                                       command = self.toggleMapViz)
                                       command = self.toggleMapViz)
         self.mapSnapButton.pack(side = 'left', fill = 'x')
         self.mapSnapButton.pack(side = 'left', fill = 'x')
-        buttonFrame2.pack(fill = 'both')
+        buttonFrame2.pack(fill = 'x')
 
 
         self.colorEntry = VectorWidgets.ColorEntry(
         self.colorEntry = VectorWidgets.ColorEntry(
             colorPage, text = 'Select Color',
             colorPage, text = 'Select Color',
             command = self.updateSelectedObjColor)
             command = self.updateSelectedObjColor)
-        self.colorEntry.pack(fill = X)
+        self.colorEntry.pack(fill = 'x')
+
+        self.sceneGraphExplorer = SceneGraphExplorer(
+            parent = sceneGraphPage,
+            root = self.levelEditor.getLevelObjects())
+        self.sceneGraphExplorer.pack(expand = 1, fill = 'both')
         
         
     def toggleGrid(self):
     def toggleGrid(self):
         if self.fGrid.get():
         if self.fGrid.get():
@@ -3442,206 +3459,8 @@ class LevelEditorPanel(Pmw.MegaToplevel):
         else:
         else:
             self.balloon.configure(state = 'none')
             self.balloon.configure(state = 'none')
 
 
-"""
-
-    def initializePropButtons(self):
-	# Initialize Hooks and Buttons for the wall type buttons
-	newPtopTypes = self.getCatalogCodes('prop')
-	methodArray = []
-        for
-		newPtopTypes collect: [ :prop | 
-			{ prop copyFrom: 5 to: prop size . prop asSymbol } ].
-
-	hooksSet = self.hooksDictionary['prop ifAbsent: [ Set new. ].
-	methodArray do: [ :pair | hooksSet add: (pair at: 2) ].
-	self.hooksDictionary['prop'] = hooksSet.
-
-	# Create wall module buttons
-	# First get rid of any existing buttons
-	self.clickBoxDictionary['prop ifPresent: [ :clickBoxList | clickBoxList disable ].
-	buttons = ClickBoxList new table: methodArray x: 0.95 y: 0.90.
-	buttons addButtonWithText: 'back' event: #mainMenuEnable.	
-	buttons alignRight.
-	buttons.setScale(0.06.
-	buttons.setColor(0.6 g: 0.6 b: 0.6 a: 0.8.
-	self.clickBoxDictionary['prop'] = buttons.
-
-	self.categorySet add: #prop.
-! !
-
-!Level methodsFor: 'initialization' stamp: 'panda 00/00/0000 00:00'!
-initializeStreetButtons
-	| streetTypes methodArray hooksSet buttons |
-	# Initialize Hooks and Buttons for the wall type buttons
-	streetTypes = self.getCatalogCodes(#street.	
-	methodArray = 
-		streetTypes collect: [ :street | { street copyFrom: 8 to: street size . street asSymbol } ].
-
-	hooksSet = self.hooksDictionary['street ifAbsent: [ Set new. ].
-	methodArray do: [ :pair | hooksSet add: (pair at: 2) ].
-	self.hooksDictionary['street'] = hooksSet.
-
-	# Create wall module buttons
-	# First get rid of any existing buttons
-	self.clickBoxDictionary['street ifPresent: [ :clickBoxList | clickBoxList disable ].
-	buttons = ClickBoxList new table: methodArray x: 0.95 y: 0.90.
-	buttons addButtonWithText: 'back' event: #mainMenuEnable.	
-	buttons alignRight.
-	buttons.setScale(0.06.
-	buttons.setColor(0.6 g: 0.6 b: 0.6 a: 0.8.
-	self.clickBoxDictionary['street'] = buttons.
-
-	self.categorySet add: #street.
-! !
-
-!Level methodsFor: 'initialization' stamp: 'panda 00/00/0000 00:00'!
-initializeWallButtons
-	| methodArray hooksSet buttons |
-
-	# Initialize Hooks and Buttons for the wall type buttons
-	methodArray = { 
-		{ 'Random 20' . #random20 } .
-		{ 'Random 30' . #random30 } .
-		{ '10-10' . #toonTenTen } .
-		{ '20' . #toonTwenty } .
-		{ '10-20' . #toonTenTwenty } .
-		{ '20-10' . #toonTwentyTen } .
-		{ '10-10-10' . #toonTenTenTen } .
-		{ '30' . #toonThirty } }.
-
-	hooksSet = self.hooksDictionary['wall ifAbsent: [ Set new. ].
-	methodArray do: [ :pair | hooksSet add: (pair at: 2) ].
-	self.hooksDictionary['wall'] = hooksSet.
-
-	# Create wall module buttons
-	# First get rid of any existing buttons
-	self.clickBoxDictionary['wall ifPresent: [ :clickBoxList | clickBoxList disable ].
-	buttons = ClickBoxList new table: methodArray x: 0.95 y: 0.90.
-	buttons addButtonWithText: 'back' event: #mainMenuEnable.	
-	buttons.setColor(0.6 g: 0.6 b: 0.6 a: 0.8.
-	buttons.setScale(0.06.
-	buttons alignRight.
-	buttons makeAllWideAsWidest.
-	self.clickBoxDictionary['wall'] = buttons.
-
-	# Initialize Hooks and Buttons for the wall width buttons
-	methodArray = { { '5 ft' . #fiveFt } .
-					{ '10 ft' . #tenFt } .
-					{ '15 ft' . #fifteenFt } .
-					{ '20 ft' . #twentyFt } .
-					{ '25 ft' . #twentyFiveFt } }.
-
-	hooksSet = self.hooksDictionary['wallWidths ifAbsent: [ Set new. ].
-	methodArray do: [ :pair | hooksSet add: (pair at: 2) ].
-	self.hooksDictionary['wallWidths'] = hooksSet.
-
-	# Create wall width buttons
-	# First get rid of any existing buttons
-	self.clickBoxDictionary['wallWidths ifPresent: [ :clickBoxList | clickBoxList disable ].
-	buttons = ClickBoxList new table: methodArray x: 0.95 y: -0.40.
-	buttons.setColor(0.6 g: 0.6 b: 0.6 a: 0.8.
-	buttons.setScale(0.06.
-	buttons alignRight.
-	self.clickBoxDictionary['wallWidths'] = buttons.
-
-	self.categorySet add: #wall.
-! !
-
-
-    def clearHighlightedObjects(self):
-	highlightedObjects getChildren forEachPathPerform: #removeNode.! !
-
-!Level methodsFor: 'object operations' stamp: 'panda 00/00/0000 00:00'!
-followMouse: aNodePath
-	# Plant target object on grid at cursor projection point
-	| roundVal |
-	roundVal = (self.grid gridSpacing roundTo: 1).
-	(self.grid getMouseIntersectionPoint: self.hitPt ) ifTrue: [ 
-		self.grid xyzSnap ifTrue: [
-			aNodePath setPos: self.grid 
-				x: (((self.hitPt at: 0) + (self.offset at: 0)) roundTo: roundVal)
-				y: (((self.hitPt at: 1) + (self.offset at: 1)) roundTo: roundVal)
-				z: (((self.hitPt at: 2) + (self.offset at: 2)) roundTo: roundVal).
-			]
-		ifFalse: [
-			aNodePath setPos: self.grid pos: (self.hitPt + self.offset).
-			].
-		]
-! !
-
-!Level methodsFor: 'object operations' stamp: 'panda 00/00/0000 00:00'!
-followMouseStart: aNodePath
-	| gridToObjectHandles hitPtToObjectHandles |
-
-	# Where is the mouse relative to the grid?
-	self.grid getMouseIntersectionPoint: self.hitPt.
-	
-	# Record crank origin
-	self.crankOrigin operatorAssign: (direct selectedNodePath getPos: self.grid).
-
-	# Record the offset
-	self.offset = self.crankOrigin - self.hitPt.
-	# Init crankDir
-	self.crankDir operatorAssign: self.offset negated.
-	self.crankDir normalize.
-	# Compute crankAngle
-	startAngle = self.getCrankAngle.
-	startH = direct selectedNodePath getH.
-
-	# Transform hitPt into object handles space to determine manipulation mode
-	# Where is the mouse relative to the widget? 
-	 Don't snap to grid since we want to test hitPt relative to widget
-	self.grid getMouseIntersectionPoint: self.hitPt xyzSnap: 0.
-	gridToObjectHandles = self.grid getMat: direct objectHandles.
-	hitPtToObjectHandles = (Vec3 new: (gridToObjectHandles xformPoint: self.hitPt)) length.
-
-	# Are we inside rotation ring?
-	((hitPtToObjectHandles > 0.8) and: [(hitPtToObjectHandles < 1.2)])	
-	ifTrue: [[[true] taskWhileTrue: [ self.mouseCrank: aNodePath]] 
-				spawnTaskNamed: #levelMouseCrank.]
-	ifFalse: [[[true] taskWhileTrue: [ self.followMouse: aNodePath]] 
-				spawnTaskNamed: #levelFollowMouse.]
-
-! !
-
-!Level methodsFor: 'object operations' stamp: 'panda 00/00/0000 00:00'!
-followMouseStop
-	| selectedNode |
-	# Stop moving object
-	Task removeTasksNamed: #levelFollowMouse.
-	Task removeTasksNamed: #levelMouseCrank.
-
-	# Move grid to line up with object
-	selectedNode = direct selectedNodePath.
-	selectedNode notNone ifTrue: [
-		self.updateDNAPosHpr: selectedNode.
-		# Position grid for placing next object
-		self.autoPositionGrid.
-	].
 
 
-! !
-
-!Level methodsFor: 'object operations' stamp: 'panda 00/00/0000 00:00'!
-getCrankAngle
-	| newAngle |
-	self.crankDir normalize.
-	# Just look at Y component (equiv to dot product with (0 1 0)
-	newAngle = (self.crankDir at: 1) arcCos radiansToDegrees.
-	((self.crankDir at: 0) > 0.0) ifTrue: [ newAngle = newAngle negated. ].
-	# Force it to 0 to 360.0 range
- 	return newAngle + 180.0.							
-! !
-
-!Level methodsFor: 'object operations' stamp: 'panda 00/00/0000 00:00'!
-highlightNodePath: aNodePath
-	| pose highlightedNode |
-	# First clear out old highlighted nodes
-	self.clearHighlightedObjects.
-	# Place an instance of the object under the highlightedObjects node	
-	highlightedNode = aNodePath instanceTo: highlightedObjects.
-	pose = aNodePath getMat: self.levelObjects.
-	highlightedNode setMat: self.levelObjects mat: pose.
-	! !
+"""
 
 
 !Level methodsFor: 'object operations' stamp: 'panda 00/00/0000 00:00'!
 !Level methodsFor: 'object operations' stamp: 'panda 00/00/0000 00:00'!
 keyboardRotateNodePath: aNodePath key: arrowDirection 
 keyboardRotateNodePath: aNodePath key: arrowDirection 
@@ -3750,154 +3569,6 @@ mouseCrank: aNodePath
     def keyboardXformNodePath(self,x):
     def keyboardXformNodePath(self,x):
         pass
         pass
 
 
-    def levelHandleMouse1(self):
-        selectedNodePath = self.direct.selected.last
-        if selectedNodePath:
-            self.followMouseStart(selectedNodePath)
-            
-    def levelHandleMouse1Up(self):
-	self.followMouseStop()
-
-        # MRM
-    def activateLandmarkButtons(self):
-	# Switch menus to reveal street menu
-	self.mainMenuDisable()
-	self.categoryEnable('toon_landmark')
-
-        # MRM
-    def activatePropButtons(self):
-	# Switch menus to reveal street menu
-	self.mainMenuDisable()
-	self.categoryEnable('prop')
-
-    def activateStreetModuleButtons(self):
-	# Switch menus to reveal street menu
-	self.mainMenuDisable()
-	self.categoryEnable('street')
-
-    def activateVizObjectsButtons(self):
-	# Switch menus to reveal viz region menu
-	#self.mainMenuDisable()
-	#self.clickBoxDictionary['vizRegionButtons'].enable()
-	self.accept('addVizRegion', self.addVizRegion)
-	self.accept('addCollisionSphere', self.addCollisionSphere)
-	self.grid.setGridSpacing(10.0)
-
-        # MRM
-    def activateWallModuleButtons(self):
-	# Switch menus to reveal street menu
-	self.mainMenuDisable()
-	self.categoryEnable('wall')
-
-
-
-    def addHook(self, hook, function):
-	self.accept(hook, function, [hook])
-
-    def allMenuDisable(self):
-	self.mainMenuDisable()
-	self.gridMenuDisable()
-	self.subMenuDisable()
-	self.dnaMenuDisable()
-
-    def categoryDisable(self, categoryName):
-        clickBoxList = self.clickBoxDictionary.set(categoryName,None)
-        if clickBoxList:
-            clickBoxList.disable()
-
-        hooks = self.hooksDictionary.get(categoryName, None)
-        if hooks:
-            for hook in hooks:
-                self.ignore(hook)
-
-	# Do any category specific disabilizaton here
-        if categoryName == 'wall':
-            clickBoxList = self.clickBoxDictionary.get('wallWidths',None)
-            if clickBoxList:
-                clickBoxList.disable()
-                hooks = self.hooksDictionary.get('wallWidths', None)
-                if hooks:
-                    for hook in hooks:
-                        self.ignore(hook)
-
-	# Clear out space and insert hooks
-	self.ignore('space')
-	self.ignore('insert')
-
-    def categoryEnable(self,categoryName):
-	# First activate this category's main buttons
-        clickBoxList = self.clickBoxDictionary.get(categoryName,None)
-        if clickBoxList:
-            clickBoxList.enable()
-	# Now activate hooks and any supplemental actions
-        if categoryName == 'street':
-            # activate street module hooks
-            hooks = self.hooksDictionary.get(categoryName,None)
-            if hooks:
-                for hook in hooks:
-                    self.addHook(hook,self.addStreetModule)
-        elif categoryName == 'wall':
-            # Activate wall module hooks	
-            hooks = self.hooksDictionary.get(categoryName,None)
-            if hooks:
-                for hook in hooks:
-                    self.addHook(hook, self.addFlatBuilding)
-            # Also activate wall width buttons and hooks
-            clickBoxList = self.clickBoxDictionary.get('wallWidths', None)
-            if clickBoxList:
-                clickBoxList.enable()
-            hooks = self.hooksDictionary.get('wallWidths',None)
-            if hooks:
-                for hook in hooks:
-                    self.addHook(hook,self.wallWidthSym)
-        elif categoryName == 'toon_landmark':
-            # activate landmark hooks
-            hooks = self.hooksDictionary.get(categoryName,None)
-            if hooks:
-                for hook in hooks:
-                    self.addHook(hook,self.addLandmark)
-        elif categoryName == 'prop':
-            # activate prop hooks
-            hooks = self.hooksDictionary.get(categoryName,None)
-            if hooks:
-                for hook in hooks:
-                    self.addHook(hook,self.addProp)
-
-    def getClickBoxDictionary(self):
-	return self.clickBoxDictionary
-
-    def dnaMenuDisable(self):
-	# Disable DNA menu
-	self.clickBoxDictionary['groupButton'].disable()
-	self.ignore('createNewLevelGroup')
-	self.clickBoxDictionary['saveButton'].disable()
-	self.ignore('outputDNA:')
-	self.clickBoxDictionary['mapButton'].disable()
-	self.ignore('toggleMapViz')
-
-    def dnaMenuEnable(self):
-	# Enable DNA menu
-	self.clickBoxDictionary['groupButton'].enable()
-	self.accept('createNewLevelGroup', self.createNewLevelGroup)
-	self.clickBoxDictionary['saveButton'].enable()
-	self.accept('outputDNA', self.outputDNA)
-	self.clickBoxDictionary['mapButton'].enable()
-	self.accept('toggleMapViz', self.toggleMapViz)
-
-
-    def gridMenuDisable(self):
-	self.clickBoxDictionary['gridMenuButtons'].disable()
-	self.ignore('showGrid')
-	self.ignore('xyzSnap')
-	self.ignore('hprSnap')
-
-    def gridMenuEnable(self):
-	# Enable grid menu
-	self.clickBoxDictionary['gridMenuButtons'].enable()
-	self.accept('showGrid', self.showGrid)
-	self.accept('xyzSnap', self.xyzSnap)
-	self.accept('hprSnap', self.hprSnap)
-
     def ignoreArrowKeys(self):
     def ignoreArrowKeys(self):
 	# Accept arrow key events for swinging piece around
 	# Accept arrow key events for swinging piece around
 	self.ignore('left')
 	self.ignore('left')
@@ -3905,85 +3576,5 @@ mouseCrank: aNodePath
 	self.ignore('up')
 	self.ignore('up')
 	self.ignore('down')
 	self.ignore('down')
 
 
-    def mainMenuDisable(self):
-	self.clickBoxDictionary['mainMenuButtons'].disable()
-	self.ignore('activateWallModuleButtons')
-	self.ignore('activateStreetModuleButtons')
-	self.ignore('activateLandmarkButtons')
-	self.ignore('activatePropButtons')
-
-    def mainMenuEnable(self):
-	# Make sure all submenus are hidden
-	self.subMenuDisable()
-	# Now enable main menu
-	self.clickBoxDictionary['mainMenuButtons'].enable
-	self.accept('activateWallModuleButtons', self.activateWallModuleButtons)
-	self.accept('activateStreetModuleButtons', self.activateStreetModuleButtons)
-	self.accept('activateLandmarkButtons', self.activateLandmarkButtons)
-	self.accept('activatePropButtons', self.activatePropButtons)
-
-    def subMenuDisable(self):
-        for category in self.categorySet:
-            self.categoryDisable(category)
-
-    def vizMenuDisable(self):
-	self.clickBoxDictionary['vizRegionButtons'].disable()
-	self.ignore('addVizRegion')
-	self.ignore('addCollisionSphere')
-	self.grid.setGridSpacing(5.0)
-
-    def initializeLevelEditorButtons(self):
-	newClickBoxObject = ToggleBoxList(
-            [('Show Grid', self.showGrid, 0)
-             ('XYZ Snap', self.xyzSnap, 1),
-             ('HPR Snap', self.hprSnap, 1)],
-            -0.95, 0.90)
-	newClickBoxObject.alignLeft()
-	newClickBoxObject.setScale(0.06)
-	newClickBoxObject.makeAllWideAsWidest()
-	newClickBoxObject.enable()
-	self.clickBoxDictionary['gridMenuButtons'] = newClickBoxObject
-
-	newClickBoxObject = ClickBoxList(
-            [('Street modules', self.activateStreetModuleButtons),
-             ('Toon Walls', self.activateWallModuleButtons),
-             ('Landmark bldgs', self.activateLandmarkButtons),
-             ('Props', self.activatePropButtons)],
-            0.95, 0.90)
-	newClickBoxObject.setColor(0.5, 0.5, 0.5, 0.5)
-	newClickBoxObject.setScale(0.06)
-	newClickBoxObject.alignRight()
-	self.clickBoxDictionary['mainMenuButtons'] = newClickBoxObject
-
-	newClickBoxObject = ClickBox(
-            'New Group', 0.3, 0.3, -0.95, -0.9,
-            self.createNewLevelGroup, 0)
-	newClickBoxObject.nodePath().node().setCardColor(Point4(1,1,1,.5))
-	newClickBoxObject.setScale(0.05)
-	self.clickBoxDictionary['groupButton'] = newClickBoxObject
-
-	newClickBoxObject = ClickBox(
-            'Save DNA', 0.3, 0.3, -0.7, -0.9,
-            self.outputDNA, ['toontown.dna'],0)
-	newClickBoxObject.nodePath().node().setCardColor(Point4(1,1,1,.5))
-	newClickBoxObject.setScale(0.05)
-	self.clickBoxDictionary['saveButton'] = newClickBoxObject
-
-	newClickBoxObject = ToggleBox(
-            'Level Map', 0.3, 0.3, -0.47, -0.9,
-            self.toggleMapViz, [], 0)
-	newClickBoxObject.nodePath().node().setCardColor(Point4(1,1,1,.5))
-	newClickBoxObject.setButtonState(0)
-	newClickBoxObject.setScale(0.05)
-	self.clickBoxDictionary['mapButton'] = newClickBoxObject
-
-	self.dnaMenuEnable()
-
-	# Initialize module Dictionary with pointers to module
-                                   # node paths and create module buttons
-	self.initializeStreetButtons()
-	self.initializeWallButtons()
-	self.initializeLandmarkButtons()
-	self.initializePropButtons()
 
 
 """
 """

+ 457 - 0
direct/src/tkwidgets/SceneGraphExplorer.py

@@ -0,0 +1,457 @@
+from PandaObject import *
+from Tkinter import *
+from Tree import *
+import Pmw
+
+
+# Initialize icon directory
+f = Filename('icons')
+f.resolveFilename(getModelPath())
+ICONDIR = f.toOsSpecific()
+if not os.path.isdir(ICONDIR):
+    raise RuntimeError, "can't find DIRECT icon directory (%s)" % `ICONDIR`
+
+class TreeNode:
+
+    def __init__(self, canvas, parent, item, menuList = []):
+        self.canvas = canvas
+        self.parent = parent
+        self.item = item
+        self.state = 'collapsed'
+        self.selected = 0
+        self.children = {}
+        self.kidKeys = []
+        self.x = self.y = None
+        self.iconimages = {} # cache of PhotoImage instances for icons
+        self.menuList = menuList
+        self.menuVar = IntVar()
+        self.menuVar.set(0)
+        self._popupMenu = None
+        if self.menuList:
+            self._popupMenu = Menu(self.canvas, tearoff = 0)
+            for i in range(len(self.menuList)):
+                item = self.menuList[i]
+                self._popupMenu.add_radiobutton(
+                    label = item,
+                    variable = self.menuVar,
+                    value = i,
+                    indicatoron = 0,
+                    command = self.popupMenuCommand)
+
+    def destroy(self):
+        for key in self.kidKeys:
+            c = self.children[key]
+            del self.children[key]
+            c.destroy()
+        self.parent = None
+
+    def geticonimage(self, name):
+        try:
+            return self.iconimages[name]
+        except KeyError:
+            pass
+        file, ext = os.path.splitext(name)
+        ext = ext or ".gif"
+        fullname = os.path.join(ICONDIR, file + ext)
+        image = PhotoImage(master=self.canvas, file=fullname)
+        self.iconimages[name] = image
+        return image
+
+    def select(self, event=None):
+        if self.selected:
+            return
+        self.deselectall()
+        self.selected = 1
+        self.canvas.delete(self.image_id)
+        self.drawicon()
+        self.drawtext()
+        self.item.OnSelect()
+
+    def deselect(self, event=None):
+        if not self.selected:
+            return
+        self.selected = 0
+        self.canvas.delete(self.image_id)
+        self.drawicon()
+        self.drawtext()
+
+    def deselectall(self):
+        if self.parent:
+            self.parent.deselectall()
+        else:
+            self.deselecttree()
+
+    def deselecttree(self):
+        if self.selected:
+            self.deselect()
+        for key in self.kidKeys:
+            child = self.children[key]
+            child.deselecttree()
+
+    def flip(self, event=None):
+        if self.state == 'expanded':
+            self.collapse()
+        else:
+            self.expand()
+        self.item.OnDoubleClick()
+        return "break"
+
+    def selectAndPopupMenu(self, event=None):
+        self.select()
+        if self._popupMenu:
+            self._popupMenu.post(event.widget.winfo_pointerx(),
+                                 event.widget.winfo_pointery())
+            return "break"
+
+    def popupMenuCommand(self):
+        self.item.MenuCommand(self.menuList[self.menuVar.get()])
+
+    def expand(self, event=None):
+        if not self.item._IsExpandable():
+            return
+        if self.state != 'expanded':
+            self.state = 'expanded'
+            self.update()
+            self.view()
+
+    def collapse(self, event=None):
+        if self.state != 'collapsed':
+            self.state = 'collapsed'
+            self.update()
+
+    def view(self):
+        top = self.y - 2
+        bottom = self.lastvisiblechild().y + 17
+        height = bottom - top
+        visible_top = self.canvas.canvasy(0)
+        visible_height = self.canvas.winfo_height()
+        visible_bottom = self.canvas.canvasy(visible_height)
+        if visible_top <= top and bottom <= visible_bottom:
+            return
+        x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
+        if top >= visible_top and height <= visible_height:
+            fraction = top + height - visible_height
+        else:
+            fraction = top
+        fraction = float(fraction) / y1
+        self.canvas.yview_moveto(fraction)
+
+    def lastvisiblechild(self):
+        if self.kidKeys and self.state == 'expanded':
+            return self.children[self.kidKeys[-1]].lastvisiblechild()
+        else:
+            return self
+
+    def update(self):
+        if self.parent:
+            self.parent.update()
+        else:
+            oldcursor = self.canvas['cursor']
+            self.canvas['cursor'] = "watch"
+            self.canvas.update()
+            self.canvas.delete(ALL)     # XXX could be more subtle
+            self.draw(7, 2)
+            x0, y0, x1, y1 = self.canvas.bbox(ALL)
+            self.canvas.configure(scrollregion=(0, 0, x1, y1))
+            self.canvas['cursor'] = oldcursor
+
+    def draw(self, x, y):
+        # XXX This hard-codes too many geometry constants!
+        self.x, self.y = x, y
+        self.drawicon()
+        self.drawtext()
+        if self.state != 'expanded':
+            return y+17
+        # draw children
+        #if not self.children:
+        #self.children = []
+        sublist = self.item._GetSubList()
+        if not sublist:
+            # _IsExpandable() was mistaken; that's allowed
+            return y+17
+        self.kidKeys = []
+        for item in sublist:
+            key = item.nodePath.id()
+            if self.children.has_key(key):
+                child = self.children[key]
+            else:
+                child = TreeNode(self.canvas, self, item, self.menuList)
+            self.children[key] = child
+        cx = x+20
+        cy = y+17
+        cylast = 0
+        for key in self.kidKeys:
+            child = self.children[key]
+            cylast = cy
+            self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
+            cy = child.draw(cx, cy)
+            if child.item._IsExpandable():
+                if child.state == 'expanded':
+                    iconname = "minusnode"
+                    callback = child.collapse
+                else:
+                    iconname = "plusnode"
+                    callback = child.expand
+                image = self.geticonimage(iconname)
+                id = self.canvas.create_image(x+9, cylast+7, image=image)
+                # XXX This leaks bindings until canvas is deleted:
+                self.canvas.tag_bind(id, "<1>", callback)
+                self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
+        id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
+            ##stipple="gray50",     # XXX Seems broken in Tk 8.0.x
+            fill="gray50")
+        self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
+        return cy
+
+    def drawicon(self):
+        if self.selected:
+            imagename = (self.item.GetSelectedIconName() or
+                         self.item.GetIconName() or
+                         "openfolder")
+        else:
+            imagename = self.item.GetIconName() or "folder"
+        image = self.geticonimage(imagename)
+        id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
+        self.image_id = id
+        self.canvas.tag_bind(id, "<1>", self.select)
+        self.canvas.tag_bind(id, "<Double-1>", self.flip)
+        self.canvas.tag_bind(id, "<3>", self.selectAndPopupMenu)
+        
+    def drawtext(self):
+        textx = self.x+20-1
+        texty = self.y-1
+        labeltext = self.item.GetLabelText()
+        if labeltext:
+            id = self.canvas.create_text(textx, texty, anchor="nw",
+                                         text=labeltext)
+            self.canvas.tag_bind(id, "<1>", self.select)
+            self.canvas.tag_bind(id, "<Double-1>", self.flip)
+            self.canvas.tag_bind(id, "<3>", self.selectAndPopupMenu)
+            x0, y0, x1, y1 = self.canvas.bbox(id)
+            textx = max(x1, 200) + 10
+        text = self.item.GetText() or "<no text>"
+        try:
+            self.entry
+        except AttributeError:
+            pass
+        else:
+            self.edit_finish()
+        try:
+            label = self.label
+        except AttributeError:
+            # padding carefully selected (on Windows) to match Entry widget:
+            self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
+        if self.selected:
+            self.label.configure(fg="white", bg="darkblue")
+        else:
+            self.label.configure(fg="black", bg="white")
+        id = self.canvas.create_window(textx, texty,
+                                       anchor="nw", window=self.label)
+        self.label.bind("<1>", self.select_or_edit)
+        self.label.bind("<Double-1>", self.flip)
+        self.text_id = id
+
+    def select_or_edit(self, event=None):
+        if self.selected and self.item.IsEditable():
+            self.edit(event)
+        else:
+            self.select(event)
+
+    def edit(self, event=None):
+        self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
+        self.entry.insert(0, self.label['text'])
+        self.entry.selection_range(0, END)
+        self.entry.pack(ipadx=5)
+        self.entry.focus_set()
+        self.entry.bind("<Return>", self.edit_finish)
+        self.entry.bind("<Escape>", self.edit_cancel)
+
+    def edit_finish(self, event=None):
+        try:
+            entry = self.entry
+            del self.entry
+        except AttributeError:
+            return
+        text = entry.get()
+        entry.destroy()
+        if text and text != self.item.GetText():
+            self.item.SetText(text)
+        text = self.item.GetText()
+        self.label['text'] = text
+        self.drawtext()
+        self.canvas.focus_set()
+
+    def edit_cancel(self, event=None):
+        self.drawtext()
+        self.canvas.focus_set()
+
+
+class TreeItem:
+
+    """Abstract class representing tree items.
+
+    Methods should typically be overridden, otherwise a default action
+    is used.
+
+    """
+
+    def __init__(self):
+        """Constructor.  Do whatever you need to do."""
+
+    def GetText(self):
+        """Return text string to display."""
+
+    def GetLabelText(self):
+        """Return label text string to display in front of text (if any)."""
+
+    expandable = None
+
+    def _IsExpandable(self):
+        """Do not override!  Called by TreeNode."""
+        if self.expandable is None:
+            self.expandable = self.IsExpandable()
+        return self.expandable
+
+    def IsExpandable(self):
+        """Return whether there are subitems."""
+        return 1
+
+    def _GetSubList(self):
+        """Do not override!  Called by TreeNode."""
+        if not self.IsExpandable():
+            return []
+        sublist = self.GetSubList()
+        return sublist
+
+    def IsEditable(self):
+        """Return whether the item's text may be edited."""
+
+    def SetText(self, text):
+        """Change the item's text (if it is editable)."""
+
+    def GetIconName(self):
+        """Return name of icon to be displayed normally."""
+
+    def GetSelectedIconName(self):
+        """Return name of icon to be displayed when selected."""
+
+    def GetSubList(self):
+        """Return list of items forming sublist."""
+
+    def OnDoubleClick(self):
+        """Called on a double-click on the item."""
+
+    def OnSelect(self):
+        """Called when item selected."""
+
+
+class SceneGraphExplorer(Pmw.MegaWidget):
+    "Graphical display of a scene graph"
+    def __init__(self, root = render, parent = None, **kw):
+        # Define the megawidget options.
+        optiondefs = ()
+        self.defineoptions(kw, optiondefs)
+ 
+        # Initialise superclass
+        Pmw.MegaWidget.__init__(self, parent)
+        
+        # Initialize some class variables
+        self.root = root
+
+        # Create the components.
+        
+        # Setup up container
+        interior = self.interior()
+        interior.configure(relief = GROOVE, borderwidth = 2)
+        
+        # Create a label and an entry
+        self._scrolledCanvas = self.createcomponent(
+            'scrolledCanvas',
+            (), None,
+            Pmw.ScrolledCanvas, (interior,),
+            hull_width = 200, hull_height = 400,
+            usehullsize = 1)
+        self._canvas = self._scrolledCanvas.component('canvas')
+        self._canvas['scrollregion'] = ('0i', '0i', '2i', '4i')
+        self._scrolledCanvas.resizescrollregion()
+        self._scrolledCanvas.pack(padx = 5, pady = 5, expand=1, fill = BOTH)
+        
+        self._canvas.bind('<ButtonPress-2>', self.mouse2Down)
+        self._canvas.bind('<B2-Motion>', self.mouse2Motion)
+        self._canvas.bind('<Configure>',
+                          lambda e, sc = self._scrolledCanvas:
+                          sc.resizescrollregion())
+
+        # Create the contents
+        self._treeItem = SceneGraphExplorerItem(self.root)
+
+        self._node = TreeNode(self._canvas, None, self._treeItem,
+                              ['SGESelect'])
+        self._node.expand()
+
+        # Check keywords and initialise options based on input values.
+        self.initialiseoptions(SceneGraphExplorer)
+
+    def mouse2Down(self, event):
+        self._width = 1.0 * self._canvas.winfo_width()
+        self._height = 1.0 * self._canvas.winfo_height()
+        xview = self._canvas.xview()
+        yview = self._canvas.yview()        
+        self._left = xview[0]
+        self._top = yview[0]
+        self._dxview = xview[1] - xview[0]
+        self._dyview = yview[1] - yview[0]
+        self._2lx = event.x
+        self._2ly = event.y
+
+    def mouse2Motion(self,event):
+        newx = self._left - ((event.x - self._2lx)/self._width) * self._dxview
+        self._canvas.xview_moveto(newx)
+        newy = self._top - ((event.y - self._2ly)/self._height) * self._dyview
+        self._canvas.yview_moveto(newy)
+        self._2lx = event.x
+        self._2ly = event.y
+        self._left = self._canvas.xview()[0]
+        self._top = self._canvas.yview()[0]
+
+
+class SceneGraphExplorerItem(TreeItem):
+
+    """Example TreeItem subclass -- browse the file system."""
+
+    def __init__(self, nodePath):
+        self.nodePath = nodePath
+
+    def GetText(self):
+        type = self.nodePath.node().getType().getName()
+        name = self.nodePath.getNodePathName()
+        return type + "  " + name
+
+    def IsEditable(self):
+        return issubclass(self.nodePath.node().__class__, NamedNode)
+
+    def SetText(self, text):
+        try:
+            self.nodePath.node().setName(text)
+        except AttributeError:
+            pass
+
+    def GetIconName(self):
+        if not self.IsExpandable():
+            return "sphere2" # XXX wish there was a "file" icon
+
+    def IsExpandable(self):
+        return self.nodePath.getNumChildren() != 0
+
+    def GetSubList(self):
+        sublist = []
+        for nodePath in self.nodePath.getChildrenAsList():
+            item = SceneGraphExplorerItem(nodePath)
+            sublist.append(item)
+        return sublist
+
+    def MenuCommand(self, command):
+        if (command == 'SGESelect'):
+            messenger.send('SGESelectNodePath', [self.nodePath])
+
+

+ 356 - 0
direct/src/tkwidgets/Tree.py

@@ -0,0 +1,356 @@
+# ADAPTED FROM IDLE TreeWidget.py
+# XXX TO DO:
+# - popup menu
+# - support partial or total redisplay
+# - key bindings (instead of quick-n-dirty bindings on Canvas):
+#   - up/down arrow keys to move focus around
+#   - ditto for page up/down, home/end
+#   - left/right arrows to expand/collapse & move out/in
+# - more doc strings
+# - add icons for "file", "module", "class", "method"; better "python" icon
+# - callback for selection???
+# - multiple-item selection
+# - tooltips
+# - redo geometry without magic numbers
+# - keep track of object ids to allow more careful cleaning
+# - optimize tree redraw after expand of subnode
+
+import os
+import sys
+import string
+from Tkinter import *
+from PandaObject import *
+
+# Initialize icon directory
+f = Filename('icons')
+f.resolveFilename(getModelPath())
+ICONDIR = f.toOsSpecific()
+if not os.path.isdir(ICONDIR):
+    raise RuntimeError, "can't find DIRECT icon directory (%s)" % `ICONDIR`
+
+class TreeNode:
+
+    def __init__(self, canvas, parent, item, menuList = []):
+        self.canvas = canvas
+        self.parent = parent
+        self.item = item
+        self.state = 'collapsed'
+        self.selected = 0
+        self.children = []
+        self.x = self.y = None
+        self.iconimages = {} # cache of PhotoImage instances for icons
+        self.menuList = menuList
+        self.menuVar = IntVar()
+        self.menuVar.set(0)
+        self._popupMenu = None
+        if self.menuList:
+            self._popupMenu = Menu(self.canvas, tearoff = 0)
+            for i in range(len(self.menuList)):
+                item = self.menuList[i]
+                self._popupMenu.add_radiobutton(
+                    label = item,
+                    variable = self.menuVar,
+                    value = i,
+                    indicatoron = 0,
+                    command = self.popupMenuCommand)
+
+    def destroy(self):
+        for c in self.children[:]:
+            self.children.remove(c)
+            c.destroy()
+        self.parent = None
+
+    def geticonimage(self, name):
+        try:
+            return self.iconimages[name]
+        except KeyError:
+            pass
+        file, ext = os.path.splitext(name)
+        ext = ext or ".gif"
+        fullname = os.path.join(ICONDIR, file + ext)
+        image = PhotoImage(master=self.canvas, file=fullname)
+        self.iconimages[name] = image
+        return image
+
+    def select(self, event=None):
+        if self.selected:
+            return
+        self.deselectall()
+        self.selected = 1
+        self.canvas.delete(self.image_id)
+        self.drawicon()
+        self.drawtext()
+        self.item.OnSelect()
+
+    def deselect(self, event=None):
+        if not self.selected:
+            return
+        self.selected = 0
+        self.canvas.delete(self.image_id)
+        self.drawicon()
+        self.drawtext()
+
+    def deselectall(self):
+        if self.parent:
+            self.parent.deselectall()
+        else:
+            self.deselecttree()
+
+    def deselecttree(self):
+        if self.selected:
+            self.deselect()
+        for child in self.children:
+            child.deselecttree()
+
+    def flip(self, event=None):
+        if self.state == 'expanded':
+            self.collapse()
+        else:
+            self.expand()
+        self.item.OnDoubleClick()
+        return "break"
+
+    def selectAndPopupMenu(self, event=None):
+        self.select()
+        if self._popupMenu:
+            self._popupMenu.post(event.widget.winfo_pointerx(),
+                                 event.widget.winfo_pointery())
+            return "break"
+
+    def popupMenuCommand(self):
+        self.item.MenuCommand(self.menuList[self.menuVar.get()])
+
+    def expand(self, event=None):
+        if not self.item._IsExpandable():
+            return
+        if self.state != 'expanded':
+            self.state = 'expanded'
+            self.update()
+            self.view()
+
+    def collapse(self, event=None):
+        if self.state != 'collapsed':
+            self.state = 'collapsed'
+            self.update()
+
+    def view(self):
+        top = self.y - 2
+        bottom = self.lastvisiblechild().y + 17
+        height = bottom - top
+        visible_top = self.canvas.canvasy(0)
+        visible_height = self.canvas.winfo_height()
+        visible_bottom = self.canvas.canvasy(visible_height)
+        if visible_top <= top and bottom <= visible_bottom:
+            return
+        x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
+        if top >= visible_top and height <= visible_height:
+            fraction = top + height - visible_height
+        else:
+            fraction = top
+        fraction = float(fraction) / y1
+        self.canvas.yview_moveto(fraction)
+
+    def lastvisiblechild(self):
+        if self.children and self.state == 'expanded':
+            return self.children[-1].lastvisiblechild()
+        else:
+            return self
+
+    def update(self):
+        if self.parent:
+            self.parent.update()
+        else:
+            oldcursor = self.canvas['cursor']
+            self.canvas['cursor'] = "watch"
+            self.canvas.update()
+            self.canvas.delete(ALL)     # XXX could be more subtle
+            self.draw(7, 2)
+            x0, y0, x1, y1 = self.canvas.bbox(ALL)
+            self.canvas.configure(scrollregion=(0, 0, x1, y1))
+            self.canvas['cursor'] = oldcursor
+
+    def draw(self, x, y):
+        # XXX This hard-codes too many geometry constants!
+        self.x, self.y = x, y
+        self.drawicon()
+        self.drawtext()
+        if self.state != 'expanded':
+            return y+17
+        # draw children
+        #if not self.children:
+        sublist = self.item._GetSubList()
+        if not sublist:
+            # _IsExpandable() was mistaken; that's allowed
+            return y+17
+        #self.children = []
+        for item in sublist:
+            child = TreeNode(self.canvas, self, item, self.menuList)
+            self.children.append(child)
+        cx = x+20
+        cy = y+17
+        cylast = 0
+        for child in self.children:
+            cylast = cy
+            self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
+            cy = child.draw(cx, cy)
+            if child.item._IsExpandable():
+                if child.state == 'expanded':
+                    iconname = "minusnode"
+                    callback = child.collapse
+                else:
+                    iconname = "plusnode"
+                    callback = child.expand
+                image = self.geticonimage(iconname)
+                id = self.canvas.create_image(x+9, cylast+7, image=image)
+                # XXX This leaks bindings until canvas is deleted:
+                self.canvas.tag_bind(id, "<1>", callback)
+                self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
+        id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
+            ##stipple="gray50",     # XXX Seems broken in Tk 8.0.x
+            fill="gray50")
+        self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
+        return cy
+
+    def drawicon(self):
+        if self.selected:
+            imagename = (self.item.GetSelectedIconName() or
+                         self.item.GetIconName() or
+                         "openfolder")
+        else:
+            imagename = self.item.GetIconName() or "folder"
+        image = self.geticonimage(imagename)
+        id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
+        self.image_id = id
+        self.canvas.tag_bind(id, "<1>", self.select)
+        self.canvas.tag_bind(id, "<Double-1>", self.flip)
+        self.canvas.tag_bind(id, "<3>", self.selectAndPopupMenu)
+        
+    def drawtext(self):
+        textx = self.x+20-1
+        texty = self.y-1
+        labeltext = self.item.GetLabelText()
+        if labeltext:
+            id = self.canvas.create_text(textx, texty, anchor="nw",
+                                         text=labeltext)
+            self.canvas.tag_bind(id, "<1>", self.select)
+            self.canvas.tag_bind(id, "<Double-1>", self.flip)
+            self.canvas.tag_bind(id, "<3>", self.selectAndPopupMenu)
+            x0, y0, x1, y1 = self.canvas.bbox(id)
+            textx = max(x1, 200) + 10
+        text = self.item.GetText() or "<no text>"
+        try:
+            self.entry
+        except AttributeError:
+            pass
+        else:
+            self.edit_finish()
+        try:
+            label = self.label
+        except AttributeError:
+            # padding carefully selected (on Windows) to match Entry widget:
+            self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
+        if self.selected:
+            self.label.configure(fg="white", bg="darkblue")
+        else:
+            self.label.configure(fg="black", bg="white")
+        id = self.canvas.create_window(textx, texty,
+                                       anchor="nw", window=self.label)
+        self.label.bind("<1>", self.select_or_edit)
+        self.label.bind("<Double-1>", self.flip)
+        self.text_id = id
+
+    def select_or_edit(self, event=None):
+        if self.selected and self.item.IsEditable():
+            self.edit(event)
+        else:
+            self.select(event)
+
+    def edit(self, event=None):
+        self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
+        self.entry.insert(0, self.label['text'])
+        self.entry.selection_range(0, END)
+        self.entry.pack(ipadx=5)
+        self.entry.focus_set()
+        self.entry.bind("<Return>", self.edit_finish)
+        self.entry.bind("<Escape>", self.edit_cancel)
+
+    def edit_finish(self, event=None):
+        try:
+            entry = self.entry
+            del self.entry
+        except AttributeError:
+            return
+        text = entry.get()
+        entry.destroy()
+        if text and text != self.item.GetText():
+            self.item.SetText(text)
+        text = self.item.GetText()
+        self.label['text'] = text
+        self.drawtext()
+        self.canvas.focus_set()
+
+    def edit_cancel(self, event=None):
+        self.drawtext()
+        self.canvas.focus_set()
+
+
+class TreeItem:
+
+    """Abstract class representing tree items.
+
+    Methods should typically be overridden, otherwise a default action
+    is used.
+
+    """
+
+    def __init__(self):
+        """Constructor.  Do whatever you need to do."""
+
+    def GetText(self):
+        """Return text string to display."""
+
+    def GetLabelText(self):
+        """Return label text string to display in front of text (if any)."""
+
+    expandable = None
+
+    def _IsExpandable(self):
+        """Do not override!  Called by TreeNode."""
+        if self.expandable is None:
+            self.expandable = self.IsExpandable()
+        return self.expandable
+
+    def IsExpandable(self):
+        """Return whether there are subitems."""
+        return 1
+
+    def _GetSubList(self):
+        """Do not override!  Called by TreeNode."""
+        if not self.IsExpandable():
+            return []
+        sublist = self.GetSubList()
+        if not sublist:
+            self.expandable = 0
+        return sublist
+
+    def IsEditable(self):
+        """Return whether the item's text may be edited."""
+
+    def SetText(self, text):
+        """Change the item's text (if it is editable)."""
+
+    def GetIconName(self):
+        """Return name of icon to be displayed normally."""
+
+    def GetSelectedIconName(self):
+        """Return name of icon to be displayed when selected."""
+
+    def GetSubList(self):
+        """Return list of items forming sublist."""
+
+    def OnDoubleClick(self):
+        """Called on a double-click on the item."""
+
+    def OnSelect(self):
+        """Called when item selected."""
+