DirectEntry.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. """Undocumented Module"""
  2. __all__ = ['DirectEntry']
  3. from pandac.PandaModules import *
  4. import DirectGuiGlobals as DGG
  5. from DirectFrame import *
  6. from OnscreenText import OnscreenText
  7. import string,types
  8. # DirectEntry States:
  9. ENTRY_FOCUS_STATE = PGEntry.SFocus # 0
  10. ENTRY_NO_FOCUS_STATE = PGEntry.SNoFocus # 1
  11. ENTRY_INACTIVE_STATE = PGEntry.SInactive # 2
  12. class DirectEntry(DirectFrame):
  13. """
  14. DirectEntry(parent) - Create a DirectGuiWidget which responds
  15. to keyboard buttons
  16. """
  17. directWtext = ConfigVariableBool('direct-wtext', 1)
  18. def __init__(self, parent = None, **kw):
  19. # Inherits from DirectFrame
  20. # A Direct Frame can have:
  21. # - A background texture (pass in path to image, or Texture Card)
  22. # - A midground geometry item (pass in geometry)
  23. # - A foreground text Node (pass in text string or Onscreen Text)
  24. # For a direct entry:
  25. # Each button has 3 states (focus, noFocus, disabled)
  26. # The same image/geom/text can be used for all three states or each
  27. # state can have a different text/geom/image
  28. # State transitions happen automatically based upon mouse interaction
  29. optiondefs = (
  30. # Define type of DirectGuiWidget
  31. ('pgFunc', PGEntry, None),
  32. ('numStates', 3, None),
  33. ('state', DGG.NORMAL, None),
  34. ('entryFont', None, DGG.INITOPT),
  35. ('width', 10, self.setup),
  36. ('numLines', 1, self.setup),
  37. ('focus', 0, self.setFocus),
  38. ('cursorKeys', 1, self.setCursorKeysActive),
  39. ('obscured', 0, self.setObscureMode),
  40. # Setting backgroundFocus allows the entry box to get keyboard
  41. # events that are not handled by other things (i.e. events that
  42. # fall through to the background):
  43. ('backgroundFocus', 0, self.setBackgroundFocus),
  44. # Text used for the PGEntry text node
  45. # NOTE: This overrides the DirectFrame text option
  46. ('initialText', '', DGG.INITOPT),
  47. # Command to be called on hitting Enter
  48. ('command', None, None),
  49. ('extraArgs', [], None),
  50. # Command to be called when enter is hit but we fail to submit
  51. ('failedCommand', None, None),
  52. ('failedExtraArgs',[], None),
  53. # commands to be called when focus is gained or lost
  54. ('focusInCommand', None, None),
  55. ('focusInExtraArgs', [], None),
  56. ('focusOutCommand', None, None),
  57. ('focusOutExtraArgs', [], None),
  58. # Sounds to be used for button events
  59. ('rolloverSound', DGG.getDefaultRolloverSound(), self.setRolloverSound),
  60. ('clickSound', DGG.getDefaultClickSound(), self.setClickSound),
  61. )
  62. # Merge keyword options with default options
  63. self.defineoptions(kw, optiondefs)
  64. # Initialize superclasses
  65. DirectFrame.__init__(self, parent)
  66. if self['entryFont'] == None:
  67. font = DGG.getDefaultFont()
  68. else:
  69. font = self['entryFont']
  70. # Create Text Node Component
  71. self.onscreenText = self.createcomponent(
  72. 'text', (), None,
  73. OnscreenText,
  74. (), parent = hidden,
  75. # Pass in empty text to avoid extra work, since its really
  76. # The PGEntry which will use the TextNode to generate geometry
  77. text = '',
  78. align = TextNode.ALeft,
  79. font = font,
  80. scale = 1,
  81. # Don't get rid of the text node
  82. mayChange = 1)
  83. # We can get rid of the node path since we're just using the
  84. # onscreenText as an easy way to access a text node as a
  85. # component
  86. self.onscreenText.removeNode()
  87. # Bind command function
  88. self.bind(DGG.ACCEPT, self.commandFunc)
  89. self.bind(DGG.ACCEPTFAILED, self.failedCommandFunc)
  90. self.accept(self.guiItem.getFocusInEvent(), self.focusInCommandFunc)
  91. self.accept(self.guiItem.getFocusOutEvent(), self.focusOutCommandFunc)
  92. # Call option initialization functions
  93. self.initialiseoptions(DirectEntry)
  94. # Update TextNodes for each state
  95. for i in range(self['numStates']):
  96. self.guiItem.setTextDef(i, self.onscreenText.textNode)
  97. # Update initial text
  98. self.unicodeText = 0
  99. if self['initialText']:
  100. self.set(self['initialText'])
  101. def destroy(self):
  102. self.ignore(self.guiItem.getFocusInEvent())
  103. self.ignore(self.guiItem.getFocusOutEvent())
  104. DirectFrame.destroy(self)
  105. def setup(self):
  106. self.node().setup(self['width'], self['numLines'])
  107. def setFocus(self):
  108. PGEntry.setFocus(self.guiItem, self['focus'])
  109. def setCursorKeysActive(self):
  110. PGEntry.setCursorKeysActive(self.guiItem, self['cursorKeys'])
  111. def setObscureMode(self):
  112. PGEntry.setObscureMode(self.guiItem, self['obscured'])
  113. def setBackgroundFocus(self):
  114. PGEntry.setBackgroundFocus(self.guiItem, self['backgroundFocus'])
  115. def setRolloverSound(self):
  116. rolloverSound = self['rolloverSound']
  117. if rolloverSound:
  118. self.guiItem.setSound(DGG.ENTER + self.guiId, rolloverSound)
  119. else:
  120. self.guiItem.clearSound(DGG.ENTER + self.guiId)
  121. def setClickSound(self):
  122. clickSound = self['clickSound']
  123. if clickSound:
  124. self.guiItem.setSound(DGG.ACCEPT + self.guiId, clickSound)
  125. else:
  126. self.guiItem.clearSound(DGG.ACCEPT + self.guiId)
  127. def commandFunc(self, event):
  128. if self['command']:
  129. # Pass any extra args to command
  130. apply(self['command'], [self.get()] + self['extraArgs'])
  131. def failedCommandFunc(self, event):
  132. if self['failedCommand']:
  133. # Pass any extra args
  134. apply(self['failedCommand'], [self.get()] + self['failedExtraArgs'])
  135. def focusInCommandFunc(self):
  136. if self['focusInCommand']:
  137. apply(self['focusInCommand'], self['focusInExtraArgs'])
  138. def focusOutCommandFunc(self):
  139. if self['focusOutCommand']:
  140. apply(self['focusOutCommand'], self['focusOutExtraArgs'])
  141. def set(self, text):
  142. """ Changes the text currently showing in the typable region;
  143. does not change the current cursor position. Also see
  144. enterText(). """
  145. self.unicodeText = isinstance(text, types.UnicodeType)
  146. if self.unicodeText:
  147. self.guiItem.setWtext(text)
  148. else:
  149. self.guiItem.setText(text)
  150. def get(self, plain = False):
  151. """ Returns the text currently showing in the typable region.
  152. If plain is True, the returned text will not include any
  153. formatting characters like nested color-change codes. """
  154. wantWide = self.unicodeText or self.guiItem.isWtext()
  155. if not self.directWtext.getValue():
  156. # If the user has configured wide-text off, then always
  157. # return an 8-bit string. This will be encoded if
  158. # necessary, according to Panda's default encoding.
  159. wantWide = False
  160. if plain:
  161. if wantWide:
  162. return self.guiItem.getPlainWtext()
  163. else:
  164. return self.guiItem.getPlainText()
  165. else:
  166. if wantWide:
  167. return self.guiItem.getWtext()
  168. else:
  169. return self.guiItem.getText()
  170. def setCursorPosition(self, pos):
  171. if (pos < 0):
  172. self.guiItem.setCursorPosition(self.guiItem.getNumCharacters() + pos)
  173. else:
  174. self.guiItem.setCursorPosition(pos)
  175. def enterText(self, text):
  176. """ sets the entry's text, and moves the cursor to the end """
  177. self.set(text)
  178. self.setCursorPosition(self.guiItem.getNumCharacters())
  179. def getBounds(self, state = 0):
  180. # Compute the width and height for the entry itself, ignoring
  181. # geometry etc.
  182. tn = self.onscreenText.textNode
  183. mat = tn.getTransform()
  184. align = tn.getAlign()
  185. lineHeight = tn.getLineHeight()
  186. numLines = self['numLines']
  187. width = self['width']
  188. if align == TextNode.ALeft:
  189. left = 0.0
  190. right = width
  191. elif align == TextNode.ACenter:
  192. left = -width / 2.0
  193. right = width / 2.0
  194. elif align == TextNode.ARight:
  195. left = -width
  196. right = 0.0
  197. bottom = -0.3 * lineHeight - (lineHeight * (numLines - 1))
  198. top = lineHeight
  199. self.ll.set(left, 0.0, bottom)
  200. self.ur.set(right, 0.0, top)
  201. self.ll = mat.xformPoint(self.ll)
  202. self.ur = mat.xformPoint(self.ur)
  203. # Scale bounds to give a pad around graphics. We also want to
  204. # scale around the border width.
  205. pad = self['pad']
  206. borderWidth = self['borderWidth']
  207. self.bounds = [self.ll[0] - pad[0] - borderWidth[0],
  208. self.ur[0] + pad[0] + borderWidth[0],
  209. self.ll[2] - pad[1] - borderWidth[1],
  210. self.ur[2] + pad[1] + borderWidth[1]]
  211. return self.bounds