DirectGuiBase.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. from DirectGuiGlobals import *
  2. from OnscreenText import *
  3. from OnscreenGeom import *
  4. from OnscreenImage import *
  5. from direct.directtools.DirectUtil import ROUND_TO
  6. from direct.showbase import DirectObject
  7. from direct.task import Task
  8. import string
  9. from direct.showbase import ShowBase
  10. """
  11. Base class for all Direct Gui items. Handles composite widgets and
  12. command line argument parsing.
  13. """
  14. # Symbolic constants for the indexes into an optionInfo list.
  15. _OPT_DEFAULT = 0
  16. _OPT_VALUE = 1
  17. _OPT_FUNCTION = 2
  18. """
  19. Code Overview:
  20. 1 Each widget defines a set of options (optiondefs) as a list of tuples
  21. of the form ('name', defaultValue, handler).
  22. 'name' is the name of the option (used during construction of configure)
  23. handler can be: None, method, or INITOPT. If a method is specified,
  24. it will be called during widget construction (via initialiseoptions),
  25. if the Handler is specified as an INITOPT, this is an option that can
  26. only be set during widget construction.
  27. 2) DirectGuiBase.defineoptions is called. defineoption creates:
  28. self._constructorKeywords = { keyword: [value, useFlag] }
  29. a dictionary of the keyword options specified as part of the constructor
  30. keywords can be of the form 'component_option', where component is
  31. the name of a widget's component, a component group or a component alias
  32. self._dynamicGroups, a list of group names for which it is permissible
  33. to specify options before components of that group are created.
  34. If a widget is a derived class the order of execution would be:
  35. foo.optiondefs = {}
  36. foo.defineoptions()
  37. fooParent()
  38. fooParent.optiondefs = {}
  39. fooParent.defineoptions()
  40. 3) addoptions is called. This combines options specified as keywords to
  41. the widget constructor (stored in self._constuctorKeywords)
  42. with the default options (stored in optiondefs). Results are stored in
  43. self._optionInfo = { keyword: [default, current, handler] }
  44. If a keyword is of the form 'component_option' it is left in the
  45. self._constructorKeywords dictionary (for use by component constructors),
  46. otherwise it is 'used', and deleted from self._constructorKeywords.
  47. Notes: - constructor keywords override the defaults.
  48. - derived class default values override parent class defaults
  49. - derived class handler functions override parent class functions
  50. 4) Superclass initialization methods are called (resulting in nested calls
  51. to define options (see 2 above)
  52. 5) Widget components are created via calls to self.createcomponent.
  53. User can specify aliases and groups for each component created.
  54. Aliases are alternate names for components, e.g. a widget may have a
  55. component with a name 'entryField', which itself may have a component
  56. named 'entry', you could add an alias 'entry' for the 'entryField_entry'
  57. These are stored in self.__componentAliases. If an alias is found,
  58. all keyword entries which use that alias are expanded to their full
  59. form (to avoid conversion later)
  60. Groups allow option specifications that apply to all members of the group.
  61. If a widget has components: 'text1', 'text2', and 'text3' which all belong
  62. to the 'text' group, they can be all configured with keywords of the form:
  63. 'text_keyword' (e.g. text_font = 'comic.rgb'). A component's group
  64. is stored as the fourth element of its entry in self.__componentInfo
  65. Note: the widget constructors have access to all remaining keywords in
  66. _constructorKeywords (those not transferred to _optionInfo by
  67. define/addoptions). If a component defines an alias that applies to
  68. one of the keywords, that keyword is replaced with a new keyword with
  69. the alias expanded.
  70. If a keyword (or substituted alias keyword) is used during creation of the
  71. component, it is deleted from self._constructorKeywords. If a group
  72. keyword applies to the component, that keyword is marked as used, but is
  73. not deleted from self._constructorKeywords, in case it applies to another
  74. component. If any constructor keywords remain at the end of component
  75. construction (and initialisation), an error is raised.
  76. 5) initialiseoptions is called. This method calls any option handlers to
  77. respond to any keyword/default values, then checks to see if any keywords
  78. are left unused. If so, an error is raised.
  79. """
  80. class DirectGuiBase(DirectObject.DirectObject):
  81. def __init__(self):
  82. # Default id of all gui object, subclasses should override this
  83. self.guiId = 'guiObject'
  84. # List of all post initialization functions
  85. self.postInitialiseFuncList = []
  86. # To avoid doing things redundantly during initialisation
  87. self.fInit = 1
  88. # Mapping from each megawidget option to a list of information
  89. # about the option
  90. # - default value
  91. # - current value
  92. # - function to call when the option is initialised in the
  93. # call to initialiseoptions() in the constructor or
  94. # modified via configure(). If this is INITOPT, the
  95. # option is an initialisation option (an option that can
  96. # be set by the call to the constructor but can not be
  97. # used with configure).
  98. # This mapping is not initialised here, but in the call to
  99. # defineoptions() which precedes construction of this base class.
  100. #
  101. # self._optionInfo = {}
  102. # Mapping from each component name to a tuple of information
  103. # about the component.
  104. # - component widget instance
  105. # - configure function of widget instance
  106. # - the class of the widget (Frame, EntryField, etc)
  107. # - cget function of widget instance
  108. # - the name of the component group of this component, if any
  109. self.__componentInfo = {}
  110. # Mapping from alias names to the names of components or
  111. # sub-components.
  112. self.__componentAliases = {}
  113. # Contains information about the keywords provided to the
  114. # constructor. It is a mapping from the keyword to a tuple
  115. # containing:
  116. # - value of keyword
  117. # - a boolean indicating if the keyword has been used.
  118. # A keyword is used if, during the construction of a megawidget,
  119. # - it is defined in a call to defineoptions() or addoptions(), or
  120. # - it references, by name, a component of the megawidget, or
  121. # - it references, by group, at least one component
  122. # At the end of megawidget construction, a call is made to
  123. # initialiseoptions() which reports an error if there are
  124. # unused options given to the constructor.
  125. #
  126. # self._constructorKeywords = {}
  127. # List of dynamic component groups. If a group is included in
  128. # this list, then it not an error if a keyword argument for
  129. # the group is given to the constructor or to configure(), but
  130. # no components with this group have been created.
  131. # self._dynamicGroups = ()
  132. def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
  133. """ defineoptions(keywords, optionDefs, dynamicGroups = {}) """
  134. # Create options, providing the default value and the method
  135. # to call when the value is changed. If any option created by
  136. # base classes has the same name as one in <optionDefs>, the
  137. # base class's value and function will be overriden.
  138. # keywords is a dictionary of keyword/value pairs from the constructor
  139. # optionDefs is a dictionary of default options for the widget
  140. # dynamicGroups is a tuple of component groups for which you can
  141. # specify options even though no components of this group have
  142. # been created
  143. # This should be called before the constructor of the base
  144. # class, so that default values defined in the derived class
  145. # override those in the base class.
  146. if not hasattr(self, '_constructorKeywords'):
  147. tmp = {}
  148. for option, value in keywords.items():
  149. tmp[option] = [value, 0]
  150. self._constructorKeywords = tmp
  151. self._optionInfo = {}
  152. # Initialize dictionary of dynamic groups
  153. if not hasattr(self, '_dynamicGroups'):
  154. self._dynamicGroups = ()
  155. self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
  156. # Reconcile command line and default options
  157. self.addoptions(optionDefs)
  158. def addoptions(self, optionDefs):
  159. """ addoptions(optionDefs) - add option def to option info """
  160. # Add additional options, providing the default value and the
  161. # method to call when the value is changed. See
  162. # "defineoptions" for more details
  163. # optimisations:
  164. optionInfo = self._optionInfo
  165. optionInfo_has_key = optionInfo.has_key
  166. keywords = self._constructorKeywords
  167. keywords_has_key = keywords.has_key
  168. FUNCTION = _OPT_FUNCTION
  169. for name, default, function in optionDefs:
  170. if '_' not in name:
  171. # The option will already exist if it has been defined
  172. # in a derived class. In this case, do not override the
  173. # default value of the option or the callback function
  174. # if it is not None.
  175. if not optionInfo_has_key(name):
  176. if keywords_has_key(name):
  177. # Overridden by keyword, use keyword value
  178. value = keywords[name][0]
  179. optionInfo[name] = [default, value, function]
  180. # Delete it from self._constructorKeywords
  181. del keywords[name]
  182. else:
  183. # Use optionDefs value
  184. optionInfo[name] = [default, default, function]
  185. elif optionInfo[name][FUNCTION] is None:
  186. # Only override function if not defined by derived class
  187. optionInfo[name][FUNCTION] = function
  188. else:
  189. # This option is of the form "component_option". If this is
  190. # not already defined in self._constructorKeywords add it.
  191. # This allows a derived class to override the default value
  192. # of an option of a component of a base class.
  193. if not keywords_has_key(name):
  194. keywords[name] = [default, 0]
  195. def initialiseoptions(self, myClass):
  196. """
  197. Call all initialisation functions to initialize widget
  198. options to default of keyword value
  199. """
  200. # This is to make sure this method class is only called by
  201. # the most specific class in the class hierarchy
  202. if self.__class__ is myClass:
  203. # Call the configuration callback function for every option.
  204. FUNCTION = _OPT_FUNCTION
  205. self.fInit = 1
  206. for info in self._optionInfo.values():
  207. func = info[FUNCTION]
  208. if func is not None and func is not INITOPT:
  209. func()
  210. self.fInit = 0
  211. # Now check if anything is left over
  212. unusedOptions = []
  213. keywords = self._constructorKeywords
  214. for name in keywords.keys():
  215. used = keywords[name][1]
  216. if not used:
  217. # This keyword argument has not been used. If it
  218. # does not refer to a dynamic group, mark it as
  219. # unused.
  220. index = string.find(name, '_')
  221. if index < 0 or name[:index] not in self._dynamicGroups:
  222. unusedOptions.append(name)
  223. self._constructorKeywords = {}
  224. if len(unusedOptions) > 0:
  225. if len(unusedOptions) == 1:
  226. text = 'Unknown option "'
  227. else:
  228. text = 'Unknown options "'
  229. raise KeyError, text + string.join(unusedOptions, ', ') + \
  230. '" for ' + myClass.__name__
  231. # Can now call post init func
  232. self.postInitialiseFunc()
  233. def postInitialiseFunc(self):
  234. for func in self.postInitialiseFuncList:
  235. func()
  236. def isinitoption(self, option):
  237. """
  238. Is this opition one that can only be specified at construction?
  239. """
  240. return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
  241. def options(self):
  242. """
  243. Print out a list of available widget options.
  244. Does not include subcomponent options.
  245. """
  246. options = []
  247. if hasattr(self, '_optionInfo'):
  248. for option, info in self._optionInfo.items():
  249. isinit = info[_OPT_FUNCTION] is INITOPT
  250. default = info[_OPT_DEFAULT]
  251. options.append((option, default, isinit))
  252. options.sort()
  253. return options
  254. def configure(self, option=None, **kw):
  255. """
  256. configure(option = None)
  257. Query or configure the megawidget options.
  258. """
  259. #
  260. # If not empty, *kw* is a dictionary giving new
  261. # values for some of the options of this gui item
  262. # For options defined for this widget, set
  263. # the value of the option to the new value and call the
  264. # configuration callback function, if any.
  265. #
  266. # If *option* is None, return all gui item configuration
  267. # options and settings. Options are returned as standard 3
  268. # element tuples
  269. #
  270. # If *option* is a string, return the 3 element tuple for the
  271. # given configuration option.
  272. # First, deal with the option queries.
  273. if len(kw) == 0:
  274. # This configure call is querying the values of one or all options.
  275. # Return 3-tuples:
  276. # (optionName, default, value)
  277. if option is None:
  278. rtn = {}
  279. for option, config in self._optionInfo.items():
  280. rtn[option] = (option,
  281. config[_OPT_DEFAULT],
  282. config[_OPT_VALUE])
  283. return rtn
  284. else:
  285. config = self._optionInfo[option]
  286. return (option, config[_OPT_DEFAULT], config[_OPT_VALUE])
  287. # optimizations:
  288. optionInfo = self._optionInfo
  289. optionInfo_has_key = optionInfo.has_key
  290. componentInfo = self.__componentInfo
  291. componentInfo_has_key = componentInfo.has_key
  292. componentAliases = self.__componentAliases
  293. componentAliases_has_key = componentAliases.has_key
  294. VALUE = _OPT_VALUE
  295. FUNCTION = _OPT_FUNCTION
  296. # This will contain a list of options in *kw* which
  297. # are known to this gui item.
  298. directOptions = []
  299. # This will contain information about the options in
  300. # *kw* of the form <component>_<option>, where
  301. # <component> is a component of this megawidget. It is a
  302. # dictionary whose keys are the configure method of each
  303. # component and whose values are a dictionary of options and
  304. # values for the component.
  305. indirectOptions = {}
  306. indirectOptions_has_key = indirectOptions.has_key
  307. for option, value in kw.items():
  308. if optionInfo_has_key(option):
  309. # This is one of the options of this gui item.
  310. # Check it is an initialisation option.
  311. if optionInfo[option][FUNCTION] is INITOPT:
  312. print 'Cannot configure initialisation option "' \
  313. + option + '" for ' + self.__class__.__name__
  314. break
  315. #raise KeyError, \
  316. # 'Cannot configure initialisation option "' \
  317. # + option + '" for ' + self.__class__.__name__
  318. optionInfo[option][VALUE] = value
  319. directOptions.append(option)
  320. else:
  321. index = string.find(option, '_')
  322. if index >= 0:
  323. # This option may be of the form <component>_<option>.
  324. # e.g. if alias ('efEntry', 'entryField_entry')
  325. # and option = efEntry_width
  326. # component = efEntry, componentOption = width
  327. component = option[:index]
  328. componentOption = option[(index + 1):]
  329. # Expand component alias
  330. if componentAliases_has_key(component):
  331. # component = entryField, subcomponent = entry
  332. component, subComponent = componentAliases[component]
  333. if subComponent is not None:
  334. # componentOption becomes entry_width
  335. componentOption = subComponent + '_' \
  336. + componentOption
  337. # Expand option string to write on error
  338. # option = entryField_entry_width
  339. option = component + '_' + componentOption
  340. # Does this component exist
  341. if componentInfo_has_key(component):
  342. # Get the configure func for the named component
  343. # component = entryField
  344. componentConfigFuncs = [componentInfo[component][1]]
  345. else:
  346. # Check if this is a group name and configure all
  347. # components in the group.
  348. componentConfigFuncs = []
  349. # For each component
  350. for info in componentInfo.values():
  351. # Check if it is a member of this group
  352. if info[4] == component:
  353. # Yes, append its config func
  354. componentConfigFuncs.append(info[1])
  355. if len(componentConfigFuncs) == 0 and \
  356. component not in self._dynamicGroups:
  357. raise KeyError, 'Unknown option "' + option + \
  358. '" for ' + self.__class__.__name__
  359. # Add the configure method(s) (may be more than
  360. # one if this is configuring a component group)
  361. # and option/value to dictionary.
  362. for componentConfigFunc in componentConfigFuncs:
  363. if not indirectOptions_has_key(componentConfigFunc):
  364. indirectOptions[componentConfigFunc] = {}
  365. # Create a dictionary of keyword/values keyed
  366. # on configuration function
  367. indirectOptions[componentConfigFunc][componentOption] \
  368. = value
  369. else:
  370. raise KeyError, 'Unknown option "' + option + \
  371. '" for ' + self.__class__.__name__
  372. # Call the configure methods for any components.
  373. # Pass in the dictionary of keyword/values created above
  374. map(apply, indirectOptions.keys(),
  375. ((),) * len(indirectOptions), indirectOptions.values())
  376. # Call the configuration callback function for each option.
  377. for option in directOptions:
  378. info = optionInfo[option]
  379. func = info[_OPT_FUNCTION]
  380. if func is not None:
  381. func()
  382. # Allow index style references
  383. def __setitem__(self, key, value):
  384. apply(self.configure, (), {key: value})
  385. def cget(self, option):
  386. """
  387. Get current configuration setting for this option
  388. """
  389. # Return the value of an option, for example myWidget['font'].
  390. if self._optionInfo.has_key(option):
  391. return self._optionInfo[option][_OPT_VALUE]
  392. else:
  393. index = string.find(option, '_')
  394. if index >= 0:
  395. component = option[:index]
  396. componentOption = option[(index + 1):]
  397. # Expand component alias
  398. if self.__componentAliases.has_key(component):
  399. component, subComponent = self.__componentAliases[
  400. component]
  401. if subComponent is not None:
  402. componentOption = subComponent + '_' + componentOption
  403. # Expand option string to write on error
  404. option = component + '_' + componentOption
  405. if self.__componentInfo.has_key(component):
  406. # Call cget on the component.
  407. componentCget = self.__componentInfo[component][3]
  408. return componentCget(componentOption)
  409. else:
  410. # If this is a group name, call cget for one of
  411. # the components in the group.
  412. for info in self.__componentInfo.values():
  413. if info[4] == component:
  414. componentCget = info[3]
  415. return componentCget(componentOption)
  416. # Option not found
  417. raise KeyError, 'Unknown option "' + option + \
  418. '" for ' + self.__class__.__name__
  419. # Allow index style refererences
  420. __getitem__ = cget
  421. def createcomponent(self, componentName, componentAliases, componentGroup,
  422. widgetClass, *widgetArgs, **kw):
  423. """
  424. Create a component (during construction or later) for this widget.
  425. """
  426. # Check for invalid component name
  427. if '_' in componentName:
  428. raise ValueError, \
  429. 'Component name "%s" must not contain "_"' % componentName
  430. # Get construction keywords
  431. if hasattr(self, '_constructorKeywords'):
  432. keywords = self._constructorKeywords
  433. else:
  434. keywords = {}
  435. for alias, component in componentAliases:
  436. # Create aliases to the component and its sub-components.
  437. index = string.find(component, '_')
  438. if index < 0:
  439. # Just a shorter name for one of this widget's components
  440. self.__componentAliases[alias] = (component, None)
  441. else:
  442. # An alias for a component of one of this widget's components
  443. mainComponent = component[:index]
  444. subComponent = component[(index + 1):]
  445. self.__componentAliases[alias] = (mainComponent, subComponent)
  446. # Remove aliases from the constructor keyword arguments by
  447. # replacing any keyword arguments that begin with *alias*
  448. # with corresponding keys beginning with *component*.
  449. alias = alias + '_'
  450. aliasLen = len(alias)
  451. for option in keywords.keys():
  452. if len(option) > aliasLen and option[:aliasLen] == alias:
  453. newkey = component + '_' + option[aliasLen:]
  454. keywords[newkey] = keywords[option]
  455. del keywords[option]
  456. # Find any keyword arguments for this component
  457. componentPrefix = componentName + '_'
  458. nameLen = len(componentPrefix)
  459. # First, walk through the option list looking for arguments
  460. # than refer to this component's group.
  461. for option in keywords.keys():
  462. # Check if this keyword argument refers to the group
  463. # of this component. If so, add this to the options
  464. # to use when constructing the widget. Mark the
  465. # keyword argument as being used, but do not remove it
  466. # since it may be required when creating another
  467. # component.
  468. index = string.find(option, '_')
  469. if index >= 0 and componentGroup == option[:index]:
  470. rest = option[(index + 1):]
  471. kw[rest] = keywords[option][0]
  472. keywords[option][1] = 1
  473. # Now that we've got the group arguments, walk through the
  474. # option list again and get out the arguments that refer to
  475. # this component specifically by name. These are more
  476. # specific than the group arguments, above; we walk through
  477. # the list afterwards so they will override.
  478. for option in keywords.keys():
  479. if len(option) > nameLen and option[:nameLen] == componentPrefix:
  480. # The keyword argument refers to this component, so add
  481. # this to the options to use when constructing the widget.
  482. kw[option[nameLen:]] = keywords[option][0]
  483. # And delete it from main construction keywords
  484. del keywords[option]
  485. # Return None if no widget class is specified
  486. if widgetClass is None:
  487. return None
  488. # Get arguments for widget constructor
  489. if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
  490. # Arguments to the constructor can be specified as either
  491. # multiple trailing arguments to createcomponent() or as a
  492. # single tuple argument.
  493. widgetArgs = widgetArgs[0]
  494. # Create the widget
  495. widget = apply(widgetClass, widgetArgs, kw)
  496. componentClass = widget.__class__.__name__
  497. self.__componentInfo[componentName] = (widget, widget.configure,
  498. componentClass, widget.cget, componentGroup)
  499. return widget
  500. def component(self, name):
  501. # Return a component widget of the megawidget given the
  502. # component's name
  503. # This allows the user of a megawidget to access and configure
  504. # widget components directly.
  505. # Find the main component and any subcomponents
  506. index = string.find(name, '_')
  507. if index < 0:
  508. component = name
  509. remainingComponents = None
  510. else:
  511. component = name[:index]
  512. remainingComponents = name[(index + 1):]
  513. # Expand component alias
  514. # Example entry which is an alias for entryField_entry
  515. if self.__componentAliases.has_key(component):
  516. # component = entryField, subComponent = entry
  517. component, subComponent = self.__componentAliases[component]
  518. if subComponent is not None:
  519. if remainingComponents is None:
  520. # remainingComponents = entry
  521. remainingComponents = subComponent
  522. else:
  523. remainingComponents = subComponent + '_' \
  524. + remainingComponents
  525. # Get the component from __componentInfo dictionary
  526. widget = self.__componentInfo[component][0]
  527. if remainingComponents is None:
  528. # Not looking for subcomponent
  529. return widget
  530. else:
  531. # Recursive call on subcomponent
  532. return widget.component(remainingComponents)
  533. def components(self):
  534. # Return a list of all components.
  535. names = self.__componentInfo.keys()
  536. names.sort()
  537. return names
  538. def hascomponent(self, component):
  539. return self.__componentInfo.has_key(component)
  540. def destroycomponent(self, name):
  541. # Remove a megawidget component.
  542. # This command is for use by megawidget designers to destroy a
  543. # megawidget component.
  544. self.__componentInfo[name][0].destroy()
  545. del self.__componentInfo[name]
  546. def destroy(self):
  547. # Clean out any hooks
  548. self.ignoreAll()
  549. del self._optionInfo
  550. del self.__componentInfo
  551. del self.postInitialiseFuncList
  552. def bind(self, event, command, extraArgs = []):
  553. """
  554. Bind the command (which should expect one arg) to the specified
  555. event (such as ENTER, EXIT, B1PRESS, B1CLICK, etc.)
  556. See DirectGuiGlobals for possible events
  557. """
  558. # Need to tack on gui item specific id
  559. gEvent = event + self.guiId
  560. if base.config.GetBool('debug-directgui-msgs', False):
  561. from direct.showbase.PythonUtil import StackTrace
  562. print gEvent
  563. print StackTrace()
  564. self.accept(gEvent, command, extraArgs = extraArgs)
  565. def unbind(self, event):
  566. """
  567. Unbind the specified event
  568. """
  569. # Need to tack on gui item specific id
  570. gEvent = event + self.guiId
  571. self.ignore(gEvent)
  572. def toggleGuiGridSnap():
  573. DirectGuiWidget.snapToGrid = 1 - DirectGuiWidget.snapToGrid
  574. def setGuiGridSpacing(spacing):
  575. DirectGuiWidget.gridSpacing = spacing
  576. class DirectGuiWidget(DirectGuiBase, NodePath):
  577. # Toggle if you wish widget's to snap to grid when draggin
  578. snapToGrid = 0
  579. gridSpacing = 0.05
  580. # Determine the default initial state for inactive (or
  581. # unclickable) components. If we are in edit mode, these are
  582. # actually clickable by default.
  583. #guiEdit = base.config.GetBool('direct-gui-edit', 0)
  584. guiEdit = config.GetBool('direct-gui-edit', 0)
  585. if guiEdit:
  586. inactiveInitState = NORMAL
  587. else:
  588. inactiveInitState = DISABLED
  589. guiDict = {}
  590. def __init__(self, parent = None, **kw):
  591. # Direct gui widgets are node paths
  592. # Direct gui widgets have:
  593. # - stateNodePaths (to hold visible representation of widget)
  594. # State node paths can have:
  595. # - a frame of type (None, FLAT, RAISED, GROOVE, RIDGE)
  596. # - arbitrary geometry for each state
  597. # They inherit from DirectGuiWidget
  598. # - Can create components (with aliases and groups)
  599. # - Can bind to mouse events
  600. # They inherit from NodePath
  601. # - Can position/scale them
  602. optiondefs = (
  603. # Widget's constructor
  604. ('pgFunc', PGItem, None),
  605. ('numStates', 1, None),
  606. ('invertedFrames', (), None),
  607. ('sortOrder', 0, None),
  608. # Widget's initial state
  609. ('state', NORMAL, self.setState),
  610. # Widget's frame characteristics
  611. ('relief', FLAT, self.setRelief),
  612. ('borderWidth', (.1, .1), self.setBorderWidth),
  613. ('frameSize', None, self.setFrameSize),
  614. ('frameColor', (.8, .8, .8, 1), self.setFrameColor),
  615. ('frameTexture', None, self.setFrameTexture),
  616. ('frameVisibleScale', (1, 1), self.setFrameVisibleScale),
  617. ('pad', (0, 0), self.resetFrameSize),
  618. # Override button id (beware! your name may not be unique!)
  619. ('guiId', None, INITOPT),
  620. # Initial pos/scale of the widget
  621. ('pos', None, INITOPT),
  622. ('hpr', None, INITOPT),
  623. ('scale', None, INITOPT),
  624. ('color', None, INITOPT),
  625. # Do events pass through this widget?
  626. ('suppressMouse', 1, INITOPT),
  627. ('suppressKeys', 0, INITOPT),
  628. ('enableEdit', 1, INITOPT),
  629. )
  630. # Merge keyword options with default options
  631. self.defineoptions(kw, optiondefs)
  632. # Initialize the base classes (after defining the options).
  633. DirectGuiBase.__init__(self)
  634. NodePath.__init__(self)
  635. # Create a button
  636. self.guiItem = self['pgFunc']('')
  637. # Override automatically generated guiId
  638. if self['guiId']:
  639. self.guiItem.setId(self['guiId'])
  640. self.guiId = self.guiItem.getId()
  641. if __dev__ or (hasattr(base, 'cr') and base.cr.wantE3hacks):
  642. # track gui items by guiId for tracking down leaks
  643. if hasattr(base, 'guiItems'):
  644. if self.guiId in base.guiItems:
  645. base.notify.warning('duplicate guiId: %s (%s stomping %s)' %
  646. (self.guiId, self,
  647. base.guiItems[self.guiId]))
  648. base.guiItems[self.guiId] = self
  649. # Attach button to parent and make that self
  650. if (parent == None):
  651. parent = aspect2d
  652. self.assign(parent.attachNewNode(self.guiItem, self['sortOrder']))
  653. # Update pose to initial values
  654. if self['pos']:
  655. pos = self['pos']
  656. # Can either be a VBase3 or a tuple of 3 values
  657. if isinstance(pos, VBase3):
  658. self.setPos(pos)
  659. else:
  660. apply(self.setPos, pos)
  661. if self['hpr']:
  662. hpr = self['hpr']
  663. # Can either be a VBase3 or a tuple of 3 values
  664. if isinstance(hpr, VBase3):
  665. self.setHpr(hpr)
  666. else:
  667. apply(self.setHpr, hpr)
  668. if self['scale']:
  669. scale = self['scale']
  670. # Can either be a Vec3 or a tuple of 3 values
  671. if (isinstance(scale, Vec3) or
  672. (type(scale) == types.IntType) or
  673. (type(scale) == types.FloatType)):
  674. self.setScale(scale)
  675. else:
  676. apply(self.setScale, scale)
  677. if self['color']:
  678. color = self['color']
  679. # Can either be a Vec4 or a tuple of 4 values
  680. if (isinstance(color, Vec4)):
  681. self.setColor(color)
  682. else:
  683. apply(self.setColor, color)
  684. # Initialize names
  685. self.setName(self.guiId)
  686. # Create
  687. self.stateNodePath = []
  688. for i in range(self['numStates']):
  689. self.stateNodePath.append(NodePath(self.guiItem.getStateDef(i)))
  690. # Initialize frame style
  691. self.frameStyle = []
  692. for i in range(self['numStates']):
  693. self.frameStyle.append(PGFrameStyle())
  694. # For holding bounds info
  695. self.ll = Point3(0)
  696. self.ur = Point3(0)
  697. # Is drag and drop enabled?
  698. if self['enableEdit'] and self.guiEdit:
  699. self.enableEdit()
  700. # Set up event handling
  701. suppressFlags = 0
  702. if self['suppressMouse']:
  703. suppressFlags |= MouseWatcherRegion.SFMouseButton
  704. suppressFlags |= MouseWatcherRegion.SFMousePosition
  705. if self['suppressKeys']:
  706. suppressFlags |= MouseWatcherRegion.SFOtherButton
  707. self.guiItem.setSuppressFlags(suppressFlags)
  708. # Bind destroy hook
  709. self.guiDict[self.guiId] = self
  710. # self.bind(DESTROY, self.destroy)
  711. # Update frame when everything has been initialized
  712. self.postInitialiseFuncList.append(self.frameInitialiseFunc)
  713. # Call option initialization functions
  714. self.initialiseoptions(DirectGuiWidget)
  715. def frameInitialiseFunc(self):
  716. # Now allow changes to take effect
  717. self.updateFrameStyle()
  718. if not self['frameSize']:
  719. self.resetFrameSize()
  720. def enableEdit(self):
  721. self.bind(B2PRESS, self.editStart)
  722. self.bind(B2RELEASE, self.editStop)
  723. self.bind(PRINT, self.printConfig)
  724. # Can we move this to showbase
  725. # Certainly we don't need to do this for every button!
  726. #mb = base.mouseWatcherNode.getModifierButtons()
  727. #mb.addButton(KeyboardButton.control())
  728. #base.mouseWatcherNode.setModifierButtons(mb)
  729. def disableEdit(self):
  730. self.unbind(B2PRESS)
  731. self.unbind(B2RELEASE)
  732. self.unbind(PRINT)
  733. #mb = base.mouseWatcherNode.getModifierButtons()
  734. #mb.removeButton(KeyboardButton.control())
  735. #base.mouseWatcherNode.setModifierButtons(mb)
  736. def editStart(self, event):
  737. taskMgr.remove('guiEditTask')
  738. vWidget2render2d = self.getPos(render2d)
  739. vMouse2render2d = Point3(event.getMouse()[0], 0, event.getMouse()[1])
  740. editVec = Vec3(vWidget2render2d - vMouse2render2d)
  741. if base.mouseWatcherNode.getModifierButtons().isDown(
  742. KeyboardButton.control()):
  743. t = taskMgr.add(self.guiScaleTask, 'guiEditTask')
  744. t.refPos = vWidget2render2d
  745. t.editVecLen = editVec.length()
  746. t.initScale = self.getScale()
  747. else:
  748. t = taskMgr.add(self.guiDragTask, 'guiEditTask')
  749. t.editVec = editVec
  750. def guiScaleTask(self, state):
  751. mwn = base.mouseWatcherNode
  752. if mwn.hasMouse():
  753. vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])
  754. newEditVecLen = Vec3(state.refPos - vMouse2render2d).length()
  755. self.setScale(state.initScale * (newEditVecLen/state.editVecLen))
  756. return Task.cont
  757. def guiDragTask(self, state):
  758. mwn = base.mouseWatcherNode
  759. if mwn.hasMouse():
  760. vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])
  761. newPos = vMouse2render2d + state.editVec
  762. self.setPos(render2d, newPos)
  763. if DirectGuiWidget.snapToGrid:
  764. newPos = self.getPos()
  765. newPos.set(
  766. ROUND_TO(newPos[0], DirectGuiWidget.gridSpacing),
  767. ROUND_TO(newPos[1], DirectGuiWidget.gridSpacing),
  768. ROUND_TO(newPos[2], DirectGuiWidget.gridSpacing))
  769. self.setPos(newPos)
  770. return Task.cont
  771. def editStop(self, event):
  772. taskMgr.remove('guiEditTask')
  773. def setState(self):
  774. if type(self['state']) == type(0):
  775. self.guiItem.setActive(self['state'])
  776. elif (self['state'] == NORMAL) or (self['state'] == 'normal'):
  777. self.guiItem.setActive(1)
  778. else:
  779. self.guiItem.setActive(0)
  780. def resetFrameSize(self):
  781. if not self.fInit:
  782. self.setFrameSize(fClearFrame = 1)
  783. def setFrameSize(self, fClearFrame = 0):
  784. # Use ready state to determine frame Type
  785. frameType = self.getFrameType()
  786. if self['frameSize']:
  787. # Use user specified bounds
  788. self.bounds = self['frameSize']
  789. #print "%s bounds = %s" % (self.getName(), self.bounds)
  790. bw = (0, 0)
  791. else:
  792. if fClearFrame and (frameType != PGFrameStyle.TNone):
  793. self.frameStyle[0].setType(PGFrameStyle.TNone)
  794. self.guiItem.setFrameStyle(0, self.frameStyle[0])
  795. # To force an update of the button
  796. self.guiItem.getStateDef(0)
  797. # Clear out frame before computing bounds
  798. self.getBounds()
  799. # Restore frame style if necessary
  800. if (frameType != PGFrameStyle.TNone):
  801. self.frameStyle[0].setType(frameType)
  802. self.guiItem.setFrameStyle(0, self.frameStyle[0])
  803. if ((frameType != PGFrameStyle.TNone) and
  804. (frameType != PGFrameStyle.TFlat)):
  805. bw = self['borderWidth']
  806. else:
  807. bw = (0, 0)
  808. # Set frame to new dimensions
  809. self.guiItem.setFrame(
  810. self.bounds[0] - bw[0],
  811. self.bounds[1] + bw[0],
  812. self.bounds[2] - bw[1],
  813. self.bounds[3] + bw[1])
  814. def getBounds(self, state = 0):
  815. self.stateNodePath[state].calcTightBounds(self.ll, self.ur)
  816. # Scale bounds to give a pad around graphics
  817. self.bounds = [self.ll[0] - self['pad'][0],
  818. self.ur[0] + self['pad'][0],
  819. self.ll[2] - self['pad'][1],
  820. self.ur[2] + self['pad'][1]]
  821. return self.bounds
  822. def getWidth(self):
  823. return self.bounds[1] - self.bounds[0]
  824. def getHeight(self):
  825. return self.bounds[3] - self.bounds[2]
  826. def getCenter(self):
  827. x = self.bounds[0] + (self.bounds[1] - self.bounds[0])/2.0
  828. y = self.bounds[2] + (self.bounds[3] - self.bounds[2])/2.0
  829. return (x, y)
  830. def getFrameType(self, state = 0):
  831. return self.frameStyle[state].getType()
  832. def updateFrameStyle(self):
  833. if not self.fInit:
  834. for i in range(self['numStates']):
  835. self.guiItem.setFrameStyle(i, self.frameStyle[i])
  836. def setRelief(self, fSetStyle = 1):
  837. relief = self['relief']
  838. # Convert None, and string arguments
  839. if relief == None:
  840. relief = PGFrameStyle.TNone
  841. elif isinstance(relief, types.StringTypes):
  842. # Convert string to frame style int
  843. relief = FrameStyleDict[relief]
  844. # Set style
  845. if relief == RAISED:
  846. for i in range(self['numStates']):
  847. if i in self['invertedFrames']:
  848. self.frameStyle[1].setType(SUNKEN)
  849. else:
  850. self.frameStyle[i].setType(RAISED)
  851. elif relief == SUNKEN:
  852. for i in range(self['numStates']):
  853. if i in self['invertedFrames']:
  854. self.frameStyle[1].setType(RAISED)
  855. else:
  856. self.frameStyle[i].setType(SUNKEN)
  857. else:
  858. for i in range(self['numStates']):
  859. self.frameStyle[i].setType(relief)
  860. # Apply styles
  861. self.updateFrameStyle()
  862. def setFrameColor(self):
  863. # this might be a single color or a list of colors
  864. colors = self['frameColor']
  865. if type(colors[0]) == types.IntType or \
  866. type(colors[0]) == types.FloatType:
  867. colors = (colors,)
  868. for i in range(self['numStates']):
  869. if i >= len(colors):
  870. color = colors[-1]
  871. else:
  872. color = colors[i]
  873. self.frameStyle[i].setColor(color[0], color[1], color[2], color[3])
  874. self.updateFrameStyle()
  875. def setFrameTexture(self):
  876. # this might be a single texture or a list of textures
  877. textures = self['frameTexture']
  878. if textures == None or \
  879. isinstance(textures, Texture) or \
  880. isinstance(textures, types.StringTypes):
  881. textures = (textures,) * self['numStates']
  882. for i in range(self['numStates']):
  883. if i >= len(textures):
  884. texture = textures[-1]
  885. else:
  886. texture = textures[i]
  887. if isinstance(texture, types.StringTypes):
  888. texture = loader.loadTexture(texture)
  889. if texture:
  890. self.frameStyle[i].setTexture(texture)
  891. self.updateFrameStyle()
  892. def setFrameVisibleScale(self):
  893. scale = self['frameVisibleScale']
  894. for i in range(self['numStates']):
  895. self.frameStyle[i].setVisibleScale(scale[0], scale[1])
  896. self.updateFrameStyle()
  897. def setBorderWidth(self):
  898. width = self['borderWidth']
  899. for i in range(self['numStates']):
  900. self.frameStyle[i].setWidth(width[0], width[1])
  901. self.updateFrameStyle()
  902. def destroy(self):
  903. if __dev__ or (hasattr(base, 'cr') and base.cr.wantE3hacks):
  904. if hasattr(base, 'guiItems'):
  905. if self.guiId in base.guiItems:
  906. del base.guiItems[self.guiId]
  907. else:
  908. base.notify.warning(
  909. 'DirectGuiWidget.destroy(): '
  910. 'gui item %s not in base.guiItems' %
  911. self.guiId)
  912. if hasattr(self, "frameStyle"):
  913. # Destroy children
  914. for child in self.getChildrenAsList():
  915. childGui = self.guiDict.get(child.getName())
  916. if childGui: childGui.destroy()
  917. # messenger.send(DESTROY + child.getName())
  918. del self.guiDict[self.guiId]
  919. del self.frameStyle
  920. # Get rid of node path
  921. self.removeNode()
  922. for nodePath in self.stateNodePath:
  923. nodePath.removeNode()
  924. del self.stateNodePath
  925. del self.guiItem
  926. # Call superclass destruction method (clears out hooks)
  927. DirectGuiBase.destroy(self)
  928. def printConfig(self, indent = 0):
  929. space = ' ' * indent
  930. print space + self.guiId, '-', self.__class__.__name__
  931. print space + 'Pos: ' + self.getPos().pPrintValues()
  932. print space + 'Scale: ' + self.getScale().pPrintValues()
  933. # Print out children info
  934. for child in self.getChildrenAsList():
  935. messenger.send(PRINT + child.getName(), [indent + 2])
  936. def copyOptions(self, other):
  937. """
  938. Copy other's options into our self so we look and feel like other
  939. """
  940. for key, value in other._optionInfo.items():
  941. self[key] = value[1]
  942. def taskName(self, idString):
  943. return (idString + "-" + str(self.guiId))
  944. def setProp(self, propString, value):
  945. """
  946. Allows you to set a property like frame['text'] = 'Joe' in
  947. a function instead of an assignment.
  948. This is useful for setting properties inside function intervals
  949. where must input a function and extraArgs, not an assignment.
  950. """
  951. self[propString] = value