DirectScrolledList.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. """Undocumented Module"""
  2. __all__ = ['DirectScrolledListItem', 'DirectScrolledList']
  3. from pandac.PandaModules import *
  4. import DirectGuiGlobals as DGG
  5. from direct.directnotify import DirectNotifyGlobal
  6. from direct.task.Task import Task
  7. from DirectFrame import *
  8. from DirectButton import *
  9. import string, types
  10. class DirectScrolledListItem(DirectButton):
  11. """
  12. While you are not required to use a DirectScrolledListItem for a
  13. DirectScrolledList, doing so takes care of the highlighting and
  14. unhighlighting of the list items.
  15. """
  16. notify = DirectNotifyGlobal.directNotify.newCategory("DirectScrolledListItem")
  17. def __init__(self, parent=None, **kw):
  18. assert self.notify.debugStateCall(self)
  19. self.parent = parent
  20. if kw.has_key("command"):
  21. self.nextCommand = kw.get("command")
  22. del kw["command"]
  23. if kw.has_key("extraArgs"):
  24. self.nextCommandExtraArgs = kw.get("extraArgs")
  25. del kw["extraArgs"]
  26. optiondefs = (
  27. ('parent', self.parent, None),
  28. ('command', self.select, None),
  29. )
  30. # Merge keyword options with default options
  31. self.defineoptions(kw, optiondefs)
  32. DirectButton.__init__(self)
  33. self.initialiseoptions(DirectScrolledListItem)
  34. def select(self):
  35. assert self.notify.debugStateCall(self)
  36. apply(self.nextCommand, self.nextCommandExtraArgs)
  37. self.parent.selectListItem(self)
  38. class DirectScrolledList(DirectFrame):
  39. notify = DirectNotifyGlobal.directNotify.newCategory("DirectScrolledList")
  40. def __init__(self, parent = None, **kw):
  41. assert self.notify.debugStateCall(self)
  42. self.index = 0
  43. self.forceHeight = None
  44. """ If one were to want a scrolledList that makes and adds its items
  45. as needed, simply pass in an items list of strings (type 'str')
  46. and when that item is needed, itemMakeFunction will be called
  47. with the text, the index, and itemMakeExtraArgs. If itemMakeFunction
  48. is not specified, it will create a DirectFrame with the text."""
  49. # if 'items' is a list of strings, make a copy for our use
  50. # so we can modify it without mangling the user's list
  51. if kw.has_key('items'):
  52. for item in kw['items']:
  53. if type(item) != type(''):
  54. break
  55. else:
  56. # we get here if every item in 'items' is a string
  57. # make a copy
  58. kw['items'] = kw['items'][:]
  59. self.nextItemID = 10
  60. # Inherits from DirectFrame
  61. optiondefs = (
  62. # Define type of DirectGuiWidget
  63. ('items', [], None),
  64. ('itemsAlign', TextNode.ACenter, DGG.INITOPT),
  65. ('itemsWordwrap', None, DGG.INITOPT),
  66. ('command', None, None),
  67. ('extraArgs', [], None),
  68. ('itemMakeFunction', None, None),
  69. ('itemMakeExtraArgs', [], None),
  70. ('numItemsVisible', 1, self.setNumItemsVisible),
  71. ('scrollSpeed', 8, self.setScrollSpeed),
  72. ('forceHeight', None, self.setForceHeight),
  73. )
  74. # Merge keyword options with default options
  75. self.defineoptions(kw, optiondefs)
  76. # Initialize superclasses
  77. DirectFrame.__init__(self, parent)
  78. self.incButton = self.createcomponent("incButton", (), None,
  79. DirectButton, (self,),
  80. )
  81. self.incButton.bind(DGG.B1PRESS, self.__incButtonDown)
  82. self.incButton.bind(DGG.B1RELEASE, self.__buttonUp)
  83. self.decButton = self.createcomponent("decButton", (), None,
  84. DirectButton, (self,),
  85. )
  86. self.decButton.bind(DGG.B1PRESS, self.__decButtonDown)
  87. self.decButton.bind(DGG.B1RELEASE, self.__buttonUp)
  88. self.itemFrame = self.createcomponent("itemFrame", (), None,
  89. DirectFrame, (self,),
  90. )
  91. for item in self["items"]:
  92. if item.__class__.__name__ != 'str':
  93. item.reparentTo(self.itemFrame)
  94. self.initialiseoptions(DirectScrolledList)
  95. self.recordMaxHeight()
  96. #if len(self["items"]) > 0:
  97. # self.scrollTo(0)
  98. self.scrollTo(0)
  99. def setForceHeight(self):
  100. assert self.notify.debugStateCall(self)
  101. self.forceHeight = self["forceHeight"]
  102. def recordMaxHeight(self):
  103. assert self.notify.debugStateCall(self)
  104. if self.forceHeight is not None:
  105. self.maxHeight = self.forceHeight
  106. else:
  107. self.maxHeight = 0.0
  108. for item in self["items"]:
  109. if item.__class__.__name__ != 'str':
  110. self.maxHeight = max(self.maxHeight, item.getHeight())
  111. def setScrollSpeed(self):
  112. assert self.notify.debugStateCall(self)
  113. # Items per second to move
  114. self.scrollSpeed = self["scrollSpeed"]
  115. if self.scrollSpeed <= 0:
  116. self.scrollSpeed = 1
  117. def setNumItemsVisible(self):
  118. assert self.notify.debugStateCall(self)
  119. # Items per second to move
  120. self.numItemsVisible = self["numItemsVisible"]
  121. def destroy(self):
  122. assert self.notify.debugStateCall(self)
  123. taskMgr.remove(self.taskName("scroll"))
  124. if hasattr(self, "currentSelected"):
  125. del self.currentSelected
  126. self.incButton.destroy()
  127. self.decButton.destroy()
  128. DirectFrame.destroy(self)
  129. def selectListItem(self, item):
  130. assert self.notify.debugStateCall(self)
  131. if hasattr(self, "currentSelected"):
  132. self.currentSelected['state']=DGG.NORMAL
  133. item['state']=DGG.DISABLED
  134. self.currentSelected=item
  135. def scrollBy(self, delta):
  136. assert self.notify.debugStateCall(self)
  137. # print "scrollBy[", delta,"]"
  138. return self.scrollTo(self.index + delta)
  139. def getItemIndexForItemID(self, itemID):
  140. assert self.notify.debugStateCall(self)
  141. #for i in range(len(self["items"])):
  142. # print "buttontext[", i,"]", self["items"][i]["text"]
  143. if(len(self["items"])==0):
  144. return 0
  145. if(type(self["items"][0])!=types.InstanceType):
  146. print "warning: getItemIndexForItemID: cant find itemID for non-class list items!"
  147. return 0
  148. for i in range(len(self["items"])):
  149. if(self["items"][i].itemID == itemID):
  150. return i
  151. print "warning: getItemIndexForItemID: item not found!"
  152. return 0
  153. def scrollToItemID(self, itemID, centered=0):
  154. assert self.notify.debugStateCall(self)
  155. self.scrollTo(self.getItemIndexForItemID(itemID), centered)
  156. def scrollTo(self, index, centered=0):
  157. """ scrolls list so selected index is at top, or centered in box"""
  158. assert self.notify.debugStateCall(self)
  159. # print "scrollTo[", index,"] called, len(self[items])=", len(self["items"])," self[numItemsVisible]=", self["numItemsVisible"]
  160. try:
  161. self["numItemsVisible"]
  162. except:
  163. # RAU hack to kill 27637
  164. self.notify.info('crash 27637 fixed!')
  165. return
  166. numItemsVisible=self["numItemsVisible"]
  167. numItemsTotal = len(self["items"])
  168. if(centered):
  169. self.index = index - (numItemsVisible/2)
  170. else:
  171. self.index = index
  172. # Not enough items to even worry about scrolling,
  173. # just disable the buttons and do nothing
  174. if (len(self["items"]) <= numItemsVisible):
  175. self.incButton['state'] = DGG.DISABLED
  176. self.decButton['state'] = DGG.DISABLED
  177. # Hmm.. just reset self.index to 0 and bail out
  178. self.index = 0
  179. ret = 0
  180. else:
  181. if (self.index <= 0):
  182. self.index = 0
  183. self.decButton['state'] = DGG.DISABLED
  184. self.incButton['state'] = DGG.NORMAL
  185. ret = 0
  186. elif (self.index >= (numItemsTotal - numItemsVisible)):
  187. self.index = numItemsTotal - numItemsVisible
  188. # print "at list end, ", len(self["items"])," ", self["numItemsVisible"]
  189. self.incButton['state'] = DGG.DISABLED
  190. self.decButton['state'] = DGG.NORMAL
  191. ret = 0
  192. else:
  193. self.incButton['state'] = DGG.NORMAL
  194. self.decButton['state'] = DGG.NORMAL
  195. ret = 1
  196. # print "self.index set to ", self.index
  197. # Hide them all
  198. for item in self["items"]:
  199. if item.__class__.__name__ != 'str':
  200. item.hide()
  201. # Then show the ones in range, and stack their positions
  202. upperRange = min(numItemsTotal, numItemsVisible)
  203. for i in range(self.index, self.index + upperRange):
  204. item = self["items"][i]
  205. #print "stacking buttontext[", i,"]", self["items"][i]["text"]
  206. # If the item is a 'str', then it has not been created (scrolled list is 'as needed')
  207. # Therefore, use the the function given to make it or just make it a frame
  208. if item.__class__.__name__ == 'str':
  209. if self['itemMakeFunction']:
  210. # If there is a function to create the item
  211. item = apply(self['itemMakeFunction'], (item, i, self['itemMakeExtraArgs']))
  212. else:
  213. item = DirectFrame(text = item,
  214. text_align = self['itemsAlign'],
  215. text_wordwrap = self['itemsWordwrap'],
  216. relief = None)
  217. #print "str stacking buttontext[", i,"]", self["items"][i]["text"]
  218. # Then add the newly formed item back into the normal item list
  219. self["items"][i] = item
  220. item.reparentTo(self.itemFrame)
  221. self.recordMaxHeight()
  222. item.show()
  223. item.setPos(0, 0, -(i-self.index) * self.maxHeight)
  224. #print 'height bug tracker: i-%s idx-%s h-%s' % (i, self.index, self.maxHeight)
  225. if self['command']:
  226. # Pass any extra args to command
  227. apply(self['command'], self['extraArgs'])
  228. return ret
  229. def makeAllItems(self):
  230. assert self.notify.debugStateCall(self)
  231. for i in range(len(self['items'])):
  232. item = self["items"][i]
  233. # If the item is a 'str', then it has not been created
  234. # Therefore, use the the function given to make it or
  235. # just make it a frame
  236. print "Making " + str(item)
  237. if item.__class__.__name__ == 'str':
  238. if self['itemMakeFunction']:
  239. # If there is a function to create the item
  240. item = apply(self['itemMakeFunction'],
  241. (item, i, self['itemMakeExtraArgs']))
  242. else:
  243. item = DirectFrame(text = item,
  244. text_align = self['itemsAlign'],
  245. text_wordwrap = self['itemsWordwrap'],
  246. relief = None)
  247. # Then add the newly formed item back into the normal item list
  248. self["items"][i] = item
  249. item.reparentTo(self.itemFrame)
  250. self.recordMaxHeight()
  251. def __scrollByTask(self, task):
  252. assert self.notify.debugStateCall(self)
  253. if ((task.time - task.prevTime) < task.delayTime):
  254. return Task.cont
  255. else:
  256. ret = self.scrollBy(task.delta)
  257. task.prevTime = task.time
  258. if ret:
  259. return Task.cont
  260. else:
  261. return Task.done
  262. def __incButtonDown(self, event):
  263. assert self.notify.debugStateCall(self)
  264. task = Task(self.__scrollByTask)
  265. task.delayTime = (1.0 / self.scrollSpeed)
  266. task.prevTime = 0.0
  267. task.delta = 1
  268. self.scrollBy(task.delta)
  269. taskMgr.add(task, self.taskName("scroll"))
  270. def __decButtonDown(self, event):
  271. assert self.notify.debugStateCall(self)
  272. task = Task(self.__scrollByTask)
  273. task.delayTime = (1.0 / self.scrollSpeed)
  274. task.prevTime = 0.0
  275. task.delta = -1
  276. self.scrollBy(task.delta)
  277. taskMgr.add(task, self.taskName("scroll"))
  278. def __buttonUp(self, event):
  279. assert self.notify.debugStateCall(self)
  280. taskMgr.remove(self.taskName("scroll"))
  281. def addItem(self, item, refresh=1):
  282. """
  283. Add this string and extraArg to the list
  284. """
  285. assert self.notify.debugStateCall(self)
  286. if(type(item) == types.InstanceType):
  287. # cant add attribs to non-classes (like strings & ints)
  288. item.itemID = self.nextItemID
  289. self.nextItemID += 1
  290. self['items'].append(item)
  291. if type(item) != type(''):
  292. item.reparentTo(self.itemFrame)
  293. if refresh:
  294. self.refresh()
  295. if(type(item) == types.InstanceType):
  296. return item.itemID # to pass to scrollToItemID
  297. def removeItem(self, item, refresh=1):
  298. """
  299. Remove this item from the panel
  300. """
  301. assert self.notify.debugStateCall(self)
  302. #print "remove item called", item
  303. #print "items list", self['items']
  304. if item in self["items"]:
  305. #print "removing item", item
  306. if hasattr(self, "currentSelected") and self.currentSelected is item:
  307. del self.currentSelected
  308. self["items"].remove(item)
  309. if type(item) != type(''):
  310. item.reparentTo(hidden)
  311. self.refresh()
  312. return 1
  313. else:
  314. return 0
  315. def removeAllItems(self, refresh=1):
  316. """
  317. Remove this item from the panel
  318. Warning 2006_10_19 tested only in the trolley metagame
  319. """
  320. assert self.notify.debugStateCall(self)
  321. retval = 0
  322. #print "remove item called", item
  323. #print "items list", self['items']
  324. while len (self["items"]):
  325. item = self['items'][0]
  326. #print "removing item", item
  327. if hasattr(self, "currentSelected") and self.currentSelected is item:
  328. del self.currentSelected
  329. self["items"].remove(item)
  330. if type(item) != type(''):
  331. #RAU possible leak here, let's try to do the right thing
  332. #item.reparentTo(hidden)
  333. item.removeNode()
  334. retval = 1
  335. if (refresh):
  336. self.refresh()
  337. return retval
  338. def refresh(self):
  339. """
  340. Update the list - useful when adding or deleting items
  341. or changing properties that would affect the scrolling
  342. """
  343. assert self.notify.debugStateCall(self)
  344. self.recordMaxHeight()
  345. #print "refresh called"
  346. self.scrollTo(self.index)
  347. def getSelectedIndex(self):
  348. assert self.notify.debugStateCall(self)
  349. return self.index
  350. def getSelectedText(self):
  351. assert self.notify.debugStateCall(self)
  352. return self['items'][self.index]['text']
  353. """
  354. from DirectGui import *
  355. def makeButton(itemName, itemNum, *extraArgs):
  356. def buttonCommand():
  357. print itemName, itemNum
  358. return DirectButton(text = itemName,
  359. relief = DGG.RAISED,
  360. frameSize = (-3.5, 3.5, -0.2, 0.8),
  361. scale = 0.85,
  362. command = buttonCommand)
  363. s = scrollList = DirectScrolledList(
  364. parent = aspect2d,
  365. relief = None,
  366. # Use the default dialog box image as the background
  367. image = DGG.getDefaultDialogGeom(),
  368. # Scale it to fit around everyting
  369. image_scale = (0.7, 1, .8),
  370. # Give it a label
  371. text = "Scrolled List Example",
  372. text_scale = 0.06,
  373. text_align = TextNode.ACenter,
  374. text_pos = (0, 0.3),
  375. text_fg = (0, 0, 0, 1),
  376. # inc and dec are DirectButtons
  377. # They can contain a combination of text, geometry and images
  378. # Just a simple text one for now
  379. incButton_text = 'Increment',
  380. incButton_relief = DGG.RAISED,
  381. incButton_pos = (0.0, 0.0, -0.36),
  382. incButton_scale = 0.1,
  383. # Same for the decrement button
  384. decButton_text = 'Decrement',
  385. decButton_relief = DGG.RAISED,
  386. decButton_pos = (0.0, 0.0, 0.175),
  387. decButton_scale = 0.1,
  388. # each item is a button with text on it
  389. numItemsVisible = 4,
  390. itemMakeFunction = makeButton,
  391. items = ['Able', 'Baker', 'Charlie', 'Delta', 'Echo', 'Foxtrot',
  392. 'Golf', 'Hotel', 'India', 'Juliet', 'Kilo', 'Lima'],
  393. # itemFrame is a DirectFrame
  394. # Use it to scale up or down the items and to place it relative
  395. # to eveything else
  396. itemFrame_pos = (0, 0, 0.06),
  397. itemFrame_scale = 0.1,
  398. itemFrame_frameSize = (-3.1, 3.1, -3.3, 0.8),
  399. itemFrame_relief = DGG.GROOVE,
  400. )
  401. """