hierlist.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. # hierlist
  2. #
  3. # IMPORTANT - Please read before using.
  4. # This module exposes an API for a Hierarchical Tree Control.
  5. # Previously, a custom tree control was included in Pythonwin which
  6. # has an API very similar to this.
  7. # The current control used is the common "Tree Control". This module exists now
  8. # to provide an API similar to the old control, but for the new Tree control.
  9. # If you need to use the Tree Control, you may still find this API a reasonable
  10. # choice. However, you should investigate using the tree control directly
  11. # to provide maximum flexibility (but with extra work).
  12. import sys
  13. import win32ui
  14. import win32con
  15. import win32api
  16. from win32api import RGB
  17. from pywin.mfc import object, window, docview, dialog
  18. import commctrl
  19. # helper to get the text of an arbitary item
  20. def GetItemText(item):
  21. if type(item)==type(()) or type(item)==type([]):
  22. use = item[0]
  23. else:
  24. use = item
  25. if type(use)==type(''):
  26. return use
  27. else:
  28. return repr(item)
  29. class HierDialog(dialog.Dialog):
  30. def __init__(self, title, hierList, bitmapID = win32ui.IDB_HIERFOLDERS, dlgID = win32ui.IDD_TREE, dll = None, childListBoxID = win32ui.IDC_LIST1):
  31. dialog.Dialog.__init__(self, dlgID, dll ) # reuse this dialog.
  32. self.hierList=hierList
  33. self.dlgID = dlgID
  34. self.title=title
  35. # self.childListBoxID = childListBoxID
  36. def OnInitDialog(self):
  37. self.SetWindowText(self.title)
  38. self.hierList.HierInit(self)
  39. return dialog.Dialog.OnInitDialog(self)
  40. class HierList(object.Object):
  41. def __init__(self, root, bitmapID = win32ui.IDB_HIERFOLDERS, listBoxId = None, bitmapMask = None): # used to create object.
  42. self.listControl = None
  43. self.bitmapID = bitmapID
  44. self.root = root
  45. self.listBoxId = listBoxId
  46. self.itemHandleMap = {}
  47. self.filledItemHandlesMap = {}
  48. self.bitmapMask = bitmapMask
  49. def __getattr__(self, attr):
  50. try:
  51. return getattr(self.listControl, attr)
  52. except AttributeError:
  53. return object.Object.__getattr__(self, attr)
  54. def ItemFromHandle(self, handle):
  55. return self.itemHandleMap[handle]
  56. def SetStyle(self, newStyle):
  57. hwnd = self.listControl.GetSafeHwnd()
  58. style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE);
  59. win32api.SetWindowLong(hwnd, win32con.GWL_STYLE, (style | newStyle) )
  60. def HierInit(self, parent, listControl = None ): # Used when window first exists.
  61. # this also calls "Create" on the listbox.
  62. # params - id of listbbox, ID of bitmap, size of bitmaps
  63. if self.bitmapMask is None:
  64. bitmapMask = RGB(0,0,255)
  65. else:
  66. bitmapMask = self.bitmapMask
  67. self.imageList = win32ui.CreateImageList(self.bitmapID, 16, 0, bitmapMask)
  68. if listControl is None:
  69. if self.listBoxId is None: self.listBoxId = win32ui.IDC_LIST1
  70. self.listControl = parent.GetDlgItem(self.listBoxId)
  71. else:
  72. self.listControl = listControl
  73. lbid = listControl.GetDlgCtrlID()
  74. assert self.listBoxId is None or self.listBoxId == lbid, "An invalid listbox control ID has been specified (specified as %s, but exists as %s)" % (self.listBoxId, lbid)
  75. self.listBoxId = lbid
  76. self.listControl.SetImageList(self.imageList, commctrl.LVSIL_NORMAL)
  77. # self.list.AttachObject(self)
  78. ## ??? Need a better way to do this - either some way to detect if it's compiled with UNICODE
  79. ## defined, and/or a way to switch the constants based on UNICODE ???
  80. if sys.version_info[0] < 3:
  81. parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGA)
  82. parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDA)
  83. else:
  84. parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGW)
  85. parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDW)
  86. parent.HookNotify(self.OnTreeItemDoubleClick, commctrl.NM_DBLCLK)
  87. self.notify_parent = parent
  88. if self.root:
  89. self.AcceptRoot(self.root)
  90. def DeleteAllItems(self):
  91. self.listControl.DeleteAllItems()
  92. self.root = None
  93. self.itemHandleMap = {}
  94. self.filledItemHandlesMap = {}
  95. def HierTerm(self):
  96. # Dont want notifies as we kill the list.
  97. parent = self.notify_parent # GetParentFrame()
  98. if sys.version_info[0] < 3:
  99. parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGA)
  100. parent.HookNotify(None, commctrl.TVN_SELCHANGEDA)
  101. else:
  102. parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGW)
  103. parent.HookNotify(None, commctrl.TVN_SELCHANGEDW)
  104. parent.HookNotify(None, commctrl.NM_DBLCLK)
  105. self.DeleteAllItems()
  106. self.listControl = None
  107. self.notify_parent = None # Break a possible cycle
  108. def OnTreeItemDoubleClick(self, info, extra):
  109. (hwndFrom, idFrom, code) = info
  110. if idFrom != self.listBoxId: return None
  111. item = self.itemHandleMap[self.listControl.GetSelectedItem()]
  112. self.TakeDefaultAction(item)
  113. return 1
  114. def OnTreeItemExpanding(self, info, extra):
  115. (hwndFrom, idFrom, code) = info
  116. if idFrom != self.listBoxId: return None
  117. action, itemOld, itemNew, pt = extra
  118. itemHandle = itemNew[0]
  119. if itemHandle not in self.filledItemHandlesMap:
  120. item = self.itemHandleMap[itemHandle]
  121. self.AddSubList(itemHandle, self.GetSubList(item))
  122. self.filledItemHandlesMap[itemHandle] = None
  123. return 0
  124. def OnTreeItemSelChanged(self, info, extra):
  125. (hwndFrom, idFrom, code) = info
  126. if idFrom != self.listBoxId: return None
  127. action, itemOld, itemNew, pt = extra
  128. itemHandle = itemNew[0]
  129. item = self.itemHandleMap[itemHandle]
  130. self.PerformItemSelected(item)
  131. return 1
  132. def AddSubList(self, parentHandle, subItems):
  133. for item in subItems:
  134. self.AddItem(parentHandle, item)
  135. def AddItem(self, parentHandle, item, hInsertAfter = commctrl.TVI_LAST):
  136. text = self.GetText(item)
  137. if self.IsExpandable(item):
  138. cItems = 1 # Trick it !!
  139. else:
  140. cItems = 0
  141. bitmapCol = self.GetBitmapColumn(item)
  142. bitmapSel = self.GetSelectedBitmapColumn(item)
  143. if bitmapSel is None: bitmapSel = bitmapCol
  144. ## if type(text) is str:
  145. ## text = text.encode("mbcs")
  146. hitem = self.listControl.InsertItem(parentHandle, hInsertAfter, (None, None, None, text, bitmapCol, bitmapSel, cItems, 0))
  147. self.itemHandleMap[hitem] = item
  148. return hitem
  149. def _GetChildHandles(self, handle):
  150. ret = []
  151. try:
  152. handle = self.listControl.GetChildItem(handle)
  153. while 1:
  154. ret.append(handle)
  155. handle = self.listControl.GetNextItem(handle, commctrl.TVGN_NEXT)
  156. except win32ui.error:
  157. # out of children
  158. pass
  159. return ret
  160. def ItemFromHandle(self, handle):
  161. return self.itemHandleMap[handle]
  162. def Refresh(self, hparent = None):
  163. # Attempt to refresh the given item's sub-entries, but maintain the tree state
  164. # (ie, the selected item, expanded items, etc)
  165. if hparent is None: hparent = commctrl.TVI_ROOT
  166. if hparent not in self.filledItemHandlesMap:
  167. # This item has never been expanded, so no refresh can possibly be required.
  168. return
  169. root_item = self.itemHandleMap[hparent]
  170. old_handles = self._GetChildHandles(hparent)
  171. old_items = list(map( self.ItemFromHandle, old_handles ))
  172. new_items = self.GetSubList(root_item)
  173. # Now an inefficient technique for synching the items.
  174. inew = 0
  175. hAfter = commctrl.TVI_FIRST
  176. for iold in range(len(old_items)):
  177. inewlook = inew
  178. matched = 0
  179. while inewlook < len(new_items):
  180. if old_items[iold] == new_items[inewlook]:
  181. matched = 1
  182. break
  183. inewlook = inewlook + 1
  184. if matched:
  185. # Insert the new items.
  186. # print "Inserting after", old_items[iold], old_handles[iold]
  187. for i in range(inew, inewlook):
  188. # print "Inserting index %d (%s)" % (i, new_items[i])
  189. hAfter = self.AddItem(hparent, new_items[i], hAfter)
  190. inew = inewlook + 1
  191. # And recursively refresh iold
  192. hold = old_handles[iold]
  193. if hold in self.filledItemHandlesMap:
  194. self.Refresh(hold)
  195. else:
  196. # Remove the deleted items.
  197. # print "Deleting %d (%s)" % (iold, old_items[iold])
  198. hdelete = old_handles[iold]
  199. # First recurse and remove the children from the map.
  200. for hchild in self._GetChildHandles(hdelete):
  201. del self.itemHandleMap[hchild]
  202. if hchild in self.filledItemHandlesMap:
  203. del self.filledItemHandlesMap[hchild]
  204. self.listControl.DeleteItem(hdelete)
  205. hAfter = old_handles[iold]
  206. # Fill any remaining new items:
  207. for newItem in new_items[inew:]:
  208. # print "Inserting new item", newItem
  209. self.AddItem(hparent, newItem)
  210. def AcceptRoot(self, root):
  211. self.listControl.DeleteAllItems()
  212. self.itemHandleMap = {commctrl.TVI_ROOT : root}
  213. self.filledItemHandlesMap = {commctrl.TVI_ROOT : root}
  214. subItems = self.GetSubList(root)
  215. self.AddSubList(0, subItems)
  216. def GetBitmapColumn(self, item):
  217. if self.IsExpandable(item):
  218. return 0
  219. else:
  220. return 4
  221. def GetSelectedBitmapColumn(self, item):
  222. return None # Use standard.
  223. def GetSelectedBitmapColumn(self, item):
  224. return 0
  225. def CheckChangedChildren(self):
  226. return self.listControl.CheckChangedChildren()
  227. def GetText(self,item):
  228. return GetItemText(item)
  229. def PerformItemSelected(self, item):
  230. try:
  231. win32ui.SetStatusText('Selected ' + self.GetText(item))
  232. except win32ui.error: # No status bar!
  233. pass
  234. def TakeDefaultAction(self, item):
  235. win32ui.MessageBox('Got item ' + self.GetText(item))
  236. ##########################################################################
  237. #
  238. # Classes for use with seperate HierListItems.
  239. #
  240. #
  241. class HierListWithItems(HierList):
  242. def __init__(self, root, bitmapID = win32ui.IDB_HIERFOLDERS, listBoxID = None, bitmapMask = None): # used to create object.
  243. HierList.__init__(self, root, bitmapID, listBoxID, bitmapMask )
  244. def DelegateCall( self, fn):
  245. return fn()
  246. def GetBitmapColumn(self, item):
  247. rc = self.DelegateCall(item.GetBitmapColumn)
  248. if rc is None:
  249. rc = HierList.GetBitmapColumn(self, item)
  250. return rc
  251. def GetSelectedBitmapColumn(self, item):
  252. return self.DelegateCall(item.GetSelectedBitmapColumn)
  253. def IsExpandable(self, item):
  254. return self.DelegateCall( item.IsExpandable)
  255. def GetText(self, item):
  256. return self.DelegateCall( item.GetText )
  257. def GetSubList(self, item):
  258. return self.DelegateCall(item.GetSubList)
  259. def PerformItemSelected(self, item):
  260. func = getattr(item, "PerformItemSelected", None)
  261. if func is None:
  262. return HierList.PerformItemSelected( self, item )
  263. else:
  264. return self.DelegateCall(func)
  265. def TakeDefaultAction(self, item):
  266. func = getattr(item, "TakeDefaultAction", None)
  267. if func is None:
  268. return HierList.TakeDefaultAction( self, item )
  269. else:
  270. return self.DelegateCall(func)
  271. # A hier list item - for use with a HierListWithItems
  272. class HierListItem:
  273. def __init__(self):
  274. pass
  275. def GetText(self):
  276. pass
  277. def GetSubList(self):
  278. pass
  279. def IsExpandable(self):
  280. pass
  281. def GetBitmapColumn(self):
  282. return None # indicate he should do it.
  283. def GetSelectedBitmapColumn(self):
  284. return None # same as other
  285. # for py3k/rich-comp sorting compatibility.
  286. def __lt__(self, other):
  287. # we want unrelated items to be sortable...
  288. return id(self) < id(other)
  289. # for py3k/rich-comp equality compatibility.
  290. def __eq__(self, other):
  291. return False