WindowsXP/Source/XPSP1/NT/sdktools/sdv/treelist.cpp
2024-08-03 16:30:48 +02:00

607 lines
14 KiB
C++

/*****************************************************************************
*
* treelist.cpp
*
* A tree-like listview. (Worst of both worlds!)
*
*****************************************************************************/
//
// state icon: Doesn't get ugly highlight when selected
// but indent doesn't work unless there is a small imagelist
//
// image: gets ugly highlight
// but at least indent works
#include "sdview.h"
TreeItem *TreeItem::NextVisible()
{
if (IsExpanded()) {
return FirstChild();
}
TreeItem *pti = this;
do {
if (pti->NextSibling()) {
return pti->NextSibling();
}
pti = pti->Parent();
} while (pti);
return NULL;
}
BOOL TreeItem::IsVisibleOrRoot()
{
TreeItem *pti = Parent();
while (pti) {
ASSERT(pti->IsExpandable());
if (!pti->IsExpanded())
{
return FALSE;
}
pti = pti->Parent();
}
// Made it all the way to the root without incident
return TRUE;
}
BOOL TreeItem::IsVisible()
{
TreeItem *pti = Parent();
//
// The root itself is not visible.
//
if (!pti) {
return FALSE;
}
return IsVisibleOrRoot();
}
Tree::Tree(TreeItem *ptiRoot)
: _ptiRoot(ptiRoot)
, _iHint(-1)
, _ptiHint(ptiRoot)
{
if (_ptiRoot) {
_ptiRoot->_ptiChild = PTI_ONDEMAND;
_ptiRoot->_iVisIndex = -1;
_ptiRoot->_iDepth = -1;
}
}
Tree::~Tree()
{
DeleteNode(_ptiRoot);
}
void Tree::SetHWND(HWND hwnd)
{
_hwnd = hwnd;
SHFILEINFO sfi;
HIMAGELIST himl = ImageList_LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_PLUS),
16, 0, RGB(0xFF, 0x00, 0xFF));
ListView_SetImageList(_hwnd, himl, LVSIL_STATE);
ListView_SetCallbackMask(_hwnd, LVIS_STATEIMAGEMASK | LVIS_OVERLAYMASK);
}
HIMAGELIST Tree::SetImageList(HIMAGELIST himl)
{
return RECAST(HIMAGELIST, ListView_SetImageList(_hwnd, himl, LVSIL_SMALL));
}
LRESULT Tree::SendNotify(int code, NMHDR *pnm)
{
pnm->hwndFrom = _hwnd;
pnm->code = code;
pnm->idFrom = GetDlgCtrlID(_hwnd);
return ::SendMessage(GetParent(_hwnd), WM_NOTIFY, pnm->idFrom, RECAST(LPARAM, pnm));
}
LRESULT Tree::OnCacheHint(NMLVCACHEHINT *phint)
{
_ptiHint = IndexToItem(phint->iFrom);
_iHint = phint->iFrom;
return 0;
}
//
// pti = the first item that needs to be recalced
//
void Tree::Recalc(TreeItem *pti)
{
int iItem = pti->_iVisIndex;
if (_iHint > iItem) {
_iHint = iItem;
_ptiHint = pti;
}
do {
pti->_iVisIndex = iItem;
pti = pti->NextVisible();
iItem++;
} while (pti);
}
TreeItem* Tree::IndexToItem(int iItem)
{
int iHave;
TreeItem *ptiHave;
if (iItem >= _iHint && _ptiHint) {
iHave = _iHint;
ptiHave = _ptiHint;
ASSERT(ptiHave->_iVisIndex == iHave);
} else {
iHave = -1;
ptiHave = _ptiRoot;
}
while (iHave < iItem && ptiHave) {
ASSERT(ptiHave->_iVisIndex == iHave);
ptiHave = ptiHave->NextVisible();
iHave++;
}
return ptiHave;
}
int Tree::InsertListviewItem(int iItem)
{
LVITEM lvi;
lvi.iItem = iItem;
lvi.iSubItem = 0;
lvi.mask = 0;
return ListView_InsertItem(_hwnd, &lvi);
}
BOOL Tree::Insert(TreeItem *pti, TreeItem *ptiParent, TreeItem *ptiAfter)
{
pti->_ptiParent = ptiParent;
TreeItem **pptiUpdate;
// Convenience: PTI_APPEND appends as last child
if (ptiAfter == PTI_APPEND) {
ptiAfter = ptiParent->FirstChild();
if (ptiAfter == PTI_ONDEMAND) {
ptiAfter = NULL;
} else if (ptiAfter) {
while (ptiAfter->NextSibling()) {
ptiAfter = ptiAfter->NextSibling();
}
}
}
if (ptiAfter) {
pti->_iVisIndex = ptiAfter->_iVisIndex + 1;
pptiUpdate = &ptiAfter->_ptiNext;
} else {
pti->_iVisIndex = ptiParent->_iVisIndex + 1;
pptiUpdate = &ptiParent->_ptiChild;
if (ptiParent->_ptiChild == PTI_ONDEMAND) {
ptiParent->_ptiChild = NULL;
}
}
if (ptiParent->IsExpanded()) {
if (InsertListviewItem(pti->_iVisIndex) < 0) {
return FALSE;
}
ptiParent->_cVisKids++;
}
pti->_ptiNext = *pptiUpdate;
*pptiUpdate = pti;
pti->_iDepth = ptiParent->_iDepth + 1;
if (ptiParent->IsExpanded()) {
Recalc(pti);
}
return TRUE;
}
//
// Update the visible kids count for pti and all its parents.
// Sop when we find a node that is collapsed (which means
// the visible kids counter is no longer being kept track of).
//
void Tree::UpdateVisibleCounts(TreeItem *pti, int cDelta)
{
//
// Earlying-out the cDelta==0 case is a clear optimization,
// and it's actually important in the goofy scenario where
// an expand failed (so the item being updated isn't even
// expandable any more).
//
if (cDelta) {
do {
ASSERT(pti->IsExpandable());
pti->_cVisKids += cDelta;
pti = pti->Parent();
} while (pti && pti->IsExpanded());
}
}
int Tree::Expand(TreeItem *ptiRoot)
{
if (ptiRoot->IsExpanded()) {
return 0;
}
if (!ptiRoot->IsExpandable()) {
return 0;
}
if (ptiRoot->FirstChild() == PTI_ONDEMAND) {
NMTREELIST tl;
tl.pti = ptiRoot;
SendNotify(TLN_FILLCHILDREN, &tl.hdr);
//
// If the callback failed to insert any items, then turn the
// entry into an unexpandable item. (We need to redraw it
// so the new button shows up.)
//
if (ptiRoot->FirstChild() == PTI_ONDEMAND) {
ptiRoot->SetNotExpandable();
}
}
BOOL fRootVisible = ptiRoot->IsVisibleOrRoot();
TreeItem *pti = ptiRoot->FirstChild();
int iNewIndex = ptiRoot->_iVisIndex + 1;
int cExpanded = 0;
while (pti) {
cExpanded += 1 + pti->_cVisKids;
if (fRootVisible) {
// Start at -1 so we also include the item itself
for (int i = -1; i < pti->_cVisKids; i++) {
InsertListviewItem(iNewIndex);
iNewIndex++;
}
}
pti = pti->NextSibling();
}
UpdateVisibleCounts(ptiRoot, cExpanded);
if (fRootVisible) {
Recalc(ptiRoot);
// Also need to redraw the root item because its button changed
ListView_RedrawItems(_hwnd, ptiRoot->_iVisIndex, ptiRoot->_iVisIndex);
}
return cExpanded;
}
int Tree::Collapse(TreeItem *ptiRoot)
{
if (!ptiRoot->IsExpanded()) {
return 0;
}
if (!ptiRoot->IsExpandable()) {
return 0;
}
TreeItem *pti = ptiRoot->FirstChild();
int iDelIndex = ptiRoot->_iVisIndex + 1;
int cCollapsed = 0;
BOOL fRootVisible = ptiRoot->IsVisibleOrRoot();
//
// HACKHACK for some reason, listview in ownerdata mode animates
// deletes but not insertions. What's worse, the deletion animation
// occurs even if the item being deleted isn't even visible (because
// we deleted a screenful of items ahead of it). So let's just disable
// redraws while doing collapses.
//
if (fRootVisible) {
SetWindowRedraw(_hwnd, FALSE);
}
while (pti) {
cCollapsed += 1 + pti->_cVisKids;
if (fRootVisible) {
// Start at -1 so we also include the item itself
for (int i = -1; i < pti->_cVisKids; i++) {
ListView_DeleteItem(_hwnd, iDelIndex);
}
}
pti = pti->NextSibling();
}
UpdateVisibleCounts(ptiRoot, -cCollapsed);
if (fRootVisible) {
Recalc(ptiRoot);
// Also need to redraw the root item because its button changed
ListView_RedrawItems(_hwnd, ptiRoot->_iVisIndex, ptiRoot->_iVisIndex);
SetWindowRedraw(_hwnd, TRUE);
}
return cCollapsed;
}
int Tree::ToggleExpand(TreeItem *pti)
{
if (pti->IsExpandable()) {
if (pti->IsExpanded()) {
return -Collapse(pti);
} else {
return Expand(pti);
}
}
return 0;
}
void Tree::RedrawItem(TreeItem *pti)
{
if (pti->IsVisible()) {
ListView_RedrawItems(_hwnd, pti->_iVisIndex, pti->_iVisIndex);
}
}
LRESULT Tree::OnClick(NMITEMACTIVATE *pia)
{
if (pia->iSubItem == 0) {
// Maybe it was a click on the +/- button
LVHITTESTINFO hti;
hti.pt = pia->ptAction;
ListView_HitTest(_hwnd, &hti);
if (hti.flags & (LVHT_ONITEMICON | LVHT_ONITEMSTATEICON)) {
TreeItem *pti = IndexToItem(pia->iItem);
if (pti) {
ToggleExpand(pti);
}
}
}
return 0;
}
LRESULT Tree::OnItemActivate(int iItem)
{
NMTREELIST tl;
tl.pti = IndexToItem(iItem);
if (tl.pti) {
SendNotify(TLN_ITEMACTIVATE, &tl.hdr);
}
return 0;
}
//
// Classic treeview keys:
//
// Ctrl+(Left, Right, PgUp, Home, PgDn, End, Up, Down) = scroll the
// window without changing selection.
//
// Enter = activate
// PgUp, PgDn, Home, End = navigate
// Numpad+, Numpad- = expand/collapse
// Numpad* = expand all
// Left = collapse focus item or move to parent
// Right = expand focus item or move down
// Backspace = move to parent
//
// We don't mimic it perfectly, but we get close enough that hopefully
// nobody will notice.
//
LRESULT Tree::OnKeyDown(NMLVKEYDOWN *pkd)
{
if (GetKeyState(VK_CONTROL) < 0) {
// Allow key to go through - listview will do the work
} else {
TreeItem *pti;
switch (pkd->wVKey) {
case VK_ADD:
pti = GetCurSel();
if (pti) {
Expand(pti);
}
return 1;
case VK_SUBTRACT:
pti = GetCurSel();
if (pti) {
Collapse(pti);
}
return 1;
case VK_LEFT:
pti = GetCurSel();
if (pti) {
if (pti->IsExpanded()) {
Collapse(pti);
} else {
SetCurSel(pti->Parent());
}
}
return 1;
case VK_BACK:
pti = GetCurSel();
if (pti) {
SetCurSel(pti->Parent());
}
return 1;
case VK_RIGHT:
pti = GetCurSel();
if (pti) {
if (!Expand(pti)) {
pti = pti->NextVisible();
if (pti) {
SetCurSel(pti);
}
}
}
return 1;
}
}
return 0;
}
//
// Convert the item number into a tree item.
//
LRESULT Tree::OnGetDispInfo(NMLVDISPINFO *plvd)
{
TreeItem *pti = IndexToItem(plvd->item.iItem);
ASSERT(pti);
if (!pti) {
return 0;
}
if (plvd->item.mask & LVIF_STATE) {
if (pti->IsExpandable()) {
// State images are 1-based
plvd->item.state |= INDEXTOSTATEIMAGEMASK(pti->IsExpanded() ? 1 : 2);
}
}
if (plvd->item.mask & LVIF_INDENT) {
plvd->item.iIndent = pti->_iDepth;
}
NMTREELIST tl;
tl.pti = pti;
if (plvd->item.mask & (LVIF_IMAGE | LVIF_STATE)) {
tl.iSubItem = -1;
tl.cchTextMax = 0;
SendNotify(TLN_GETDISPINFO, &tl.hdr);
plvd->item.iImage = tl.iSubItem;
if (plvd->item.stateMask & LVIS_OVERLAYMASK) {
plvd->item.state |= tl.cchTextMax;
}
}
if (plvd->item.mask & LVIF_TEXT) {
tl.iSubItem = plvd->item.iSubItem;
tl.pszText = plvd->item.pszText;
tl.cchTextMax = plvd->item.cchTextMax;
SendNotify(TLN_GETDISPINFO, &tl.hdr);
plvd->item.pszText = tl.pszText;
}
return 0;
}
LRESULT Tree::OnGetInfoTip(NMLVGETINFOTIP *pgit)
{
TreeItem *pti = IndexToItem(pgit->iItem);
ASSERT(pti);
if (pti) {
NMTREELIST tl;
tl.pti = pti;
tl.pszText = pgit->pszText;
tl.cchTextMax = pgit->cchTextMax;
SendNotify(TLN_GETINFOTIP, &tl.hdr);
pgit->pszText = tl.pszText;
}
return 0;
}
LRESULT Tree::OnGetContextMenu(int iItem)
{
TreeItem *pti = IndexToItem(iItem);
ASSERT(pti);
if (pti) {
NMTREELIST tl;
tl.pti = pti;
return SendNotify(TLN_GETCONTEXTMENU, &tl.hdr);
}
return 0;
}
LRESULT Tree::OnCopyToClipboard(int iMin, int iMax)
{
TreeItem *pti = IndexToItem(iMin);
ASSERT(pti);
if (pti) {
TreeItem *ptiMax = IndexToItem(iMax);
String str;
while (pti != ptiMax) {
NMTREELIST tl;
tl.pti = pti;
tl.pszText = NULL;
tl.cchTextMax = 0;
SendNotify(TLN_GETINFOTIP, &tl.hdr);
if (tl.pszText) {
str << tl.pszText << TEXT("\r\n");
}
pti = pti->NextVisible();
}
SetClipboardText(_hwnd, str);
}
return 0;
}
TreeItem *Tree::GetCurSel()
{
int iItem = ListView_GetCurSel(_hwnd);
if (iItem >= 0) {
return IndexToItem(iItem);
}
return NULL;
}
void Tree::SetCurSel(TreeItem *pti)
{
if (pti->IsVisible()) {
ListView_SetCurSel(_hwnd, pti->_iVisIndex);
ListView_EnsureVisible(_hwnd, pti->_iVisIndex, FALSE);
}
}
void Tree::DeleteNode(TreeItem *pti)
{
if (pti) {
// Nuke all the kids, recursively
TreeItem *ptiKid = pti->FirstChild();
if (!ptiKid->IsSentinel()) {
do {
TreeItem *ptiNext = ptiKid->NextSibling();
DeleteNode(ptiKid);
ptiKid = ptiNext;
} while (ptiKid);
}
// This is moved to a subroutine so we don't eat stack
// in this highly-recursive function.
SendDeleteNotify(pti);
}
}
void Tree::SendDeleteNotify(TreeItem *pti)
{
NMTREELIST tl;
tl.pti = pti;
SendNotify(TLN_DELETEITEM, &tl.hdr);
}