Windows2000/private/shell/win16/commctrl/tvmem.c

582 lines
16 KiB
C
Raw Normal View History

2001-01-01 00:00:00 +01:00
#include "ctlspriv.h"
#include "treeview.h"
// 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 NEAR TV_DeleteItemRecurse(PTREE pTree, TREEITEM FAR * hItem, UINT flags)
{
TREEITEM FAR *hKid;
TREEITEM FAR *hNext;
TREEITEM FAR *hParent;
ValidateTreeItem(hItem, FALSE);
// 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)) // BUGBUG: this is not set by anyone
{
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);
SendNotify(pTree->hwndParent, pTree->hwnd, TVN_DELETEITEM, &nm.hdr);
}
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);
if (hItem->lpstr != LPSTR_TEXTCALLBACK)
Str_Set(&hItem->lpstr, NULL);
#ifdef DEBUG
hItem->dbg_sig = 0;
#endif
// 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 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), FALSE, 0);
Assert(pTree->hCaret != hItem);
}
// BUGBUG: might want to really do this
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 NEAR TV_DeleteItem(PTREE pTree, TREEITEM FAR * hItem, UINT flags)
{
// BUGBUG: validate hItem
if (hItem == TVI_ROOT || !hItem)
hItem = pTree->hRoot;
// BUGUBG: send TVN_DELETEALLITEMS and TVDI_NONOTIFY if they respond
// if (hItem == pTree->hRoot)
// etc.
ValidateTreeItem(hItem, FALSE);
// Collapse first to speed things up (not as much scroll bar recalcs) and
// to set the top index correctly after the remove.
TV_Expand(pTree, TVE_COLLAPSE, hItem, FALSE);
// Invalidate everything below this item; must be done AFTER setting the
// selection
if (pTree->style & TVS_HASLINES) {
if (hItem->hParent == pTree->hRoot || hItem == pTree->hRoot || ITEM_VISIBLE(hItem->hParent)) {
if (pTree->fRedraw) {
InvalidateRect(pTree->hwnd, NULL, TRUE);
}
} else {
TV_ScrollBelow(pTree, hItem->hParent, FALSE, FALSE);
}
} else {
TV_ScrollBelow(pTree, hItem, 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?
// BUGBUG: this fails because we don't touch hTop if redraw is off
// in TV_DeleteItemRecurse()
// AssertMsg(pTree->hTop == NULL, "hTop not NULL, but empty tree");
pTree->hTop = NULL;
AssertMsg(pTree->hCaret == NULL, "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;
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 NEAR PASCAL TV_CreateRoot(PTREE pTree)
{
TREEITEM FAR * 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;
hRoot->iShownIndex = (WORD)-1;
#ifdef DEBUG
hRoot->dbg_sig = DEBUG_SIG;
#endif
pTree->hRoot = hRoot;
return TRUE;
}
#ifdef DEBUG
void NEAR DumpItem(TREEITEM FAR *hItem)
{
LPSTR p;
if (hItem->lpstr == LPSTR_TEXTCALLBACK)
p = "(callback)";
else if (hItem->lpstr == NULL)
p = "(null)";
else
p = hItem->lpstr;
DebugMsg(DM_TRACE, p);
DebugMsg(DM_TRACE, "\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
TREEITEM FAR * NEAR TV_InsertItem(PTREE pTree, LPTV_INSERTSTRUCT lpis)
{
TREEITEM FAR *hNewItem, FAR *hItem;
TREEITEM FAR *hParent = lpis->hParent;
TREEITEM FAR *hInsertAfter = lpis->hInsertAfter;
if (!lpis)
return NULL; //BUGBUG: Validate LPTV_INSERTSTRUCT
// don't allow undefined bits
AssertMsg((lpis->item.mask & ~TVIF_ALL) == 0, "Invalid TVIF mask specified");
TV_DismissEdit(pTree, FALSE);
ValidateTreeItem(hParent, TRUE); // NULL means TVI_ROOT
ValidateTreeItem(hInsertAfter, FALSE);
hNewItem = ControlAlloc(pTree->hheap, sizeof(TREEITEM));
if (!hNewItem)
{
DebugMsg(DM_ERROR, "TreeView: Out of memory");
return NULL;
}
#ifdef DEBUG
hNewItem->dbg_sig = DEBUG_SIG;
#endif
if (lpis->item.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->item.pszText || (lpis->item.pszText == LPSTR_TEXTCALLBACK))
{
hNewItem->lpstr = LPSTR_TEXTCALLBACK;
}
else
{
if (!Str_Set(&hNewItem->lpstr, lpis->item.pszText))
{
// Memory allocation failure...
DebugMsg(DM_ERROR, "TreeView: Out of memory");
#ifdef DEBUG
hNewItem->dbg_sig = 0;
#endif
ControlFree(pTree->hheap, hNewItem);
return NULL;
}
}
} else
Str_Set(&hNewItem->lpstr, c_szNULL);
AssertMsg(hNewItem->lpstr != NULL, "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)
{
#ifdef DEBUG
hNewItem->dbg_sig = 0;
#endif
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
{
// BUGBUG: 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 (pTree->hTop == hNewItem)
hNewItem->iShownIndex = 0; // calc me please!
else
hNewItem->iShownIndex = (WORD)-1; // calc me please!
if (lpis->item.mask & TVIF_IMAGE)
hNewItem->iImage = lpis->item.iImage;
if (lpis->item.mask & TVIF_SELECTEDIMAGE)
hNewItem->iSelectedImage = lpis->item.iSelectedImage;
if (lpis->item.mask & TVIF_PARAM)
hNewItem->lParam = lpis->item.lParam;
if (lpis->item.mask & TVIF_STATE)
hNewItem->state = lpis->item.state & lpis->item.stateMask;
if ((hNewItem->state & TVIS_BOLD) && !pTree->hFontBold) //$BOLD
TV_CreateBoldFont(pTree); //$BOLD
// DebugMsg(DM_TRACE, "Tree: Inserting i = %d state = %d", TV_StateIndex(&lpis->item), lpis->item.state);
if (lpis->item.mask & TVIF_CHILDREN) {
switch (lpis->item.cChildren) {
case I_CHILDRENCALLBACK:
hNewItem->fKids = KIDS_CALLBACK;
break;
case 0:
hNewItem->fKids = KIDS_FORCE_NO;
break;
default:
hNewItem->fKids = KIDS_FORCE_YES;
break;
}
}
// accept state bits on create?
// lpis->item.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)
{
TV_ITEM sThisItem, sNextItem;
char szThis[64], szNext[64]; // BUGBUG: these are too small
sThisItem.pszText = szThis;
sThisItem.cchTextMax = sizeof(szThis);
TV_GetItem(pTree, hNewItem, TVIF_TEXT, &sThisItem);
sNextItem.pszText = szNext;
sNextItem.cchTextMax = sizeof(szNext);
// We know that the first kid of hParent is hNewItem
for (hItem = hNewItem->hNext; hItem; hItem = hItem->hNext)
{
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)
{
if (TV_ScrollBarsAfterAdd(pTree, hNewItem))
TV_ScrollBelow(pTree, hNewItem, FALSE, TRUE);
// connect the lines, add the buttons, etc. on the item above
// TV_GetPrevVisItem only works after TV_Scroll* stuff is done
if (pTree->style & TVS_HASLINES) {
RECT rc;
RECT rc2;
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)) {
rc2.bottom = rc.bottom;
RedrawWindow(pTree->hwnd, &rc2, NULL, RDW_INVALIDATE | RDW_ERASE);
}
}
}
}
// DumpItem(hNewItem);
return hNewItem;
}
// Frees all allocated memory and objects associated with the tree.
void NEAR TV_DestroyTree(PTREE pTree)
{
HWND hwnd = pTree->hwnd;
Assert(pTree->hRoot);
pTree->fRedraw = FALSE;
// BUGUBG: send TVN_DELETEALLITEMS and TVDI_NONOTIFY if they respond
TV_DeleteItem(pTree, pTree->hRoot, TVDI_CHILDRENONLY | TVDI_NOSELCHANGE);
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) //$BOLD
DeleteObject(pTree->hFontBold); //$BOLD
NearFree(pTree);
// Don't try to use this var when window is destroyed...
SetWindowInt(hwnd, 0, 0);
}
// Allocates space for the tree and initializes the tree's data
LRESULT NEAR TV_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreate)
{
PTREE pTree = NearAlloc(sizeof(TREE));
if (!pTree)
return -1; // fail the create window
#ifdef WIN32
pTree->hheap = GetProcessHeap();
#endif
if (!TV_CreateRoot(pTree)) {
NearFree((HLOCAL)pTree);
return -1; // fail the create window
}
SetWindowInt(hwnd, 0, (UINT)pTree);
pTree->hwnd = hwnd;
pTree->hwndParent = lpCreate->hwndParent;
pTree->style = lpCreate->style;
pTree->fRedraw = TRUE;
// 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->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()
TV_OnSetFont(pTree, NULL, TRUE);
SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
return 0; // success
}