DirectDialog.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. from DirectGuiGlobals import *
  2. from DirectFrame import *
  3. from DirectButton import *
  4. class DirectDialog(DirectFrame):
  5. def __init__(self, parent = guiTop, **kw):
  6. """
  7. DirectDialog(kw)
  8. Creates a popup dialog to alert and/or interact with user.
  9. Some of the main keywords that can be used to customize the dialog:
  10. Keyword Definition
  11. ------- ----------
  12. text Text message/query displayed to user
  13. geom Geometry to be displayed in dialog
  14. buttonTextList List of text to show on each button
  15. buttonGeomList List of geometry to show on each button
  16. buttonImageList List of images to show on each button
  17. buttonValueList List of values sent to dialog command for
  18. each button. If value is [] then the
  19. ordinal rank of the button is used as
  20. its value
  21. buttonHotKeyList List of hotkeys to bind to each button.
  22. Typing hotkey is equivalent to pressing
  23. the corresponding button.
  24. supressKeys Set to true if you wish to supress keys
  25. (i.e. Dialog eats key event), false if
  26. you wish Dialog to pass along key event
  27. buttonSize 4-tuple used to specify custom size for
  28. each button (to make bigger then geom/text
  29. for example)
  30. pad Space between border and interior graphics
  31. topPad Extra space added above text/geom/image
  32. midPad Extra space added between text/buttons
  33. buttonPadSF Scale factor used to expand/contract
  34. button horizontal spacing
  35. command Callback command used when a button is
  36. pressed. Value supplied to command
  37. depends on values in buttonValueList
  38. Note: Number of buttons on the dialog depends upon the maximum
  39. length of any button[Text|Geom|Image|Value]List specified.
  40. Values of None are substituted for lists that are shorter
  41. than the max length
  42. """
  43. # Inherits from DirectFrame
  44. optiondefs = (
  45. # Define type of DirectGuiWidget
  46. ('pad', (0.1, 0.1), None),
  47. ('text', '', None),
  48. ('text_align', TMALIGNLEFT, None),
  49. ('text_scale', 0.06, None),
  50. ('image', getDefaultDialogGeom(), None),
  51. ('relief', None, None),
  52. ('buttonTextList', [], INITOPT),
  53. ('buttonGeomList', [], INITOPT),
  54. ('buttonImageList', [], INITOPT),
  55. ('buttonValueList', [], INITOPT),
  56. ('buttonHotKeyList', [], INITOPT),
  57. ('button_borderWidth',(.01,.01), None),
  58. ('button_pad', (.01,.01), None),
  59. ('button_relief', RAISED, None),
  60. ('button_text_scale' , 0.06, None),
  61. ('buttonSize', None, INITOPT),
  62. ('topPad', 0.06, INITOPT),
  63. ('midPad', 0.12, INITOPT),
  64. ('buttonPadSF', 1.05, INITOPT),
  65. ('command', None, None),
  66. ('extraArgs', [], None),
  67. )
  68. # Merge keyword options with default options
  69. self.defineoptions(kw, optiondefs, dynamicGroups = ("button",))
  70. # Initialize superclasses
  71. DirectFrame.__init__(self, parent)
  72. # Determine number of buttons
  73. self.numButtons = max(len(self['buttonTextList']),
  74. len(self['buttonGeomList']),
  75. len(self['buttonImageList']),
  76. len(self['buttonValueList']))
  77. # Create buttons
  78. self.buttonList = []
  79. index = 0
  80. for i in range(self.numButtons):
  81. name = 'Button' + `i`
  82. try:
  83. text = self['buttonTextList'][i]
  84. except IndexError:
  85. text = None
  86. try:
  87. geom = self['buttonGeomList'][i]
  88. except IndexError:
  89. geom = None
  90. try:
  91. image = self['buttonImageList'][i]
  92. except IndexError:
  93. image = None
  94. try:
  95. value = self['buttonValueList'][i]
  96. except IndexError:
  97. value = i
  98. self['buttonValueList'].append(i)
  99. try:
  100. hotKey = self['buttonHotKeyList'][i]
  101. except IndexError:
  102. hotKey = None
  103. button = self.createcomponent(
  104. name, (), "button",
  105. DirectButton, (self,),
  106. text = text,
  107. geom = geom,
  108. image = image,
  109. suppressKeys = self['suppressKeys'],
  110. frameSize = self['buttonSize'],
  111. command = lambda s = self, v = value: s.buttonCommand(v)
  112. )
  113. self.buttonList.append(button)
  114. # Update dialog when everything has been initialised
  115. self.postInitialiseFuncList.append(self.configureDialog)
  116. self.initialiseoptions(DirectDialog)
  117. def configureDialog(self):
  118. # Set up hot key bindings
  119. bindList = zip(self.buttonList, self['buttonHotKeyList'],
  120. self['buttonValueList'])
  121. for button, hotKey, value in bindList:
  122. if ((type(hotKey) == types.ListType) or
  123. (type(hotKey) == types.TupleType)):
  124. for key in hotKey:
  125. button.bind('press-' + key + '-', self.buttonCommand,
  126. extraArgs = [value])
  127. self.bind('press-' + key + '-', self.buttonCommand,
  128. extraArgs = [value])
  129. else:
  130. button.bind('press-' + hotKey + '-',self.buttonCommand,
  131. extraArgs = [value])
  132. self.bind('press-' + hotKey + '-', self.buttonCommand,
  133. extraArgs = [value])
  134. # Position buttons and text
  135. pad = self['pad']
  136. bpad = self['button_pad']
  137. image = self.component('image0')
  138. # Get size of text/geom without image (for state 0)
  139. if image:
  140. image.reparentTo(hidden)
  141. bounds = self.stateNodePath[0].getTightBounds()
  142. if image:
  143. image.reparentTo(self.stateNodePath[0])
  144. l = bounds[0][0]
  145. r = bounds[1][0]
  146. b = bounds[0][2]
  147. t = bounds[1][2]
  148. # Center text and geom around origin
  149. # How far is center of text from origin?
  150. xOffset = -(l+r)/2.0
  151. zOffset = -(b+t)/2.0
  152. # Update bounds to reflect text movement
  153. l += xOffset
  154. r += xOffset
  155. b += zOffset
  156. t += zOffset
  157. # Offset text and geom to center
  158. if self['text']:
  159. self['text_pos'] = (self['text_pos'][0] + xOffset,
  160. self['text_pos'][1] + zOffset)
  161. if self['geom']:
  162. self['geom_pos'] = Point3(self['geom_pos'][0] + xOffset,
  163. self['geom_pos'][1],
  164. self['geom_pos'][2] + zOffset)
  165. # Get button size
  166. if self['buttonSize']:
  167. # Either use given size
  168. buttonSize = self['buttonSize']
  169. bl = buttonSize[0]
  170. br = buttonSize[1]
  171. bb = buttonSize[2]
  172. bt = buttonSize[3]
  173. else:
  174. # Or get bounds of union of buttons
  175. bl = br = bb = bt = 0
  176. for button in self.buttonList:
  177. bounds = button.stateNodePath[0].getTightBounds()
  178. bl = min(bl, bounds[0][0])
  179. br = max(br, bounds[1][0])
  180. bb = min(bb, bounds[0][2])
  181. bt = max(bt, bounds[1][2])
  182. bl -= bpad[0]
  183. br += bpad[0]
  184. bb -= bpad[1]
  185. bt += bpad[1]
  186. # Now resize buttons to match largest
  187. for button in self.buttonList:
  188. button['frameSize'] = (bl,br,bb,bt)
  189. # Must compensate for scale
  190. scale = self['button_scale']
  191. # Can either be a Vec3 or a tuple of 3 values
  192. if (isinstance(scale, Vec3) or
  193. (type(scale) == types.ListType) or
  194. (type(scale) == types.TupleType)):
  195. sx = scale[0]
  196. sz = scale[2]
  197. elif ((type(scale) == types.IntType) or
  198. (type(scale) == types.FloatType)):
  199. sx = sz = scale
  200. else:
  201. sx = sz = 1
  202. bl *= sx
  203. br *= sx
  204. bb *= sz
  205. bt *= sz
  206. # Position buttons
  207. # Calc button width and height
  208. bHeight = bt - bb
  209. bWidth = br - bl
  210. # Add pad between buttons
  211. bSpacing = self['buttonPadSF'] * bWidth
  212. if self.numButtons != 0:
  213. bPos = -bSpacing * (self.numButtons - 1)/2.0
  214. index = 0
  215. for button in self.buttonList:
  216. button.setPos(bPos + index * bSpacing, 0,
  217. b - self['midPad'] - bpad[1] - bt)
  218. index += 1
  219. bMax = bPos + bSpacing * (self.numButtons - 1)
  220. else:
  221. bPos = 0
  222. bMax = 0
  223. # Resize frame to fit text and buttons
  224. l = min(bPos + bl, l) - pad[0]
  225. r = max(bMax + br, r) + pad[0]
  226. # reduce bottom by pad, button height and 2*button pad
  227. b = min(b - self['midPad'] - bpad[1] - bHeight - bpad[1], b) - pad[1]
  228. t = t + self['topPad'] + pad[1]
  229. self['image_scale'] = (r - l, 1, t - b)
  230. # Center frame about text and buttons
  231. self['image_pos'] = ((l+r)/2.0, 0.0,(b+t)/2.0)
  232. self.resetFrameSize()
  233. def buttonCommand(self, value, event = None):
  234. if self['command']:
  235. self['command'](value)
  236. def destroy(self):
  237. DirectFrame.destroy(self)
  238. class OKDialog(DirectDialog):
  239. def __init__(self, parent = guiTop, **kw):
  240. # Inherits from DirectFrame
  241. optiondefs = (
  242. # Define type of DirectGuiWidget
  243. ('buttonTextList', ['OK'], INITOPT),
  244. ('buttonValueList', [1], INITOPT),
  245. )
  246. # Merge keyword options with default options
  247. self.defineoptions(kw, optiondefs)
  248. DirectDialog.__init__(self, parent)
  249. self.initialiseoptions(OKDialog)
  250. class OKCancelDialog(DirectDialog):
  251. def __init__(self, parent = guiTop, **kw):
  252. # Inherits from DirectFrame
  253. optiondefs = (
  254. # Define type of DirectGuiWidget
  255. ('buttonTextList', ['OK','Cancel'], INITOPT),
  256. ('buttonValueList', [1,-1], INITOPT),
  257. )
  258. # Merge keyword options with default options
  259. self.defineoptions(kw, optiondefs)
  260. DirectDialog.__init__(self, parent)
  261. self.initialiseoptions(OKCancelDialog)
  262. class YesNoDialog(DirectDialog):
  263. def __init__(self, parent = guiTop, **kw):
  264. # Inherits from DirectFrame
  265. optiondefs = (
  266. # Define type of DirectGuiWidget
  267. ('buttonTextList', ['Yes', 'No'], INITOPT),
  268. ('buttonValueList', [1,0], INITOPT),
  269. )
  270. # Merge keyword options with default options
  271. self.defineoptions(kw, optiondefs)
  272. DirectDialog.__init__(self, parent)
  273. self.initialiseoptions(YesNoDialog)
  274. class YesNoCancelDialog(DirectDialog):
  275. def __init__(self, parent = guiTop, **kw):
  276. # Inherits from DirectFrame
  277. optiondefs = (
  278. # Define type of DirectGuiWidget
  279. ('buttonTextList', ['Yes', 'No', 'Cancel'], INITOPT),
  280. ('buttonValueList', [1,0,-1], INITOPT),
  281. )
  282. # Merge keyword options with default options
  283. self.defineoptions(kw, optiondefs)
  284. DirectDialog.__init__(self, parent)
  285. self.initialiseoptions(YesNoCancelDialog)
  286. class RetryCancelDialog(DirectDialog):
  287. def __init__(self, parent = guiTop, **kw):
  288. # Inherits from DirectFrame
  289. optiondefs = (
  290. # Define type of DirectGuiWidget
  291. ('buttonTextList', ['Retry','Cancel'], INITOPT),
  292. ('buttonValueList', [1,-1], INITOPT),
  293. )
  294. # Merge keyword options with default options
  295. self.defineoptions(kw, optiondefs)
  296. DirectDialog.__init__(self, parent)
  297. self.initialiseoptions(RetryCancelDialog)
  298. class TTOkCancelDialog(DirectDialog):
  299. def __init__(self, parent = guiTop, **kw):
  300. # Inherits from DirectDialog
  301. buttons = loader.loadModelOnce(
  302. 'phase_3/models/gui/dialog_box_buttons_gui')
  303. okImageList = (buttons.find('**/ChtBx_OKBtn_UP'),
  304. buttons.find('**/ChtBx_OKBtn_DN'),
  305. buttons.find('**/ChtBx_OKBtn_Rllvr'))
  306. okTextList = ('', 'OK', 'OK')
  307. cancelImageList = (buttons.find('**/CloseBtn_UP'),
  308. buttons.find('**/CloseBtn_DN'),
  309. buttons.find('**/CloseBtn_Rllvr'))
  310. cancelTextList = ('', 'Cancel', 'Cancel')
  311. optiondefs = (
  312. # Define type of DirectGuiWidget
  313. ('buttonTextList', [okTextList, cancelTextList], INITOPT),
  314. ('buttonImageList', [okImageList, cancelImageList], INITOPT),
  315. ('buttonValueList', [1,-1], INITOPT),
  316. ('button_pad', (0,0), None),
  317. ('button_relief', None, None),
  318. ('button_text_pos', (0,-0.1), None),
  319. ('buttonSize', (-.05,.05,-.05,.05), None),
  320. )
  321. # Merge keyword options with default options
  322. self.defineoptions(kw, optiondefs)
  323. DirectDialog.__init__(self, parent)
  324. self.initialiseoptions(TTOkCancelDialog)
  325. buttons.removeNode()