DirectDialog.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. """Undocumented Module"""
  2. __all__ = ['findDialog', 'cleanupDialog', 'DirectDialog', 'OkDialog', 'OkCancelDialog', 'YesNoDialog', 'YesNoCancelDialog', 'RetryCancelDialog']
  3. from panda3d.core import *
  4. import DirectGuiGlobals as DGG
  5. from DirectFrame import *
  6. from DirectButton import *
  7. import types
  8. def findDialog(uniqueName):
  9. """findPanel(string uniqueName)
  10. Returns the panel whose uniqueName is given. This is mainly
  11. useful for debugging, to get a pointer to the current onscreen
  12. panel of a particular type.
  13. """
  14. if uniqueName in DirectDialog.AllDialogs:
  15. return DirectDialog.AllDialogs[uniqueName]
  16. return None
  17. def cleanupDialog(uniqueName):
  18. """cleanupPanel(string uniqueName)
  19. Cleans up (removes) the panel with the given uniqueName. This
  20. may be useful when some panels know about each other and know
  21. that opening panel A should automatically close panel B, for
  22. instance.
  23. """
  24. if uniqueName in DirectDialog.AllDialogs:
  25. # calling cleanup() will remove it out of the AllDialogs dict
  26. # This way it will get removed from the dict even it we did
  27. # not clean it up using this interface (ie somebody called
  28. # self.cleanup() directly
  29. DirectDialog.AllDialogs[uniqueName].cleanup()
  30. class DirectDialog(DirectFrame):
  31. AllDialogs = {}
  32. PanelIndex = 0
  33. def __init__(self, parent = None, **kw):
  34. """
  35. DirectDialog(kw)
  36. Creates a popup dialog to alert and/or interact with user.
  37. Some of the main keywords that can be used to customize the dialog:
  38. Keyword Definition
  39. ------- ----------
  40. text Text message/query displayed to user
  41. geom Geometry to be displayed in dialog
  42. buttonTextList List of text to show on each button
  43. buttonGeomList List of geometry to show on each button
  44. buttonImageList List of images to show on each button
  45. buttonValueList List of values sent to dialog command for
  46. each button. If value is [] then the
  47. ordinal rank of the button is used as
  48. its value
  49. buttonHotKeyList List of hotkeys to bind to each button.
  50. Typing hotkey is equivalent to pressing
  51. the corresponding button.
  52. suppressKeys Set to true if you wish to suppress keys
  53. (i.e. Dialog eats key event), false if
  54. you wish Dialog to pass along key event
  55. buttonSize 4-tuple used to specify custom size for
  56. each button (to make bigger then geom/text
  57. for example)
  58. pad Space between border and interior graphics
  59. topPad Extra space added above text/geom/image
  60. midPad Extra space added between text/buttons
  61. sidePad Extra space added to either side of
  62. text/buttons
  63. buttonPadSF Scale factor used to expand/contract
  64. button horizontal spacing
  65. command Callback command used when a button is
  66. pressed. Value supplied to command
  67. depends on values in buttonValueList
  68. Note: Number of buttons on the dialog depends upon the maximum
  69. length of any button[Text|Geom|Image|Value]List specified.
  70. Values of None are substituted for lists that are shorter
  71. than the max length
  72. """
  73. # Inherits from DirectFrame
  74. optiondefs = (
  75. # Define type of DirectGuiWidget
  76. ('dialogName', 'DirectDialog_' + repr(DirectDialog.PanelIndex), DGG.INITOPT),
  77. # Default position is slightly forward in Y, so as not to
  78. # intersect the near plane, which is incorrectly set to 0
  79. # in DX for some reason.
  80. ('pos', (0, 0.1, 0), None),
  81. ('pad', (0.1, 0.1), None),
  82. ('text', '', None),
  83. ('text_align', TextNode.ALeft, None),
  84. ('text_scale', 0.06, None),
  85. ('image', None, None),
  86. ('relief', DGG.RAISED, None),
  87. ('borderWidth', (0.01, 0.01), None),
  88. ('buttonTextList', [], DGG.INITOPT),
  89. ('buttonGeomList', [], DGG.INITOPT),
  90. ('buttonImageList', [], DGG.INITOPT),
  91. ('buttonValueList', [], DGG.INITOPT),
  92. ('buttonHotKeyList', [], DGG.INITOPT),
  93. ('button_borderWidth', (.01, .01), None),
  94. ('button_pad', (.01, .01), None),
  95. ('button_relief', DGG.RAISED, None),
  96. ('button_text_scale', 0.06, None),
  97. ('buttonSize', None, DGG.INITOPT),
  98. ('topPad', 0.06, DGG.INITOPT),
  99. ('midPad', 0.12, DGG.INITOPT),
  100. ('sidePad', 0., DGG.INITOPT),
  101. ('buttonPadSF', 1.1, DGG.INITOPT),
  102. # Alpha of fade screen behind dialog
  103. ('fadeScreen', 0, None),
  104. ('command', None, None),
  105. ('extraArgs', [], None),
  106. ('sortOrder', NO_FADE_SORT_INDEX, None),
  107. )
  108. # Merge keyword options with default options
  109. self.defineoptions(kw, optiondefs, dynamicGroups = ("button",))
  110. # Initialize superclasses
  111. DirectFrame.__init__(self, parent)
  112. #if not self['dialogName']:
  113. # self['dialogName'] = 'DirectDialog_' + repr(DirectDialog.PanelIndex)
  114. # Clean up any previously existing panel with the same unique
  115. # name. We don't allow any two panels with the same name to
  116. # coexist.
  117. cleanupDialog(self['dialogName'])
  118. # Store this panel in our map of all open panels.
  119. DirectDialog.AllDialogs[self['dialogName']] = self
  120. DirectDialog.PanelIndex += 1
  121. # Determine number of buttons
  122. self.numButtons = max(len(self['buttonTextList']),
  123. len(self['buttonGeomList']),
  124. len(self['buttonImageList']),
  125. len(self['buttonValueList']))
  126. # Create buttons
  127. self.buttonList = []
  128. index = 0
  129. for i in range(self.numButtons):
  130. name = 'Button' + repr(i)
  131. try:
  132. text = self['buttonTextList'][i]
  133. except IndexError:
  134. text = None
  135. try:
  136. geom = self['buttonGeomList'][i]
  137. except IndexError:
  138. geom = None
  139. try:
  140. image = self['buttonImageList'][i]
  141. except IndexError:
  142. image = None
  143. try:
  144. value = self['buttonValueList'][i]
  145. except IndexError:
  146. value = i
  147. self['buttonValueList'].append(i)
  148. try:
  149. hotKey = self['buttonHotKeyList'][i]
  150. except IndexError:
  151. hotKey = None
  152. button = self.createcomponent(
  153. name, (), "button",
  154. DirectButton, (self,),
  155. text = text,
  156. geom = geom,
  157. image = image,
  158. suppressKeys = self['suppressKeys'],
  159. frameSize = self['buttonSize'],
  160. command = lambda s = self, v = value: s.buttonCommand(v)
  161. )
  162. self.buttonList.append(button)
  163. # Update dialog when everything has been initialised
  164. self.postInitialiseFuncList.append(self.configureDialog)
  165. self.initialiseoptions(DirectDialog)
  166. def configureDialog(self):
  167. # Set up hot key bindings
  168. bindList = zip(self.buttonList, self['buttonHotKeyList'],
  169. self['buttonValueList'])
  170. for button, hotKey, value in bindList:
  171. if ((type(hotKey) == types.ListType) or
  172. (type(hotKey) == types.TupleType)):
  173. for key in hotKey:
  174. button.bind('press-' + key + '-', self.buttonCommand,
  175. extraArgs = [value])
  176. self.bind('press-' + key + '-', self.buttonCommand,
  177. extraArgs = [value])
  178. else:
  179. button.bind('press-' + hotKey + '-', self.buttonCommand,
  180. extraArgs = [value])
  181. self.bind('press-' + hotKey + '-', self.buttonCommand,
  182. extraArgs = [value])
  183. # Position buttons and text
  184. pad = self['pad']
  185. if self.hascomponent('image0'):
  186. image = self.component('image0')
  187. else:
  188. image = None
  189. # Get size of text/geom without image (for state 0)
  190. if image:
  191. image.reparentTo(hidden)
  192. bounds = self.stateNodePath[0].getTightBounds()
  193. if image:
  194. image.reparentTo(self.stateNodePath[0])
  195. if bounds is None:
  196. l = 0
  197. r = 0
  198. b = 0
  199. t = 0
  200. else:
  201. l = bounds[0][0]
  202. r = bounds[1][0]
  203. b = bounds[0][2]
  204. t = bounds[1][2]
  205. # Center text and geom around origin
  206. # How far is center of text from origin?
  207. xOffset = -(l+r)*0.5
  208. zOffset = -(b+t)*0.5
  209. # Update bounds to reflect text movement
  210. l += xOffset
  211. r += xOffset
  212. b += zOffset
  213. t += zOffset
  214. # Offset text and geom to center
  215. if self['text']:
  216. self['text_pos'] = (self['text_pos'][0] + xOffset,
  217. self['text_pos'][1] + zOffset)
  218. if self['geom']:
  219. self['geom_pos'] = Point3(self['geom_pos'][0] + xOffset,
  220. self['geom_pos'][1],
  221. self['geom_pos'][2] + zOffset)
  222. if self.numButtons != 0:
  223. bpad = self['button_pad']
  224. # Get button size
  225. if self['buttonSize']:
  226. # Either use given size
  227. buttonSize = self['buttonSize']
  228. bl = buttonSize[0]
  229. br = buttonSize[1]
  230. bb = buttonSize[2]
  231. bt = buttonSize[3]
  232. else:
  233. # Or get bounds of union of buttons
  234. bl = br = bb = bt = 0
  235. for button in self.buttonList:
  236. bounds = button.stateNodePath[0].getTightBounds()
  237. if bounds is None:
  238. bl = 0
  239. br = 0
  240. bb = 0
  241. bt = 0
  242. else:
  243. bl = min(bl, bounds[0][0])
  244. br = max(br, bounds[1][0])
  245. bb = min(bb, bounds[0][2])
  246. bt = max(bt, bounds[1][2])
  247. bl -= bpad[0]
  248. br += bpad[0]
  249. bb -= bpad[1]
  250. bt += bpad[1]
  251. # Now resize buttons to match largest
  252. for button in self.buttonList:
  253. button['frameSize'] = (bl, br, bb, bt)
  254. # Must compensate for scale
  255. scale = self['button_scale']
  256. # Can either be a Vec3 or a tuple of 3 values
  257. if (isinstance(scale, Vec3) or
  258. (type(scale) == types.ListType) or
  259. (type(scale) == types.TupleType)):
  260. sx = scale[0]
  261. sz = scale[2]
  262. elif ((type(scale) == types.IntType) or
  263. (type(scale) == types.FloatType)):
  264. sx = sz = scale
  265. else:
  266. sx = sz = 1
  267. bl *= sx
  268. br *= sx
  269. bb *= sz
  270. bt *= sz
  271. # Position buttons
  272. # Calc button width and height
  273. bHeight = bt - bb
  274. bWidth = br - bl
  275. # Add pad between buttons
  276. bSpacing = self['buttonPadSF'] * bWidth
  277. bPos = -bSpacing * (self.numButtons - 1)*0.5
  278. index = 0
  279. for button in self.buttonList:
  280. button.setPos(bPos + index * bSpacing, 0,
  281. b - self['midPad'] - bpad[1] - bt)
  282. index += 1
  283. bMax = bPos + bSpacing * (self.numButtons - 1)
  284. else:
  285. bpad = 0
  286. bl = br = bb = bt = 0
  287. bPos = 0
  288. bMax = 0
  289. bpad = (0, 0)
  290. bHeight = bWidth = 0
  291. # Resize frame to fit text and buttons
  292. l = min(bPos + bl, l) - pad[0]
  293. r = max(bMax + br, r) + pad[0]
  294. sidePad = self['sidePad']
  295. l -= sidePad
  296. r += sidePad
  297. # reduce bottom by pad, button height and 2*button pad
  298. b = min(b - self['midPad'] - bpad[1] - bHeight - bpad[1], b) - pad[1]
  299. t = t + self['topPad'] + pad[1]
  300. self['frameSize'] = (l, r, b, t)
  301. self['image_scale'] = (r - l, 1, t - b)
  302. # Center frame about text and buttons
  303. self['image_pos'] = ((l+r)*0.5, 0.0, (b+t)*0.5)
  304. self.resetFrameSize()
  305. def show(self):
  306. if self['fadeScreen']:
  307. base.transitions.fadeScreen(self['fadeScreen'])
  308. self.setBin('gui-popup', 0)
  309. NodePath.show(self)
  310. def hide(self):
  311. if self['fadeScreen']:
  312. base.transitions.noTransitions()
  313. NodePath.hide(self)
  314. def buttonCommand(self, value, event = None):
  315. if self['command']:
  316. self['command'](value, *self['extraArgs'])
  317. def setMessage(self, message):
  318. self['text'] = message
  319. self.configureDialog()
  320. def cleanup(self):
  321. # Remove this panel out of the AllDialogs list
  322. uniqueName = self['dialogName']
  323. if uniqueName in DirectDialog.AllDialogs:
  324. del DirectDialog.AllDialogs[uniqueName]
  325. self.destroy()
  326. def destroy(self):
  327. if self['fadeScreen']:
  328. base.transitions.noTransitions()
  329. for button in self.buttonList:
  330. button.destroy()
  331. DirectFrame.destroy(self)
  332. class OkDialog(DirectDialog):
  333. def __init__(self, parent = None, **kw):
  334. # Inherits from DirectFrame
  335. optiondefs = (
  336. # Define type of DirectGuiWidget
  337. ('buttonTextList', ['OK'], DGG.INITOPT),
  338. ('buttonValueList', [DGG.DIALOG_OK], DGG.INITOPT),
  339. )
  340. # Merge keyword options with default options
  341. self.defineoptions(kw, optiondefs)
  342. DirectDialog.__init__(self, parent)
  343. self.initialiseoptions(OkDialog)
  344. class OkCancelDialog(DirectDialog):
  345. def __init__(self, parent = None, **kw):
  346. # Inherits from DirectFrame
  347. optiondefs = (
  348. # Define type of DirectGuiWidget
  349. ('buttonTextList', ['OK','Cancel'], DGG.INITOPT),
  350. ('buttonValueList', [DGG.DIALOG_OK, DGG.DIALOG_CANCEL], DGG.INITOPT),
  351. )
  352. # Merge keyword options with default options
  353. self.defineoptions(kw, optiondefs)
  354. DirectDialog.__init__(self, parent)
  355. self.initialiseoptions(OkCancelDialog)
  356. class YesNoDialog(DirectDialog):
  357. def __init__(self, parent = None, **kw):
  358. # Inherits from DirectFrame
  359. optiondefs = (
  360. # Define type of DirectGuiWidget
  361. ('buttonTextList', ['Yes', 'No'], DGG.INITOPT),
  362. ('buttonValueList', [DGG.DIALOG_YES, DGG.DIALOG_NO], DGG.INITOPT),
  363. )
  364. # Merge keyword options with default options
  365. self.defineoptions(kw, optiondefs)
  366. DirectDialog.__init__(self, parent)
  367. self.initialiseoptions(YesNoDialog)
  368. class YesNoCancelDialog(DirectDialog):
  369. def __init__(self, parent = None, **kw):
  370. # Inherits from DirectFrame
  371. optiondefs = (
  372. # Define type of DirectGuiWidget
  373. ('buttonTextList', ['Yes', 'No', 'Cancel'], DGG.INITOPT),
  374. ('buttonValueList', [DGG.DIALOG_YES, DGG.DIALOG_NO, DGG.DIALOG_CANCEL],
  375. DGG.INITOPT),
  376. )
  377. # Merge keyword options with default options
  378. self.defineoptions(kw, optiondefs)
  379. DirectDialog.__init__(self, parent)
  380. self.initialiseoptions(YesNoCancelDialog)
  381. class RetryCancelDialog(DirectDialog):
  382. def __init__(self, parent = None, **kw):
  383. # Inherits from DirectFrame
  384. optiondefs = (
  385. # Define type of DirectGuiWidget
  386. ('buttonTextList', ['Retry','Cancel'], DGG.INITOPT),
  387. ('buttonValueList', [DGG.DIALOG_RETRY, DGG.DIALOG_CANCEL], DGG.INITOPT),
  388. )
  389. # Merge keyword options with default options
  390. self.defineoptions(kw, optiondefs)
  391. DirectDialog.__init__(self, parent)
  392. self.initialiseoptions(RetryCancelDialog)