def makePickableQuests(self, list): quests = [] for questId in list: quests.append(Quests.Quest(questId, 0, 0, list.index(questId))) positions = [(0, 0, 0.6), (0, 0, 0), (0, 0, -0.6)] self.questNotes = base.localAvatar.questManager.makeQuestNotes(quests = quests) self.questFrame = DirectFrame(parent = base.a2dLeftCenter, relief = None, pos = (0.5, 0, 0), geom = DGG.getDefaultDialogGeom(), geom_color=Vec4(0.8, 0.6, 0.4, 1), geom_scale=(1.85, 1, 0.9), geom_hpr=(0, 0, -90)) self.questBtns = [] for i in xrange(len(self.questNotes)): note = self.questNotes[i] note.setPos(0, 0, 0) if quests[i].currentObjective.type in Quests.DefeatObjectives: note.progressText.hide() btn = DirectButton(geom = note, parent = self.questFrame, pos = positions[i], command = self.d_pickedQuest, extraArgs = [quests[i]], relief = None) btn.setScale(1.15) note.reparentTo(btn.stateNodePath[0], 20) note.instanceTo(btn.stateNodePath[1], 20) note.instanceTo(btn.stateNodePath[2], 20) note.show() self.questBtns.append(btn)
def _createButtons(self, cfg): ''' Creates DirectGui elements for displaying the paging and scrolling buttons. The sprite names are read from the configuration. The create DirectButtons use sprites as images. @param cfg: a ConfigVars instance ''' # button to display next page of items nxPgBtnSprite = cfg.get(PanoConstants.CVAR_INVENTORY_NEXTPAGE_SPRITE) nxPgBtnPressedSprite = cfg.get( PanoConstants.CVAR_INVENTORY_NEXTPAGE_PRESSED_SPRITE) nxPgBtnHoverSprite = cfg.get( PanoConstants.CVAR_INVENTORY_NEXTPAGE_HOVER_SPRITE) nxPgBtnPos = cfg.getVec2(PanoConstants.CVAR_INVENTORY_NEXTPAGE_POS) # button to display previous page of items pvPgBtnSprite = cfg.get(PanoConstants.CVAR_INVENTORY_PREVPAGE_SPRITE) pvPgBtnPressedSprite = cfg.get( PanoConstants.CVAR_INVENTORY_PREVPAGE_PRESSED_SPRITE) pvPgBtnHoverSprite = cfg.get( PanoConstants.CVAR_INVENTORY_PREVPAGE_HOVER_SPRITE) pvPgBtnPos = cfg.getVec2(PanoConstants.CVAR_INVENTORY_PREVPAGE_POS) # button to scroll to next items scrNxBtnSprite = cfg.get( PanoConstants.CVAR_INVENTORY_SCROLLNEXT_SPRITE) scrNxBtnPressedSprite = cfg.get( PanoConstants.CVAR_INVENTORY_SCROLLNEXT_PRESSED_SPRITE) scrNxBtnHoverSprite = cfg.get( PanoConstants.CVAR_INVENTORY_SCROLLNEXT_HOVER_SPRITE) scrNxBtnPos = cfg.getVec2(PanoConstants.CVAR_INVENTORY_SCROLLNEXT_POS) # button to scroll to previous items scrPvBtnSprite = cfg.get( PanoConstants.CVAR_INVENTORY_SCROLLPREV_SPRITE) scrPvBtnPressedSprite = cfg.get( PanoConstants.CVAR_INVENTORY_SCROLLPREV_PRESSED_SPRITE) scrPvBtnHoverSprite = cfg.get( PanoConstants.CVAR_INVENTORY_SCROLLPREV_HOVER_SPRITE) scrPvBtnPos = cfg.getVec2(PanoConstants.CVAR_INVENTORY_SCROLLPREV_POS) sprites = self.game.getView().getSpritesFactory() origin = aspect2d.getRelativePoint(screen2d, VBase3(0, 0, 0)) # for every button define property name, position, callback, list of sprites for normal, pressed and hover state pagingButtons = [ ('nextPageButton', nxPgBtnPos, self._nextPageCallback, [(nxPgBtnSprite, 'next_page_sprite'), (nxPgBtnPressedSprite, 'next_page_pressed_sprite'), (nxPgBtnHoverSprite, 'next_page_hover_sprite')]), ('prevPageButton', pvPgBtnPos, self._previousPageCallback, [(pvPgBtnSprite, 'previous_page_sprite'), (pvPgBtnPressedSprite, 'previous_page_pressed_sprite'), (pvPgBtnHoverSprite, 'previous_page_hover_sprite')]), ('scrollNextButton', scrNxBtnPos, self._scrollNextCallback, [(scrNxBtnSprite, 'scroll_next_sprite'), (scrNxBtnPressedSprite, 'scroll_next_pressed_sprite'), (scrNxBtnHoverSprite, 'scroll_next_hover_sprite')]), ('scrollPrevButton', scrPvBtnPos, self._scrollPreviousCallback, [(scrPvBtnSprite, 'scroll_previous_sprite'), (scrPvBtnPressedSprite, 'scroll_previous_pressed_sprite'), (scrPvBtnHoverSprite, 'scroll_previous_hover_sprite')]), ] for buttonName, buttonPos, buttonCallback, spritesList in pagingButtons: buttonGeoms = [None, None, None, None] btnScrBounds = [0, 0, 0] i = 0 for spriteFile, spriteName in spritesList: print 'adding sprite %s' % spriteName if spriteFile is not None: spr = None if spriteFile.rindex('.') >= 0: ext = spriteFile[spriteFile.rindex('.'):] print ext if ResourcesTypes.isExtensionOfType( ext, PanoConstants.RES_TYPE_IMAGES): spr = Sprite(spriteName) spr.image = spriteFile else: spr = self.game.getResources().loadSprite(spriteFile) if spr: buttonGeoms[i] = sprites.createSprite(spr).nodepath buttonGeoms[i].setScale(1.0) btnScrBounds = aspect2d.getRelativePoint( screen2d, VBase3(spr.width, 1.0, spr.height)) - origin btnScrBounds[2] *= -1 i += 1 if buttonGeoms[0] is not None: b = DirectButton( geom=(buttonGeoms[0], buttonGeoms[1] if buttonGeoms[1] else buttonGeoms[0], buttonGeoms[2] if buttonGeoms[2] else buttonGeoms[0], buttonGeoms[3] if buttonGeoms[3] else buttonGeoms[0]), relief=None) b['geom_pos'] = (0, 0, 0) b.setTransparency(1) # if position is omitted from the configuration, put the button on the upper left corner if buttonPos is not None: b.setPos( aspect2d.getRelativePoint( screen2d, VBase3(buttonPos[0], 1.0, buttonPos[1]))) else: b.setPos(origin[0], 1.0, origin[2]) b.setScale(btnScrBounds[0], 1.0, btnScrBounds[2]) b.setFrameSize((0, btnScrBounds[0], 1.0, btnScrBounds[2])) b['command'] = buttonCallback b['extraArgs'] = (self.msn, ) b.hide() else: b = None setattr(self, buttonName, b)
def makePickableQuests(self, questList): self.doCameraNPCInteraction(True) quests = [] for questId in questList: quest = Quest(questId, base.localAvatar.questManager) quest.setupCurrentObjectiveFromData(-1, 0, None) quests.append(quest) positions = [(0, 0, 0.65), (0, 0, 0.1), (0, 0, -0.45)] self.questFrame = DirectFrame(relief=None, pos=(-0.8, 0, 0), geom=DGG.getDefaultDialogGeom(), geom_color=Vec4(0.8, 0.6, 0.4, 1), geom_scale=(1.85, 1, 0.9), geom_hpr=(0, 0, -90)) self.cancelBtn = DirectButton(text="Cancel", geom=CIGlobals.getDefaultBtnGeom(), geom_scale=(0.6, 0.75, 0.75), relief=None, parent=self.questFrame, pos=(0.2, 0, -0.8), text_scale=0.045, text_pos=(0, -0.015), scale=1.1, command=self.__cancelQuestPicker, extraArgs=[False]) self.timer = Timer() self.timer.load() self.timer.setScale(0.3) self.timer.reparentTo(self.questFrame) self.timer.setPos(-0.1, 0, -0.8) self.timer.setInitialTime(20) self.timer.setZeroCommand(self.__cancelQuestPicker) self.timer.startTiming() self.questPosters = [] self.questBtns = [] for i in xrange(len(quests)): poster = None quest = quests[i] poster = QuestGlobals.generatePoster(quest, parent=aspect2d) poster.setScale(0.85) poster.setPos(0, 0, 0) poster.progressBar.hide() self.questPosters.append(poster) # Let's setup the choose button. btn = DirectButton(geom=CIGlobals.getDefaultBtnGeom(), parent=poster, pos=(0.35, 0, 0.215), text='Choose', text_scale=0.08, text_pos=(0, -0.025), relief=None, command=self.d_pickedQuest, extraArgs=[quests[i]]) btn.setScale(0.4) btn.setBin('gui-popup', 60) btn.initialiseoptions(DirectButton) poster.reparentTo(self.questFrame.stateNodePath[0], 20) poster.setPos(positions[i]) poster.show() self.questBtns.append(btn)
class DirectWindow(DirectFrame): def __init__( self, pos=(-.5, .5), title='Title', curSize=(1, 1), maxSize=(1, 1), minSize=(.5, .5), backgroundColor=(1, 1, 1, 1), borderColor=(1, 1, 1, 1), titleColor=(1, 1, 1, 1), borderSize=0.04, titleSize=0.06, closeButton=False, windowParent=aspect2d, preserve=True, preserveWhole=True, ): self.preserve = preserve self.preserveWhole = preserveWhole self.windowParent = windowParent self.windowPos = pos DirectFrame.__init__( self, parent=windowParent, pos=(self.windowPos[0], 0, self.windowPos[1]), frameColor=(0, 0, 0, 0), frameTexture=loader.loadTexture(DIRECTORY + 'transparent.png')) self.setTransparency(True) # the title part of the window, drag around to move the window self.headerHeight = titleSize h = -self.headerHeight self.windowHeaderLeft = DirectButton( parent=self, frameTexture=DEFAULT_TITLE_GEOM_LEFT, frameSize=(-.5, .5, -.5, .5), borderWidth=(0, 0), relief=DGG.FLAT, frameColor=titleColor, ) self.windowHeaderCenter = DirectButton( parent=self, frameTexture=DEFAULT_TITLE_GEOM_CENTER, frameSize=(-.5, .5, -.5, .5), borderWidth=(0, 0), relief=DGG.FLAT, frameColor=titleColor, ) if closeButton: rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT_CLOSE command = self.destroy else: rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT command = None self.windowHeaderRight = DirectButton(parent=self, frameTexture=rightTitleGeom, frameSize=(-.5, .5, -.5, .5), borderWidth=(0, 0), relief=DGG.FLAT, frameColor=titleColor, command=command) self.windowHeaderLeft.setTransparency(True) self.windowHeaderCenter.setTransparency(True) self.windowHeaderRight.setTransparency(True) self.windowHeaderLeft.bind(DGG.B1PRESS, self.startWindowDrag) self.windowHeaderCenter.bind(DGG.B1PRESS, self.startWindowDrag) self.windowHeaderRight.bind(DGG.B1PRESS, self.startWindowDrag) # this is not handled correctly, if a window is dragged which has been # created before another it will not be released # check the bugfixed startWindowDrag function #self.windowHeader.bind(DGG.B1RELEASE,self.stopWindowDrag) text = TextNode('WindowTitleTextNode') text.setText(title) text.setAlign(TextNode.ACenter) text.setTextColor(0, 0, 0, 1) text.setShadow(0.05, 0.05) text.setShadowColor(1, 1, 1, 1) self.textNodePath = self.attachNewNode(text) self.textNodePath.setScale(self.headerHeight * 0.8) # the content part of the window, put stuff beneath # contentWindow.getCanvas() to put it into it self.maxVirtualSize = maxSize self.minVirtualSize = minSize self.resizeSize = borderSize self.contentWindow = DirectScrolledFrame( parent=self, pos=(0, 0, -self.headerHeight), canvasSize=(0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1]), frameColor=( 0, 0, 0, 0), # defines the background color of the resize-button relief=DGG.FLAT, borderWidth=(0, 0), verticalScroll_frameSize=[0, self.resizeSize, 0, 1], horizontalScroll_frameSize=[0, 1, 0, self.resizeSize], # resize the scrollbar according to window size verticalScroll_resizeThumb=False, horizontalScroll_resizeThumb=False, # define the textures for the scrollbars verticalScroll_frameTexture=VERTICALSCROLL_FRAMETEXTURE, verticalScroll_incButton_frameTexture= VERTICALSCROLL_INCBUTTON_FRAMETEXTURE, verticalScroll_decButton_frameTexture= VERTICALSCROLL_DECBUTTON_FRAMETEXTURE, verticalScroll_thumb_frameTexture=VERTICALSCROLL_TUMB_FRAMETEXTURE, horizontalScroll_frameTexture=HORIZONTALSCROLL_FRAMETEXTURE, horizontalScroll_incButton_frameTexture= HORIZONTALSCROLL_INCBUTTON_FRAMETEXTURE, horizontalScroll_decButton_frameTexture= HORIZONTALSCROLL_DECBUTTON_FRAMETEXTURE, horizontalScroll_thumb_frameTexture= HORIZONTALSCROLL_TUMB_FRAMETEXTURE, # make all flat, so the texture is as we want it verticalScroll_relief=DGG.FLAT, verticalScroll_thumb_relief=DGG.FLAT, verticalScroll_decButton_relief=DGG.FLAT, verticalScroll_incButton_relief=DGG.FLAT, horizontalScroll_relief=DGG.FLAT, horizontalScroll_thumb_relief=DGG.FLAT, horizontalScroll_decButton_relief=DGG.FLAT, horizontalScroll_incButton_relief=DGG.FLAT, # colors verticalScroll_frameColor=borderColor, verticalScroll_incButton_frameColor=borderColor, verticalScroll_decButton_frameColor=borderColor, verticalScroll_thumb_frameColor=borderColor, horizontalScroll_frameColor=borderColor, horizontalScroll_incButton_frameColor=borderColor, horizontalScroll_decButton_frameColor=borderColor, horizontalScroll_thumb_frameColor=borderColor, ) self.contentWindow.setTransparency(True) # background color self.backgroundColor = DirectFrame( parent=self.contentWindow.getCanvas(), frameSize=(0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1]), frameColor=backgroundColor, relief=DGG.FLAT, borderWidth=(.01, .01), ) self.backgroundColor.setTransparency(True) # Add a box self.box = boxes.VBox(parent=self.getCanvas()) # is needed for some nicer visuals of the resize button (background) self.windowResizeBackground = DirectButton( parent=self, frameSize=(-.5, .5, -.5, .5), borderWidth=(0, 0), scale=(self.resizeSize, 1, self.resizeSize), relief=DGG.FLAT, frameColor=backgroundColor, ) # the resize button of the window self.windowResize = DirectButton( parent=self, frameSize=(-.5, .5, -.5, .5), borderWidth=(0, 0), scale=(self.resizeSize, 1, self.resizeSize), relief=DGG.FLAT, frameTexture=DEFAULT_RESIZE_GEOM, frameColor=borderColor, ) self.windowResize.setTransparency(True) self.windowResize.bind(DGG.B1PRESS, self.startResizeDrag) self.windowResize.bind(DGG.B1RELEASE, self.stopResizeDrag) # offset then clicking on the resize button from the mouse to the resizebutton # position, required to calculate the position / scaling self.offset = None self.taskName = "resizeTask-%s" % str(hash(self)) # do sizing of the window (minimum) #self.resize( Vec3(0,0,0), Vec3(0,0,0) ) # maximum #self.resize( Vec3(100,0,-100), Vec3(0,0,0) ) self.resize(Vec3(curSize[0], 0, -curSize[1]), Vec3(0, 0, 0)) def getCanvas(self): return self.contentWindow.getCanvas() # dragging functions def startWindowDrag(self, param): self.wrtReparentTo(aspect2dMouseNode) self.ignoreAll() self.accept('mouse1-up', self.stopWindowDrag) def stopWindowDrag(self, param=None): # this is called 2 times (bug), so make sure it's not already parented to aspect2d if self.getParent() != self.windowParent: self.wrtReparentTo(self.windowParent) if self.preserve: if self.preserveWhole: if self.getZ() > 1: self.setZ(1) elif self.getZ() < -1 - self.getHeight(): self.setZ(-1 - self.getHeight()) if self.getX() > base.a2dRight - self.getWidth(): self.setX(base.a2dRight - self.getWidth()) elif self.getX() < base.a2dLeft: self.setX(base.a2dLeft) else: if self.getZ() > 1: self.setZ(1) elif self.getZ() < -1 + self.headerHeight: self.setZ(-1 + self.headerHeight) if self.getX() > base.a2dRight - self.headerHeight: self.setX(base.a2dRight - self.headerHeight) elif self.getX( ) < base.a2dLeft + self.headerHeight - self.getWidth(): self.setX(base.a2dLeft + self.headerHeight - self.getWidth()) #else: #Window moved beyond reach. Destroy window? # resize functions def resize(self, mPos, offset): mXPos = max(min(mPos.getX(), self.maxVirtualSize[0]), self.minVirtualSize[0]) mZPos = max(min(mPos.getZ(), -self.minVirtualSize[1]), -self.maxVirtualSize[1] - self.headerHeight) self.windowResize.setPos(mXPos - self.resizeSize / 2., 0, mZPos + self.resizeSize / 2.) self.windowResizeBackground.setPos(mXPos - self.resizeSize / 2., 0, mZPos + self.resizeSize / 2.) self['frameSize'] = (0, mXPos, 0, mZPos) self.windowHeaderLeft.setPos(self.headerHeight / 2., 0, -self.headerHeight / 2.) self.windowHeaderLeft.setScale(self.headerHeight, 1, self.headerHeight) self.windowHeaderCenter.setPos(mXPos / 2., 0, -self.headerHeight / 2.) self.windowHeaderCenter.setScale(mXPos - self.headerHeight * 2., 1, self.headerHeight) self.windowHeaderRight.setPos(mXPos - self.headerHeight / 2., 0, -self.headerHeight / 2.) self.windowHeaderRight.setScale(self.headerHeight, 1, self.headerHeight) self.contentWindow['frameSize'] = (0, mXPos, mZPos + self.headerHeight, 0) self.textNodePath.setPos(mXPos / 2., 0, -self.headerHeight / 3. * 2.) # show and hide that small background for the window sizer if mXPos == self.maxVirtualSize[0] and \ mZPos == -self.maxVirtualSize[1]-self.headerHeight: self.windowResizeBackground.hide() else: self.windowResizeBackground.show() def resizeTask(self, task=None): mPos = aspect2dMouseNode.getPos(self) + self.offset self.resize(mPos, self.offset) return task.cont def startResizeDrag(self, param): self.offset = self.windowResize.getPos(aspect2dMouseNode) taskMgr.remove(self.taskName) taskMgr.add(self.resizeTask, self.taskName) def stopResizeDrag(self, param): taskMgr.remove(self.taskName) # get the window to the front self.wrtReparentTo(self.windowParent) def addHorizontal(self, widgets): """ Accepts a list of directgui objects which are added to a horizontal box, which is then added to the vertical stack. """ hbox = boxes.HBox() for widget in widgets: hbox.pack(widget) self.box.pack(hbox) self.updateMaxSize() def addVertical(self, widgets): """ Accepts a list of directgui objects which are added to a vertical box, which is then added to the vertical stack. May cause funky layout results. """ #vbox = boxes.VBox() for widget in widgets: self.box.pack(widget) self.updateMaxSize() def add(self, widgets): """Shortcut function for addVertical""" self.addVertical(widgets) def updateMaxSize(self): """Updates the max canvas size to include all items packed. Window is resized to show all contents.""" bottomLeft, topRight = self.box.getTightBounds() self.maxVirtualSize = (topRight[0], -bottomLeft[2]) self.contentWindow['canvasSize'] = (0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0) self.backgroundColor['frameSize'] = (0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0) #perhaps this should be optional -- automatically resize for new elements self.reset() def reset(self): """Poorly named function that resizes window to fit all contents""" self.resize( Vec3(self.maxVirtualSize[0], 0, -self.maxVirtualSize[1] - self.headerHeight), Vec3(0, 0, 0))
class DirectWindow( DirectFrame ): def __init__( self, pos = ( -.5, .5), title = 'Title', curSize = ( 1, 1), maxSize = ( 1, 1 ), minSize = ( .5, .5 ), backgroundColor = ( 1, 1, 1, 1 ), borderColor = ( 1, 1, 1, 1 ), titleColor = ( 1, 1, 1, 1 ), borderSize = 0.04, titleSize = 0.06, closeButton = False, windowParent = aspect2d, preserve = True, preserveWhole = True, ): self.preserve = preserve self.preserveWhole = preserveWhole self.windowParent = windowParent self.windowPos = pos DirectFrame.__init__( self, parent = windowParent, pos = ( self.windowPos[0], 0, self.windowPos[1] ), frameColor = ( 0, 0, 0, 0 ), frameTexture = loader.loadTexture( DIRECTORY+'transparent.png' ) ) self.setTransparency(True) # the title part of the window, drag around to move the window self.headerHeight = titleSize h = -self.headerHeight self.windowHeaderLeft = DirectButton( parent = self, frameTexture = DEFAULT_TITLE_GEOM_LEFT, frameSize = ( -.5, .5, -.5, .5 ), borderWidth = ( 0, 0 ), relief = DGG.FLAT, frameColor = titleColor, ) self.windowHeaderCenter = DirectButton( parent = self, frameTexture = DEFAULT_TITLE_GEOM_CENTER, frameSize = ( -.5, .5, -.5, .5 ), borderWidth = ( 0, 0 ), relief = DGG.FLAT, frameColor = titleColor, ) if closeButton: rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT_CLOSE command = self.destroy else: rightTitleGeom = DEFAULT_TITLE_GEOM_RIGHT command = None self.windowHeaderRight = DirectButton( parent = self, frameTexture = rightTitleGeom, frameSize = ( -.5, .5, -.5, .5 ), borderWidth = ( 0, 0 ), relief = DGG.FLAT, frameColor = titleColor, command = command ) self.windowHeaderLeft.setTransparency(True) self.windowHeaderCenter.setTransparency(True) self.windowHeaderRight.setTransparency(True) self.windowHeaderLeft.bind( DGG.B1PRESS, self.startWindowDrag ) self.windowHeaderCenter.bind( DGG.B1PRESS, self.startWindowDrag ) self.windowHeaderRight.bind( DGG.B1PRESS, self.startWindowDrag ) # this is not handled correctly, if a window is dragged which has been # created before another it will not be released # check the bugfixed startWindowDrag function #self.windowHeader.bind(DGG.B1RELEASE,self.stopWindowDrag) text = TextNode('WindowTitleTextNode') text.setText(title) text.setAlign(TextNode.ACenter) text.setTextColor( 0, 0, 0, 1 ) text.setShadow(0.05, 0.05) text.setShadowColor( 1, 1, 1, 1 ) self.textNodePath = self.attachNewNode(text) self.textNodePath.setScale(self.headerHeight*0.8) # the content part of the window, put stuff beneath # contentWindow.getCanvas() to put it into it self.maxVirtualSize = maxSize self.minVirtualSize = minSize self.resizeSize = borderSize self.contentWindow = DirectScrolledFrame( parent = self, pos = ( 0, 0, -self.headerHeight ), canvasSize = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ), frameColor = ( 0, 0, 0, 0), # defines the background color of the resize-button relief = DGG.FLAT, borderWidth = (0, 0), verticalScroll_frameSize = [0, self.resizeSize, 0, 1], horizontalScroll_frameSize = [0, 1, 0, self.resizeSize], # resize the scrollbar according to window size verticalScroll_resizeThumb = False, horizontalScroll_resizeThumb = False, # define the textures for the scrollbars verticalScroll_frameTexture = VERTICALSCROLL_FRAMETEXTURE, verticalScroll_incButton_frameTexture = VERTICALSCROLL_INCBUTTON_FRAMETEXTURE, verticalScroll_decButton_frameTexture = VERTICALSCROLL_DECBUTTON_FRAMETEXTURE, verticalScroll_thumb_frameTexture = VERTICALSCROLL_TUMB_FRAMETEXTURE, horizontalScroll_frameTexture = HORIZONTALSCROLL_FRAMETEXTURE, horizontalScroll_incButton_frameTexture = HORIZONTALSCROLL_INCBUTTON_FRAMETEXTURE, horizontalScroll_decButton_frameTexture = HORIZONTALSCROLL_DECBUTTON_FRAMETEXTURE, horizontalScroll_thumb_frameTexture = HORIZONTALSCROLL_TUMB_FRAMETEXTURE, # make all flat, so the texture is as we want it verticalScroll_relief = DGG.FLAT, verticalScroll_thumb_relief = DGG.FLAT, verticalScroll_decButton_relief = DGG.FLAT, verticalScroll_incButton_relief = DGG.FLAT, horizontalScroll_relief = DGG.FLAT, horizontalScroll_thumb_relief = DGG.FLAT, horizontalScroll_decButton_relief = DGG.FLAT, horizontalScroll_incButton_relief = DGG.FLAT, # colors verticalScroll_frameColor = borderColor, verticalScroll_incButton_frameColor = borderColor, verticalScroll_decButton_frameColor = borderColor, verticalScroll_thumb_frameColor = borderColor, horizontalScroll_frameColor = borderColor, horizontalScroll_incButton_frameColor = borderColor, horizontalScroll_decButton_frameColor = borderColor, horizontalScroll_thumb_frameColor = borderColor, ) self.contentWindow.setTransparency(True) # background color self.backgroundColor = DirectFrame( parent = self.contentWindow.getCanvas(), frameSize = ( 0, self.maxVirtualSize[0], 0, self.maxVirtualSize[1] ), frameColor = backgroundColor, relief = DGG.FLAT, borderWidth = ( .01, .01), ) self.backgroundColor.setTransparency(True) # Add a box self.box = boxes.VBox(parent = self.getCanvas()) # is needed for some nicer visuals of the resize button (background) self.windowResizeBackground = DirectButton( parent = self, frameSize = ( -.5, .5, -.5, .5 ), borderWidth = ( 0, 0 ), scale = ( self.resizeSize, 1, self.resizeSize ), relief = DGG.FLAT, frameColor = backgroundColor, ) # the resize button of the window self.windowResize = DirectButton( parent = self, frameSize = ( -.5, .5, -.5, .5 ), borderWidth = ( 0, 0 ), scale = ( self.resizeSize, 1, self.resizeSize ), relief = DGG.FLAT, frameTexture = DEFAULT_RESIZE_GEOM, frameColor = borderColor, ) self.windowResize.setTransparency(True) self.windowResize.bind(DGG.B1PRESS,self.startResizeDrag) self.windowResize.bind(DGG.B1RELEASE,self.stopResizeDrag) # offset then clicking on the resize button from the mouse to the resizebutton # position, required to calculate the position / scaling self.offset = None self.taskName = "resizeTask-%s" % str(hash(self)) # do sizing of the window (minimum) #self.resize( Vec3(0,0,0), Vec3(0,0,0) ) # maximum #self.resize( Vec3(100,0,-100), Vec3(0,0,0) ) self.resize( Vec3(curSize[0], 0, -curSize[1]), Vec3(0,0,0)) def getCanvas(self): return self.contentWindow.getCanvas() # dragging functions def startWindowDrag( self, param ): self.wrtReparentTo( aspect2dMouseNode ) self.ignoreAll() self.accept( 'mouse1-up', self.stopWindowDrag ) def stopWindowDrag( self, param=None ): # this is called 2 times (bug), so make sure it's not already parented to aspect2d if self.getParent() != self.windowParent: self.wrtReparentTo( self.windowParent ) if self.preserve: if self.preserveWhole: if self.getZ() > 1: self.setZ(1) elif self.getZ() < -1 - self.getHeight(): self.setZ(-1 - self.getHeight()) if self.getX() > base.a2dRight - self.getWidth(): self.setX(base.a2dRight - self.getWidth()) elif self.getX() < base.a2dLeft: self.setX(base.a2dLeft) else: if self.getZ() > 1: self.setZ(1) elif self.getZ() < -1 + self.headerHeight: self.setZ(-1 + self.headerHeight) if self.getX() > base.a2dRight - self.headerHeight: self.setX(base.a2dRight - self.headerHeight) elif self.getX() < base.a2dLeft + self.headerHeight - self.getWidth(): self.setX(base.a2dLeft + self.headerHeight - self.getWidth()) #else: #Window moved beyond reach. Destroy window? # resize functions def resize( self, mPos, offset ): mXPos = max( min( mPos.getX(), self.maxVirtualSize[0] ), self.minVirtualSize[0]) mZPos = max( min( mPos.getZ(), -self.minVirtualSize[1] ), -self.maxVirtualSize[1]-self.headerHeight) self.windowResize.setPos( mXPos-self.resizeSize/2., 0, mZPos+self.resizeSize/2. ) self.windowResizeBackground.setPos( mXPos-self.resizeSize/2., 0, mZPos+self.resizeSize/2. ) self['frameSize'] = (0, mXPos, 0, mZPos) self.windowHeaderLeft.setPos( self.headerHeight/2., 0, -self.headerHeight/2. ) self.windowHeaderLeft.setScale( self.headerHeight, 1, self.headerHeight ) self.windowHeaderCenter.setPos( mXPos/2., 0, -self.headerHeight/2. ) self.windowHeaderCenter.setScale( mXPos - self.headerHeight*2., 1, self.headerHeight ) self.windowHeaderRight.setPos( mXPos-self.headerHeight/2., 0, -self.headerHeight/2. ) self.windowHeaderRight.setScale( self.headerHeight, 1, self.headerHeight ) self.contentWindow['frameSize'] = ( 0, mXPos, mZPos+self.headerHeight, 0) self.textNodePath.setPos( mXPos/2., 0, -self.headerHeight/3.*2. ) # show and hide that small background for the window sizer if mXPos == self.maxVirtualSize[0] and \ mZPos == -self.maxVirtualSize[1]-self.headerHeight: self.windowResizeBackground.hide() else: self.windowResizeBackground.show() def resizeTask( self, task=None ): mPos = aspect2dMouseNode.getPos( self )+self.offset self.resize( mPos, self.offset ) return task.cont def startResizeDrag( self, param ): self.offset = self.windowResize.getPos( aspect2dMouseNode ) taskMgr.remove( self.taskName ) taskMgr.add( self.resizeTask, self.taskName ) def stopResizeDrag( self, param ): taskMgr.remove( self.taskName ) # get the window to the front self.wrtReparentTo( self.windowParent ) def addHorizontal(self, widgets): """ Accepts a list of directgui objects which are added to a horizontal box, which is then added to the vertical stack. """ hbox = boxes.HBox() for widget in widgets: hbox.pack(widget) self.box.pack(hbox) self.updateMaxSize() def addVertical(self, widgets): """ Accepts a list of directgui objects which are added to a vertical box, which is then added to the vertical stack. May cause funky layout results. """ #vbox = boxes.VBox() for widget in widgets: self.box.pack(widget) self.updateMaxSize() def add(self, widgets): """Shortcut function for addVertical""" self.addVertical(widgets) def updateMaxSize(self): """Updates the max canvas size to include all items packed. Window is resized to show all contents.""" bottomLeft, topRight = self.box.getTightBounds() self.maxVirtualSize = (topRight[0], -bottomLeft[2]) self.contentWindow['canvasSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0) self.backgroundColor['frameSize'] = ( 0, self.maxVirtualSize[0], -self.maxVirtualSize[1], 0 ) #perhaps this should be optional -- automatically resize for new elements self.reset() def reset(self): """Poorly named function that resizes window to fit all contents""" self.resize( Vec3(self.maxVirtualSize[0], 0, -self.maxVirtualSize[1]-self.headerHeight), Vec3(0,0,0))
def __init__(self, items, parent=None, buttonThrower=None, onDestroy=None, font=None, baselineOffset=.0, scale=.05, itemHeight=1., leftPad=.0, separatorHeight=.5, underscoreThickness=1, BGColor=(0, 0, 0, .7), BGBorderColor=(1, .85, .4, 1), separatorColor=(1, 1, 1, 1), frameColorHover=(1, .85, .4, 1), frameColorPress=(0, 1, 0, 1), textColorReady=(1, 1, 1, 1), textColorHover=(0, 0, 0, 1), textColorPress=(0, 0, 0, 1), textColorDisabled=(.5, .5, .5, 1), minZ=None, useMouseZ=True): ''' items : a collection of menu items Item format : ( 'Item text', 'path/to/image', command ) OR ( 'Item text', 'path/to/image', command, arg1,arg2,.... ) If you don't want to use an image, pass 0. To create disabled item, pass 0 for the command : ( 'Item text', 'path/to/image', 0 ) so, you can easily switch between enabled or disabled : ( 'Item text', 'path/to/image', command if commandEnabled else 0 ) OR ( 'Item text', 'path/to/image', (0,command)[commandEnabled] ) To create submenu, pass a sequence of submenu items for the command. To create disabled submenu, pass an empty sequence for the command. To enable hotkey, insert an underscore before the character, e.g. hotkey of 'Item te_xt' is 'x' key. To add shortcut key text at the right side of the item, append it at the end of the item text, separated by "more than" sign, e.g. 'Item text>Ctrl-T'. To insert separator line, pass 0 for the whole item. parent : where to attach the menu, defaults to aspect2d buttonThrower : button thrower whose thrown events are blocked temporarily when the menu is displayed. If not given, the default button thrower is used onDestroy : user function which will be called after the menu is fully destroyed font : text font baselineOffset : text's baseline Z offset scale : text scale itemHeight : spacing between items, defaults to 1 leftPad : blank space width before text separatorHeight : separator line height, relative to itemHeight underscoreThickness : underscore line thickness BGColor, BGBorderColor, separatorColor, frameColorHover, frameColorPress, textColorReady, textColorHover, textColorPress, textColorDisabled are some of the menu components' color minZ : minimum Z position to restrain menu's bottom from going offscreen (-1..1). If it's None, it will be set a little above the screen's bottom. ''' self.parent = parent if parent else aspect2d self.onDestroy = onDestroy self.BT = buttonThrower if buttonThrower else base.buttonThrowers[ 0].node() self.menu = NodePath('menu-%s' % id(self)) self.parentMenu = None self.submenu = None self.BTprefix = self.menu.getName() + '>' self.submenuCreationTaskName = 'createSubMenu-' + self.BTprefix self.submenuRemovalTaskName = 'removeSubMenu-' + self.BTprefix self.font = font if font else TextNode.getDefaultFont() self.baselineOffset = baselineOffset self.scale = scale self.itemHeight = itemHeight self.leftPad = leftPad self.separatorHeight = separatorHeight self.underscoreThickness = underscoreThickness self.BGColor = BGColor self.BGBorderColor = BGBorderColor self.separatorColor = separatorColor self.frameColorHover = frameColorHover self.frameColorPress = frameColorPress self.textColorReady = textColorReady self.textColorHover = textColorHover self.textColorPress = textColorPress self.textColorDisabled = textColorDisabled self.minZ = minZ self.mpos = Point2(base.mouseWatcherNode.getMouse()) self.itemCommand = [] self.hotkeys = {} self.numItems = 0 self.sel = -1 self.selByKey = False bgPad = self.bgPad = .0125 texMargin = self.font.getTextureMargin() * self.scale * .25 b = DirectButton(parent=NodePath(''), text='^|g_', text_font=self.font, scale=self.scale) fr = b.node().getFrame() b.getParent().removeNode() baselineToCenter = (fr[2] + fr[3]) * self.scale LH = (fr[3] - fr[2]) * self.itemHeight * self.scale imageHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .85 arrowHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .5 baselineToTop = (fr[3] * self.itemHeight * self.scale / LH) / (1. + self.baselineOffset) baselineToBot = LH / self.scale - baselineToTop itemZcenter = (baselineToTop - baselineToBot) * .5 separatorHalfHeight = .5 * separatorHeight * LH LSseparator = LineSegs() LSseparator.setColor(.5, .5, .5, .2) arrowVtx = [ (0, itemZcenter), (-2 * arrowHalfHeight, itemZcenter + arrowHalfHeight), (-arrowHalfHeight, itemZcenter), (-2 * arrowHalfHeight, itemZcenter - arrowHalfHeight), ] tri = Triangulator() vdata = GeomVertexData('trig', GeomVertexFormat.getV3(), Geom.UHStatic) vwriter = GeomVertexWriter(vdata, 'vertex') for x, z in arrowVtx: vi = tri.addVertex(x, z) vwriter.addData3f(x, 0, z) tri.addPolygonVertex(vi) tri.triangulate() prim = GeomTriangles(Geom.UHStatic) for i in range(tri.getNumTriangles()): prim.addVertices(tri.getTriangleV0(i), tri.getTriangleV1(i), tri.getTriangleV2(i)) prim.closePrimitive() geom = Geom(vdata) geom.addPrimitive(prim) geomNode = GeomNode('arrow') geomNode.addGeom(geom) realArrow = NodePath(geomNode) z = -baselineToTop * self.scale - bgPad maxWidth = .1 / self.scale shortcutTextMaxWidth = 0 anyImage = False anyArrow = False anyShortcut = False arrows = [] shortcutTexts = [] loadPrcFileData('', 'text-flatten 0') for item in items: if item: t, imgPath, f = item[:3] haveSubmenu = type(f) in SEQUENCE_TYPES anyArrow |= haveSubmenu anyImage |= bool(imgPath) disabled = not len(f) if haveSubmenu else not callable(f) args = item[3:] underlinePos = t.find('_') t = t.replace('_', '') shortcutSepPos = t.find('>') if shortcutSepPos > -1: if haveSubmenu: print( "\nA SHORTCUT KEY POINTING TO A SUBMENU IS NON-SENSE, DON'T YOU AGREE ?" ) else: shortcutText = NodePath( OnscreenText( parent=self.menu, text=t[shortcutSepPos + 1:], font=self.font, scale=1, fg=(1, 1, 1, 1), align=TextNode.ARight, )) shortcutTextMaxWidth = max( shortcutTextMaxWidth, abs(shortcutText.getTightBounds()[0][0])) anyShortcut = True t = t[:shortcutSepPos] else: shortcutText = '' EoLcount = t.count('\n') arrowZpos = -self.font.getLineHeight() * EoLcount * .5 if disabled: b = NodePath( OnscreenText( parent=self.menu, text=t, font=self.font, scale=1, fg=textColorDisabled, align=TextNode.ALeft, )) # don't pass the scale and position to OnscreenText constructor, # to maintain correctness between the OnscreenText and DirectButton items # due to the new text generation implementation b.setScale(self.scale) b.setZ(z) maxWidth = max(maxWidth, b.getTightBounds()[1][0] / self.scale) if shortcutText: shortcutText.reparentTo(b) shortcutText.setColor(Vec4(*textColorDisabled), 1) shortcutText.setZ(arrowZpos) shortcutTexts.append(shortcutText) else: b = DirectButton( parent=self.menu, text=t, text_font=self.font, scale=self.scale, pos=(0, 0, z), text_fg=textColorReady, # text color when mouse over text2_fg=textColorHover, # text color when pressed text1_fg=textColorHover if haveSubmenu else textColorPress, # framecolor when pressed frameColor=frameColorHover if haveSubmenu else frameColorPress, command=(lambda: 0) if haveSubmenu else self.__runCommand, extraArgs=[] if haveSubmenu else [f, args], text_align=TextNode.ALeft, relief=DGG.FLAT, rolloverSound=0, clickSound=0, pressEffect=0) b.stateNodePath[2].setColor( *frameColorHover) # framecolor when mouse over b.stateNodePath[0].setColor(0, 0, 0, 0) # framecolor when ready bframe = Vec4(b.node().getFrame()) if EoLcount: bframe.setZ(EoLcount * 10) b['frameSize'] = bframe maxWidth = max(maxWidth, bframe[1]) if shortcutText: for snpi, col in ((0, textColorReady), (1, textColorPress), (2, textColorHover)): sct = shortcutText.copyTo(b.stateNodePath[snpi], sort=10) sct.setColor(Vec4(*col), 1) sct.setZ(arrowZpos) shortcutTexts.append(sct) shortcutText.removeNode() if imgPath: img = loader.loadTexture(imgPath) if disabled: if imgPath in PopupMenu.grayImages: img = PopupMenu.grayImages[imgPath] else: pnm = PNMImage() img.store(pnm) pnm.makeGrayscale(.2, .2, .2) img = Texture() img.load(pnm) PopupMenu.grayImages[imgPath] = img img.setMinfilter(Texture.FTLinearMipmapLinear) img.setWrapU(Texture.WMClamp) img.setWrapV(Texture.WMClamp) CM = CardMaker('') CM.setFrame(-2 * imageHalfHeight - leftPad, -leftPad, itemZcenter - imageHalfHeight, itemZcenter + imageHalfHeight) imgCard = b.attachNewNode(CM.generate()) imgCard.setTexture(img) if underlinePos > -1: oneLineText = t[:underlinePos + 1] oneLineText = oneLineText[oneLineText.rfind('\n') + 1:] tn = TextNode('') tn.setFont(self.font) tn.setText(oneLineText) tnp = NodePath(tn.getInternalGeom()) underlineXend = tnp.getTightBounds()[1][0] tnp.removeNode() tn.setText(t[underlinePos]) tnp = NodePath(tn.getInternalGeom()) b3 = tnp.getTightBounds() underlineXstart = underlineXend - (b3[1] - b3[0])[0] tnp.removeNode() underlineZpos = -.7 * baselineToBot - self.font.getLineHeight( ) * t[:underlinePos].count('\n') LSunder = LineSegs() LSunder.setThickness(underscoreThickness) LSunder.moveTo(underlineXstart + texMargin, 0, underlineZpos) LSunder.drawTo(underlineXend - texMargin, 0, underlineZpos) if disabled: underline = b.attachNewNode(LSunder.create()) underline.setColor(Vec4(*textColorDisabled), 1) else: underline = b.stateNodePath[0].attachNewNode( LSunder.create()) underline.setColor(Vec4(*textColorReady), 1) underline.copyTo(b.stateNodePath[1], 10).setColor( Vec4(*textColorHover if haveSubmenu else textColorPress), 1) underline.copyTo(b.stateNodePath[2], 10).setColor(Vec4(*textColorHover), 1) hotkey = t[underlinePos].lower() if hotkey in self.hotkeys: self.hotkeys[hotkey].append(self.numItems) else: self.hotkeys[hotkey] = [self.numItems] self.accept(self.BTprefix + hotkey, self.__processHotkey, [hotkey]) self.accept(self.BTprefix + 'alt-' + hotkey, self.__processHotkey, [hotkey]) if haveSubmenu: if disabled: arrow = realArrow.instanceUnderNode(b, '') arrow.setColor(Vec4(*textColorDisabled), 1) arrow.setZ(arrowZpos) else: arrow = realArrow.instanceUnderNode( b.stateNodePath[0], 'r') arrow.setColor(Vec4(*textColorReady), 1) arrow.setZ(arrowZpos) arrPress = realArrow.instanceUnderNode( b.stateNodePath[1], 'p') arrPress.setColor(Vec4(*textColorHover), 1) arrPress.setZ(arrowZpos) arrHover = realArrow.instanceUnderNode( b.stateNodePath[2], 'h') arrHover.setColor(Vec4(*textColorHover), 1) arrHover.setZ(arrowZpos) # weird, if sort order is 0, it's obscured by the frame for a in (arrPress, arrHover): a.reparentTo(a.getParent(), sort=10) if not disabled: extraArgs = [self.numItems, f if haveSubmenu else 0] self.accept(DGG.ENTER + b.guiId, self.__hoverOnItem, extraArgs) self.accept(DGG.EXIT + b.guiId, self.__offItem) #~ self.itemCommand.append((None,0) if haveSubmenu else (f,args)) self.itemCommand.append((f, args)) if self.numItems == 0: self.firstButtonIdx = int(b.guiId[2:]) self.numItems += 1 z -= LH + self.font.getLineHeight() * self.scale * EoLcount else: # SEPARATOR LINE z += LH - separatorHalfHeight - baselineToBot * self.scale LSseparator.moveTo(0, 0, z) LSseparator.drawTo(self.scale * .5, 0, z) LSseparator.drawTo(self.scale, 0, z) z -= separatorHalfHeight + baselineToTop * self.scale maxWidth += 7 * arrowHalfHeight * ( anyArrow or anyShortcut) + .2 + shortcutTextMaxWidth arrowXpos = maxWidth - arrowHalfHeight realArrow.setX(arrowXpos) if anyImage: leftPad += 2 * imageHalfHeight + leftPad for sct in shortcutTexts: sct.setX(maxWidth - 2 * (arrowHalfHeight * anyArrow + .2)) for c in asList(self.menu.findAllMatches('**/DirectButton*')): numLines = c.node().getFrame()[2] c.node().setFrame( Vec4( -leftPad, maxWidth, -baselineToBot - (numLines * .1 * self.itemHeight if numLines >= 10 else 0), baselineToTop)) loadPrcFileData('', 'text-flatten 1') try: minZ = self.menu.getChild(0).getRelativePoint( b, Point3(0, 0, b.node().getFrame()[2]))[2] except: minZ = self.menu.getChild(0).getRelativePoint( self.menu, Point3( 0, 0, b.getTightBounds()[0][2]))[2] - baselineToBot * .5 try: top = self.menu.getChild(0).node().getFrame()[3] except: top = self.menu.getChild(0).getZ() + baselineToTop l, r, b, t = -leftPad - bgPad / self.scale, maxWidth + bgPad / self.scale, minZ - bgPad / self.scale, top + bgPad / self.scale menuBG = DirectFrame(parent=self.menu.getChild(0), frameSize=(l, r, b, t), frameColor=BGColor, state=DGG.NORMAL, suppressMouse=1) menuBorder = self.menu.getChild(0).attachNewNode('border') borderVtx = ( (l, 0, b), (l, 0, .5 * (b + t)), (l, 0, t), (.5 * (l + r), 0, t), (r, 0, t), (r, 0, .5 * (b + t)), (r, 0, b), (.5 * (l + r), 0, b), (l, 0, b), ) LSborderBG = LineSegs() LSborderBG.setThickness(4) LSborderBG.setColor(0, 0, 0, .7) LSborderBG.moveTo(*(borderVtx[0])) for v in borderVtx[1:]: LSborderBG.drawTo(*v) # fills the gap at corners for v in range(0, 7, 2): LSborderBG.moveTo(*(borderVtx[v])) menuBorder.attachNewNode(LSborderBG.create()) LSborder = LineSegs() LSborder.setThickness(2) LSborder.setColor(*BGBorderColor) LSborder.moveTo(*(borderVtx[0])) for v in borderVtx[1:]: LSborder.drawTo(*v) menuBorder.attachNewNode(LSborder.create()) for v in range(1, 8, 2): LSborderBG.setVertexColor(v, Vec4(0, 0, 0, .1)) LSborder.setVertexColor(v, Vec4(.3, .3, .3, .5)) menuBorderB3 = menuBorder.getTightBounds() menuBorderDims = menuBorderB3[1] - menuBorderB3[0] menuBG.wrtReparentTo(self.menu, sort=-1) self.menu.reparentTo(self.parent) x = -menuBorderB3[0][0] * self.scale for c in asList(self.menu.getChildren()): c.setX(x) self.maxWidth = maxWidth = menuBorderDims[0] self.height = menuBorderDims[2] maxWidthR2D = maxWidth * self.menu.getChild(0).getSx(render2d) separatorLines = self.menu.attachNewNode(LSseparator.create(), 10) separatorLines.setSx(maxWidth) for v in range(1, LSseparator.getNumVertices(), 3): LSseparator.setVertexColor(v, Vec4(*separatorColor)) x = clampScalar(-.98, .98 - maxWidthR2D, self.mpos[0] - maxWidthR2D * .5) minZ = (-.98 if self.minZ is None else self.minZ) z = clampScalar( minZ + menuBorderDims[2] * self.scale * self.parent.getSz(render2d), .98, self.mpos[1] if useMouseZ else -1000) self.menu.setPos(render2d, x, 0, z) self.menu.setTransparency(1) self.origBTprefix = self.BT.getPrefix() self.BT.setPrefix(self.BTprefix) self.accept(self.BTprefix + 'escape', self.destroy) for e in ('mouse1', 'mouse3'): self.accept(self.BTprefix + e, self.destroy, [True]) self.accept(self.BTprefix + 'arrow_down', self.__nextItem) self.accept(self.BTprefix + 'arrow_down-repeat', self.__nextItem) self.accept(self.BTprefix + 'arrow_up', self.__prevItem) self.accept(self.BTprefix + 'arrow_up-repeat', self.__prevItem) self.accept(self.BTprefix + 'enter', self.__runSelItemCommand) self.accept(self.BTprefix + 'space', self.__runSelItemCommand)