codekingpro/portable-devtools
114k
1# hierlist2#3# IMPORTANT - Please read before using.4 5# This module exposes an API for a Hierarchical Tree Control.6# Previously, a custom tree control was included in Pythonwin which7# has an API very similar to this.8 9# The current control used is the common "Tree Control". This module exists now10# to provide an API similar to the old control, but for the new Tree control.11 12# If you need to use the Tree Control, you may still find this API a reasonable13# choice. However, you should investigate using the tree control directly14# to provide maximum flexibility (but with extra work).15 16import sys17 18import commctrl19import win32api20import win32con21import win32ui22from pywin.mfc import dialog, docview, object, window23from win32api import RGB24 25 26# helper to get the text of an arbitary item27def GetItemText(item):28 if type(item) == type(()) or type(item) == type([]):29 use = item[0]30 else:31 use = item32 if type(use) == type(""):33 return use34 else:35 return repr(item)36 37 38class HierDialog(dialog.Dialog):39 def __init__(40 self,41 title,42 hierList,43 bitmapID=win32ui.IDB_HIERFOLDERS,44 dlgID=win32ui.IDD_TREE,45 dll=None,46 childListBoxID=win32ui.IDC_LIST1,47 ):48 dialog.Dialog.__init__(self, dlgID, dll) # reuse this dialog.49 self.hierList = hierList50 self.dlgID = dlgID51 self.title = title52 53 # self.childListBoxID = childListBoxID54 def OnInitDialog(self):55 self.SetWindowText(self.title)56 self.hierList.HierInit(self)57 return dialog.Dialog.OnInitDialog(self)58 59 60class HierList(object.Object):61 def __init__(62 self, root, bitmapID=win32ui.IDB_HIERFOLDERS, listBoxId=None, bitmapMask=None63 ): # used to create object.64 self.listControl = None65 self.bitmapID = bitmapID66 self.root = root67 self.listBoxId = listBoxId68 self.itemHandleMap = {}69 self.filledItemHandlesMap = {}70 self.bitmapMask = bitmapMask71 72 def __getattr__(self, attr):73 try:74 return getattr(self.listControl, attr)75 except AttributeError:76 return object.Object.__getattr__(self, attr)77 78 def ItemFromHandle(self, handle):79 return self.itemHandleMap[handle]80 81 def SetStyle(self, newStyle):82 hwnd = self.listControl.GetSafeHwnd()83 style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE)84 win32api.SetWindowLong(hwnd, win32con.GWL_STYLE, (style | newStyle))85 86 def HierInit(self, parent, listControl=None): # Used when window first exists.87 # this also calls "Create" on the listbox.88 # params - id of listbbox, ID of bitmap, size of bitmaps89 if self.bitmapMask is None:90 bitmapMask = RGB(0, 0, 255)91 else:92 bitmapMask = self.bitmapMask93 self.imageList = win32ui.CreateImageList(self.bitmapID, 16, 0, bitmapMask)94 if listControl is None:95 if self.listBoxId is None:96 self.listBoxId = win32ui.IDC_LIST197 self.listControl = parent.GetDlgItem(self.listBoxId)98 else:99 self.listControl = listControl100 lbid = listControl.GetDlgCtrlID()101 assert self.listBoxId is None or self.listBoxId == lbid, (102 "An invalid listbox control ID has been specified (specified as %s, but exists as %s)"103 % (self.listBoxId, lbid)104 )105 self.listBoxId = lbid106 self.listControl.SetImageList(self.imageList, commctrl.LVSIL_NORMAL)107 # self.list.AttachObject(self)108 109 ## ??? Need a better way to do this - either some way to detect if it's compiled with UNICODE110 ## defined, and/or a way to switch the constants based on UNICODE ???111 if sys.version_info[0] < 3:112 parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGA)113 parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDA)114 else:115 parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGW)116 parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDW)117 parent.HookNotify(self.OnTreeItemDoubleClick, commctrl.NM_DBLCLK)118 self.notify_parent = parent119 120 if self.root:121 self.AcceptRoot(self.root)122 123 def DeleteAllItems(self):124 self.listControl.DeleteAllItems()125 self.root = None126 self.itemHandleMap = {}127 self.filledItemHandlesMap = {}128 129 def HierTerm(self):130 # Dont want notifies as we kill the list.131 parent = self.notify_parent # GetParentFrame()132 if sys.version_info[0] < 3:133 parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGA)134 parent.HookNotify(None, commctrl.TVN_SELCHANGEDA)135 else:136 parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGW)137 parent.HookNotify(None, commctrl.TVN_SELCHANGEDW)138 parent.HookNotify(None, commctrl.NM_DBLCLK)139 140 self.DeleteAllItems()141 self.listControl = None142 self.notify_parent = None # Break a possible cycle143 144 def OnTreeItemDoubleClick(self, info, extra):145 (hwndFrom, idFrom, code) = info146 if idFrom != self.listBoxId:147 return None148 item = self.itemHandleMap[self.listControl.GetSelectedItem()]149 self.TakeDefaultAction(item)150 return 1151 152 def OnTreeItemExpanding(self, info, extra):153 (hwndFrom, idFrom, code) = info154 if idFrom != self.listBoxId:155 return None156 action, itemOld, itemNew, pt = extra157 itemHandle = itemNew[0]158 if itemHandle not in self.filledItemHandlesMap:159 item = self.itemHandleMap[itemHandle]160 self.AddSubList(itemHandle, self.GetSubList(item))161 self.filledItemHandlesMap[itemHandle] = None162 return 0163 164 def OnTreeItemSelChanged(self, info, extra):165 (hwndFrom, idFrom, code) = info166 if idFrom != self.listBoxId:167 return None168 action, itemOld, itemNew, pt = extra169 itemHandle = itemNew[0]170 item = self.itemHandleMap[itemHandle]171 self.PerformItemSelected(item)172 return 1173 174 def AddSubList(self, parentHandle, subItems):175 for item in subItems:176 self.AddItem(parentHandle, item)177 178 def AddItem(self, parentHandle, item, hInsertAfter=commctrl.TVI_LAST):179 text = self.GetText(item)180 if self.IsExpandable(item):181 cItems = 1 # Trick it !!182 else:183 cItems = 0184 bitmapCol = self.GetBitmapColumn(item)185 bitmapSel = self.GetSelectedBitmapColumn(item)186 if bitmapSel is None:187 bitmapSel = bitmapCol188 ## if type(text) is str:189 ## text = text.encode("mbcs")190 hitem = self.listControl.InsertItem(191 parentHandle,192 hInsertAfter,193 (None, None, None, text, bitmapCol, bitmapSel, cItems, 0),194 )195 self.itemHandleMap[hitem] = item196 return hitem197 198 def _GetChildHandles(self, handle):199 ret = []200 try:201 handle = self.listControl.GetChildItem(handle)202 while 1:203 ret.append(handle)204 handle = self.listControl.GetNextItem(handle, commctrl.TVGN_NEXT)205 except win32ui.error:206 # out of children207 pass208 return ret209 210 def Refresh(self, hparent=None):211 # Attempt to refresh the given item's sub-entries, but maintain the tree state212 # (ie, the selected item, expanded items, etc)213 if hparent is None:214 hparent = commctrl.TVI_ROOT215 if hparent not in self.filledItemHandlesMap:216 # This item has never been expanded, so no refresh can possibly be required.217 return218 root_item = self.itemHandleMap[hparent]219 old_handles = self._GetChildHandles(hparent)220 old_items = list(map(self.ItemFromHandle, old_handles))221 new_items = self.GetSubList(root_item)222 # Now an inefficient technique for synching the items.223 inew = 0224 hAfter = commctrl.TVI_FIRST225 for iold in range(len(old_items)):226 inewlook = inew227 matched = 0228 while inewlook < len(new_items):229 if old_items[iold] == new_items[inewlook]:230 matched = 1231 break232 inewlook = inewlook + 1233 if matched:234 # Insert the new items.235 # print "Inserting after", old_items[iold], old_handles[iold]236 for i in range(inew, inewlook):237 # print "Inserting index %d (%s)" % (i, new_items[i])238 hAfter = self.AddItem(hparent, new_items[i], hAfter)239 240 inew = inewlook + 1241 # And recursively refresh iold242 hold = old_handles[iold]243 if hold in self.filledItemHandlesMap:244 self.Refresh(hold)245 else:246 # Remove the deleted items.247 # print "Deleting %d (%s)" % (iold, old_items[iold])248 hdelete = old_handles[iold]249 # First recurse and remove the children from the map.250 for hchild in self._GetChildHandles(hdelete):251 del self.itemHandleMap[hchild]252 if hchild in self.filledItemHandlesMap:253 del self.filledItemHandlesMap[hchild]254 self.listControl.DeleteItem(hdelete)255 hAfter = old_handles[iold]256 # Fill any remaining new items:257 for newItem in new_items[inew:]:258 # print "Inserting new item", newItem259 self.AddItem(hparent, newItem)260 261 def AcceptRoot(self, root):262 self.listControl.DeleteAllItems()263 self.itemHandleMap = {commctrl.TVI_ROOT: root}264 self.filledItemHandlesMap = {commctrl.TVI_ROOT: root}265 subItems = self.GetSubList(root)266 self.AddSubList(0, subItems)267 268 def GetBitmapColumn(self, item):269 if self.IsExpandable(item):270 return 0271 else:272 return 4273 274 def GetSelectedBitmapColumn(self, item):275 return 0276 277 def CheckChangedChildren(self):278 return self.listControl.CheckChangedChildren()279 280 def GetText(self, item):281 return GetItemText(item)282 283 def PerformItemSelected(self, item):284 try:285 win32ui.SetStatusText("Selected " + self.GetText(item))286 except win32ui.error: # No status bar!287 pass288 289 def TakeDefaultAction(self, item):290 win32ui.MessageBox("Got item " + self.GetText(item))291 292 293##########################################################################294#295# Classes for use with seperate HierListItems.296#297#298class HierListWithItems(HierList):299 def __init__(300 self, root, bitmapID=win32ui.IDB_HIERFOLDERS, listBoxID=None, bitmapMask=None301 ): # used to create object.302 HierList.__init__(self, root, bitmapID, listBoxID, bitmapMask)303 304 def DelegateCall(self, fn):305 return fn()306 307 def GetBitmapColumn(self, item):308 rc = self.DelegateCall(item.GetBitmapColumn)309 if rc is None:310 rc = HierList.GetBitmapColumn(self, item)311 return rc312 313 def GetSelectedBitmapColumn(self, item):314 return self.DelegateCall(item.GetSelectedBitmapColumn)315 316 def IsExpandable(self, item):317 return self.DelegateCall(item.IsExpandable)318 319 def GetText(self, item):320 return self.DelegateCall(item.GetText)321 322 def GetSubList(self, item):323 return self.DelegateCall(item.GetSubList)324 325 def PerformItemSelected(self, item):326 func = getattr(item, "PerformItemSelected", None)327 if func is None:328 return HierList.PerformItemSelected(self, item)329 else:330 return self.DelegateCall(func)331 332 def TakeDefaultAction(self, item):333 func = getattr(item, "TakeDefaultAction", None)334 if func is None:335 return HierList.TakeDefaultAction(self, item)336 else:337 return self.DelegateCall(func)338 339 340# A hier list item - for use with a HierListWithItems341class HierListItem:342 def __init__(self):343 pass344 345 def GetText(self):346 pass347 348 def GetSubList(self):349 pass350 351 def IsExpandable(self):352 pass353 354 def GetBitmapColumn(self):355 return None # indicate he should do it.356 357 def GetSelectedBitmapColumn(self):358 return None # same as other359 360 # for py3k/rich-comp sorting compatibility.361 def __lt__(self, other):362 # we want unrelated items to be sortable...363 return id(self) < id(other)364 365 # for py3k/rich-comp equality compatibility.366 def __eq__(self, other):367 return False368 