diff --git a/direct/src/leveleditor/LevelEditor.py b/direct/src/leveleditor/LevelEditor.py index 05d2880315..624b208b0e 100644 --- a/direct/src/leveleditor/LevelEditor.py +++ b/direct/src/leveleditor/LevelEditor.py @@ -4,6 +4,7 @@ from OnscreenText import * from whrandom import * from Tkinter import * from DirectGeometry import * +from SceneGraphExplorer import * import Pmw import Dial import Floater @@ -111,7 +112,7 @@ class LevelEditor(NodePath, PandaObject): base.cam.node().setNear(5.0) 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 self.editToontownCentral() @@ -374,6 +375,7 @@ class LevelEditor(NodePath, PandaObject): self.ignore('createNewLevelGroup') self.ignore('setNodePathName') self.ignore('manipulateObjectCleanup') + self.ignore('SGESelectNodePath') self.ignore('showAll') self.ignore('p') self.disableManipulation() @@ -453,6 +455,7 @@ class LevelEditor(NodePath, PandaObject): self.accept('createNewLevelGroup', self.createNewLevelGroup) self.accept('setNodePathName', self.setNodePathName) self.accept('manipulateObjectCleanup', self.updateSelectedPose) + self.accept('SGESelectNodePath', self.selectNodePath) self.accept('showAll', self.showAll) self.accept('p',self.plantSelectedNodePath) self.enableManipulation() @@ -3216,6 +3219,7 @@ class LevelEditorPanel(Pmw.MegaToplevel): # suitBuildingsPage = notebook.add('Suit Buildings') propsPage = notebook.add('Props') colorPage = notebook.add('Set Color') + sceneGraphPage = notebook.add('SceneGraph') self.addStreetButton = Button( streetsPage, @@ -3228,6 +3232,8 @@ class LevelEditorPanel(Pmw.MegaToplevel): listheight = 200, labelpos = W, label_text = 'Street type:', + label_width = 12, + label_anchor = W, entry_width = 24, selectioncommand = self.setStreetModuleType, scrolledlist_items = map(lambda s: s[7:], @@ -3236,7 +3242,7 @@ class LevelEditorPanel(Pmw.MegaToplevel): ) self.streetModuleType = levelEditor.getCatalogCode('street',0) self.streetSelector.selectitem(self.streetModuleType[7:]) - self.streetSelector.pack(expand = 1, fill = 'x') + self.streetSelector.pack(expand = 1, fill = 'both') self.addToonBuildingButton = Button( toonBuildingsPage, @@ -3248,7 +3254,9 @@ class LevelEditorPanel(Pmw.MegaToplevel): dropdown = 0, listheight = 200, labelpos = W, - label_text = 'Toon building type:', + label_width = 12, + label_anchor = W, + label_text = 'Toon bldg type:', entry_width = 24, selectioncommand = self.setFlatBuildingType, scrolledlist_items = ('random20', 'random30', @@ -3258,13 +3266,13 @@ class LevelEditorPanel(Pmw.MegaToplevel): ) self.toonBuildingType = 'random20' self.toonBuildingSelector.selectitem(self.toonBuildingType) - self.toonBuildingSelector.pack(expand = 0) + self.toonBuildingSelector.pack(expand = 1, fill = 'both') self.toonBuildingWidthScale = EntryScale.EntryScale( toonBuildingsPage, min = 1.0, max = 30.0, resolution = 0.01, text = 'Wall Width', command = self.updateSelectedWallWidth) - self.toonBuildingWidthScale.pack(side = TOP, fill = 'x') + self.toonBuildingWidthScale.pack(fill = 'x') self.addLandmarkBuildingButton = Button( landmarkBuildingsPage, @@ -3276,6 +3284,8 @@ class LevelEditorPanel(Pmw.MegaToplevel): dropdown = 0, listheight = 200, labelpos = W, + label_width = 12, + label_anchor = W, label_text = 'Landmark Building type:', entry_width = 24, selectioncommand = self.setLandmarkType, @@ -3287,7 +3297,7 @@ class LevelEditorPanel(Pmw.MegaToplevel): 'toon_landmark',0) self.landmarkBuildingSelector.selectitem( levelEditor.getCatalogCode('toon_landmark',0)[14:]) - self.landmarkBuildingSelector.pack(side = 'left', expand = 0) + self.landmarkBuildingSelector.pack(expand = 1, fill = 'both') self.addPropsButton = Button( propsPage, @@ -3299,6 +3309,8 @@ class LevelEditorPanel(Pmw.MegaToplevel): dropdown = 0, listheight = 200, labelpos = W, + label_width = 12, + label_anchor = W, label_text = 'Prop type:', entry_width = 24, selectioncommand = self.setPropType, @@ -3308,7 +3320,7 @@ class LevelEditorPanel(Pmw.MegaToplevel): self.propType = levelEditor.getCatalogCode('prop',0) self.propSelector.selectitem( levelEditor.getCatalogCode('prop',0)[5:]) - self.propSelector.pack(side = 'left', expand = 0) + self.propSelector.pack(expand = 1, fill = 'both') # Compact down notebook notebook.setnaturalsize() @@ -3336,7 +3348,7 @@ class LevelEditorPanel(Pmw.MegaToplevel): variable = self.fHprSnap, command = self.toggleHprSnap) self.hprSnapButton.pack(side = 'left', fill = 'x') - buttonFrame.pack(fill = 'both') + buttonFrame.pack(expand = 1, fill = 'x') buttonFrame2 = Frame(hull) self.groupButton = Button( @@ -3356,12 +3368,17 @@ class LevelEditorPanel(Pmw.MegaToplevel): variable = self.fMapViz, command = self.toggleMapViz) self.mapSnapButton.pack(side = 'left', fill = 'x') - buttonFrame2.pack(fill = 'both') + buttonFrame2.pack(fill = 'x') self.colorEntry = VectorWidgets.ColorEntry( colorPage, text = 'Select Color', 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): if self.fGrid.get(): @@ -3442,207 +3459,9 @@ class LevelEditorPanel(Pmw.MegaToplevel): else: 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'! keyboardRotateNodePath: aNodePath key: arrowDirection | pos hpr scale | @@ -3750,154 +3569,6 @@ mouseCrank: aNodePath def keyboardXformNodePath(self,x): 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): # Accept arrow key events for swinging piece around self.ignore('left') @@ -3905,85 +3576,5 @@ mouseCrank: aNodePath self.ignore('up') 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() """ diff --git a/direct/src/tkwidgets/SceneGraphExplorer.py b/direct/src/tkwidgets/SceneGraphExplorer.py new file mode 100644 index 0000000000..99aebbb3cf --- /dev/null +++ b/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, "", 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, "", 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, "", 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 "" + 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("", 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("", self.edit_finish) + self.entry.bind("", 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('', self.mouse2Down) + self._canvas.bind('', self.mouse2Motion) + self._canvas.bind('', + 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]) + + diff --git a/direct/src/tkwidgets/Tree.py b/direct/src/tkwidgets/Tree.py new file mode 100644 index 0000000000..738d80cfdf --- /dev/null +++ b/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, "", 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, "", 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, "", 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 "" + 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("", 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("", self.edit_finish) + self.entry.bind("", 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.""" +