Spaces:
Sleeping
Sleeping
| # hierlist | |
| # | |
| # IMPORTANT - Please read before using. | |
| # This module exposes an API for a Hierarchical Tree Control. | |
| # Previously, a custom tree control was included in Pythonwin which | |
| # has an API very similar to this. | |
| # The current control used is the common "Tree Control". This module exists now | |
| # to provide an API similar to the old control, but for the new Tree control. | |
| # If you need to use the Tree Control, you may still find this API a reasonable | |
| # choice. However, you should investigate using the tree control directly | |
| # to provide maximum flexibility (but with extra work). | |
| import sys | |
| import commctrl | |
| import win32api | |
| import win32con | |
| import win32ui | |
| from pywin.mfc import dialog, docview, object, window | |
| from win32api import RGB | |
| # helper to get the text of an arbitary item | |
| def GetItemText(item): | |
| if type(item) == type(()) or type(item) == type([]): | |
| use = item[0] | |
| else: | |
| use = item | |
| if type(use) == type(""): | |
| return use | |
| else: | |
| return repr(item) | |
| class HierDialog(dialog.Dialog): | |
| def __init__( | |
| self, | |
| title, | |
| hierList, | |
| bitmapID=win32ui.IDB_HIERFOLDERS, | |
| dlgID=win32ui.IDD_TREE, | |
| dll=None, | |
| childListBoxID=win32ui.IDC_LIST1, | |
| ): | |
| dialog.Dialog.__init__(self, dlgID, dll) # reuse this dialog. | |
| self.hierList = hierList | |
| self.dlgID = dlgID | |
| self.title = title | |
| # self.childListBoxID = childListBoxID | |
| def OnInitDialog(self): | |
| self.SetWindowText(self.title) | |
| self.hierList.HierInit(self) | |
| return dialog.Dialog.OnInitDialog(self) | |
| class HierList(object.Object): | |
| def __init__( | |
| self, root, bitmapID=win32ui.IDB_HIERFOLDERS, listBoxId=None, bitmapMask=None | |
| ): # used to create object. | |
| self.listControl = None | |
| self.bitmapID = bitmapID | |
| self.root = root | |
| self.listBoxId = listBoxId | |
| self.itemHandleMap = {} | |
| self.filledItemHandlesMap = {} | |
| self.bitmapMask = bitmapMask | |
| def __getattr__(self, attr): | |
| try: | |
| return getattr(self.listControl, attr) | |
| except AttributeError: | |
| return object.Object.__getattr__(self, attr) | |
| def ItemFromHandle(self, handle): | |
| return self.itemHandleMap[handle] | |
| def SetStyle(self, newStyle): | |
| hwnd = self.listControl.GetSafeHwnd() | |
| style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE) | |
| win32api.SetWindowLong(hwnd, win32con.GWL_STYLE, (style | newStyle)) | |
| def HierInit(self, parent, listControl=None): # Used when window first exists. | |
| # this also calls "Create" on the listbox. | |
| # params - id of listbbox, ID of bitmap, size of bitmaps | |
| if self.bitmapMask is None: | |
| bitmapMask = RGB(0, 0, 255) | |
| else: | |
| bitmapMask = self.bitmapMask | |
| self.imageList = win32ui.CreateImageList(self.bitmapID, 16, 0, bitmapMask) | |
| if listControl is None: | |
| if self.listBoxId is None: | |
| self.listBoxId = win32ui.IDC_LIST1 | |
| self.listControl = parent.GetDlgItem(self.listBoxId) | |
| else: | |
| self.listControl = listControl | |
| lbid = listControl.GetDlgCtrlID() | |
| 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) | |
| ) | |
| self.listBoxId = lbid | |
| self.listControl.SetImageList(self.imageList, commctrl.LVSIL_NORMAL) | |
| # self.list.AttachObject(self) | |
| ## ??? Need a better way to do this - either some way to detect if it's compiled with UNICODE | |
| ## defined, and/or a way to switch the constants based on UNICODE ??? | |
| if sys.version_info[0] < 3: | |
| parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGA) | |
| parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDA) | |
| else: | |
| parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGW) | |
| parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDW) | |
| parent.HookNotify(self.OnTreeItemDoubleClick, commctrl.NM_DBLCLK) | |
| self.notify_parent = parent | |
| if self.root: | |
| self.AcceptRoot(self.root) | |
| def DeleteAllItems(self): | |
| self.listControl.DeleteAllItems() | |
| self.root = None | |
| self.itemHandleMap = {} | |
| self.filledItemHandlesMap = {} | |
| def HierTerm(self): | |
| # Dont want notifies as we kill the list. | |
| parent = self.notify_parent # GetParentFrame() | |
| if sys.version_info[0] < 3: | |
| parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGA) | |
| parent.HookNotify(None, commctrl.TVN_SELCHANGEDA) | |
| else: | |
| parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGW) | |
| parent.HookNotify(None, commctrl.TVN_SELCHANGEDW) | |
| parent.HookNotify(None, commctrl.NM_DBLCLK) | |
| self.DeleteAllItems() | |
| self.listControl = None | |
| self.notify_parent = None # Break a possible cycle | |
| def OnTreeItemDoubleClick(self, info, extra): | |
| (hwndFrom, idFrom, code) = info | |
| if idFrom != self.listBoxId: | |
| return None | |
| item = self.itemHandleMap[self.listControl.GetSelectedItem()] | |
| self.TakeDefaultAction(item) | |
| return 1 | |
| def OnTreeItemExpanding(self, info, extra): | |
| (hwndFrom, idFrom, code) = info | |
| if idFrom != self.listBoxId: | |
| return None | |
| action, itemOld, itemNew, pt = extra | |
| itemHandle = itemNew[0] | |
| if itemHandle not in self.filledItemHandlesMap: | |
| item = self.itemHandleMap[itemHandle] | |
| self.AddSubList(itemHandle, self.GetSubList(item)) | |
| self.filledItemHandlesMap[itemHandle] = None | |
| return 0 | |
| def OnTreeItemSelChanged(self, info, extra): | |
| (hwndFrom, idFrom, code) = info | |
| if idFrom != self.listBoxId: | |
| return None | |
| action, itemOld, itemNew, pt = extra | |
| itemHandle = itemNew[0] | |
| item = self.itemHandleMap[itemHandle] | |
| self.PerformItemSelected(item) | |
| return 1 | |
| def AddSubList(self, parentHandle, subItems): | |
| for item in subItems: | |
| self.AddItem(parentHandle, item) | |
| def AddItem(self, parentHandle, item, hInsertAfter=commctrl.TVI_LAST): | |
| text = self.GetText(item) | |
| if self.IsExpandable(item): | |
| cItems = 1 # Trick it !! | |
| else: | |
| cItems = 0 | |
| bitmapCol = self.GetBitmapColumn(item) | |
| bitmapSel = self.GetSelectedBitmapColumn(item) | |
| if bitmapSel is None: | |
| bitmapSel = bitmapCol | |
| ## if type(text) is str: | |
| ## text = text.encode("mbcs") | |
| hitem = self.listControl.InsertItem( | |
| parentHandle, | |
| hInsertAfter, | |
| (None, None, None, text, bitmapCol, bitmapSel, cItems, 0), | |
| ) | |
| self.itemHandleMap[hitem] = item | |
| return hitem | |
| def _GetChildHandles(self, handle): | |
| ret = [] | |
| try: | |
| handle = self.listControl.GetChildItem(handle) | |
| while 1: | |
| ret.append(handle) | |
| handle = self.listControl.GetNextItem(handle, commctrl.TVGN_NEXT) | |
| except win32ui.error: | |
| # out of children | |
| pass | |
| return ret | |
| def Refresh(self, hparent=None): | |
| # Attempt to refresh the given item's sub-entries, but maintain the tree state | |
| # (ie, the selected item, expanded items, etc) | |
| if hparent is None: | |
| hparent = commctrl.TVI_ROOT | |
| if hparent not in self.filledItemHandlesMap: | |
| # This item has never been expanded, so no refresh can possibly be required. | |
| return | |
| root_item = self.itemHandleMap[hparent] | |
| old_handles = self._GetChildHandles(hparent) | |
| old_items = list(map(self.ItemFromHandle, old_handles)) | |
| new_items = self.GetSubList(root_item) | |
| # Now an inefficient technique for synching the items. | |
| inew = 0 | |
| hAfter = commctrl.TVI_FIRST | |
| for iold in range(len(old_items)): | |
| inewlook = inew | |
| matched = 0 | |
| while inewlook < len(new_items): | |
| if old_items[iold] == new_items[inewlook]: | |
| matched = 1 | |
| break | |
| inewlook = inewlook + 1 | |
| if matched: | |
| # Insert the new items. | |
| # print "Inserting after", old_items[iold], old_handles[iold] | |
| for i in range(inew, inewlook): | |
| # print "Inserting index %d (%s)" % (i, new_items[i]) | |
| hAfter = self.AddItem(hparent, new_items[i], hAfter) | |
| inew = inewlook + 1 | |
| # And recursively refresh iold | |
| hold = old_handles[iold] | |
| if hold in self.filledItemHandlesMap: | |
| self.Refresh(hold) | |
| else: | |
| # Remove the deleted items. | |
| # print "Deleting %d (%s)" % (iold, old_items[iold]) | |
| hdelete = old_handles[iold] | |
| # First recurse and remove the children from the map. | |
| for hchild in self._GetChildHandles(hdelete): | |
| del self.itemHandleMap[hchild] | |
| if hchild in self.filledItemHandlesMap: | |
| del self.filledItemHandlesMap[hchild] | |
| self.listControl.DeleteItem(hdelete) | |
| hAfter = old_handles[iold] | |
| # Fill any remaining new items: | |
| for newItem in new_items[inew:]: | |
| # print "Inserting new item", newItem | |
| self.AddItem(hparent, newItem) | |
| def AcceptRoot(self, root): | |
| self.listControl.DeleteAllItems() | |
| self.itemHandleMap = {commctrl.TVI_ROOT: root} | |
| self.filledItemHandlesMap = {commctrl.TVI_ROOT: root} | |
| subItems = self.GetSubList(root) | |
| self.AddSubList(0, subItems) | |
| def GetBitmapColumn(self, item): | |
| if self.IsExpandable(item): | |
| return 0 | |
| else: | |
| return 4 | |
| def GetSelectedBitmapColumn(self, item): | |
| return 0 | |
| def CheckChangedChildren(self): | |
| return self.listControl.CheckChangedChildren() | |
| def GetText(self, item): | |
| return GetItemText(item) | |
| def PerformItemSelected(self, item): | |
| try: | |
| win32ui.SetStatusText("Selected " + self.GetText(item)) | |
| except win32ui.error: # No status bar! | |
| pass | |
| def TakeDefaultAction(self, item): | |
| win32ui.MessageBox("Got item " + self.GetText(item)) | |
| ########################################################################## | |
| # | |
| # Classes for use with seperate HierListItems. | |
| # | |
| # | |
| class HierListWithItems(HierList): | |
| def __init__( | |
| self, root, bitmapID=win32ui.IDB_HIERFOLDERS, listBoxID=None, bitmapMask=None | |
| ): # used to create object. | |
| HierList.__init__(self, root, bitmapID, listBoxID, bitmapMask) | |
| def DelegateCall(self, fn): | |
| return fn() | |
| def GetBitmapColumn(self, item): | |
| rc = self.DelegateCall(item.GetBitmapColumn) | |
| if rc is None: | |
| rc = HierList.GetBitmapColumn(self, item) | |
| return rc | |
| def GetSelectedBitmapColumn(self, item): | |
| return self.DelegateCall(item.GetSelectedBitmapColumn) | |
| def IsExpandable(self, item): | |
| return self.DelegateCall(item.IsExpandable) | |
| def GetText(self, item): | |
| return self.DelegateCall(item.GetText) | |
| def GetSubList(self, item): | |
| return self.DelegateCall(item.GetSubList) | |
| def PerformItemSelected(self, item): | |
| func = getattr(item, "PerformItemSelected", None) | |
| if func is None: | |
| return HierList.PerformItemSelected(self, item) | |
| else: | |
| return self.DelegateCall(func) | |
| def TakeDefaultAction(self, item): | |
| func = getattr(item, "TakeDefaultAction", None) | |
| if func is None: | |
| return HierList.TakeDefaultAction(self, item) | |
| else: | |
| return self.DelegateCall(func) | |
| # A hier list item - for use with a HierListWithItems | |
| class HierListItem: | |
| def __init__(self): | |
| pass | |
| def GetText(self): | |
| pass | |
| def GetSubList(self): | |
| pass | |
| def IsExpandable(self): | |
| pass | |
| def GetBitmapColumn(self): | |
| return None # indicate he should do it. | |
| def GetSelectedBitmapColumn(self): | |
| return None # same as other | |
| # for py3k/rich-comp sorting compatibility. | |
| def __lt__(self, other): | |
| # we want unrelated items to be sortable... | |
| return id(self) < id(other) | |
| # for py3k/rich-comp equality compatibility. | |
| def __eq__(self, other): | |
| return False | |