DirectDialog.py 16 KB

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