Browse Source

*** empty log message ***

Mark Mine 24 years ago
parent
commit
28ab5a02f6

+ 64 - 26
direct/src/gui/DirectButton.py

@@ -1,5 +1,9 @@
 from DirectGuiBase import *
 from DirectGuiBase import *
 
 
+IMAGE_SORT_INDEX = 10
+GEOM_SORT_INDEX = 20
+TEXT_SORT_INDEX = 30
+
 class DirectButton(DirectGuiBase, NodePath):
 class DirectButton(DirectGuiBase, NodePath):
     def __init__(self, parent = guiTop, **kw):
     def __init__(self, parent = guiTop, **kw):
         # Pass in a background texture, and/or a geometry object,
         # Pass in a background texture, and/or a geometry object,
@@ -14,39 +18,57 @@ class DirectButton(DirectGuiBase, NodePath):
         #  - a VBase4(L,R,B,T)
         #  - a VBase4(L,R,B,T)
         #  - a bounding box object
         #  - a bounding box object
         optiondefs = (
         optiondefs = (
+            # Button can have:
+            # A background texture
             ('image',           None,       self.setImage),
             ('image',           None,       self.setImage),
+            # A midground geometry item
             ('geom',            None,       self.setGeom),
             ('geom',            None,       self.setGeom),
-            ('text',            '',         self.setText),
+            # A foreground text node
+            ('text',            None,       self.setText),
+            # Command to be called on button click
             ('command',         None,       None),
             ('command',         None,       None),
+            ('extraArgs',       [],         None),
+            # Which mouse buttons can be used to click the button
             ('commandButtons',  (1,),       self.setCommandButtons),
             ('commandButtons',  (1,),       self.setCommandButtons),
+            # Buttons initial state
+            ('state',           NORMAL,     self.setState),
+            # Button frame characteristics
             ('relief',          FLAT,       self.setRelief),
             ('relief',          FLAT,       self.setRelief),
             ('frameColor',      (1,1,1,1),  self.setFrameColor),
             ('frameColor',      (1,1,1,1),  self.setFrameColor),
             ('borderWidth',     (.1,.1),    self.setBorderWidth),
             ('borderWidth',     (.1,.1),    self.setBorderWidth),
             ('frameSize',       None,       self.setFrameSize),
             ('frameSize',       None,       self.setFrameSize),
-            ('pressEffect',     1,          None),
-            ('padSX',           1.2,        None),
-            ('padSZ',           1.1,        None),
-            ('pos',             None,       None),
-            ('scale',           None,       None),
-            ('state',           NORMAL,     self.setState),
+            ('pad',             (.25,.15),  self.resetFrameSize),
+            # Sounds to be used for button events
             ('rolloverSound',   None,       None),
             ('rolloverSound',   None,       None),
             ('clickSound',      None,       None),
             ('clickSound',      None,       None),
+            # Can only be specified at time of widget contruction
+            # Do the text/graphics appear to move when the button is clicked
+            ('pressEffect',     1,          INITOPT),
+            # Initial pos/scale of the button
+            ('pos',             None,       INITOPT),
+            ('scale',           None,       INITOPT),
             )
             )
-        # Update options to reflect keyword parameters
-        apply(DirectGuiBase.__init__, (self, optiondefs, ('text',)), kw)
-        # Initialize the superclass
+        # Merge keyword options with default options
+        self.defineoptions(kw, optiondefs,
+                           dynamicGroups = ('text', 'geom', 'image'))
+
+        # Initialize superclasses
+        DirectGuiBase.__init__(self)
         NodePath.__init__(self)
         NodePath.__init__(self)
         # Create a button
         # Create a button
         self.guiItem = PGButton()
         self.guiItem = PGButton()
         self.guiId = self.guiItem.getId()
         self.guiId = self.guiItem.getId()
         # Attach button to parent and make that self
         # Attach button to parent and make that self
         self.assign(parent.attachNewNode( self.guiItem ) )
         self.assign(parent.attachNewNode( self.guiItem ) )
-        # Set up names
+        # Initialize names
         self.guiItem.setName(self.guiId)
         self.guiItem.setName(self.guiId)
         self.setName(self.guiId + 'NodePath')
         self.setName(self.guiId + 'NodePath')
+        # Get a handle on the button's hidden node paths for each state
         self.stateNodePath = []
         self.stateNodePath = []
         for i in range(4):
         for i in range(4):
             self.stateNodePath.append(NodePath(self.guiItem.getStateDef(i)))
             self.stateNodePath.append(NodePath(self.guiItem.getStateDef(i)))
+        # If specifed, add scaling to the pressed state to make it look
+        # like the button is moving when you press it
         if self['pressEffect']:
         if self['pressEffect']:
             np = self.stateNodePath[1].attachNewNode('pressEffect')
             np = self.stateNodePath[1].attachNewNode('pressEffect')
             np.setScale(0.98)
             np.setScale(0.98)
@@ -58,33 +80,38 @@ class DirectButton(DirectGuiBase, NodePath):
         # For holding bounds info
         # For holding bounds info
         self.ll = Point3(0)
         self.ll = Point3(0)
         self.ur = Point3(0)
         self.ur = Point3(0)
-        # Call initialization functions if necessary
-        # To avoid doing things redundantly
+        # Call option initialization functions
+        # To avoid doing things redundantly set fInit flag
         self.fInit = 1
         self.fInit = 1
         self.initialiseoptions(DirectButton)
         self.initialiseoptions(DirectButton)
         self.fInit = 0
         self.fInit = 0
-        # Allow changes to take effect
+        # Now allow changes to take effect
         self.updateFrameStyle()
         self.updateFrameStyle()
         if not self['frameSize']:
         if not self['frameSize']:
             self.setFrameSize()
             self.setFrameSize()
-        # Update pose
+        # Update pose to initial values
         if self['pos']:
         if self['pos']:
-            if type(self['pos']) == type(()):
-                apply(self.setPos, self['pos'])
+            pos = self['pos']
+            # Can either be a Point3 or a tuple of 3 values
+            if isintance(pos, Point3):
+                self.setPos(pos)
             else:
             else:
-                apply(self.setPos, (self['pos'],))
+                apply(self.setPos, pos)
         if self['scale']:
         if self['scale']:
-            if type(self['scale']) == type(()):
-                apply(self.setScale, self['scale'])
+            scale = self['scale']
+            # Can either be a Vec3 or a tuple of 3 values
+            if (isinstance(scale, Vec3) or
+                (type(scale) == types.IntType) or
+                (type(scale) == types.FloatType)):
+                self.setScale(scale)
             else:
             else:
-                apply(self.setScale, (self['scale'],))
+                apply(self.setScale, self['scale'])
 
 
     def updateFrameStyle(self):
     def updateFrameStyle(self):
         for i in range(4):
         for i in range(4):
             self.guiItem.setFrameStyle(i, self.frameStyle[i])
             self.guiItem.setFrameStyle(i, self.frameStyle[i])
 
 
     def setRelief(self, fSetStyle = 1):
     def setRelief(self, fSetStyle = 1):
-        print 'setting Frame'
         relief = self['relief']
         relief = self['relief']
         if relief == None:
         if relief == None:
             for i in range(4):
             for i in range(4):
@@ -104,7 +131,8 @@ class DirectButton(DirectGuiBase, NodePath):
             self.updateFrameStyle()
             self.updateFrameStyle()
 
 
     def resetFrameSize(self):
     def resetFrameSize(self):
-        self.setFrameSize(fClearFrame = 1)
+        if not self.fInit:
+            self.setFrameSize(fClearFrame = 1)
         
         
     def setFrameSize(self, fClearFrame = 0):
     def setFrameSize(self, fClearFrame = 0):
         if self['frameSize']:
         if self['frameSize']:
@@ -121,8 +149,10 @@ class DirectButton(DirectGuiBase, NodePath):
             # Clear out frame before computing bounds
             # Clear out frame before computing bounds
             self.stateNodePath[0].calcTightBounds(self.ll, self.ur)
             self.stateNodePath[0].calcTightBounds(self.ll, self.ur)
             # Scale bounds to give a pad around graphics
             # Scale bounds to give a pad around graphics
-            bounds = (self.ll[0] * self['padSX'], self.ur[0] * self['padSX'],
-                      self.ll[2] * self['padSZ'], self.ur[2] * self['padSZ'])
+            bounds = (self.ll[0] - self['pad'][0],
+                      self.ur[0] + self['pad'][0],
+                      self.ll[2] - self['pad'][1],
+                      self.ur[2] + self['pad'][1])
             # Restore frame style if necessary
             # Restore frame style if necessary
             if (frameType != PGFrameStyle.TNone):
             if (frameType != PGFrameStyle.TNone):
                 self.frameStyle[0].setType(frameType)
                 self.frameStyle[0].setType(frameType)
@@ -160,6 +190,7 @@ class DirectButton(DirectGuiBase, NodePath):
                     OnscreenText.OnscreenText,
                     OnscreenText.OnscreenText,
                     (), parent = self.stateNodePath[i],
                     (), parent = self.stateNodePath[i],
                     text = text[i], scale = 1,
                     text = text[i], scale = 1,
+                    sort = TEXT_SORT_INDEX,
                     mayChange = 1)
                     mayChange = 1)
             else:
             else:
                 self[component + '_text'] = text[i]
                 self[component + '_text'] = text[i]
@@ -179,6 +210,7 @@ class DirectButton(DirectGuiBase, NodePath):
                     component, (), 'geom',
                     component, (), 'geom',
                     OnscreenGeom.OnscreenGeom,
                     OnscreenGeom.OnscreenGeom,
                     (), parent = self.stateNodePath[i],
                     (), parent = self.stateNodePath[i],
+                    sort = GEOM_SORT_INDEX,
                     geom = geom[i], scale = 1)
                     geom = geom[i], scale = 1)
             else:
             else:
                 self[component + '_geom'] = geom[i]
                 self[component + '_geom'] = geom[i]
@@ -206,6 +238,7 @@ class DirectButton(DirectGuiBase, NodePath):
                     component, (), 'image',
                     component, (), 'image',
                     OnscreenImage.OnscreenImage,
                     OnscreenImage.OnscreenImage,
                     (), parent = self.stateNodePath[i],
                     (), parent = self.stateNodePath[i],
+                    sort = IMAGE_SORT_INDEX,
                     image = image[i], scale = 1)
                     image = image[i], scale = 1)
             else:
             else:
                 self[component + '_image'] = image[i]
                 self[component + '_image'] = image[i]
@@ -239,5 +272,10 @@ class DirectButton(DirectGuiBase, NodePath):
 
 
     def commandFunc(self, event):
     def commandFunc(self, event):
         if self['command']:
         if self['command']:
-            self['command'](event)
+            # Pass any extra args to command
+            apply(self['command'], self['extraArgs'])
             
             
+    def destroy(self):
+        DirectGuiBase.destroy(self)
+        # Get rid of node path
+        self.removeNode()

+ 176 - 28
direct/src/gui/DirectGuiBase.py

@@ -1,14 +1,95 @@
 from DirectGuiGlobals import *
 from DirectGuiGlobals import *
+"""
+Base class for all Direct Gui items.  Handles composite widgets and
+command line argument parsing.
+"""
 
 
 # Symbolic constants for the indexes into an optionInfo list.
 # Symbolic constants for the indexes into an optionInfo list.
 _OPT_DEFAULT         = 0
 _OPT_DEFAULT         = 0
 _OPT_VALUE           = 1
 _OPT_VALUE           = 1
 _OPT_FUNCTION        = 2
 _OPT_FUNCTION        = 2
 
 
+"""
+Code Overview:
+
+1   Each widget defines a set of options (optiondefs) as a list of tuples
+    of the form ('name', defaultValue, handler).
+    'name' is the name of the option (used during construction of configure)
+    handler can be: None, method, or INITOPT.  If a method is specified,
+    it will be called during widget construction (via initialiseoptions),
+    if the Handler is specified as an INITOPT, this is an option that can
+    only be set during widget construction.
+
+2)  DirectGuiBase.defineoptions is called.  defineoption creates:
+
+    self._constructorKeywords = { keyword : [value, useFlag] }
+    a dictionary of the keyword options specified as part of the constructor
+    keywords can be of the form 'component_option', where component is
+    the name of a widget's component, a component group or a component alias
+    
+    self._dynamicGroups, a list of group names for which it is permissible
+    to specify options before components of that group are created.
+    If a widget is a derived class the order of execution would be:
+    foo.optiondefs = {}
+    foo.defineoptions()
+      fooParent()
+         fooParent.optiondefs = {}
+         fooParent.definoptions()
+
+3)  addoptions is called.  This combines options specified as keywords to
+    the widget constructor (stored in self._constuctorKeywords)
+    with the default options (stored in optiondefs).  Results are stored in
+    self._optionInfo = { keyword: [default, current, handler] }
+    If a keyword is of the form 'component_option' it is left in the
+    self._constructorKeywords dictionary (for use by component constructors),
+    otherwise it is 'used', and deleted from self._constructorKeywords.
+    Notes: - constructor keywords override the defaults.
+           - derived class default values override parent class defaults
+           - derived class handler functions override parent class functions
+
+4)  Superclass initialization methods are called (resulting in nested calls
+    to define options (see 2 above)
+
+5)  Widget components are created via calls to self.createcomponent.
+    User can specify aliases and groups for each component created.
+
+    Aliases are alternate names for components, e.g. a widget may have a
+    component with a name 'entryField', which itself may have a component
+    named 'entry', you could add an alias 'entry' for the 'entryField_entry'
+    These are stored in self.__componentAliases.  If an alias is found,
+    all keyword entries which use that alias are expanded to their full
+    form (to avoid conversion later)
+
+    Groups allow option specifications that apply to all members of the group.
+    If a widget has components: 'text1', 'text2', and 'text3' which all belong
+    to the 'text' group, they can be all configured with keywords of the form:
+    'text_keyword' (e.g. text_font = 'comic.rgb').  A component's group
+    is stored as the fourth element of its entry in self.__componentInfo
+
+    Note: the widget constructors have access to all remaining keywords in
+    _constructorKeywords (those not transferred to _optionInfo by
+    define/addoptions).  If a component defines an alias that applies to
+    one of the keywords, that keyword is replaced with a new keyword with
+    the alias expanded.
+
+    If a keyword (or substituted alias keyword) is used during creation of the
+    component, it is deleted from self._constructorKeywords.  If a group
+    keyword applies to the component, that keyword is marked as used, but is
+    not deleted from self._constructorKeywords, in case it applies to another
+    component.  If any constructor keywords remain at the end of component
+    construction (and initialisation), an error is raised.
+
+5)  initialiseoptions is called.  This method calls any option handlers to
+    respond to any keyword/default values, then checks to see if any keywords
+    are left unused.  If so, an error is raised.
+"""
+
 class DirectGuiBase(PandaObject):
 class DirectGuiBase(PandaObject):
-    def __init__(self, optiondefs, dynamicGroups, **kw):
+    def __init__(self):
         # Default id of all gui object, subclasses should override this
         # Default id of all gui object, subclasses should override this
         self.guiId = 'guiObject'
         self.guiId = 'guiObject'
+        # List of all active hooks
+        self.hookDict = {}
 	# Mapping from each megawidget option to a list of information
 	# Mapping from each megawidget option to a list of information
 	# about the option
 	# about the option
 	#   - default value
 	#   - default value
@@ -58,14 +139,19 @@ class DirectGuiBase(PandaObject):
         # no components with this group have been created.
         # no components with this group have been created.
         # self._dynamicGroups = ()
         # self._dynamicGroups = ()
 
 
-        self.defineoptions(kw, optiondefs, dynamicGroups)
-        
     def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
     def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
+        """ defineoptions(keywords, optionDefs, dynamicGroups = {}) """
 	# Create options, providing the default value and the method
 	# Create options, providing the default value and the method
 	# to call when the value is changed.  If any option created by
 	# to call when the value is changed.  If any option created by
 	# base classes has the same name as one in <optionDefs>, the
 	# base classes has the same name as one in <optionDefs>, the
 	# base class's value and function will be overriden.
 	# base class's value and function will be overriden.
         
         
+        # keywords is a dictionary of keyword/value pairs from the constructor
+        # optionDefs is a dictionary of default options for the widget
+        # dynamicGroups is a tuple of component groups for which you can
+        # specify options even though no components of this group have
+        # been created
+
 	# This should be called before the constructor of the base
 	# This should be called before the constructor of the base
 	# class, so that default values defined in the derived class
 	# class, so that default values defined in the derived class
 	# override those in the base class.
 	# override those in the base class.
@@ -83,6 +169,7 @@ class DirectGuiBase(PandaObject):
         self.addoptions(optionDefs)
         self.addoptions(optionDefs)
         
         
     def addoptions(self, optionDefs):
     def addoptions(self, optionDefs):
+        """ addoptions(optionDefs) - add option def to option info """
 	# Add additional options, providing the default value and the
 	# Add additional options, providing the default value and the
 	# method to call when the value is changed.  See
 	# method to call when the value is changed.  See
 	# "defineoptions" for more details
 	# "defineoptions" for more details
@@ -105,12 +192,13 @@ class DirectGuiBase(PandaObject):
                         # Overridden by keyword, use keyword value
                         # Overridden by keyword, use keyword value
                         value = keywords[name][0]
                         value = keywords[name][0]
                         optionInfo[name] = [default, value, function]
                         optionInfo[name] = [default, value, function]
+                        # Delete it from self._constructorKeywords
                         del keywords[name]
                         del keywords[name]
                     else:
                     else:
                         # Use optionDefs value
                         # Use optionDefs value
                         optionInfo[name] = [default, default, function]
                         optionInfo[name] = [default, default, function]
                 elif optionInfo[name][FUNCTION] is None:
                 elif optionInfo[name][FUNCTION] is None:
-                    # Override function
+                    # Only override function if not defined by derived class
                     optionInfo[name][FUNCTION] = function
                     optionInfo[name][FUNCTION] = function
 	    else:
 	    else:
 		# This option is of the form "component_option".  If this is
 		# This option is of the form "component_option".  If this is
@@ -121,11 +209,24 @@ class DirectGuiBase(PandaObject):
 		    keywords[name] = [default, 0]
 		    keywords[name] = [default, 0]
                 
                 
     def initialiseoptions(self, myClass):
     def initialiseoptions(self, myClass):
+        """
+        initialiseoptions(myClass) - call all initialisation functions
+        to initialize widget options to default of keyword value
+        """
+        # This is to make sure this method class is only called by
+        # the most specific class in the class hierarchy
 	if self.__class__ is myClass:
 	if self.__class__ is myClass:
+	    # Call the configuration callback function for every option.
+	    FUNCTION = _OPT_FUNCTION
+	    for info in self._optionInfo.values():
+		func = info[FUNCTION]
+		if func is not None and func is not INITOPT:
+		    func()
+
+            # Now check if anything is left over
 	    unusedOptions = []
 	    unusedOptions = []
 	    keywords = self._constructorKeywords
 	    keywords = self._constructorKeywords
 	    for name in keywords.keys():
 	    for name in keywords.keys():
-                print name
 		used = keywords[name][1]
 		used = keywords[name][1]
 		if not used:
 		if not used:
                     # This keyword argument has not been used.  If it
                     # This keyword argument has not been used.  If it
@@ -142,18 +243,20 @@ class DirectGuiBase(PandaObject):
 		    text = 'Unknown options "'
 		    text = 'Unknown options "'
 		raise KeyError, text + string.join(unusedOptions, ', ') + \
 		raise KeyError, text + string.join(unusedOptions, ', ') + \
 			'" for ' + myClass.__name__
 			'" for ' + myClass.__name__
-            
-	    # Call the configuration callback function for every option.
-	    FUNCTION = _OPT_FUNCTION
-	    for info in self._optionInfo.values():
-		func = info[FUNCTION]
-		if func is not None and func is not INITOPT:
-		    func()
                     
                     
     def isinitoption(self, option):
     def isinitoption(self, option):
+        """
+        isinitoption(option)
+        Is this opition one that can only be specified at construction?
+        """
 	return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
 	return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
     
     
     def options(self):
     def options(self):
+        """
+        options()
+        Print out a list of available widget options.
+        Does not include subcomponent options.
+        """
 	options = []
 	options = []
 	if hasattr(self, '_optionInfo'):
 	if hasattr(self, '_optionInfo'):
 	    for option, info in self._optionInfo.items():
 	    for option, info in self._optionInfo.items():
@@ -164,7 +267,10 @@ class DirectGuiBase(PandaObject):
 	return options
 	return options
     
     
     def configure(self, option=None, **kw):
     def configure(self, option=None, **kw):
-	# Query or configure the megawidget options.
+        """
+        configure(option = None)
+	Query or configure the megawidget options.
+        """
 	#
 	#
 	# If not empty, *kw* is a dictionary giving new
 	# If not empty, *kw* is a dictionary giving new
 	# values for some of the options of this gui item
 	# values for some of the options of this gui item
@@ -232,29 +338,39 @@ class DirectGuiBase(PandaObject):
 		index = string.find(option, '_')
 		index = string.find(option, '_')
 		if index >= 0:
 		if index >= 0:
 		    # This option may be of the form <component>_<option>.
 		    # This option may be of the form <component>_<option>.
+                    # e.g. if alias ('efEntry', 'entryField_entry')
+                    # and option = efEntry_width
+                    # component = efEntry, componentOption = width
 		    component = option[:index]
 		    component = option[:index]
 		    componentOption = option[(index + 1):]
 		    componentOption = option[(index + 1):]
 
 
 		    # Expand component alias
 		    # Expand component alias
 		    if componentAliases_has_key(component):
 		    if componentAliases_has_key(component):
+                        # component = entryField, subcomponent = entry
 			component, subComponent = componentAliases[component]
 			component, subComponent = componentAliases[component]
 			if subComponent is not None:
 			if subComponent is not None:
+                            # componentOption becomes entry_width
 			    componentOption = subComponent + '_' \
 			    componentOption = subComponent + '_' \
 				    + componentOption
 				    + componentOption
 
 
 			# Expand option string to write on error
 			# Expand option string to write on error
+                        # option = entryField_entry_width
 			option = component + '_' + componentOption
 			option = component + '_' + componentOption
 
 
                     # Does this component exist
                     # Does this component exist
 		    if componentInfo_has_key(component):
 		    if componentInfo_has_key(component):
 			# Get the configure func for the named component
 			# Get the configure func for the named component
+                        # component = entryField
 			componentConfigFuncs = [componentInfo[component][1]]
 			componentConfigFuncs = [componentInfo[component][1]]
 		    else:
 		    else:
 			# Check if this is a group name and configure all
 			# Check if this is a group name and configure all
 			# components in the group.
 			# components in the group.
 			componentConfigFuncs = []
 			componentConfigFuncs = []
+                        # For each component
 			for info in componentInfo.values():
 			for info in componentInfo.values():
+                            # Check if it is a member of this group
 			    if info[4] == component:
 			    if info[4] == component:
+                                # Yes, append its config func
 			        componentConfigFuncs.append(info[1])
 			        componentConfigFuncs.append(info[1])
 
 
                         if len(componentConfigFuncs) == 0 and \
                         if len(componentConfigFuncs) == 0 and \
@@ -268,6 +384,8 @@ class DirectGuiBase(PandaObject):
 		    for componentConfigFunc in componentConfigFuncs:
 		    for componentConfigFunc in componentConfigFuncs:
 			if not indirectOptions_has_key(componentConfigFunc):
 			if not indirectOptions_has_key(componentConfigFunc):
 			    indirectOptions[componentConfigFunc] = {}
 			    indirectOptions[componentConfigFunc] = {}
+                        # Create a dictionary of keyword/values keyed
+                        # on configuration function
 			indirectOptions[componentConfigFunc][componentOption] \
 			indirectOptions[componentConfigFunc][componentOption] \
 				= value
 				= value
 		else:
 		else:
@@ -275,6 +393,7 @@ class DirectGuiBase(PandaObject):
 			    '" for ' + self.__class__.__name__
 			    '" for ' + self.__class__.__name__
 
 
 	# Call the configure methods for any components.
 	# Call the configure methods for any components.
+        # Pass in the dictionary of keyword/values created above
 	map(apply, indirectOptions.keys(),
 	map(apply, indirectOptions.keys(),
 		((),) * len(indirectOptions), indirectOptions.values())
 		((),) * len(indirectOptions), indirectOptions.values())
             
             
@@ -290,8 +409,10 @@ class DirectGuiBase(PandaObject):
         apply(self.configure, (), {key: value})
         apply(self.configure, (), {key: value})
         
         
     def cget(self, option):
     def cget(self, option):
-	# Get current configuration setting.
-        
+        """
+        cget(option)
+	Get current configuration setting for this option
+        """
 	# Return the value of an option, for example myWidget['font']. 
 	# Return the value of an option, for example myWidget['font']. 
 	if self._optionInfo.has_key(option):
 	if self._optionInfo.has_key(option):
 	    return self._optionInfo[option][_OPT_VALUE]
 	    return self._optionInfo[option][_OPT_VALUE]
@@ -332,11 +453,14 @@ class DirectGuiBase(PandaObject):
     
     
     def createcomponent(self, componentName, componentAliases, componentGroup,
     def createcomponent(self, componentName, componentAliases, componentGroup,
                         widgetClass, *widgetArgs, **kw):
                         widgetClass, *widgetArgs, **kw):
-	"""Create a component (during construction or later)."""
+	"""
+        Create a component (during construction or later) for this widget.
+        """
         # Check for invalid component name
         # Check for invalid component name
 	if '_' in componentName:
 	if '_' in componentName:
 	    raise ValueError, \
 	    raise ValueError, \
                     'Component name "%s" must not contain "_"' % componentName
                     'Component name "%s" must not contain "_"' % componentName
+        
         # Get construction keywords
         # Get construction keywords
 	if hasattr(self, '_constructorKeywords'):
 	if hasattr(self, '_constructorKeywords'):
 	    keywords = self._constructorKeywords
 	    keywords = self._constructorKeywords
@@ -347,8 +471,10 @@ class DirectGuiBase(PandaObject):
 	    # Create aliases to the component and its sub-components.
 	    # Create aliases to the component and its sub-components.
 	    index = string.find(component, '_')
 	    index = string.find(component, '_')
 	    if index < 0:
 	    if index < 0:
+                # Just a shorter name for one of this widget's components
 		self.__componentAliases[alias] = (component, None)
 		self.__componentAliases[alias] = (component, None)
 	    else:
 	    else:
+                # An alias for a component of one of this widget's components
 		mainComponent = component[:index]
 		mainComponent = component[:index]
 		subComponent = component[(index + 1):]
 		subComponent = component[(index + 1):]
 		self.__componentAliases[alias] = (mainComponent, subComponent)
 		self.__componentAliases[alias] = (mainComponent, subComponent)
@@ -356,7 +482,6 @@ class DirectGuiBase(PandaObject):
 	    # Remove aliases from the constructor keyword arguments by
 	    # Remove aliases from the constructor keyword arguments by
 	    # replacing any keyword arguments that begin with *alias*
 	    # replacing any keyword arguments that begin with *alias*
 	    # with corresponding keys beginning with *component*.
 	    # with corresponding keys beginning with *component*.
-
 	    alias = alias + '_'
 	    alias = alias + '_'
 	    aliasLen = len(alias)
 	    aliasLen = len(alias)
 	    for option in keywords.keys():
 	    for option in keywords.keys():
@@ -420,19 +545,24 @@ class DirectGuiBase(PandaObject):
 	    remainingComponents = name[(index + 1):]
 	    remainingComponents = name[(index + 1):]
 
 
 	# Expand component alias
 	# Expand component alias
+        # Example entry which is an alias for entryField_entry
 	if self.__componentAliases.has_key(component):
 	if self.__componentAliases.has_key(component):
+            # component = entryField, subComponent = entry
 	    component, subComponent = self.__componentAliases[component]
 	    component, subComponent = self.__componentAliases[component]
 	    if subComponent is not None:
 	    if subComponent is not None:
 		if remainingComponents is None:
 		if remainingComponents is None:
+                    # remainingComponents = entry
 		    remainingComponents = subComponent
 		    remainingComponents = subComponent
 		else:
 		else:
 		    remainingComponents = subComponent + '_' \
 		    remainingComponents = subComponent + '_' \
 			    + remainingComponents
 			    + remainingComponents
-
+        # Get the component from __componentInfo dictionary
 	widget = self.__componentInfo[component][0]
 	widget = self.__componentInfo[component][0]
 	if remainingComponents is None:
 	if remainingComponents is None:
+            # Not looking for subcomponent
 	    return widget
 	    return widget
 	else:
 	else:
+            # Recursive call on subcomponent
 	    return widget.component(remainingComponents)
 	    return widget.component(remainingComponents)
 
 
     def components(self):
     def components(self):
@@ -452,15 +582,33 @@ class DirectGuiBase(PandaObject):
 	del self.__componentInfo[name]
 	del self.__componentInfo[name]
 
 
     def destroy(self):
     def destroy(self):
-        # Clean up optionInfo in case it contains circular references
-        # in the function field, such as self._settitle in class
-        # MegaToplevel.
-
-	self._optionInfo = {}
-
-    def bind(self, sequence, command):
-        self.accept(sequence + '-' + self.guiId, command)
+        # Clean out any hooks
+        for event in self.hookDict.keys():
+            self.ignore(event)
+
+    def bind(self, event, command):
+        """
+        Bind the command (which should expect one arg) to the specified
+        event (such as ENTER, EXIT, B1PRESS, B1CLICK, etc.)
+        See DirectGuiGlobals for possible events
+        """
+        # Need to tack on gui item specific id
+        gEvent = event + self.guiId
+        self.accept(gEvent, command)
+        # Keep track of all events you're accepting
+        self.hookDict[gEvent] = command
         
         
-    def unbind(self, sequence):
-        self.ignore(sequence + '-' + self.guiId)
+    def unbind(self, event):
+        """
+        Unbind the specified event
+        """
+        # Need to tack on gui item specific id
+        gEvent = event + self.guiId
+        self.ignore(gEvent)
+        if self.hookDict.has_key(gEvent):
+            del(self.hookDict[gEvent])
+
+
+class DirectGuiWidget(DirectGuiBase):
+    pass
 
 

+ 14 - 3
direct/src/gui/DirectGuiGlobals.py

@@ -1,17 +1,27 @@
+"""
+Global definitions used by Direct Gui Classes and handy constants
+that can be used during widget construction
+"""
+
 from PandaObject import *
 from PandaObject import *
 from PGTop import *
 from PGTop import *
 from PGButton import *
 from PGButton import *
+# Import these after PGButton to get actual class definitions
 from PGItem import *
 from PGItem import *
 from PGFrameStyle import *
 from PGFrameStyle import *
+# Helper classes used as components of Direct Gui Widgets
 import OnscreenText
 import OnscreenText
 import OnscreenGeom
 import OnscreenGeom
 import OnscreenImage
 import OnscreenImage
 import types
 import types
 
 
 # USEFUL GUI CONSTANTS
 # USEFUL GUI CONSTANTS
+
+# Widget state
 NORMAL = 'normal'
 NORMAL = 'normal'
 DISABLED = 'disabled'
 DISABLED = 'disabled'
 
 
+# Frame style
 FLAT = PGFrameStyle.TFlat
 FLAT = PGFrameStyle.TFlat
 RAISED = PGFrameStyle.TBevelOut
 RAISED = PGFrameStyle.TBevelOut
 SUNKEN = PGFrameStyle.TBevelIn
 SUNKEN = PGFrameStyle.TBevelIn
@@ -31,10 +41,11 @@ def getGenericMouseEvent(event, mouse):
     elif event == 'release':
     elif event == 'release':
         eventFunc = PGButton().getReleaseEvent
         eventFunc = PGButton().getReleaseEvent
     eventString = eventFunc(mb)
     eventString = eventFunc(mb)
-    return eventString[:eventString.rfind('-')]
+    return eventString[:eventString.rfind('-') + 1]
 
 
-ENTER = 'enter'
-EXIT = 'exit'
+# User can bind commands to these gui events
+ENTER = 'enter-'
+EXIT = 'exit-'
 B1CLICK = getGenericMouseEvent('click', 1)
 B1CLICK = getGenericMouseEvent('click', 1)
 B2CLICK = getGenericMouseEvent('click', 2)
 B2CLICK = getGenericMouseEvent('click', 2)
 B3CLICK = getGenericMouseEvent('click', 3)
 B3CLICK = getGenericMouseEvent('click', 3)

+ 14 - 10
direct/src/gui/DirectGuiTest.py

@@ -11,18 +11,20 @@ smiley = loader.loadModel('models/directmodels/smiley')
 db = DirectButton(image = 'phase_4/maps/middayskyB.jpg',
 db = DirectButton(image = 'phase_4/maps/middayskyB.jpg',
                   geom = smiley,
                   geom = smiley,
                   text = ('Hi!', 'Ouch!', 'Bye!', 'ZZZZ!'),
                   text = ('Hi!', 'Ouch!', 'Bye!', 'ZZZZ!'),
-                  scale = .15, relief = 'raised')
+                  scale = .15, relief = 'raised',
+                  # Here we set an option for a component of the button
+                  geom1_color = Vec4(1,0,0,1),
+                  # Here is an example of a component group option
+                  text_pos = (.6, -.8))
 
 
-# Adjust text position and scale
-db['text_pos'] = (.6, -.8)
-db['text_scale'] = .5
-# Set the color of the pressed button's geometry
-db['geom1_color'] = Vec4(1,0,0,1)
+# You can set component or component group options after a gui item
+# has been created
+db['text_scale'] = 0.5
 
 
-# Specify the button's command
-# Note, command must expect one argument
-def dummyCmd(event):
+# Here we specify the button's command
+def dummyCmd():
     print 'POW!!!!'
     print 'POW!!!!'
+
 db['command'] = dummyCmd
 db['command'] = dummyCmd
 
 
 # Get a handle on the geometry for the rollover state
 # Get a handle on the geometry for the rollover state
@@ -63,6 +65,8 @@ def ouch(event):
 db.bind(ENTER, shrink)
 db.bind(ENTER, shrink)
 db.bind(EXIT, expand)
 db.bind(EXIT, expand)
 db.bind(B1PRESS, ouch)
 db.bind(B1PRESS, ouch)
-
 # Pop up placer when button 2 is pressed
 # Pop up placer when button 2 is pressed
 db.bind(B2PRESS, lambda x, s = db: s.place())
 db.bind(B2PRESS, lambda x, s = db: s.place())
+
+# To get rid of button and clear out hooks call:
+# db.destroy()

+ 5 - 3
direct/src/gui/OnscreenGeom.py

@@ -9,7 +9,8 @@ class OnscreenGeom(PandaObject, NodePath):
                  hpr = None,
                  hpr = None,
                  scale = None,
                  scale = None,
                  color = None,
                  color = None,
-                 parent = aspect2d):
+                 parent = aspect2d,
+                 sort = 0):
         """__init__(self, ...)
         """__init__(self, ...)
 
 
         Make a geom node from string or a node path,
         Make a geom node from string or a node path,
@@ -40,11 +41,12 @@ class OnscreenGeom(PandaObject, NodePath):
         # We ARE a node path.  Initially, we're an empty node path.
         # We ARE a node path.  Initially, we're an empty node path.
         NodePath.__init__(self)
         NodePath.__init__(self)
         # Assign geometry
         # Assign geometry
+        self.sort = sort
         if isinstance(geom, NodePath):
         if isinstance(geom, NodePath):
-            self.assign(geom.copyTo(parent))
+            self.assign(geom.copyTo(parent, self.sort))
         elif type(geom) == type(''):
         elif type(geom) == type(''):
             self.assign(loader.loadModelCopy(geom))
             self.assign(loader.loadModelCopy(geom))
-            self.reparentTo(parent)
+            self.reparentTo(parent, self.sort)
 
 
         # Adjust pose
         # Adjust pose
         # Set pos
         # Set pos

+ 5 - 4
direct/src/gui/OnscreenImage.py

@@ -9,7 +9,8 @@ class OnscreenImage(PandaObject, NodePath):
                  hpr = None,
                  hpr = None,
                  scale = None,
                  scale = None,
                  color = None,
                  color = None,
-                 parent = aspect2d):
+                 parent = aspect2d,
+                 sort = 0):
         """__init__(self, ...)
         """__init__(self, ...)
 
 
         Make a image node from string or a node path,
         Make a image node from string or a node path,
@@ -41,13 +42,13 @@ class OnscreenImage(PandaObject, NodePath):
         NodePath.__init__(self)
         NodePath.__init__(self)
         # Assign geometry
         # Assign geometry
         if isinstance(image, NodePath):
         if isinstance(image, NodePath):
-            self.assign(image.copyTo(parent))
+            self.assign(image.copyTo(parent, sort))
         elif type(image) == type(''):
         elif type(image) == type(''):
             # Assume its a file name and create a texture card
             # Assume its a file name and create a texture card
             tex = loader.loadTexture(image)
             tex = loader.loadTexture(image)
             cm = CardMaker()
             cm = CardMaker()
             cm.setFrame(-1, 1, -1, 1)
             cm.setFrame(-1, 1, -1, 1)
-            self.assign(parent.attachNewNode(cm.generate()))
+            self.assign(parent.attachNewNode(cm.generate(), sort))
             self.setTexture(tex)
             self.setTexture(tex)
         elif type(image) == type(()):
         elif type(image) == type(()):
             # Assume its a file+node name, extract texture from node
             # Assume its a file+node name, extract texture from node
@@ -56,7 +57,7 @@ class OnscreenImage(PandaObject, NodePath):
                 node = model.find(image[1])
                 node = model.find(image[1])
                 if node:
                 if node:
                     self.assign(node)
                     self.assign(node)
-                    self.reparentTo(parent)
+                    self.reparentTo(parent, sort)
                 else:
                 else:
                     print 'OnscreenImage: node %s not found' % image[1]
                     print 'OnscreenImage: node %s not found' % image[1]
                     return
                     return

+ 2 - 1
direct/src/gui/OnscreenText.py

@@ -29,6 +29,7 @@ class OnscreenText(PandaObject, NodePath):
                  drawOrder = GuiGlobals.getDefaultDrawOrder(),
                  drawOrder = GuiGlobals.getDefaultDrawOrder(),
                  font = GuiGlobals.getDefaultFont(),
                  font = GuiGlobals.getDefaultFont(),
                  parent = aspect2d,
                  parent = aspect2d,
+                 sort = 0,
                  mayChange = 0):
                  mayChange = 0):
         """__init__(self, ...)
         """__init__(self, ...)
 
 
@@ -204,7 +205,7 @@ class OnscreenText(PandaObject, NodePath):
 	self.isClean = 0
 	self.isClean = 0
 
 
         # Set ourselves up as the NodePath that points to this node.
         # Set ourselves up as the NodePath that points to this node.
-        self.assign(parent.attachNewNode(self.textNode))
+        self.assign(parent.attachNewNode(self.textNode, sort))
     
     
     def cleanup(self):
     def cleanup(self):
 	"""cleanup(self)
 	"""cleanup(self)