"""Undocumented Module""" __all__ = ['DirectGuiBase', 'DirectGuiWidget'] from pandac.PandaModules import * import DirectGuiGlobals as DGG from OnscreenText import * from OnscreenGeom import * from OnscreenImage import * from direct.directtools.DirectUtil import ROUND_TO from direct.showbase import DirectObject from direct.task import Task from direct.showbase import ShowBase from direct.showbase.PythonUtil import recordCreationStackStr from pandac.PandaModules import PStatCollector import types guiObjectCollector = PStatCollector("Client::GuiObjects") """ Base class for all Direct Gui items. Handles composite widgets and command line argument parsing. """ """ 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.defineoptions() 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(DirectObject.DirectObject): def __init__(self): # Default id of all gui object, subclasses should override this self.guiId = 'guiObject' # List of all post initialization functions self.postInitialiseFuncList = [] # To avoid doing things redundantly during initialisation self.fInit = 1 # 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 = () def defineoptions(self, keywords, optionDefs, dynamicGroups = ()): """ defineoptions(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 , the # 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 # 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, keywords) def addoptions(self, optionDefs, optionkeywords): """ addoptions(optionDefs) - add option def to option info """ # 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.__contains__ keywords = self._constructorKeywords keywords_has_key = keywords.__contains__ FUNCTION = DGG._OPT_FUNCTION for name, default, function in optionDefs: if '_' not in name: default = optionkeywords.get(name, default) # 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] # Delete it from self._constructorKeywords del keywords[name] else: # Use optionDefs value optionInfo[name] = [default, default, function] elif optionInfo[name][FUNCTION] is None: # Only override function if not defined by derived class 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): """ 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: # Call the configuration callback function for every option. FUNCTION = DGG._OPT_FUNCTION self.fInit = 1 for info in self._optionInfo.values(): func = info[FUNCTION] if func is not None and func is not DGG.INITOPT: func() self.fInit = 0 # Now check if anything is left over unusedOptions = [] keywords = self._constructorKeywords for name in keywords.keys(): 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 = name.find('_') 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 + ', '.join(unusedOptions) + \ '" for ' + myClass.__name__ # Can now call post init func self.postInitialiseFunc() def postInitialiseFunc(self): for func in self.postInitialiseFuncList: func() def isinitoption(self, option): """ Is this opition one that can only be specified at construction? """ return self._optionInfo[option][DGG._OPT_FUNCTION] is DGG.INITOPT def options(self): """ Print out a list of available widget options. Does not include subcomponent options. """ options = [] if hasattr(self, '_optionInfo'): for option, info in self._optionInfo.items(): isinit = info[DGG._OPT_FUNCTION] is DGG.INITOPT default = info[DGG._OPT_DEFAULT] options.append((option, default, isinit)) options.sort() return options def configure(self, option=None, **kw): """ configure(option = None) 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[DGG._OPT_DEFAULT], config[DGG._OPT_VALUE]) return rtn else: config = self._optionInfo[option] return (option, config[DGG._OPT_DEFAULT], config[DGG._OPT_VALUE]) # optimizations: optionInfo = self._optionInfo optionInfo_has_key = optionInfo.__contains__ componentInfo = self.__componentInfo componentInfo_has_key = componentInfo.__contains__ componentAliases = self.__componentAliases componentAliases_has_key = componentAliases.__contains__ VALUE = DGG._OPT_VALUE FUNCTION = DGG._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 _