123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- # 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 win32ui
- import win32con
- import win32api
- from win32api import RGB
- from pywin.mfc import object, window, docview, dialog
- import commctrl
- # 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 ItemFromHandle(self, handle):
- return self.itemHandleMap[handle]
- 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 None # Use standard.
- 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
|