908 lines
25 KiB
C
908 lines
25 KiB
C
#include "ctlspriv.h"
|
|
#include "treeview.h"
|
|
#if defined(MAINWIN)
|
|
#include <mainwin.h>
|
|
#endif
|
|
|
|
void TV_ScrollItems(PTREE pTree, int nItems, int iTopShownIndex, BOOL fDown);
|
|
|
|
|
|
// in:
|
|
// hItem item to delete
|
|
// flags controls how/what to delete
|
|
// TVDI_NORMAL delete this node and all children
|
|
// TVDI_NONOTIFY don't send notify messages
|
|
// TVDI_CHILDRENONLY just delete the kids (not the item)
|
|
|
|
void TV_DeleteItemRecurse(PTREE pTree, TREEITEM * hItem, UINT flags)
|
|
{
|
|
TREEITEM *hKid;
|
|
TREEITEM *hNext;
|
|
TREEITEM *hParent;
|
|
int i;
|
|
|
|
DBG_ValidateTreeItem(hItem, 0);
|
|
|
|
//
|
|
// We do this from DeleteItemRecurse(), kind of like how USER sends
|
|
// Destroy notifications from its FreeWindow() code, so that we get
|
|
// deletes for parent and children both.
|
|
//
|
|
NotifyWinEvent(EVENT_OBJECT_DESTROY, pTree->ci.hwnd, OBJID_CLIENT,
|
|
TV_GetAccId(hItem));
|
|
|
|
//
|
|
// While the item is still valid, clean up if it's the insertion point.
|
|
// The item needs to be valid because we're going to call other
|
|
// functions that validate their parameters...
|
|
//
|
|
if (hItem == pTree->htiInsert)
|
|
{
|
|
TV_SetInsertMark(pTree, NULL, FALSE);
|
|
ASSERT(pTree->htiInsert == NULL);
|
|
}
|
|
|
|
// remove all kids (and their kids)
|
|
for (hKid = hItem->hKids; hKid; hKid = hNext) {
|
|
hNext = hKid->hNext;
|
|
|
|
// recurse on each child
|
|
TV_DeleteItemRecurse(pTree, hKid, flags & ~TVDI_CHILDRENONLY);
|
|
}
|
|
|
|
if ((flags & TVDI_CHILDRENONLY) || !hItem->hParent)
|
|
return;
|
|
|
|
if (!(flags & TVDI_NONOTIFY))
|
|
{
|
|
NM_TREEVIEW nm;
|
|
// Let the app clean up after itself
|
|
nm.itemOld.hItem = hItem;
|
|
nm.itemOld.lParam = hItem->lParam;
|
|
nm.itemNew.mask = 0;
|
|
nm.itemOld.mask = (TVIF_HANDLE | TVIF_PARAM);
|
|
CCSendNotify(&pTree->ci, TVN_DELETEITEM, &nm.hdr);
|
|
}
|
|
|
|
//
|
|
// If anybody has a watch on our item, let him know that it's gone.
|
|
//
|
|
i = DPA_GetPtrCount(pTree->hdpaWatch);
|
|
while (--i >= 0)
|
|
{
|
|
PTVWATCHEDITEM pwi = DPA_FastGetPtr(pTree->hdpaWatch, i);
|
|
ASSERT(pwi);
|
|
if (pwi->hti == hItem) {
|
|
pwi->hti = hItem->hNext;
|
|
pwi->fStale = TRUE;
|
|
}
|
|
}
|
|
|
|
hParent = hItem->hParent;
|
|
ASSERT(hParent);
|
|
|
|
// unlink ourselves from the parent child chain
|
|
|
|
if (hParent->hKids == hItem) {
|
|
hParent->hKids = hItem->hNext;
|
|
hKid = NULL;
|
|
} else {
|
|
// not the first child, find our previous item (linear search!)
|
|
hKid = TV_GetNextItem(pTree, hItem, TVGN_PREVIOUS);
|
|
ASSERT(hKid);
|
|
hKid->hNext = hItem->hNext;
|
|
}
|
|
|
|
pTree->cItems--;
|
|
|
|
TV_ScrollBarsAfterRemove(pTree, hItem);
|
|
|
|
// reset tooltip after unlink from the parent child chain
|
|
if (pTree->hToolTip == hItem)
|
|
TV_SetToolTipTarget(pTree, NULL);
|
|
|
|
Str_Set(&hItem->lpstr, NULL);
|
|
|
|
TV_MarkAsDead(hItem);
|
|
|
|
// be careful from here down. hItem is unlinked but
|
|
// still has some valid fields
|
|
|
|
// Check to see if the user has deleted one of the
|
|
// special items that is stored in the main tree structure.
|
|
if (hItem == pTree->htiEdit)
|
|
pTree->htiEdit = NULL;
|
|
|
|
if (hItem == pTree->hDropTarget)
|
|
pTree->hDropTarget = NULL;
|
|
|
|
if (hItem == pTree->hOldDrop)
|
|
pTree->hOldDrop = NULL;
|
|
|
|
if (hItem == pTree->hHot )
|
|
pTree->hHot = NULL;
|
|
|
|
if (hItem == pTree->htiSearch )
|
|
pTree->htiSearch = NULL;
|
|
|
|
// if the caret escaped the collapsed area and landed on us, push it away
|
|
if (pTree->hCaret == hItem) {
|
|
HTREEITEM hTemp;
|
|
if (hItem->hNext)
|
|
hTemp = hItem->hNext;
|
|
else {
|
|
hTemp = VISIBLE_PARENT(hItem);
|
|
if (!hTemp)
|
|
hTemp = hKid; // set above when we unlinked from the previous item
|
|
}
|
|
// Reset the caret to NULL as to not try to reference our
|
|
// invalidated item.
|
|
pTree->hCaret = NULL;
|
|
TV_SelectItem(pTree, TVGN_CARET, hTemp, (flags & TVDI_NOSELCHANGE) ? 0 : TVSIFI_NOTIFY, 0);
|
|
ASSERT(pTree->hCaret != hItem);
|
|
}
|
|
|
|
ASSERT(pTree->hItemPainting != hItem);
|
|
|
|
ControlFree(pTree->hheap, hItem);
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Removes the given item and all children from the tree.
|
|
// Special case: if the given item is the hidden root, all children are
|
|
// removed, but the hidden root is NOT removed.
|
|
//
|
|
// sets cItems
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL TV_DeleteItem(PTREE pTree, TREEITEM * hItem, UINT flags)
|
|
{
|
|
if (hItem == TVI_ROOT || !hItem)
|
|
hItem = pTree->hRoot;
|
|
|
|
// BUGUBG: send TVN_DELETEALLITEMS and TVDI_NONOTIFY if they respond
|
|
// if (hItem == pTree->hRoot)
|
|
// etc.
|
|
|
|
if (!ValidateTreeItem(hItem, 0))
|
|
return FALSE;
|
|
|
|
// Collapse first to speed things up (not as much scroll bar recalcs) and
|
|
// to set the top index correctly after the remove.
|
|
if (hItem != pTree->hRoot)
|
|
TV_Expand(pTree, TVE_COLLAPSE, hItem, FALSE);
|
|
else
|
|
{
|
|
// TV_Expand punts on the root item, so manually iterate through it's kids
|
|
TREEITEM *hKid = hItem->hKids;
|
|
while (hKid)
|
|
{
|
|
TV_Expand(pTree, TVE_COLLAPSE, hKid, FALSE);
|
|
if (!ValidateTreeItem(hKid, 0)) break; // callback during collapse could delete
|
|
hKid = hKid->hNext;
|
|
}
|
|
}
|
|
|
|
// Invalidate everything below this item; must be done AFTER setting the
|
|
// selection
|
|
if (hItem->hParent == pTree->hRoot || hItem == pTree->hRoot || ITEM_VISIBLE(hItem->hParent)) {
|
|
if (pTree->fRedraw) {
|
|
InvalidateRect(pTree->ci.hwnd, NULL, TRUE);
|
|
}
|
|
} else {
|
|
TV_ScrollBelow(pTree, hItem->hParent, FALSE, FALSE);
|
|
}
|
|
|
|
// We can pass in the root to clear all items
|
|
if (hItem == pTree->hRoot)
|
|
flags |= TVDI_CHILDRENONLY;
|
|
|
|
TV_DeleteItemRecurse(pTree, hItem, flags);
|
|
|
|
ASSERT(pTree->hRoot); // didn't go too far, did we?
|
|
|
|
// maybe everything's gone...
|
|
// check out our cleanup job
|
|
if (!pTree->hRoot->hKids) {
|
|
// the tree itself
|
|
ASSERT(pTree->cItems == 0);
|
|
pTree->cItems = 0; // just removed it all, didn't we?
|
|
|
|
pTree->hTop = NULL;
|
|
|
|
AssertMsg(pTree->hCaret == NULL, TEXT("hCaret not NULL, but empty tree"));
|
|
pTree->hCaret = NULL;
|
|
|
|
pTree->fNameEditPending = FALSE;
|
|
pTree->cxMax = 0;
|
|
pTree->xPos = 0;
|
|
|
|
// the invisible root
|
|
ASSERT(pTree->hRoot->hNext == NULL);
|
|
pTree->hRoot->hNext = NULL;
|
|
ASSERT(pTree->hRoot->hParent == NULL);
|
|
pTree->hRoot->hParent = NULL;
|
|
ASSERT(pTree->hRoot->hKids == NULL);
|
|
pTree->hRoot->hKids = NULL;
|
|
ASSERT(pTree->hRoot->state & TVIS_EXPANDED);
|
|
pTree->hRoot->state |= (TVIS_EXPANDED | TVIS_EXPANDEDONCE);
|
|
ASSERT(pTree->hRoot->iLevel == (BYTE)-1);
|
|
pTree->hRoot->iLevel = (BYTE) -1;
|
|
ASSERT(pTree->hRoot->iShownIndex == (WORD)-1);
|
|
pTree->hRoot->iShownIndex = (WORD) -1;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Creates the hidden root node for the tree -- all items will trace up to
|
|
// this root, and the first child of the root is the first item in the tree.
|
|
//
|
|
// sets hRoot
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL TV_CreateRoot(PTREE pTree)
|
|
{
|
|
TREEITEM * hRoot = ControlAlloc(pTree->hheap, sizeof(TREEITEM));
|
|
if (!hRoot)
|
|
return FALSE;
|
|
|
|
// hRoot->hNext = NULL;
|
|
// hRoot->hKids = NULL;
|
|
// hRoot->hParent = NULL;
|
|
hRoot->iLevel = (BYTE) -1;
|
|
hRoot->state = (TVIS_EXPANDED | TVIS_EXPANDEDONCE);
|
|
hRoot->iShownIndex = (WORD)-1;
|
|
hRoot->wSignature = TV_SIG;
|
|
hRoot->dwAccId = pTree->dwLastAccId++;
|
|
|
|
pTree->hRoot = hRoot;
|
|
|
|
// OLEACC asks for the text of the root item (d'oh!)
|
|
Str_Set(&hRoot->lpstr, c_szNULL);
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
void DumpItem(TREEITEM *hItem)
|
|
{
|
|
LPTSTR p;
|
|
|
|
if (hItem->lpstr == LPSTR_TEXTCALLBACK)
|
|
p = TEXT("(callback)");
|
|
else if (hItem->lpstr == NULL)
|
|
p = TEXT("(null)");
|
|
else
|
|
p = hItem->lpstr;
|
|
|
|
TraceMsg(TF_TREEVIEW, "%s", p);
|
|
TraceMsg(TF_TREEVIEW, "\tstate:%4.4x show index:%3d level:%2d kids:%ld lparam:%4.4x",
|
|
hItem->state, hItem->iShownIndex,
|
|
hItem->iLevel, hItem->fKids, hItem->lParam);
|
|
|
|
}
|
|
|
|
#else
|
|
#define DumpItem(hItem)
|
|
#endif
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Adds the item described by the given arguments to the tree.
|
|
//
|
|
// sets hTop, cItems
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#ifdef UNICODE
|
|
TREEITEM * TV_InsertItemA(PTREE pTree, LPTV_INSERTSTRUCTA lpis) {
|
|
LPSTR pszA = NULL;
|
|
TREEITEM *ptvi;
|
|
|
|
//HACK Alert! This code assumes that TV_INSERTSTRUCTA is exactly the same
|
|
// as TV_INSERTSTRUCTW except for the text pointer in the TVITEM
|
|
COMPILETIME_ASSERT(sizeof(TV_INSERTSTRUCTA) == sizeof(TV_INSERTSTRUCTW));
|
|
|
|
if (!IsFlagPtr(lpis) && (lpis->DUMMYUNION_MEMBER(item).mask & TVIF_TEXT) && !IsFlagPtr(lpis->DUMMYUNION_MEMBER(item).pszText)) {
|
|
|
|
pszA = lpis->DUMMYUNION_MEMBER(item).pszText;
|
|
lpis->DUMMYUNION_MEMBER(item).pszText = (LPSTR)ProduceWFromA(pTree->ci.uiCodePage, lpis->DUMMYUNION_MEMBER(item).pszText);
|
|
|
|
if (lpis->DUMMYUNION_MEMBER(item).pszText == NULL) {
|
|
lpis->DUMMYUNION_MEMBER(item).pszText = pszA;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
ptvi = TV_InsertItem( pTree, (LPTV_INSERTSTRUCTW)lpis );
|
|
|
|
if (pszA) {
|
|
FreeProducedString(lpis->DUMMYUNION_MEMBER(item).pszText);
|
|
lpis->DUMMYUNION_MEMBER(item).pszText = pszA;
|
|
}
|
|
|
|
return ptvi;
|
|
}
|
|
#endif
|
|
|
|
TREEITEM * TV_InsertItem(PTREE pTree, LPTV_INSERTSTRUCT lpis)
|
|
{
|
|
TREEITEM *hNewItem, *hItem;
|
|
TREEITEM *hParent;
|
|
TREEITEM *hInsertAfter;
|
|
UINT mask;
|
|
|
|
if (!lpis)
|
|
return NULL; //Bug#94345: Validate LPTV_INSERTSTRUCT
|
|
|
|
// initialize _after_ the check for NULL!
|
|
hParent = lpis->hParent;
|
|
hInsertAfter = lpis->hInsertAfter;
|
|
mask = lpis->DUMMYUNION_MEMBER(item).mask;
|
|
|
|
// don't allow undefined bits
|
|
AssertMsg((lpis->DUMMYUNION_MEMBER(item).mask & ~TVIF_ALL) == 0, TEXT("Invalid TVIF mask specified"));
|
|
if (mask & ~TVIF_ALL) {
|
|
// if they used bogus bits,
|
|
// restrict to win95 bits only
|
|
// I'd like to fail completely, but for win95 compat, we can't
|
|
//
|
|
// this fixes QuaterDesk's CleanSweep which has bogus garbage on the stack for a mask
|
|
mask = (TVIF_WIN95 & mask);
|
|
}
|
|
|
|
TV_DismissEdit(pTree, FALSE);
|
|
|
|
//
|
|
// Zillions of apps pass garbage for hInsertAfter, so don't fail if
|
|
// it's invalid. Fortunately, we never dereference hInsertAfter, so
|
|
// garbage is okay.
|
|
|
|
if (!ValidateTreeItem(hParent, VTI_NULLOK)) // NULL means TVI_ROOT
|
|
return NULL;
|
|
|
|
DBG_ValidateTreeItem(hInsertAfter, 0);
|
|
|
|
hNewItem = ControlAlloc(pTree->hheap, sizeof(TREEITEM));
|
|
if (!hNewItem)
|
|
{
|
|
TraceMsg(TF_ERROR, "TreeView: Out of memory");
|
|
return NULL;
|
|
}
|
|
|
|
hNewItem->wSignature = TV_SIG;
|
|
|
|
if (mask & TVIF_TEXT)
|
|
{
|
|
//
|
|
// We will setup the text string next, before we link our self in
|
|
// as to handle the case where we run out of memory and need to
|
|
// destroy ourself without having to unlink.
|
|
//
|
|
if (!lpis->DUMMYUNION_MEMBER(item).pszText)
|
|
{
|
|
hNewItem->lpstr = LPSTR_TEXTCALLBACK;
|
|
}
|
|
else
|
|
{
|
|
if (!Str_Set(&hNewItem->lpstr, lpis->DUMMYUNION_MEMBER(item).pszText))
|
|
{
|
|
// Memory allocation failure...
|
|
TraceMsg(TF_ERROR, "TreeView: Out of memory");
|
|
TV_MarkAsDead(hNewItem);
|
|
ControlFree(pTree->hheap, hNewItem);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Str_Set(&hNewItem->lpstr, c_szNULL);
|
|
}
|
|
|
|
AssertMsg(hNewItem->lpstr != NULL, TEXT("Item added with NULL text"));
|
|
|
|
if ((hParent == NULL) || (hParent == TVI_ROOT))
|
|
{
|
|
hParent = pTree->hRoot;
|
|
if (!pTree->hTop)
|
|
pTree->hTop = hNewItem;
|
|
}
|
|
else if (!pTree->hRoot->hKids)
|
|
{
|
|
TV_MarkAsDead(hNewItem);
|
|
ControlFree(pTree->hheap, hNewItem);
|
|
return NULL;
|
|
}
|
|
|
|
// We will do the sort later, so we can handle TEXTCALLBACK things
|
|
if ((hInsertAfter == TVI_FIRST || hInsertAfter == TVI_SORT) || !hParent->hKids)
|
|
{
|
|
hNewItem->hNext = hParent->hKids;
|
|
hParent->hKids = hNewItem;
|
|
}
|
|
else
|
|
{
|
|
// Bug#94348: we should cache the last insert after pointer to try to
|
|
// catch the case of consecutive adds to the end of a node
|
|
|
|
if (hInsertAfter == TVI_LAST)
|
|
for (hItem = hParent->hKids; hItem->hNext; hItem = hItem->hNext)
|
|
;
|
|
else
|
|
{
|
|
for (hItem = hParent->hKids; hItem->hNext; hItem = hItem->hNext)
|
|
if (hItem == hInsertAfter)
|
|
break;
|
|
}
|
|
|
|
hNewItem->hNext = hItem->hNext;
|
|
hItem->hNext = hNewItem;
|
|
}
|
|
|
|
// hNewItem->hKids = NULL;
|
|
hNewItem->hParent = hParent;
|
|
hNewItem->iLevel = hParent->iLevel + 1;
|
|
// hNewItem->iWidth = 0;
|
|
// hNewItem->state = 0;
|
|
if ((mask & TVIF_INTEGRAL) &&
|
|
LOWORD(lpis->DUMMYUNION_MEMBER(itemex).iIntegral) > 0)
|
|
{
|
|
hNewItem->iIntegral = LOWORD(lpis->DUMMYUNION_MEMBER(itemex).iIntegral);
|
|
}
|
|
else
|
|
{
|
|
hNewItem->iIntegral = 1;
|
|
}
|
|
|
|
if (pTree->hTop == hNewItem)
|
|
hNewItem->iShownIndex = 0; // calc me please!
|
|
else
|
|
hNewItem->iShownIndex = (WORD)-1; // calc me please!
|
|
|
|
if (mask & TVIF_IMAGE)
|
|
hNewItem->iImage = (WORD) lpis->DUMMYUNION_MEMBER(item).iImage;
|
|
|
|
if (mask & TVIF_SELECTEDIMAGE)
|
|
hNewItem->iSelectedImage = (WORD) lpis->DUMMYUNION_MEMBER(item).iSelectedImage;
|
|
|
|
if (mask & TVIF_PARAM)
|
|
hNewItem->lParam = lpis->DUMMYUNION_MEMBER(item).lParam;
|
|
|
|
if (mask & TVIF_STATE)
|
|
hNewItem->state = lpis->DUMMYUNION_MEMBER(item).state & lpis->DUMMYUNION_MEMBER(item).stateMask;
|
|
|
|
// if we're in check box mode, inforce that it has a check box
|
|
if (pTree->ci.style & TVS_CHECKBOXES)
|
|
{
|
|
if ((hNewItem->state & TVIS_STATEIMAGEMASK) == 0)
|
|
{
|
|
hNewItem->state |= INDEXTOSTATEIMAGEMASK(1);
|
|
}
|
|
}
|
|
|
|
if ((hNewItem->state & TVIS_BOLD) && !pTree->hFontBold) //$BOLD
|
|
TV_CreateBoldFont(pTree); //$BOLD
|
|
|
|
// TraceMsg(TF_TRACE, "Tree: Inserting i = %d state = %d", TV_StateIndex(&lpis->item), lpis->item.state);
|
|
|
|
if (mask & TVIF_CHILDREN)
|
|
{
|
|
switch (lpis->DUMMYUNION_MEMBER(item).cChildren)
|
|
{
|
|
case I_CHILDRENCALLBACK:
|
|
hNewItem->fKids = KIDS_CALLBACK;
|
|
break;
|
|
|
|
case I_CHILDRENAUTO:
|
|
hNewItem->fKids = KIDS_COMPUTE;
|
|
break;
|
|
|
|
case 0:
|
|
hNewItem->fKids = KIDS_FORCE_NO;
|
|
break;
|
|
|
|
default:
|
|
hNewItem->fKids = KIDS_FORCE_YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hNewItem->dwAccId = pTree->dwLastAccId++;
|
|
|
|
// accept state bits on create?
|
|
// mask & TVIF_STATE
|
|
|
|
pTree->cItems++;
|
|
|
|
// I don't want to do any callbacks until the item is completed
|
|
// so sorting waits until the end
|
|
// special case an only child for speed
|
|
// (hKids && hKids->hNext means more than one child)
|
|
if ((hInsertAfter == TVI_SORT) && hParent->hKids && hParent->hKids->hNext)
|
|
{
|
|
TVITEMEX sThisItem, sNextItem;
|
|
TCHAR szThis[64], szNext[64];
|
|
|
|
sThisItem.pszText = szThis;
|
|
sThisItem.cchTextMax = ARRAYSIZE(szThis);
|
|
TV_GetItem(pTree, hNewItem, TVIF_TEXT, &sThisItem);
|
|
|
|
// We know that the first kid of hParent is hNewItem
|
|
for (hItem = hNewItem->hNext; hItem; hItem = hItem->hNext)
|
|
{
|
|
|
|
sNextItem.pszText = szNext;
|
|
sNextItem.cchTextMax = ARRAYSIZE(szNext);
|
|
|
|
TV_GetItem(pTree, hItem, TVIF_TEXT, &sNextItem);
|
|
|
|
if (lstrcmpi(sThisItem.pszText, sNextItem.pszText) < 0)
|
|
break;
|
|
|
|
hInsertAfter = hItem;
|
|
}
|
|
|
|
// Check if this is still the first item
|
|
if (hInsertAfter != TVI_SORT)
|
|
{
|
|
// Move this item from the beginning to where it
|
|
// should be
|
|
hParent->hKids = hNewItem->hNext;
|
|
hNewItem->hNext = hInsertAfter->hNext;
|
|
hInsertAfter->hNext = hNewItem;
|
|
}
|
|
}
|
|
|
|
|
|
if ((hNewItem->hNext == pTree->hTop) && !pTree->fVert)
|
|
{
|
|
|
|
// there's no scrollbars and we got added before the top
|
|
// item. we're now the top.
|
|
hNewItem->iShownIndex = 0;
|
|
pTree->hTop = hNewItem;
|
|
}
|
|
|
|
if (pTree->fRedraw)
|
|
{
|
|
BOOL fVert = pTree->fVert;
|
|
RECT rc;
|
|
RECT rc2;
|
|
|
|
if (TV_ScrollBarsAfterAdd(pTree, hNewItem))
|
|
{
|
|
// scroll everything down one
|
|
if (ITEM_VISIBLE(hNewItem))
|
|
{
|
|
int iTop = hNewItem->iShownIndex - pTree->hTop->iShownIndex;
|
|
|
|
// if there wasn't a scrollbar and we're the 0th item,
|
|
// TV_ScrollBarsAfterAdd already scrolled us
|
|
if (iTop > 0 || !fVert)
|
|
TV_ScrollItems(pTree, hNewItem->iIntegral, iTop + hNewItem->iIntegral - 1, TRUE);
|
|
}
|
|
}
|
|
|
|
// connect the lines, add the buttons, etc. on the item above
|
|
// TV_GetPrevVisItem only works after TV_Scroll* stuff is done
|
|
|
|
if (TV_GetItemRect(pTree, hNewItem, &rc, FALSE))
|
|
{
|
|
|
|
// find the previous sibling or the parent if no prev sib.
|
|
if (hParent->hKids == hNewItem)
|
|
{
|
|
hItem = hParent;
|
|
}
|
|
else
|
|
{
|
|
hItem = hParent->hKids;
|
|
while ( hItem->hNext != hNewItem )
|
|
{
|
|
ASSERT(hItem->hNext);
|
|
hItem = hItem->hNext;
|
|
}
|
|
}
|
|
|
|
// invalidate from there to the new one
|
|
if (TV_GetItemRect(pTree, hItem, &rc2, FALSE))
|
|
{
|
|
rc.top = rc2.top;
|
|
}
|
|
RedrawWindow(pTree->ci.hwnd, &rc, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
// DumpItem(hNewItem);
|
|
|
|
NotifyWinEvent(EVENT_OBJECT_CREATE, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hNewItem));
|
|
|
|
if (pTree->hToolTip)
|
|
{
|
|
TV_PopBubble(pTree);
|
|
}
|
|
|
|
return hNewItem;
|
|
}
|
|
|
|
void TV_DeleteHotFonts(PTREE pTree)
|
|
{
|
|
if (pTree->hFontHot)
|
|
DeleteObject(pTree->hFontHot);
|
|
|
|
if (pTree->hFontBoldHot)
|
|
DeleteObject(pTree->hFontBoldHot);
|
|
|
|
pTree->hFontHot = pTree->hFontBoldHot = NULL;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Frees all allocated memory and objects associated with the tree.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void TV_DestroyTree(PTREE pTree)
|
|
{
|
|
HWND hwnd = pTree->ci.hwnd;
|
|
|
|
ASSERT(pTree->hRoot);
|
|
|
|
pTree->fRedraw = FALSE;
|
|
|
|
TV_OnSetBkColor(pTree, (COLORREF)-1);
|
|
|
|
if (pTree->hbrLine)
|
|
{
|
|
DeleteObject(pTree->hbrLine);
|
|
}
|
|
|
|
if (pTree->hbrText)
|
|
{
|
|
DeleteObject(pTree->hbrText);
|
|
}
|
|
|
|
if (pTree->hCurHot)
|
|
{
|
|
DestroyCursor(pTree->hCurHot);
|
|
}
|
|
|
|
if (IsWindow(pTree->hwndToolTips))
|
|
{
|
|
DestroyWindow(pTree->hwndToolTips);
|
|
}
|
|
|
|
pTree->hwndToolTips = NULL;
|
|
|
|
if (IsWindow(pTree->hwndEdit))
|
|
{
|
|
DestroyWindow(pTree->hwndEdit);
|
|
}
|
|
|
|
pTree->hwndEdit = NULL;
|
|
|
|
TV_DeleteItem(pTree, pTree->hRoot, TVDI_CHILDRENONLY | TVDI_NOSELCHANGE);
|
|
|
|
if (pTree->hRoot)
|
|
{
|
|
Str_Set(&pTree->hRoot->lpstr, NULL);
|
|
|
|
// No point in marking dead since the entire control is going away
|
|
ControlFree(pTree->hheap, pTree->hRoot);
|
|
}
|
|
|
|
if (pTree->hdcBits)
|
|
{
|
|
if (pTree->hBmp)
|
|
{
|
|
SelectObject(pTree->hdcBits, pTree->hStartBmp);
|
|
DeleteObject(pTree->hBmp);
|
|
}
|
|
|
|
DeleteDC(pTree->hdcBits);
|
|
}
|
|
|
|
if (pTree->fCreatedFont && pTree->hFont)
|
|
{
|
|
DeleteObject(pTree->hFont);
|
|
}
|
|
|
|
if (pTree->hFontBold)
|
|
{
|
|
DeleteObject(pTree->hFontBold);
|
|
}
|
|
|
|
Str_Set(&pTree->pszTip, NULL);
|
|
|
|
if (pTree->pszTipA)
|
|
{
|
|
LocalFree(pTree->pszTipA);
|
|
}
|
|
|
|
TV_DeleteHotFonts(pTree);
|
|
|
|
if (pTree->hdpaWatch)
|
|
{
|
|
DPA_Destroy(pTree->hdpaWatch);
|
|
}
|
|
|
|
IncrementSearchFree(&pTree->is);
|
|
|
|
if (pTree->hTheme)
|
|
{
|
|
CloseThemeData(pTree->hTheme);
|
|
}
|
|
|
|
NearFree(pTree);
|
|
|
|
// Don't try to use this var when window is destroyed...
|
|
SetWindowInt(hwnd, 0, 0);
|
|
}
|
|
|
|
void TV_CreateToolTips(PTREE pTree);
|
|
|
|
void TV_InitThemeMetrics(PTREE pTree, HTHEME hTheme)
|
|
{
|
|
COLORREF cr;
|
|
|
|
HRESULT hr = GetThemeColor(hTheme, 0, 0, TMT_COLOR, &cr);
|
|
if (SUCCEEDED(hr))
|
|
SendMessage(pTree->ci.hwnd, TVM_SETBKCOLOR, 0, cr);
|
|
|
|
// Line color
|
|
hr = GetThemeColor(hTheme, TVP_BRANCH, 0, TMT_COLOR, &cr);
|
|
if (SUCCEEDED(hr))
|
|
SendMessage(pTree->ci.hwnd, TVM_SETLINECOLOR, 0, cr);
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Allocates space for the tree and initializes the tree's data
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
LRESULT TV_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreate)
|
|
{
|
|
HTHEME hTheme;
|
|
HRESULT hr = E_FAIL;
|
|
PTREE pTree = NearAlloc(sizeof(TREE));
|
|
|
|
if (!pTree)
|
|
return -1; // fail the create window
|
|
|
|
pTree->hheap = GetProcessHeap();
|
|
|
|
if (!TV_CreateRoot(pTree))
|
|
{
|
|
NearFree((HLOCAL)pTree);
|
|
return -1; // fail the create window
|
|
}
|
|
|
|
pTree->hdpaWatch = DPA_Create(8);
|
|
if (!pTree->hdpaWatch)
|
|
{
|
|
// No point in marking dead since the entire control is going away
|
|
ControlFree(pTree->hheap, pTree->hRoot);
|
|
NearFree((HLOCAL)pTree);
|
|
return -1; // fail the create window
|
|
}
|
|
|
|
SetWindowPtr(hwnd, 0, pTree);
|
|
|
|
CIInitialize(&pTree->ci, hwnd, lpCreate);
|
|
|
|
if (lpCreate->dwExStyle & WS_EX_RTLREADING)
|
|
pTree->ci.style |= TVS_RTLREADING;
|
|
|
|
pTree->fRedraw = TRUE;
|
|
pTree->clrim = CLR_DEFAULT;
|
|
pTree->clrText = (COLORREF)-1;
|
|
pTree->clrBkNonTheme = pTree->clrBk = (COLORREF)-1;
|
|
pTree->clrLineNonTheme = pTree->clrLine = CLR_DEFAULT;
|
|
pTree->hbrLine = g_hbrGrayText;
|
|
pTree->cxBorder = 3;
|
|
|
|
hTheme = OpenThemeData(pTree->ci.hwnd, L"TreeView");
|
|
|
|
|
|
if (hTheme)
|
|
{
|
|
TV_InitThemeMetrics(pTree, hTheme);
|
|
}
|
|
|
|
pTree->hTheme = hTheme;
|
|
|
|
pTree->hbrText = g_hbrWindowText;
|
|
|
|
// pTree->fHorz = FALSE;
|
|
// pTree->fVert = FALSE;
|
|
// pTree->fFocus = FALSE;
|
|
// pTree->fNameEditPending = FALSE;
|
|
// pTree->cxMax = 0;
|
|
// pTree->cxWnd = 0;
|
|
// pTree->cyWnd = 0;
|
|
// pTree->hTop = NULL;
|
|
// pTree->hCaret = NULL;
|
|
// pTree->hDropTarget = NULL;
|
|
// pTree->hOldDrop = NULL;
|
|
// pTree->cItems = 0;
|
|
// pTree->cShowing = 0;
|
|
pTree->cFullVisible = 1;
|
|
// pTree->hdcBits = NULL;
|
|
// pTree->hBmp = NULL;
|
|
// pTree->hbrBk = NULL;
|
|
// pTree->xPos = 0;
|
|
// pTree->cxIndent = 0; // init this for real in TV_OnSetFont()
|
|
// pTree->dwCDDepth = 0;
|
|
pTree->uMaxScrollTime = SSI_DEFAULT;
|
|
pTree->dwLastAccId = 1; // Start at 1 because 0 means self
|
|
// pTree->dwExStyle = 0;
|
|
// pTree->fInTextCallback = FALSE;
|
|
|
|
TV_OnSetFont(pTree, NULL, TRUE);
|
|
|
|
// You cannot combine TVS_HASLINES and TVS_FULLROWSELECT
|
|
// because it doesn't work
|
|
if (pTree->ci.style & TVS_HASLINES) {
|
|
if (pTree->ci.style & TVS_FULLROWSELECT) {
|
|
DebugMsg(DM_ERROR, TEXT("Cannot combine TVS_HASLINES and TVS_FULLROWSELECT"));
|
|
}
|
|
pTree->ci.style &= ~TVS_FULLROWSELECT;
|
|
}
|
|
|
|
if (!(pTree->ci.style & TVS_NOTOOLTIPS)) {
|
|
TV_CreateToolTips(pTree);
|
|
}
|
|
|
|
SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
|
|
SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
|
|
void TV_CreateToolTips(PTREE pTree)
|
|
{
|
|
DWORD exStyle = 0;
|
|
|
|
if(pTree->ci.style & TVS_RTLREADING)
|
|
{
|
|
exStyle |= WS_EX_RTLREADING;
|
|
}
|
|
|
|
pTree->hwndToolTips = CreateWindowEx(WS_EX_TRANSPARENT | exStyle, c_szSToolTipsClass, NULL,
|
|
WS_POPUP | TTS_NOPREFIX,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
pTree->ci.hwnd, NULL, HINST_THISDLL,
|
|
NULL);
|
|
if (pTree->hwndToolTips)
|
|
{
|
|
TOOLINFO ti;
|
|
|
|
ti.cbSize = sizeof(ti);
|
|
ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT;
|
|
ti.hwnd = pTree->ci.hwnd;
|
|
ti.uId = (UINT_PTR)pTree->ci.hwnd;
|
|
ti.lpszText = LPSTR_TEXTCALLBACK;
|
|
ti.lParam = 0;
|
|
SendMessage(pTree->hwndToolTips, TTM_ADDTOOL, 0,
|
|
(LPARAM)(LPTOOLINFO)&ti);
|
|
SendMessage(pTree->hwndToolTips, WM_SETFONT, (WPARAM)pTree->hFont, (LPARAM)TRUE);
|
|
SendMessage(pTree->hwndToolTips, TTM_SETDELAYTIME, TTDT_INITIAL, (LPARAM)500);
|
|
}
|
|
else
|
|
pTree->ci.style |= (TVS_NOTOOLTIPS);
|
|
}
|