Browse Source

*** empty log message ***

Mark Mine 24 years ago
parent
commit
36f18680dd

+ 53 - 829
direct/src/gui/DirectButton.py

@@ -1,491 +1,6 @@
-from PandaObject import *
-import OnscreenText
-from PGTop import *
-from PGButton import *
-from PGItem import *
-from PGFrameStyle import *
-import types
-import __builtin__
+from DirectGuiBase import *
 
 
-NORMAL = 'normal'
-DISABLED = 'disabled'
-
-FLAT = PGFrameStyle.TFlat
-RAISED = PGFrameStyle.TBevelOut
-SUNKEN = PGFrameStyle.TBevelIn
-
-# Constant used to indicate that an option can only be set by a call
-# to the constructor.
-INITOPT = ['initopt']
-
-# Symbolic constants for the indexes into an optionInfo list.
-_OPT_DEFAULT         = 0
-_OPT_VALUE           = 1
-_OPT_FUNCTION        = 2
-
-__builtin__.guiTop = aspect2d.attachNewNode(PGTop('DirectGuiTop'))
-guiTop.node().setMouseWatcher(base.mouseWatcher.node())
-
-class DirectGuiObject(PandaObject):
-    def __init__(self, optiondefs, dynamicGroups, **kw):
-        # Default id of all gui object, subclasses should override this
-        self.guiId = 'guiObject'
-	# Mapping from each megawidget option to a list of information
-	# about the option
-	#   - default value
-	#   - current value
-	#   - function to call when the option is initialised in the
-	#     call to initialiseoptions() in the constructor or
-	#     modified via configure().  If this is INITOPT, the
-	#     option is an initialisation option (an option that can
-	#     be set by the call to the constructor but can not be
-	#     used with configure).
-	# This mapping is not initialised here, but in the call to
-	# defineoptions() which precedes construction of this base class.
-	#
-	# self._optionInfo = {}
-
-	# Mapping from each component name to a tuple of information
-	# about the component.
-	#   - component widget instance
-	#   - configure function of widget instance
-	#   - the class of the widget (Frame, EntryField, etc)
-	#   - cget function of widget instance
-	#   - the name of the component group of this component, if any
-	self.__componentInfo = {}
-
-	# Mapping from alias names to the names of components or
-	# sub-components.
-	self.__componentAliases = {}
-
-	# Contains information about the keywords provided to the
-	# constructor.  It is a mapping from the keyword to a tuple
-	# containing:
-	#    - value of keyword
-	#    - a boolean indicating if the keyword has been used.
-	# A keyword is used if, during the construction of a megawidget,
-	#    - it is defined in a call to defineoptions() or addoptions(), or
-	#    - it references, by name, a component of the megawidget, or
-	#    - it references, by group, at least one component
-	# At the end of megawidget construction, a call is made to
-	# initialiseoptions() which reports an error if there are
-	# unused options given to the constructor.
-	#
-	# self._constructorKeywords = {}
-
-        # List of dynamic component groups.  If a group is included in
-        # this list, then it not an error if a keyword argument for
-        # the group is given to the constructor or to configure(), but
-        # no components with this group have been created.
-        # self._dynamicGroups = ()
-
-        self.defineoptions(kw, optiondefs, dynamicGroups)
-        
-    def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
-	# Create options, providing the default value and the method
-	# to call when the value is changed.  If any option created by
-	# base classes has the same name as one in <optionDefs>, the
-	# base class's value and function will be overriden.
-        
-	# This should be called before the constructor of the base
-	# class, so that default values defined in the derived class
-	# override those in the base class.
-	if not hasattr(self, '_constructorKeywords'):
-	    tmp = {}
-	    for option, value in keywords.items():
-		tmp[option] = [value, 0]
-            self._constructorKeywords = tmp
-	    self._optionInfo = {}
-        # Initialize dictionary of dynamic groups
-        if not hasattr(self, '_dynamicGroups'):
-            self._dynamicGroups = ()
-        self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
-        # Reconcile command line and default options
-        self.addoptions(optionDefs)
-        
-    def addoptions(self, optionDefs):
-	# Add additional options, providing the default value and the
-	# method to call when the value is changed.  See
-	# "defineoptions" for more details
-        
-	# optimisations:
-	optionInfo = self._optionInfo
-	optionInfo_has_key = optionInfo.has_key
-	keywords = self._constructorKeywords
-	keywords_has_key = keywords.has_key
-	FUNCTION = _OPT_FUNCTION
-        
-	for name, default, function in optionDefs:
-	    if '_' not in name:
-                # The option will already exist if it has been defined
-                # in a derived class.  In this case, do not override the
-                # default value of the option or the callback function
-                # if it is not None.
-                if not optionInfo_has_key(name):
-                    if keywords_has_key(name):
-                        # Overridden by keyword, use keyword value
-                        value = keywords[name][0]
-                        optionInfo[name] = [default, value, function]
-                        del keywords[name]
-                    else:
-                        # Use optionDefs value
-                        optionInfo[name] = [default, default, function]
-                elif optionInfo[name][FUNCTION] is None:
-                    # Override function
-                    optionInfo[name][FUNCTION] = function
-	    else:
-		# This option is of the form "component_option".  If this is
-		# not already defined in self._constructorKeywords add it.
-		# This allows a derived class to override the default value
-		# of an option of a component of a base class.
-		if not keywords_has_key(name):
-		    keywords[name] = [default, 0]
-                
-    def initialiseoptions(self, myClass):
-	if self.__class__ is myClass:
-	    unusedOptions = []
-	    keywords = self._constructorKeywords
-	    for name in keywords.keys():
-                print name
-		used = keywords[name][1]
-		if not used:
-                    # This keyword argument has not been used.  If it
-                    # does not refer to a dynamic group, mark it as
-                    # unused.
-                    index = string.find(name, '_')
-                    if index < 0 or name[:index] not in self._dynamicGroups:
-                        unusedOptions.append(name)
-	    self._constructorKeywords = {}
-	    if len(unusedOptions) > 0:
-		if len(unusedOptions) == 1:
-		    text = 'Unknown option "'
-		else:
-		    text = 'Unknown options "'
-		raise KeyError, text + string.join(unusedOptions, ', ') + \
-			'" 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):
-	return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
-    
-    def options(self):
-	options = []
-	if hasattr(self, '_optionInfo'):
-	    for option, info in self._optionInfo.items():
-		isinit = info[_OPT_FUNCTION] is INITOPT
-		default = info[_OPT_DEFAULT]
-		options.append((option, default, isinit))
-	    options.sort()
-	return options
-    
-    def configure(self, option=None, **kw):
-	# Query or configure the megawidget options.
-	#
-	# If not empty, *kw* is a dictionary giving new
-	# values for some of the options of this gui item
-	# For options defined for this widget, set
-	# the value of the option to the new value and call the
-	# configuration callback function, if any.
-	#
-	# If *option* is None, return all gui item configuration
-	# options and settings.  Options are returned as standard 3
-	# element tuples
-	#
-	# If *option* is a string, return the 3 element tuple for the
-	# given configuration option.
-        
-	# First, deal with the option queries.
-	if len(kw) == 0:
-	    # This configure call is querying the values of one or all options.
-	    # Return 3-tuples:
-	    #     (optionName, default, value)
-	    if option is None:
-		rtn = {}
-		for option, config in self._optionInfo.items():
-		    rtn[option] = (option,
-                                   config[_OPT_DEFAULT],
-                                   config[_OPT_VALUE])
-		return rtn
-	    else:
-		config = self._optionInfo[option]
-		return (option, config[_OPT_DEFAULT], config[_OPT_VALUE])
-            
-	# optimizations:
-	optionInfo = self._optionInfo
-	optionInfo_has_key = optionInfo.has_key
-	componentInfo = self.__componentInfo
-	componentInfo_has_key = componentInfo.has_key
-	componentAliases = self.__componentAliases
-	componentAliases_has_key = componentAliases.has_key
-	VALUE = _OPT_VALUE
-	FUNCTION = _OPT_FUNCTION
-        
-	# This will contain a list of options in *kw* which
-	# are known to this gui item.
-	directOptions = []
-        
-	# This will contain information about the options in
-	# *kw* of the form <component>_<option>, where
-	# <component> is a component of this megawidget.  It is a
-	# dictionary whose keys are the configure method of each
-	# component and whose values are a dictionary of options and
-	# values for the component.
-	indirectOptions = {}
-	indirectOptions_has_key = indirectOptions.has_key
-
-	for option, value in kw.items():
-	    if optionInfo_has_key(option):
-		# This is one of the options of this gui item. 
-		# Check it is an initialisation option.
-		if optionInfo[option][FUNCTION] is INITOPT:
-		    raise KeyError, \
-			    'Cannot configure initialisation option "' \
-			    + option + '" for ' + self.__class__.__name__
-		optionInfo[option][VALUE] = value
-		directOptions.append(option)
-            else:
-		index = string.find(option, '_')
-		if index >= 0:
-		    # This option may be of the form <component>_<option>.
-		    component = option[:index]
-		    componentOption = option[(index + 1):]
-
-		    # Expand component alias
-		    if componentAliases_has_key(component):
-			component, subComponent = componentAliases[component]
-			if subComponent is not None:
-			    componentOption = subComponent + '_' \
-				    + componentOption
-
-			# Expand option string to write on error
-			option = component + '_' + componentOption
-
-                    # Does this component exist
-		    if componentInfo_has_key(component):
-			# Get the configure func for the named component
-			componentConfigFuncs = [componentInfo[component][1]]
-		    else:
-			# Check if this is a group name and configure all
-			# components in the group.
-			componentConfigFuncs = []
-			for info in componentInfo.values():
-			    if info[4] == component:
-			        componentConfigFuncs.append(info[1])
-
-                        if len(componentConfigFuncs) == 0 and \
-                                component not in self._dynamicGroups:
-			    raise KeyError, 'Unknown option "' + option + \
-				    '" for ' + self.__class__.__name__
-
-		    # Add the configure method(s) (may be more than
-		    # one if this is configuring a component group)
-		    # and option/value to dictionary.
-		    for componentConfigFunc in componentConfigFuncs:
-			if not indirectOptions_has_key(componentConfigFunc):
-			    indirectOptions[componentConfigFunc] = {}
-			indirectOptions[componentConfigFunc][componentOption] \
-				= value
-		else:
-		    raise KeyError, 'Unknown option "' + option + \
-			    '" for ' + self.__class__.__name__
-
-	# Call the configure methods for any components.
-	map(apply, indirectOptions.keys(),
-		((),) * len(indirectOptions), indirectOptions.values())
-            
-	# Call the configuration callback function for each option.
-	for option in directOptions:
-	    info = optionInfo[option]
-	    func = info[_OPT_FUNCTION]
-	    if func is not None:
-	      func()
-              
-    # Allow index style references
-    def __setitem__(self, key, value):
-        apply(self.configure, (), {key: value})
-        
-    def cget(self, option):
-	# Get current configuration setting.
-        
-	# Return the value of an option, for example myWidget['font']. 
-	if self._optionInfo.has_key(option):
-	    return self._optionInfo[option][_OPT_VALUE]
-	else:
-	    index = string.find(option, '_')
-	    if index >= 0:
-		component = option[:index]
-		componentOption = option[(index + 1):]
-
-		# Expand component alias
-		if self.__componentAliases.has_key(component):
-		    component, subComponent = self.__componentAliases[
-                        component]
-		    if subComponent is not None:
-			componentOption = subComponent + '_' + componentOption
-
-		    # Expand option string to write on error
-		    option = component + '_' + componentOption
-
-		if self.__componentInfo.has_key(component):
-		    # Call cget on the component.
-		    componentCget = self.__componentInfo[component][3]
-		    return componentCget(componentOption)
-		else:
-		    # If this is a group name, call cget for one of
-		    # the components in the group.
-		    for info in self.__componentInfo.values():
-			if info[4] == component:
-			    componentCget = info[3]
-			    return componentCget(componentOption)
-
-        # Option not found
-	raise KeyError, 'Unknown option "' + option + \
-		'" for ' + self.__class__.__name__
-    
-    # Allow index style refererences
-    __getitem__ = cget
-    
-    def createcomponent(self, componentName, componentAliases, componentGroup,
-                        widgetClass, *widgetArgs, **kw):
-	"""Create a component (during construction or later)."""
-        # Check for invalid component name
-	if '_' in componentName:
-	    raise ValueError, \
-                    'Component name "%s" must not contain "_"' % componentName
-        # Get construction keywords
-	if hasattr(self, '_constructorKeywords'):
-	    keywords = self._constructorKeywords
-	else:
-	    keywords = {}
-
-	for alias, component in componentAliases:
-	    # Create aliases to the component and its sub-components.
-	    index = string.find(component, '_')
-	    if index < 0:
-		self.__componentAliases[alias] = (component, None)
-	    else:
-		mainComponent = component[:index]
-		subComponent = component[(index + 1):]
-		self.__componentAliases[alias] = (mainComponent, subComponent)
-
-	    # Remove aliases from the constructor keyword arguments by
-	    # replacing any keyword arguments that begin with *alias*
-	    # with corresponding keys beginning with *component*.
-
-	    alias = alias + '_'
-	    aliasLen = len(alias)
-	    for option in keywords.keys():
-		if len(option) > aliasLen and option[:aliasLen] == alias:
-		    newkey = component + '_' + option[aliasLen:]
-		    keywords[newkey] = keywords[option]
-		    del keywords[option]
-
-        # Find any keyword arguments for this component
-	componentPrefix = componentName + '_'
-	nameLen = len(componentPrefix)
-	for option in keywords.keys():
-	    if len(option) > nameLen and option[:nameLen] == componentPrefix:
-		# The keyword argument refers to this component, so add
-		# this to the options to use when constructing the widget.
-		kw[option[nameLen:]] = keywords[option][0]
-                # And delete it from main construction keywords
-		del keywords[option]
-	    else:
-		# Check if this keyword argument refers to the group
-		# of this component.  If so, add this to the options
-		# to use when constructing the widget.  Mark the
-		# keyword argument as being used, but do not remove it
-		# since it may be required when creating another
-		# component.
-		index = string.find(option, '_')
-		if index >= 0 and componentGroup == option[:index]:
-		    rest = option[(index + 1):]
-		    kw[rest] = keywords[option][0]
-		    keywords[option][1] = 1
-
-        # Return None if no widget class is specified
-	if widgetClass is None:
-	    return None
-        # Get arguments for widget constructor
-        if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
-            # Arguments to the constructor can be specified as either
-            # multiple trailing arguments to createcomponent() or as a
-            # single tuple argument.
-            widgetArgs = widgetArgs[0]
-        # Create the widget
-	widget = apply(widgetClass, widgetArgs, kw)
-	componentClass = widget.__class__.__name__
-	self.__componentInfo[componentName] = (widget, widget.configure,
-		componentClass, widget.cget, componentGroup)
-	return widget
-
-    def component(self, name):
-	# Return a component widget of the megawidget given the
-	# component's name
-	# This allows the user of a megawidget to access and configure
-	# widget components directly.
-
-	# Find the main component and any subcomponents
-	index = string.find(name, '_')
-	if index < 0:
-	    component = name
-	    remainingComponents = None
-	else:
-	    component = name[:index]
-	    remainingComponents = name[(index + 1):]
-
-	# Expand component alias
-	if self.__componentAliases.has_key(component):
-	    component, subComponent = self.__componentAliases[component]
-	    if subComponent is not None:
-		if remainingComponents is None:
-		    remainingComponents = subComponent
-		else:
-		    remainingComponents = subComponent + '_' \
-			    + remainingComponents
-
-	widget = self.__componentInfo[component][0]
-	if remainingComponents is None:
-	    return widget
-	else:
-	    return widget.component(remainingComponents)
-
-    def components(self):
-	# Return a list of all components.
-	names = self.__componentInfo.keys()
-	names.sort()
-	return names
-
-    def hascomponent(self, component):
-        return component in self.__componentInfo.keys()
-
-    def destroycomponent(self, name):
-	# Remove a megawidget component.
-	# This command is for use by megawidget designers to destroy a
-	# megawidget component.
-	self.__componentInfo[name][0].destroy()
-	del self.__componentInfo[name]
-
-    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)
-        
-    def unbind(self, sequence):
-        self.ignore(sequence + '-' + self.guiId)
-
-class DirectButton(DirectGuiObject, 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,
         # and/or a text string to be used as the visible
         # and/or a text string to be used as the visible
@@ -499,25 +14,26 @@ class DirectButton(DirectGuiObject, NodePath):
         #  - a VBase4(L,R,B,T)
         #  - a VBase4(L,R,B,T)
         #  - a bounding box object
         #  - a bounding box object
         optiondefs = (
         optiondefs = (
-            ('image',         None,       self.setImage),
-            ('geom',          None,       self.setGeom),
-            ('text',          '',         self.setText),
-            ('command',       None,       self.setCommand),
-            ('relief',        FLAT,       self.setRelief),
-            ('frameColor',    (1,1,1,1),  self.setFrameColor),
-            ('borderWidth',   (.1,.1),    self.setBorderWidth),
-            ('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),
-            ('rolloverSound', None,       None),
-            ('clickSound',    None,       None),
+            ('image',           None,       self.setImage),
+            ('geom',            None,       self.setGeom),
+            ('text',            '',         self.setText),
+            ('command',         None,       None),
+            ('commandButtons',  (1,),       self.setCommandButtons),
+            ('relief',          FLAT,       self.setRelief),
+            ('frameColor',      (1,1,1,1),  self.setFrameColor),
+            ('borderWidth',     (.1,.1),    self.setBorderWidth),
+            ('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),
+            ('rolloverSound',   None,       None),
+            ('clickSound',      None,       None),
             )
             )
         # Update options to reflect keyword parameters
         # Update options to reflect keyword parameters
-        apply(DirectGuiObject.__init__, (self, optiondefs, ('text',)), kw)
+        apply(DirectGuiBase.__init__, (self, optiondefs, ('text',)), kw)
         # Initialize the superclass
         # Initialize the superclass
         NodePath.__init__(self)
         NodePath.__init__(self)
         # Create a button
         # Create a button
@@ -630,10 +146,7 @@ class DirectButton(DirectGuiObject, NodePath):
 
 
     def setText(self):
     def setText(self):
         if not self['text']:
         if not self['text']:
-            print "No Text"
             return
             return
-        else:
-            print "SetText"
         if ((type(self['text']) == type(())) or
         if ((type(self['text']) == type(())) or
             (type(self['text']) == type([]))):
             (type(self['text']) == type([]))):
             text = self['text']
             text = self['text']
@@ -653,10 +166,7 @@ class DirectButton(DirectGuiObject, NodePath):
 
 
     def setGeom(self):
     def setGeom(self):
         if not self['geom']:
         if not self['geom']:
-            print "No Geom"
             return
             return
-        else:
-            print "SetGeom"
         if ((type(self['geom']) == type(())) or
         if ((type(self['geom']) == type(())) or
             (type(self['geom']) == type([]))):
             (type(self['geom']) == type([]))):
             geom = self['geom']
             geom = self['geom']
@@ -667,7 +177,7 @@ class DirectButton(DirectGuiObject, NodePath):
             if not self.hascomponent(component):
             if not self.hascomponent(component):
                 self.createcomponent(
                 self.createcomponent(
                     component, (), 'geom',
                     component, (), 'geom',
-                    OnscreenGeom,
+                    OnscreenGeom.OnscreenGeom,
                     (), parent = self.stateNodePath[i],
                     (), parent = self.stateNodePath[i],
                     geom = geom[i], scale = 1)
                     geom = geom[i], scale = 1)
             else:
             else:
@@ -675,22 +185,26 @@ class DirectButton(DirectGuiObject, NodePath):
 
 
     def setImage(self):
     def setImage(self):
         if not self['image']:
         if not self['image']:
-            print "No Image"
             return
             return
-        else:
-            print "SetImage"
-        if ((type(self['image']) == type(())) or
+        if type(self['image']) == type(''):
+            # Assume its a file name
+            image = (self['image'],) * 4
+        elif ((type(self['image']) == type(())) or
             (type(self['image']) == type([]))):
             (type(self['image']) == type([]))):
-            if len(self['image']) == 4:
+            if len(self['image']) == 2:
+                # Assume its a model/node pair of strings
+                image = (self['image'],) * 4
+            elif len(self['image']) == 4:
+                # Assume its a 4 tuple with one entry per state
                 image = self['image']
                 image = self['image']
             else:
             else:
-                image = (self['image'],) * 4
+                print 'DirectButton.setImage: wrong argument'
         for i in range(4):
         for i in range(4):
             component = 'image' + `i`
             component = 'image' + `i`
             if not self.hascomponent(component):
             if not self.hascomponent(component):
                 self.createcomponent(
                 self.createcomponent(
                     component, (), 'image',
                     component, (), 'image',
-                    OnscreenImage,
+                    OnscreenImage.OnscreenImage,
                     (), parent = self.stateNodePath[i],
                     (), parent = self.stateNodePath[i],
                     image = image[i], scale = 1)
                     image = image[i], scale = 1)
             else:
             else:
@@ -704,316 +218,26 @@ class DirectButton(DirectGuiObject, NodePath):
         else:
         else:
             self.guiItem.setActive(0)
             self.guiItem.setActive(0)
 
 
-    def setCommand(self):
-        self.unbind('click')
+    def setCommandButtons(self):
+        # Attach command function to specified buttons
+        self.unbind(B1CLICK)
+        self.guiItem.removeClickButton(MouseButton.one())
+        self.unbind(B2CLICK)
+        self.guiItem.removeClickButton(MouseButton.two())
+        self.unbind(B3CLICK)
+        self.guiItem.removeClickButton(MouseButton.three())
+        for i in self['commandButtons']:
+            if i == 1:
+                self.guiItem.addClickButton(MouseButton.one())
+                self.bind(B1CLICK, self.commandFunc)
+            elif i == 2:
+                self.guiItem.addClickButton(MouseButton.two())
+                self.bind(B2CLICK, self.commandFunc)
+            elif i == 3:
+                self.guiItem.addClickButton(MouseButton.three())
+                self.bind(B3CLICK, self.commandFunc)
+
+    def commandFunc(self, event):
         if self['command']:
         if self['command']:
-            self.bind('click', self['command'])
+            self['command'](event)
             
             
-class DirectLabel(DirectGuiObject, PGItem):
-    def __init__(self, parent = None, **kw):
-        # Pass in a background texture, and/or a geometry object,
-        # and/or a text string to be used as the visible
-        # representation of the label
-        optiondefs = (
-            ('image',         None,       self.setImage),
-            ('geom',          None,       self.setGeom),
-            ('text',          None,       self.setText),
-            ('pos',           (0,0,0),    self.setPos),
-            ('scale',         (1,1,1),    self.setScale),
-            ('bounds',        None,       self.setBounds),
-            ('imagePos',      (0,0,0),    self.setImagePos),
-            ('imageScale',    (1,1,1),    self.setImagePos),
-            ('geomPos',       (0,0,0),    self.setGeomPos),
-            ('geomScale',     (1,1,1),    self.setGeomPos),
-            ('textPos',       (0,0,0),    self.setTextPos),
-            ('textScale',     (1,1,1),    self.setTextPos),
-            )
-        apply(DirectGuiObject.__init__, (self, optiondefs, ()), kw)            
-        self.initialiseoptions(DirectLabel)
-
-    def setImage(self):
-        pass
-    def setGeom(self):
-        pass
-    def setText(self):
-        pass
-    def setPos(self):
-        pass
-    def setScale(self):
-        pass
-    def setBounds(self):
-        pass
-    def setImagePos(self):
-        pass
-    def setImagePos(self):
-        pass
-    def setGeomPos(self):
-        pass
-    def setGeomPos(self):
-        pass
-    def setTextPos(self):
-        pass
-    def setTextPos(self):
-        pass
-    def setState(self):
-        pass
-
-
-class OnscreenGeom(PandaObject, NodePath):
-    def __init__(self, geom = None,
-                 pos = None,
-                 hpr = None,
-                 scale = None,
-                 color = None,
-                 parent = aspect2d):
-        """__init__(self, ...)
-
-        Make a geom node from string or a node path,
-        put it into the 2d sg and set it up with all the indicated parameters.
-
-        The parameters are as follows:
-
-          geom: the actual geometry to display or a file name.
-                This may be omitted and specified later via setGeom()
-                if you don't have it available.
-
-          pos: the x, y, z position of the geometry on the screen.
-               This maybe a 3-tuple of floats or a vector.
-               y should be zero
-
-          hpr: the h,p,r of the geometry on the screen.
-               This maybe a 3-tuple of floats or a vector.
-
-          scale: the size of the geometry.  This may either be a single
-                 float, a 3-tuple of floats, or a vector, specifying a
-                 different x, y, z scale.  y should be 1
-
-          color: the (r, g, b, a) color of the geometry.  This is
-                 normally a 4-tuple of floats or ints.
-
-          parent: the NodePath to parent the geometry to initially.
-        """
-        # We ARE a node path.  Initially, we're an empty node path.
-        NodePath.__init__(self)
-        # Assign geometry
-        if isinstance(geom, NodePath):
-            self.assign(geom.copyTo(parent))
-        elif type(geom) == type(''):
-            self.assign(loader.loadModelCopy(geom))
-            self.reparentTo(parent)
-
-        # Adjust pose
-        # Set pos
-        if (isinstance(pos, types.TupleType) or
-            isinstance(pos, types.ListType)):
-            apply(self.setPos, pos)
-        elif isinstance(pos, VBase3):
-            self.setPos(pos)
-        # Hpr
-        if (isinstance(hpr, types.TupleType) or
-            isinstance(hpr, types.ListType)):
-            apply(self.setHpr, hpr)
-        elif isinstance(hpr, VBase3):
-            self.setPos(hpr)
-        # Scale
-        if (isinstance(scale, types.TupleType) or
-            isinstance(scale, types.ListType)):
-            apply(self.setScale, scale)
-        elif isinstance(scale, VBase3):
-            self.setPos(scale)
-        elif (isinstance(scale, types.FloatType) or
-              isinstance(scale, types.IntType)):
-            self.setScale(scale)
-
-        # Set color
-        if color:
-            # Set color, if specified
-            self.setColor(color[0], color[1], color[2], color[3])
-
-    def setGeom(self, geom):
-        # Assign geometry
-        self.removeNode()
-        # Assign geometry
-        if isinstance(geom, NodePath):
-            self.assign(geom.copyTo(parent))
-        elif type(geom) == type(''):
-            self.assign(loader.loadModelCopy(geom))
-            self.reparentTo(parent)
-
-    def getGeom(self):
-        return self
-    
-    def configure(self, option=None, **kw):
-	for option, value in kw.items():
-            # Use option string to access setter function
-            try:
-                setter = eval('self.set' +
-                              string.upper(option[0]) + option[1:])
-                if (((setter == self.setPos) or
-                     (setter == self.setHpr) or
-                     (setter == self.setScale)) and
-                    (isinstance(value, types.TupleType) or
-                     isinstance(value, types.ListType))):
-                    apply(setter,value)
-                else:
-                    setter(value)
-            except AttributeError:
-                print 'OnscreenText.configure: invalid option:', option
-
-    # Allow index style references
-    def __setitem__(self, key, value):
-        apply(self.configure, (), {key: value})
-        
-    def cget(self, option):
-	# Get current configuration setting.
-        # This is for compatability with DirectGui functions
-        getter = eval('self.get' + string.upper(option[0]) + option[1:])
-        return getter()
-
-    # Allow index style refererences
-    __getitem__ = cget
-    
-
-
-class OnscreenImage(PandaObject, NodePath):
-    def __init__(self, image = None,
-                 pos = None,
-                 hpr = None,
-                 scale = None,
-                 color = None,
-                 parent = aspect2d):
-        """__init__(self, ...)
-
-        Make a image node from string or a node path,
-        put it into the 2d sg and set it up with all the indicated parameters.
-
-        The parameters are as follows:
-
-          image: the actual geometry to display or a file name.
-                This may be omitted and specified later via setImage()
-                if you don't have it available.
-
-          pos: the x, y, z position of the geometry on the screen.
-               This maybe a 3-tuple of floats or a vector.
-               y should be zero
-
-          hpr: the h,p,r of the geometry on the screen.
-               This maybe a 3-tuple of floats or a vector.
-
-          scale: the size of the geometry.  This may either be a single
-                 float, a 3-tuple of floats, or a vector, specifying a
-                 different x, y, z scale.  y should be 1
-
-          color: the (r, g, b, a) color of the geometry.  This is
-                 normally a 4-tuple of floats or ints.
-
-          parent: the NodePath to parent the geometry to initially.
-        """
-        # We ARE a node path.  Initially, we're an empty node path.
-        NodePath.__init__(self)
-        # Assign geometry
-        if isinstance(image, NodePath):
-            self.assign(image.copyTo(parent))
-        elif type(image) == type(()):
-            model = loader.loadModelOnce(image[0])
-            self.assign(model.find(image[1]))
-            self.reparentTo(parent)
-            model.removeNode()
-
-        # Adjust pose
-        # Set pos
-        if (isinstance(pos, types.TupleType) or
-            isinstance(pos, types.ListType)):
-            apply(self.setPos, pos)
-        elif isinstance(pos, VBase3):
-            self.setPos(pos)
-        # Hpr
-        if (isinstance(hpr, types.TupleType) or
-            isinstance(hpr, types.ListType)):
-            apply(self.setHpr, hpr)
-        elif isinstance(hpr, VBase3):
-            self.setPos(hpr)
-        # Scale
-        if (isinstance(scale, types.TupleType) or
-            isinstance(scale, types.ListType)):
-            apply(self.setScale, scale)
-        elif isinstance(scale, VBase3):
-            self.setPos(scale)
-        elif (isinstance(scale, types.FloatType) or
-              isinstance(scale, types.IntType)):
-            self.setScale(scale)
-
-        # Set color
-        if color:
-            # Set color, if specified
-            self.setColor(color[0], color[1], color[2], color[3])
-
-    def setImage(self, image):
-        # Assign geometry
-        self.removeNode()
-        if isinstance(image, NodePath):
-            self.assign(image.copyTo(parent))
-        elif type(image) == type(()):
-            model = loader.loadModelOnce(image[0])
-            self.assign(model.find(image[1]))
-            self.reparentTo(parent)
-            model.removeNode()
-
-    def getImage(self):
-        return self
-    
-    def configure(self, option=None, **kw):
-	for option, value in kw.items():
-            # Use option string to access setter function
-            try:
-                setter = eval('self.set' +
-                              string.upper(option[0]) + option[1:])
-                if (((setter == self.setPos) or
-                     (setter == self.setHpr) or
-                     (setter == self.setScale)) and
-                    (isinstance(value, types.TupleType) or
-                     isinstance(value, types.ListType))):
-                    apply(setter,value)
-                else:
-                    setter(value)
-            except AttributeError:
-                print 'OnscreenText.configure: invalid option:', option
-
-    # Allow index style references
-    def __setitem__(self, key, value):
-        apply(self.configure, (), {key: value})
-        
-    def cget(self, option):
-	# Get current configuration setting.
-        # This is for compatability with DirectGui functions
-        getter = eval('self.get' + string.upper(option[0]) + option[1:])
-        return getter()
-
-    # Allow index style refererences
-    __getitem__ = cget
-    
-
-
-"""
-EXAMPLE CODE
-import DirectButton
-smiley = loader.loadModel('models/directmodels/smiley')
-db = DirectButton.DirectButton(geom = smiley, text = 'hi',
-                               scale = .15, relief = 'raised')
-db['text_pos'] = (.8, -.8)
-db['text_scale'] = .5
-db['geom1_color'] = VBase4(1,0,0,1)
-db['text2_text'] = 'bye'
-
-def dummyCmd():
-    print 'Amazing!'
-
-db['command'] = dummyCmd
-
-rolloverSmiley = db.component('geom2')
-def shrink():
-    rolloverSmiley.setScale(1)
-    rolloverSmiley.lerpScale(.1,.1,.1, 1.0, blendType = 'easeInOut',
-                             task = 'shrink')
-db.bind('enter', shrink)
-"""

+ 11 - 0
direct/src/gui/DirectGui.py

@@ -0,0 +1,11 @@
+from DirectGuiGlobals import *
+
+# Initialize global variable guiTop
+import __builtin__
+__builtin__.guiTop = aspect2d.attachNewNode(PGTop('DirectGuiTop'))
+guiTop.node().setMouseWatcher(base.mouseWatcher.node())
+
+# Direct Gui Classes
+from DirectButton import *
+from DirectLabel import *
+

+ 466 - 0
direct/src/gui/DirectGuiBase.py

@@ -0,0 +1,466 @@
+from DirectGuiGlobals import *
+
+# Symbolic constants for the indexes into an optionInfo list.
+_OPT_DEFAULT         = 0
+_OPT_VALUE           = 1
+_OPT_FUNCTION        = 2
+
+class DirectGuiBase(PandaObject):
+    def __init__(self, optiondefs, dynamicGroups, **kw):
+        # Default id of all gui object, subclasses should override this
+        self.guiId = 'guiObject'
+	# Mapping from each megawidget option to a list of information
+	# about the option
+	#   - default value
+	#   - current value
+	#   - function to call when the option is initialised in the
+	#     call to initialiseoptions() in the constructor or
+	#     modified via configure().  If this is INITOPT, the
+	#     option is an initialisation option (an option that can
+	#     be set by the call to the constructor but can not be
+	#     used with configure).
+	# This mapping is not initialised here, but in the call to
+	# defineoptions() which precedes construction of this base class.
+	#
+	# self._optionInfo = {}
+
+	# Mapping from each component name to a tuple of information
+	# about the component.
+	#   - component widget instance
+	#   - configure function of widget instance
+	#   - the class of the widget (Frame, EntryField, etc)
+	#   - cget function of widget instance
+	#   - the name of the component group of this component, if any
+	self.__componentInfo = {}
+
+	# Mapping from alias names to the names of components or
+	# sub-components.
+	self.__componentAliases = {}
+
+	# Contains information about the keywords provided to the
+	# constructor.  It is a mapping from the keyword to a tuple
+	# containing:
+	#    - value of keyword
+	#    - a boolean indicating if the keyword has been used.
+	# A keyword is used if, during the construction of a megawidget,
+	#    - it is defined in a call to defineoptions() or addoptions(), or
+	#    - it references, by name, a component of the megawidget, or
+	#    - it references, by group, at least one component
+	# At the end of megawidget construction, a call is made to
+	# initialiseoptions() which reports an error if there are
+	# unused options given to the constructor.
+	#
+	# self._constructorKeywords = {}
+
+        # List of dynamic component groups.  If a group is included in
+        # this list, then it not an error if a keyword argument for
+        # the group is given to the constructor or to configure(), but
+        # no components with this group have been created.
+        # self._dynamicGroups = ()
+
+        self.defineoptions(kw, optiondefs, dynamicGroups)
+        
+    def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
+	# Create options, providing the default value and the method
+	# to call when the value is changed.  If any option created by
+	# base classes has the same name as one in <optionDefs>, the
+	# base class's value and function will be overriden.
+        
+	# This should be called before the constructor of the base
+	# class, so that default values defined in the derived class
+	# override those in the base class.
+	if not hasattr(self, '_constructorKeywords'):
+	    tmp = {}
+	    for option, value in keywords.items():
+		tmp[option] = [value, 0]
+            self._constructorKeywords = tmp
+	    self._optionInfo = {}
+        # Initialize dictionary of dynamic groups
+        if not hasattr(self, '_dynamicGroups'):
+            self._dynamicGroups = ()
+        self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
+        # Reconcile command line and default options
+        self.addoptions(optionDefs)
+        
+    def addoptions(self, optionDefs):
+	# Add additional options, providing the default value and the
+	# method to call when the value is changed.  See
+	# "defineoptions" for more details
+        
+	# optimisations:
+	optionInfo = self._optionInfo
+	optionInfo_has_key = optionInfo.has_key
+	keywords = self._constructorKeywords
+	keywords_has_key = keywords.has_key
+	FUNCTION = _OPT_FUNCTION
+        
+	for name, default, function in optionDefs:
+	    if '_' not in name:
+                # The option will already exist if it has been defined
+                # in a derived class.  In this case, do not override the
+                # default value of the option or the callback function
+                # if it is not None.
+                if not optionInfo_has_key(name):
+                    if keywords_has_key(name):
+                        # Overridden by keyword, use keyword value
+                        value = keywords[name][0]
+                        optionInfo[name] = [default, value, function]
+                        del keywords[name]
+                    else:
+                        # Use optionDefs value
+                        optionInfo[name] = [default, default, function]
+                elif optionInfo[name][FUNCTION] is None:
+                    # Override function
+                    optionInfo[name][FUNCTION] = function
+	    else:
+		# This option is of the form "component_option".  If this is
+		# not already defined in self._constructorKeywords add it.
+		# This allows a derived class to override the default value
+		# of an option of a component of a base class.
+		if not keywords_has_key(name):
+		    keywords[name] = [default, 0]
+                
+    def initialiseoptions(self, myClass):
+	if self.__class__ is myClass:
+	    unusedOptions = []
+	    keywords = self._constructorKeywords
+	    for name in keywords.keys():
+                print name
+		used = keywords[name][1]
+		if not used:
+                    # This keyword argument has not been used.  If it
+                    # does not refer to a dynamic group, mark it as
+                    # unused.
+                    index = string.find(name, '_')
+                    if index < 0 or name[:index] not in self._dynamicGroups:
+                        unusedOptions.append(name)
+	    self._constructorKeywords = {}
+	    if len(unusedOptions) > 0:
+		if len(unusedOptions) == 1:
+		    text = 'Unknown option "'
+		else:
+		    text = 'Unknown options "'
+		raise KeyError, text + string.join(unusedOptions, ', ') + \
+			'" 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):
+	return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
+    
+    def options(self):
+	options = []
+	if hasattr(self, '_optionInfo'):
+	    for option, info in self._optionInfo.items():
+		isinit = info[_OPT_FUNCTION] is INITOPT
+		default = info[_OPT_DEFAULT]
+		options.append((option, default, isinit))
+	    options.sort()
+	return options
+    
+    def configure(self, option=None, **kw):
+	# Query or configure the megawidget options.
+	#
+	# If not empty, *kw* is a dictionary giving new
+	# values for some of the options of this gui item
+	# For options defined for this widget, set
+	# the value of the option to the new value and call the
+	# configuration callback function, if any.
+	#
+	# If *option* is None, return all gui item configuration
+	# options and settings.  Options are returned as standard 3
+	# element tuples
+	#
+	# If *option* is a string, return the 3 element tuple for the
+	# given configuration option.
+        
+	# First, deal with the option queries.
+	if len(kw) == 0:
+	    # This configure call is querying the values of one or all options.
+	    # Return 3-tuples:
+	    #     (optionName, default, value)
+	    if option is None:
+		rtn = {}
+		for option, config in self._optionInfo.items():
+		    rtn[option] = (option,
+                                   config[_OPT_DEFAULT],
+                                   config[_OPT_VALUE])
+		return rtn
+	    else:
+		config = self._optionInfo[option]
+		return (option, config[_OPT_DEFAULT], config[_OPT_VALUE])
+            
+	# optimizations:
+	optionInfo = self._optionInfo
+	optionInfo_has_key = optionInfo.has_key
+	componentInfo = self.__componentInfo
+	componentInfo_has_key = componentInfo.has_key
+	componentAliases = self.__componentAliases
+	componentAliases_has_key = componentAliases.has_key
+	VALUE = _OPT_VALUE
+	FUNCTION = _OPT_FUNCTION
+        
+	# This will contain a list of options in *kw* which
+	# are known to this gui item.
+	directOptions = []
+        
+	# This will contain information about the options in
+	# *kw* of the form <component>_<option>, where
+	# <component> is a component of this megawidget.  It is a
+	# dictionary whose keys are the configure method of each
+	# component and whose values are a dictionary of options and
+	# values for the component.
+	indirectOptions = {}
+	indirectOptions_has_key = indirectOptions.has_key
+
+	for option, value in kw.items():
+	    if optionInfo_has_key(option):
+		# This is one of the options of this gui item. 
+		# Check it is an initialisation option.
+		if optionInfo[option][FUNCTION] is INITOPT:
+		    raise KeyError, \
+			    'Cannot configure initialisation option "' \
+			    + option + '" for ' + self.__class__.__name__
+		optionInfo[option][VALUE] = value
+		directOptions.append(option)
+            else:
+		index = string.find(option, '_')
+		if index >= 0:
+		    # This option may be of the form <component>_<option>.
+		    component = option[:index]
+		    componentOption = option[(index + 1):]
+
+		    # Expand component alias
+		    if componentAliases_has_key(component):
+			component, subComponent = componentAliases[component]
+			if subComponent is not None:
+			    componentOption = subComponent + '_' \
+				    + componentOption
+
+			# Expand option string to write on error
+			option = component + '_' + componentOption
+
+                    # Does this component exist
+		    if componentInfo_has_key(component):
+			# Get the configure func for the named component
+			componentConfigFuncs = [componentInfo[component][1]]
+		    else:
+			# Check if this is a group name and configure all
+			# components in the group.
+			componentConfigFuncs = []
+			for info in componentInfo.values():
+			    if info[4] == component:
+			        componentConfigFuncs.append(info[1])
+
+                        if len(componentConfigFuncs) == 0 and \
+                                component not in self._dynamicGroups:
+			    raise KeyError, 'Unknown option "' + option + \
+				    '" for ' + self.__class__.__name__
+
+		    # Add the configure method(s) (may be more than
+		    # one if this is configuring a component group)
+		    # and option/value to dictionary.
+		    for componentConfigFunc in componentConfigFuncs:
+			if not indirectOptions_has_key(componentConfigFunc):
+			    indirectOptions[componentConfigFunc] = {}
+			indirectOptions[componentConfigFunc][componentOption] \
+				= value
+		else:
+		    raise KeyError, 'Unknown option "' + option + \
+			    '" for ' + self.__class__.__name__
+
+	# Call the configure methods for any components.
+	map(apply, indirectOptions.keys(),
+		((),) * len(indirectOptions), indirectOptions.values())
+            
+	# Call the configuration callback function for each option.
+	for option in directOptions:
+	    info = optionInfo[option]
+	    func = info[_OPT_FUNCTION]
+	    if func is not None:
+	      func()
+              
+    # Allow index style references
+    def __setitem__(self, key, value):
+        apply(self.configure, (), {key: value})
+        
+    def cget(self, option):
+	# Get current configuration setting.
+        
+	# Return the value of an option, for example myWidget['font']. 
+	if self._optionInfo.has_key(option):
+	    return self._optionInfo[option][_OPT_VALUE]
+	else:
+	    index = string.find(option, '_')
+	    if index >= 0:
+		component = option[:index]
+		componentOption = option[(index + 1):]
+
+		# Expand component alias
+		if self.__componentAliases.has_key(component):
+		    component, subComponent = self.__componentAliases[
+                        component]
+		    if subComponent is not None:
+			componentOption = subComponent + '_' + componentOption
+
+		    # Expand option string to write on error
+		    option = component + '_' + componentOption
+
+		if self.__componentInfo.has_key(component):
+		    # Call cget on the component.
+		    componentCget = self.__componentInfo[component][3]
+		    return componentCget(componentOption)
+		else:
+		    # If this is a group name, call cget for one of
+		    # the components in the group.
+		    for info in self.__componentInfo.values():
+			if info[4] == component:
+			    componentCget = info[3]
+			    return componentCget(componentOption)
+
+        # Option not found
+	raise KeyError, 'Unknown option "' + option + \
+		'" for ' + self.__class__.__name__
+    
+    # Allow index style refererences
+    __getitem__ = cget
+    
+    def createcomponent(self, componentName, componentAliases, componentGroup,
+                        widgetClass, *widgetArgs, **kw):
+	"""Create a component (during construction or later)."""
+        # Check for invalid component name
+	if '_' in componentName:
+	    raise ValueError, \
+                    'Component name "%s" must not contain "_"' % componentName
+        # Get construction keywords
+	if hasattr(self, '_constructorKeywords'):
+	    keywords = self._constructorKeywords
+	else:
+	    keywords = {}
+
+	for alias, component in componentAliases:
+	    # Create aliases to the component and its sub-components.
+	    index = string.find(component, '_')
+	    if index < 0:
+		self.__componentAliases[alias] = (component, None)
+	    else:
+		mainComponent = component[:index]
+		subComponent = component[(index + 1):]
+		self.__componentAliases[alias] = (mainComponent, subComponent)
+
+	    # Remove aliases from the constructor keyword arguments by
+	    # replacing any keyword arguments that begin with *alias*
+	    # with corresponding keys beginning with *component*.
+
+	    alias = alias + '_'
+	    aliasLen = len(alias)
+	    for option in keywords.keys():
+		if len(option) > aliasLen and option[:aliasLen] == alias:
+		    newkey = component + '_' + option[aliasLen:]
+		    keywords[newkey] = keywords[option]
+		    del keywords[option]
+
+        # Find any keyword arguments for this component
+	componentPrefix = componentName + '_'
+	nameLen = len(componentPrefix)
+	for option in keywords.keys():
+	    if len(option) > nameLen and option[:nameLen] == componentPrefix:
+		# The keyword argument refers to this component, so add
+		# this to the options to use when constructing the widget.
+		kw[option[nameLen:]] = keywords[option][0]
+                # And delete it from main construction keywords
+		del keywords[option]
+	    else:
+		# Check if this keyword argument refers to the group
+		# of this component.  If so, add this to the options
+		# to use when constructing the widget.  Mark the
+		# keyword argument as being used, but do not remove it
+		# since it may be required when creating another
+		# component.
+		index = string.find(option, '_')
+		if index >= 0 and componentGroup == option[:index]:
+		    rest = option[(index + 1):]
+		    kw[rest] = keywords[option][0]
+		    keywords[option][1] = 1
+
+        # Return None if no widget class is specified
+	if widgetClass is None:
+	    return None
+        # Get arguments for widget constructor
+        if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
+            # Arguments to the constructor can be specified as either
+            # multiple trailing arguments to createcomponent() or as a
+            # single tuple argument.
+            widgetArgs = widgetArgs[0]
+        # Create the widget
+	widget = apply(widgetClass, widgetArgs, kw)
+	componentClass = widget.__class__.__name__
+	self.__componentInfo[componentName] = (widget, widget.configure,
+		componentClass, widget.cget, componentGroup)
+	return widget
+
+    def component(self, name):
+	# Return a component widget of the megawidget given the
+	# component's name
+	# This allows the user of a megawidget to access and configure
+	# widget components directly.
+
+	# Find the main component and any subcomponents
+	index = string.find(name, '_')
+	if index < 0:
+	    component = name
+	    remainingComponents = None
+	else:
+	    component = name[:index]
+	    remainingComponents = name[(index + 1):]
+
+	# Expand component alias
+	if self.__componentAliases.has_key(component):
+	    component, subComponent = self.__componentAliases[component]
+	    if subComponent is not None:
+		if remainingComponents is None:
+		    remainingComponents = subComponent
+		else:
+		    remainingComponents = subComponent + '_' \
+			    + remainingComponents
+
+	widget = self.__componentInfo[component][0]
+	if remainingComponents is None:
+	    return widget
+	else:
+	    return widget.component(remainingComponents)
+
+    def components(self):
+	# Return a list of all components.
+	names = self.__componentInfo.keys()
+	names.sort()
+	return names
+
+    def hascomponent(self, component):
+        return component in self.__componentInfo.keys()
+
+    def destroycomponent(self, name):
+	# Remove a megawidget component.
+	# This command is for use by megawidget designers to destroy a
+	# megawidget component.
+	self.__componentInfo[name][0].destroy()
+	del self.__componentInfo[name]
+
+    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)
+        
+    def unbind(self, sequence):
+        self.ignore(sequence + '-' + self.guiId)
+

+ 50 - 0
direct/src/gui/DirectGuiGlobals.py

@@ -0,0 +1,50 @@
+from PandaObject import *
+from PGTop import *
+from PGButton import *
+from PGItem import *
+from PGFrameStyle import *
+import OnscreenText
+import OnscreenGeom
+import OnscreenImage
+import types
+
+# USEFUL GUI CONSTANTS
+NORMAL = 'normal'
+DISABLED = 'disabled'
+
+FLAT = PGFrameStyle.TFlat
+RAISED = PGFrameStyle.TBevelOut
+SUNKEN = PGFrameStyle.TBevelIn
+
+# Use to extract common prefix of mouse click, press, and release events
+def getGenericMouseEvent(event, mouse):
+    if mouse == 1:
+        mb = MouseButton.one()
+    elif mouse == 2:
+        mb = MouseButton.two()
+    elif mouse == 3:
+        mb = MouseButton.three()
+    if event == 'click':
+        eventFunc = PGButton().getClickEvent
+    elif event == 'press':
+        eventFunc = PGButton().getPressEvent
+    elif event == 'release':
+        eventFunc = PGButton().getReleaseEvent
+    eventString = eventFunc(mb)
+    return eventString[:eventString.rfind('-')]
+
+ENTER = 'enter'
+EXIT = 'exit'
+B1CLICK = getGenericMouseEvent('click', 1)
+B2CLICK = getGenericMouseEvent('click', 2)
+B3CLICK = getGenericMouseEvent('click', 3)
+B1PRESS = getGenericMouseEvent('press', 1)
+B2PRESS = getGenericMouseEvent('press', 2)
+B3PRESS = getGenericMouseEvent('press', 3)
+B1RELEASE = getGenericMouseEvent('release', 1)
+B2RELEASE = getGenericMouseEvent('release', 2)
+B3RELEASE = getGenericMouseEvent('release', 3)
+
+# Constant used to indicate that an option can only be set by a call
+# to the constructor.
+INITOPT = ['initopt']

+ 62 - 0
direct/src/gui/DirectGuiTest.py

@@ -0,0 +1,62 @@
+from DirectGui import *
+
+# EXAMPLE CODE
+# Load a model
+smiley = loader.loadModel('models/directmodels/smiley')
+
+# Create a button with a background image, smiley as a geometry element,
+# and a text overlay, set a different text for the four button states:
+# (normal, press, rollover, and disabled), set scale = .15, and relief raised
+db = DirectButton(image = 'phase_4/maps/middayskyB.jpg',
+                  geom = smiley,
+                  text = ('Hi!', 'Ouch!', 'Bye!', 'ZZZZ!'),
+                  scale = .15, relief = 'raised')
+
+# 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)
+
+# Specify the button's command
+# Note, command must expect one argument
+def dummyCmd(event):
+    print 'POW!!!!'
+db['command'] = dummyCmd
+
+# Get a handle on the geometry for the rollover state
+rolloverSmiley = db.component('geom2')
+
+# Define some commands to bind to enter, exit and click events
+def shrink(event):
+    db['text2_text'] = 'Hi!'
+    taskMgr.removeTasksNamed('shrink')
+    taskMgr.removeTasksNamed('expand')
+    rolloverSmiley.setScale(db.component('geom0').getScale()[0])
+    rolloverSmiley.lerpScale(.1,.1,.1, 1.0, blendType = 'easeInOut',
+                             task = 'shrink')
+
+def expand(event):
+    db['text0_text'] = 'Bye!'
+    taskMgr.removeTasksNamed('shrink')
+    taskMgr.removeTasksNamed('expand')
+    db.component('geom0').setScale(db.component('geom2').getScale()[0])
+    db.component('geom0').lerpScale(1,1,1, 1, blendType = 'easeInOut',
+                             task = 'expand')
+    db.component('geom2').clearColor()
+
+def ouch(event):
+    taskMgr.removeTasksNamed('shrink')
+    taskMgr.removeTasksNamed('expand')
+    db.component('geom0').setScale(db.component('geom2').getScale()[0])
+    db.component('geom1').setScale(db.component('geom2').getScale()[0])
+    db['text2_text'] = 'Ouch!'
+    db['geom2_color'] = Vec4(1,0,0,1)
+
+# Bind the commands
+db.bind(ENTER, shrink)
+db.bind(EXIT, expand)
+db.bind(B1PRESS, ouch)
+
+# Pop up placer when button 2 is pressed
+db.bind(B2PRESS, lambda x, s = db: s.place())

+ 52 - 0
direct/src/gui/DirectLabel.py

@@ -0,0 +1,52 @@
+from DirectGuiBase import *
+
+class DirectLabel(DirectGuiBase, PGItem):
+    def __init__(self, parent = None, **kw):
+        # Pass in a background texture, and/or a geometry object,
+        # and/or a text string to be used as the visible
+        # representation of the label
+        optiondefs = (
+            ('image',         None,       self.setImage),
+            ('geom',          None,       self.setGeom),
+            ('text',          None,       self.setText),
+            ('pos',           (0,0,0),    self.setPos),
+            ('scale',         (1,1,1),    self.setScale),
+            ('bounds',        None,       self.setBounds),
+            ('imagePos',      (0,0,0),    self.setImagePos),
+            ('imageScale',    (1,1,1),    self.setImagePos),
+            ('geomPos',       (0,0,0),    self.setGeomPos),
+            ('geomScale',     (1,1,1),    self.setGeomPos),
+            ('textPos',       (0,0,0),    self.setTextPos),
+            ('textScale',     (1,1,1),    self.setTextPos),
+            )
+        apply(DirectGuiBase.__init__, (self, optiondefs, ()), kw)            
+        self.initialiseoptions(DirectLabel)
+
+    def setImage(self):
+        pass
+    def setGeom(self):
+        pass
+    def setText(self):
+        pass
+    def setPos(self):
+        pass
+    def setScale(self):
+        pass
+    def setBounds(self):
+        pass
+    def setImagePos(self):
+        pass
+    def setImagePos(self):
+        pass
+    def setGeomPos(self):
+        pass
+    def setGeomPos(self):
+        pass
+    def setTextPos(self):
+        pass
+    def setTextPos(self):
+        pass
+    def setState(self):
+        pass
+
+

+ 119 - 0
direct/src/gui/OnscreenGeom.py

@@ -0,0 +1,119 @@
+"""OnscreenGeom module: contains the OnscreenGeom class"""
+
+from PandaObject import *
+import types
+
+class OnscreenGeom(PandaObject, NodePath):
+    def __init__(self, geom = None,
+                 pos = None,
+                 hpr = None,
+                 scale = None,
+                 color = None,
+                 parent = aspect2d):
+        """__init__(self, ...)
+
+        Make a geom node from string or a node path,
+        put it into the 2d sg and set it up with all the indicated parameters.
+
+        The parameters are as follows:
+
+          geom: the actual geometry to display or a file name.
+                This may be omitted and specified later via setGeom()
+                if you don't have it available.
+
+          pos: the x, y, z position of the geometry on the screen.
+               This maybe a 3-tuple of floats or a vector.
+               y should be zero
+
+          hpr: the h,p,r of the geometry on the screen.
+               This maybe a 3-tuple of floats or a vector.
+
+          scale: the size of the geometry.  This may either be a single
+                 float, a 3-tuple of floats, or a vector, specifying a
+                 different x, y, z scale.  y should be 1
+
+          color: the (r, g, b, a) color of the geometry.  This is
+                 normally a 4-tuple of floats or ints.
+
+          parent: the NodePath to parent the geometry to initially.
+        """
+        # We ARE a node path.  Initially, we're an empty node path.
+        NodePath.__init__(self)
+        # Assign geometry
+        if isinstance(geom, NodePath):
+            self.assign(geom.copyTo(parent))
+        elif type(geom) == type(''):
+            self.assign(loader.loadModelCopy(geom))
+            self.reparentTo(parent)
+
+        # Adjust pose
+        # Set pos
+        if (isinstance(pos, types.TupleType) or
+            isinstance(pos, types.ListType)):
+            apply(self.setPos, pos)
+        elif isinstance(pos, VBase3):
+            self.setPos(pos)
+        # Hpr
+        if (isinstance(hpr, types.TupleType) or
+            isinstance(hpr, types.ListType)):
+            apply(self.setHpr, hpr)
+        elif isinstance(hpr, VBase3):
+            self.setPos(hpr)
+        # Scale
+        if (isinstance(scale, types.TupleType) or
+            isinstance(scale, types.ListType)):
+            apply(self.setScale, scale)
+        elif isinstance(scale, VBase3):
+            self.setPos(scale)
+        elif (isinstance(scale, types.FloatType) or
+              isinstance(scale, types.IntType)):
+            self.setScale(scale)
+
+        # Set color
+        if color:
+            # Set color, if specified
+            self.setColor(color[0], color[1], color[2], color[3])
+
+    def setGeom(self, geom):
+        # Assign geometry
+        self.removeNode()
+        # Assign geometry
+        if isinstance(geom, NodePath):
+            self.assign(geom.copyTo(parent))
+        elif type(geom) == type(''):
+            self.assign(loader.loadModelCopy(geom))
+            self.reparentTo(parent)
+
+    def getGeom(self):
+        return self
+    
+    def configure(self, option=None, **kw):
+	for option, value in kw.items():
+            # Use option string to access setter function
+            try:
+                setter = eval('self.set' +
+                              string.upper(option[0]) + option[1:])
+                if (((setter == self.setPos) or
+                     (setter == self.setHpr) or
+                     (setter == self.setScale)) and
+                    (isinstance(value, types.TupleType) or
+                     isinstance(value, types.ListType))):
+                    apply(setter,value)
+                else:
+                    setter(value)
+            except AttributeError:
+                print 'OnscreenText.configure: invalid option:', option
+
+    # Allow index style references
+    def __setitem__(self, key, value):
+        apply(self.configure, (), {key: value})
+        
+    def cget(self, option):
+	# Get current configuration setting.
+        # This is for compatability with DirectGui functions
+        getter = eval('self.get' + string.upper(option[0]) + option[1:])
+        return getter()
+
+    # Allow index style refererences
+    __getitem__ = cget
+    

+ 140 - 0
direct/src/gui/OnscreenImage.py

@@ -0,0 +1,140 @@
+"""OnscreenImage module: contains the OnscreenImage class"""
+
+from PandaObject import *
+import types
+
+class OnscreenImage(PandaObject, NodePath):
+    def __init__(self, image = None,
+                 pos = None,
+                 hpr = None,
+                 scale = None,
+                 color = None,
+                 parent = aspect2d):
+        """__init__(self, ...)
+
+        Make a image node from string or a node path,
+        put it into the 2d sg and set it up with all the indicated parameters.
+
+        The parameters are as follows:
+
+          image: the actual geometry to display or a file name.
+                This may be omitted and specified later via setImage()
+                if you don't have it available.
+
+          pos: the x, y, z position of the geometry on the screen.
+               This maybe a 3-tuple of floats or a vector.
+               y should be zero
+
+          hpr: the h,p,r of the geometry on the screen.
+               This maybe a 3-tuple of floats or a vector.
+
+          scale: the size of the geometry.  This may either be a single
+                 float, a 3-tuple of floats, or a vector, specifying a
+                 different x, y, z scale.  y should be 1
+
+          color: the (r, g, b, a) color of the geometry.  This is
+                 normally a 4-tuple of floats or ints.
+
+          parent: the NodePath to parent the geometry to initially.
+        """
+        # We ARE a node path.  Initially, we're an empty node path.
+        NodePath.__init__(self)
+        # Assign geometry
+        if isinstance(image, NodePath):
+            self.assign(image.copyTo(parent))
+        elif type(image) == type(''):
+            # Assume its a file name and create a texture card
+            tex = loader.loadTexture(image)
+            cm = CardMaker()
+            cm.setFrame(-1, 1, -1, 1)
+            self.assign(parent.attachNewNode(cm.generate()))
+            self.setTexture(tex)
+        elif type(image) == type(()):
+            # Assume its a file+node name, extract texture from node
+            model = loader.loadModelOnce(image[0])
+            if model:
+                node = model.find(image[1])
+                if node:
+                    self.assign(node)
+                    self.reparentTo(parent)
+                else:
+                    print 'OnscreenImage: node %s not found' % image[1]
+                    return
+                model.removeNode()
+            else:
+                print 'OnscreenImage: model %s not found' % image[0]
+                return
+
+        # Adjust pose
+        # Set pos
+        if (isinstance(pos, types.TupleType) or
+            isinstance(pos, types.ListType)):
+            apply(self.setPos, pos)
+        elif isinstance(pos, VBase3):
+            self.setPos(pos)
+        # Hpr
+        if (isinstance(hpr, types.TupleType) or
+            isinstance(hpr, types.ListType)):
+            apply(self.setHpr, hpr)
+        elif isinstance(hpr, VBase3):
+            self.setPos(hpr)
+        # Scale
+        if (isinstance(scale, types.TupleType) or
+            isinstance(scale, types.ListType)):
+            apply(self.setScale, scale)
+        elif isinstance(scale, VBase3):
+            self.setPos(scale)
+        elif (isinstance(scale, types.FloatType) or
+              isinstance(scale, types.IntType)):
+            self.setScale(scale)
+
+        # Set color
+        if color:
+            # Set color, if specified
+            self.setColor(color[0], color[1], color[2], color[3])
+
+    def setImage(self, image):
+        # Assign geometry
+        self.removeNode()
+        if isinstance(image, NodePath):
+            self.assign(image.copyTo(parent))
+        elif type(image) == type(()):
+            model = loader.loadModelOnce(image[0])
+            self.assign(model.find(image[1]))
+            self.reparentTo(parent)
+            model.removeNode()
+
+    def getImage(self):
+        return self
+    
+    def configure(self, option=None, **kw):
+	for option, value in kw.items():
+            # Use option string to access setter function
+            try:
+                setter = eval('self.set' +
+                              string.upper(option[0]) + option[1:])
+                if (((setter == self.setPos) or
+                     (setter == self.setHpr) or
+                     (setter == self.setScale)) and
+                    (isinstance(value, types.TupleType) or
+                     isinstance(value, types.ListType))):
+                    apply(setter,value)
+                else:
+                    setter(value)
+            except AttributeError:
+                print 'OnscreenText.configure: invalid option:', option
+
+    # Allow index style references
+    def __setitem__(self, key, value):
+        apply(self.configure, (), {key: value})
+        
+    def cget(self, option):
+	# Get current configuration setting.
+        # This is for compatability with DirectGui functions
+        getter = eval('self.get' + string.upper(option[0]) + option[1:])
+        return getter()
+
+    # Allow index style refererences
+    __getitem__ = cget
+    
+