#include "ctlspriv.h" #include "treeview.h" BOOL NEAR TV_EnsureVisible(PTREE pTree, TREEITEM FAR * hItem); // ---------------------------------------------------------------------------- // Updates the iShownIndex for every item below (in list order) a given item // ---------------------------------------------------------------------------- int NEAR TV_UpdateShownIndexes(PTREE pTree, HTREEITEM hWalk) { WORD iShownIndex; if (hWalk == pTree->hRoot) { hWalk = pTree->hRoot->hKids; if (hWalk) { hWalk->iShownIndex = 0; } else { return -1; } } iShownIndex = hWalk->iShownIndex + hWalk->iIntegral; if (iShownIndex <= 0) { // BUGBUG: We should #define the special TVITEM_HIDDEN value and check // for it explicitly // This can happen if TV_SortCB passes in a hidden item return(-1); } while ((hWalk = TV_GetNextVisItem(hWalk)) != NULL) { hWalk->iShownIndex = iShownIndex; iShownIndex += (WORD) hWalk->iIntegral; } //#ifdef DEBUG // TraceMsg(TF_TREEVIEW, "tv: updated show indexes (now %d items)", (int)iShownIndex); //#endif return (int)iShownIndex; } // in: // hItem expanded node to count decendants of // returns: // total number of expanded descendants below the given item. UINT NEAR TV_CountVisibleDescendants(HTREEITEM hItem) { UINT cnt; for (cnt = 0, hItem = hItem->hKids; hItem; hItem = hItem->hNext) { cnt += hItem->iIntegral; if (hItem->hKids && (hItem->state & TVIS_EXPANDED)) cnt += TV_CountVisibleDescendants(hItem); } return cnt; } // scrolls nItems in the direction of fDown starting from iTopShownIndex void TV_ScrollItems(PTREE pTree, int nItems, int iTopShownIndex, BOOL fDown) { RECT rc; rc.left = 0; rc.top = (iTopShownIndex+1) * pTree->cyItem; rc.right = pTree->cxWnd; rc.bottom = pTree->cyWnd; { #ifndef UNIX SMOOTHSCROLLINFO si = { sizeof(si), SSIF_MINSCROLL | SSIF_MAXSCROLLTIME, pTree->ci.hwnd, 0, ((fDown)?1:-1) * nItems * pTree->cyItem, &rc, &rc, NULL, NULL, SW_ERASE|SW_INVALIDATE, pTree->uMaxScrollTime, 1, 1 }; #else SMOOTHSCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SSIF_MINSCROLL | SSIF_MAXSCROLLTIME; si.hwnd = pTree->ci.hwnd; si.dx = 0; si.dy = ((fDown)?1:-1) * nItems * pTree->cyItem; si.lprcSrc = &rc; si.lprcClip = &rc; si.hrgnUpdate = NULL; si.lprcUpdate = NULL; si.fuScroll = SW_ERASE|SW_INVALIDATE; si.uMaxScrollTime = pTree->uMaxScrollTime; si.cxMinScroll = 1; si.cyMinScroll = 1; si.pfnScrollProc = NULL; #endif SmoothScrollWindow(&si); } TV_UpdateToolTip(pTree); } // If fRedrawParent is FALSE, then the return value is garbage. // If fRedrawParent is TRUE, then returns the number of children scrolled. // Does not update iShownIndex for any items. UINT NEAR TV_ScrollBelow(PTREE pTree, HTREEITEM hItem, BOOL fRedrawParent, BOOL fDown) { int iTop; UINT cnt; // Do nothing if the item is not visible if (!ITEM_VISIBLE(hItem)) return 0; cnt = hItem->iIntegral; // default return val if (pTree->fRedraw) { UINT cVisDesc; BOOL fEffect; // iTop is the top edge (client coordinates) of the bottom integral // cell of the item that just got expanded/contracted. // (Confused yet? I sure am.) iTop = hItem->iShownIndex - pTree->hTop->iShownIndex + hItem->iIntegral - 1; cVisDesc = TV_CountVisibleDescendants(hItem); // See if the item being expanded/contracted has any effect on the // screen. If not, then don't TV_ScrollItems or we will end up // double-counting them when we do post-scroll adjustment. if (fDown) { // When scrolling down, we have an effect if the item that just // got expanded was below the top of the screen fEffect = iTop >= 0; } else { // When scrolling up, we have an effect if any of the items // that just got collapsed out were below the top of the screen fEffect = (int)(iTop + cVisDesc) >= 0; } if (fEffect) TV_ScrollItems(pTree, cVisDesc, iTop, fDown); TV_InvalidateItem(pTree, hItem, TRUE); if (fRedrawParent) cnt = cVisDesc; } else { if (fRedrawParent) cnt = TV_CountVisibleDescendants(hItem); } return(cnt); } // The FakeCustomDraw functions are used when you want the customdraw client // to set up a HDC so you can do stuff like GetTextExtent. // Usage: // TVFAKEDRAW tvfd; // TreeView_BeginFakeCustomDraw(pTree, &tvfd); // for each item you care about { // TreeView_BeginFakeItemDraw(&tvfd, hitem); // // TreeView_EndFakeItemDraw(&tvfd); // } // TreeView_EndFakeCustomDraw(&tvfd); void TreeView_BeginFakeCustomDraw(PTREE pTree, PTVFAKEDRAW ptvfd) { ptvfd->nmcd.nmcd.hdc = GetDC(pTree->ci.hwnd); ptvfd->nmcd.nmcd.uItemState = 0; ptvfd->nmcd.nmcd.dwItemSpec = 0; ptvfd->nmcd.nmcd.lItemlParam = 0; ptvfd->hfontPrev = (HFONT)GetCurrentObject(ptvfd->nmcd.nmcd.hdc, OBJ_FONT); // Since we aren't actually painting anything, we pass an empty // paint rectangle. Gosh, I hope no app faults when it sees an // empty paint rectangle. SetRectEmpty(&ptvfd->nmcd.nmcd.rc); ptvfd->pTree = pTree; ptvfd->dwCustomPrev = pTree->ci.dwCustom; pTree->ci.dwCustom = CIFakeCustomDrawNotify(&pTree->ci, CDDS_PREPAINT, &ptvfd->nmcd.nmcd); } DWORD TreeView_BeginFakeItemDraw(PTVFAKEDRAW ptvfd, HTREEITEM hitem) { PTREE pTree = ptvfd->pTree; // Note that if the client says CDRF_SKIPDEFAULT (i.e., is owner-draw) // we measure the item anyway, because that's what IE4 did. ptvfd->nmcd.nmcd.dwItemSpec = (DWORD_PTR)hitem; ptvfd->nmcd.nmcd.lItemlParam = hitem->lParam; if (hitem->state & TVIS_BOLD) { SelectFont(ptvfd->nmcd.nmcd.hdc, pTree->hFontBold); } else { SelectFont(ptvfd->nmcd.nmcd.hdc, pTree->hFont); } if (!(pTree->ci.dwCustom & CDRF_SKIPDEFAULT)) { // Font should not depend on colors or flags since those change // dynamically but we cache the width info forever. So we don't // need to set up uItemState. ptvfd->nmcd.clrText = pTree->clrText; ptvfd->nmcd.clrTextBk = pTree->clrBk; ptvfd->nmcd.iLevel = hitem->iLevel; ptvfd->dwCustomItem = CIFakeCustomDrawNotify(&pTree->ci, CDDS_ITEMPREPAINT, &ptvfd->nmcd.nmcd); } else { ptvfd->dwCustomItem = CDRF_DODEFAULT; } return ptvfd->dwCustomItem; } void TreeView_EndFakeItemDraw(PTVFAKEDRAW ptvfd) { PTREE pTree = ptvfd->pTree; if (!(ptvfd->dwCustomItem & CDRF_SKIPDEFAULT) && (ptvfd->dwCustomItem & CDRF_NOTIFYPOSTPAINT)) { CIFakeCustomDrawNotify(&pTree->ci, CDDS_ITEMPOSTPAINT, &ptvfd->nmcd.nmcd); } } void TreeView_EndFakeCustomDraw(PTVFAKEDRAW ptvfd) { PTREE pTree = ptvfd->pTree; // notify parent afterwards if they want us to if (!(pTree->ci.dwCustom & CDRF_SKIPDEFAULT) && pTree->ci.dwCustom & CDRF_NOTIFYPOSTPAINT) { CIFakeCustomDrawNotify(&pTree->ci, CDDS_POSTPAINT, &ptvfd->nmcd.nmcd); } // Restore previous state pTree->ci.dwCustom = ptvfd->dwCustomPrev; SelectObject(ptvfd->nmcd.nmcd.hdc, ptvfd->hfontPrev); ReleaseDC(pTree->ci.hwnd, ptvfd->nmcd.nmcd.hdc); } // ---------------------------------------------------------------------------- // Returns the width of the widest shown item in the tree // ---------------------------------------------------------------------------- UINT NEAR TV_RecomputeMaxWidth(PTREE pTree) { if (!(pTree->ci.style & TVS_NOSCROLL)) { HTREEITEM hItem; WORD wMax = 0; // REVIEW: this might not be the most efficient traversal of the tree for (hItem = pTree->hRoot->hKids; hItem; hItem = TV_GetNextVisItem(hItem)) { if (wMax < FULL_WIDTH(pTree, hItem)) wMax = FULL_WIDTH(pTree, hItem); } return((UINT)wMax); } else { return pTree->cxWnd; } } // ---------------------------------------------------------------------------- // Returns the horizontal text extent of the given item's text // ---------------------------------------------------------------------------- WORD NEAR TV_GetItemTextWidth(HDC hdc, PTREE pTree, HTREEITEM hItem) { TVITEMEX sItem; TCHAR szTemp[MAX_PATH]; SIZE size = {0,0}; sItem.pszText = szTemp; sItem.cchTextMax = ARRAYSIZE(szTemp); TV_GetItem(pTree, hItem, TVIF_TEXT, &sItem); GetTextExtentPoint(hdc, sItem.pszText, lstrlen(sItem.pszText), &size); return (WORD)(size.cx + (g_cxLabelMargin * 2)); } // ---------------------------------------------------------------------------- // Compute the text extent and the full width (indent, image, and text) of // the given item. // If there is a HDC, then we assume that the HDC has been set up with // the proper attributes (specifically, the font). If there is no HDC, // then we will set one up, measure the text, then tear it down. // If you will be measuring more than one item, it is recommended that // the caller set up the HDC and keep re-using it, because creating, // initializing, then destroy the HDC is rather slow. // ---------------------------------------------------------------------------- void NEAR TV_ComputeItemWidth(PTREE pTree, HTREEITEM hItem, HDC hdc) { TVFAKEDRAW tvfd; // in case client uses customdraw int iOldWidth = hItem->iWidth; if (hdc == NULL) { TreeView_BeginFakeCustomDraw(pTree, &tvfd); TreeView_BeginFakeItemDraw(&tvfd, hItem); } else { tvfd.nmcd.nmcd.hdc = hdc; } hItem->iWidth = TV_GetItemTextWidth(tvfd.nmcd.nmcd.hdc, pTree, hItem); if (!(pTree->ci.style & TVS_NOSCROLL) && iOldWidth != hItem->iWidth) if (pTree->cxMax < FULL_WIDTH(pTree, hItem)) { PostMessage(pTree->ci.hwnd, TVMP_CALCSCROLLBARS, 0, 0); pTree->cxMax = FULL_WIDTH(pTree, hItem); } if (hdc == NULL) { TreeView_EndFakeItemDraw(&tvfd); TreeView_EndFakeCustomDraw(&tvfd); } } // ---------------------------------------------------------------------------- // Returns TRUE if the item is expanded, FALSE otherwise // ---------------------------------------------------------------------------- BOOL NEAR TV_IsShowing(HTREEITEM hItem) { for (hItem = hItem->hParent; hItem; hItem = hItem->hParent) if (!(hItem->state & TVIS_EXPANDED)) return FALSE; return TRUE; } // ---------------------------------------------------------------------------- // If the added item is showing, update the shown (expanded) count, the max // item width -- then recompute the scroll bars. // sets cxMax, cShowing // ---------------------------------------------------------------------------- BOOL NEAR TV_ScrollBarsAfterAdd(PTREE pTree, HTREEITEM hItem) { HTREEITEM hPrev; if (!TV_IsShowing(hItem)) { // item isn't visible -- set index to NOTVISIBLE and return hItem->iShownIndex = (WORD)-1; return FALSE; } hPrev = TV_GetPrevVisItem(hItem); // increment every shown index after newly added item hItem->iShownIndex = (hPrev) ? hPrev->iShownIndex + hPrev->iIntegral : 0; TV_UpdateShownIndexes(pTree, hItem); pTree->cShowing += hItem->iIntegral; TV_ComputeItemWidth(pTree, hItem, NULL); TV_CalcScrollBars(pTree); return(TRUE); } // ---------------------------------------------------------------------------- // If the removed item was showing, update the shown (expanded) count, the max // item width -- then recompute the scroll bars. // sets cxMax, cShowing // ---------------------------------------------------------------------------- BOOL NEAR TV_ScrollBarsAfterRemove(PTREE pTree, HTREEITEM hItem) { HTREEITEM hWalk; if (!ITEM_VISIBLE(hItem)) return FALSE; // decrement every shown index after removed item hItem->iShownIndex = (WORD)-1; hWalk = TV_GetNextVisItem(hItem); if (hWalk) { hWalk->iShownIndex -= (WORD) hItem->iIntegral; TV_UpdateShownIndexes(pTree, hWalk); // If we delete the top item, the tree scrolls to the end, so ... if (pTree->hTop == hItem) { TV_SetTopItem(pTree, hWalk->iShownIndex); ASSERT(pTree->hTop != hItem); } } pTree->cShowing -= hItem->iIntegral; if (pTree->fRedraw) { if (!hItem->iWidth) TV_ComputeItemWidth(pTree, hItem, NULL); if (!(pTree->ci.style & TVS_NOSCROLL)) if (pTree->cxMax == FULL_WIDTH(pTree, hItem)) pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree); TV_CalcScrollBars(pTree); } return TRUE; } // ---------------------------------------------------------------------------- // Common worker function for // TV_ScrollBarsAfterExpand and TV_ScrollBarsAfterCollapse, since they // are completely identical save for two lines of code. // If the expanded items are / collapsed items were showing, update // the shown (expanded) count, the max item width -- then recompute // the scroll bars. // ---------------------------------------------------------------------------- #define SBAEC_COLLAPSE 0 #define SBAEC_EXPAND 1 BOOL NEAR TV_ScrollBarsAfterExpandCollapse(PTREE pTree, HTREEITEM hParent, UINT flags) { WORD cxMax = 0; HTREEITEM hWalk; TVFAKEDRAW tvfd; if (!ITEM_VISIBLE(hParent)) return FALSE; // We're going to be measuring a lot of items, so let's set up // our DC ahead of time. TreeView_BeginFakeCustomDraw(pTree, &tvfd); for (hWalk = hParent->hKids; hWalk && (hWalk->iLevel > hParent->iLevel); hWalk = TV_GetNextVisItem(hWalk)) { if (flags == SBAEC_COLLAPSE) hWalk->iShownIndex = (WORD)-1; if (!hWalk->iWidth) { TreeView_BeginFakeItemDraw(&tvfd, hWalk); TV_ComputeItemWidth(pTree, hWalk, tvfd.nmcd.nmcd.hdc); TreeView_EndFakeItemDraw(&tvfd); } if (cxMax < FULL_WIDTH(pTree, hWalk)) cxMax = FULL_WIDTH(pTree, hWalk); } TreeView_EndFakeCustomDraw(&tvfd); // update every shown index after expanded parent pTree->cShowing = TV_UpdateShownIndexes(pTree, hParent); // Update the pTree->cxMax if it is affected by the items we // expanded/collapsed. if (!(pTree->ci.style & TVS_NOSCROLL)) { if (flags == SBAEC_COLLAPSE) { // If one of our newly-hidden items was responsible for // the width being what it is, recompute the max width // since we hid those items. if (cxMax == pTree->cxMax) pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree); } else { // If one of our newly-shown items was responsible is wider // then the previous max, then we have set a new max. if (cxMax > pTree->cxMax) pTree->cxMax = cxMax; } } TV_CalcScrollBars(pTree); return(TRUE); } // ---------------------------------------------------------------------------- // If the expanded items are showing, update the shown (expanded) count, // the max item width -- then recompute the scroll bars. // sets cxMax, cShowing // ---------------------------------------------------------------------------- BOOL NEAR TV_ScrollBarsAfterExpand(PTREE pTree, HTREEITEM hParent) { return TV_ScrollBarsAfterExpandCollapse(pTree, hParent, SBAEC_EXPAND); } // ---------------------------------------------------------------------------- // If the collapsed items were showing, update the shown (expanded) count, // the max item width -- then recompute the scroll bars. // sets cxMax, cShowing // ---------------------------------------------------------------------------- BOOL NEAR TV_ScrollBarsAfterCollapse(PTREE pTree, HTREEITEM hParent) { return TV_ScrollBarsAfterExpandCollapse(pTree, hParent, SBAEC_COLLAPSE); } // ---------------------------------------------------------------------------- // If the added item changed height, then scroll thing around, // update the shown (expanded) count, recompute the scroll bars. // sets cShowing // ---------------------------------------------------------------------------- void NEAR TV_ScrollBarsAfterResize(PTREE pTree, HTREEITEM hItem, int iIntegralPrev, UINT uRDWFlags) { int iMaxIntegral = max(hItem->iIntegral, iIntegralPrev); ASSERT(hItem->iIntegral != iIntegralPrev); if (pTree->fRedraw) { int iTop = hItem->iShownIndex - pTree->hTop->iShownIndex + iMaxIntegral - 1; if (iTop >= 0) { int iGrowth = hItem->iIntegral - iIntegralPrev; TV_ScrollItems(pTree, abs(iGrowth), iTop, iGrowth > 0); } } // update every shown index after resized item pTree->cShowing = TV_UpdateShownIndexes(pTree, hItem); TV_CalcScrollBars(pTree); // Invalidate based on the worst-case height so we handle // both the grow and shrink cases. if (pTree->fRedraw) { RECT rc; if (TV_GetItemRect(pTree, hItem, &rc, FALSE)) { rc.bottom = rc.top + pTree->cyItem * iMaxIntegral; RedrawWindow(pTree->ci.hwnd, &rc, NULL, uRDWFlags); } } } // ---------------------------------------------------------------------------- // Returns the item just below the given item in the tree. // ---------------------------------------------------------------------------- TREEITEM FAR * NEAR TV_GetNext(TREEITEM FAR * hItem) { DBG_ValidateTreeItem(hItem, FALSE); if (hItem->hKids) return hItem->hKids; checkNext: if (hItem->hNext) return hItem->hNext; hItem = hItem->hParent; if (hItem) goto checkNext; return NULL; } // ---------------------------------------------------------------------------- // Go through all the items in the tree, recomputing each item's text extent // and full width (indent, image, and text). // ---------------------------------------------------------------------------- void NEAR TV_RecomputeItemWidths(PTREE pTree) { HTREEITEM hItem; TVFAKEDRAW tvfd; TreeView_BeginFakeCustomDraw(pTree, &tvfd); hItem = pTree->hRoot->hKids; while (hItem) { TreeView_BeginFakeItemDraw(&tvfd, hItem); TV_ComputeItemWidth(pTree, hItem, tvfd.nmcd.nmcd.hdc); TreeView_EndFakeItemDraw(&tvfd); hItem = TV_GetNext(hItem); } TreeView_EndFakeCustomDraw(&tvfd); } // ---------------------------------------------------------------------------- // If a single item's width changed, alter the max width if needed. // If all widths changed, recompute widths and max width. // Then recompute the scroll bars. // sets cxMax // ---------------------------------------------------------------------------- BOOL NEAR TV_ScrollBarsAfterSetWidth(PTREE pTree, HTREEITEM hItem) { if (hItem) { UINT iOldWidth = FULL_WIDTH(pTree, hItem); TV_ComputeItemWidth(pTree, hItem, NULL); if (!(pTree->ci.style & TVS_NOSCROLL)) { if (pTree->cxMax == iOldWidth) pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree); else return(FALSE); } } else { TV_RecomputeItemWidths(pTree); pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree); } TV_CalcScrollBars(pTree); return(TRUE); } // ---------------------------------------------------------------------------- // Scroll window vertically as needed to make given item fully visible // vertically // ---------------------------------------------------------------------------- BOOL NEAR TV_ScrollVertIntoView(PTREE pTree, HTREEITEM hItem) { // This function has crashed in stress before, so we need to assert the incoming parameters. ASSERT(hItem); ASSERT(pTree && pTree->hTop); // Do nothing if the parameters are invalid if (!hItem || !pTree || !(pTree->hTop)) return FALSE; // Do nothing if this item is not visible if (!ITEM_VISIBLE(hItem)) return FALSE; if (hItem->iShownIndex < pTree->hTop->iShownIndex) return(TV_SetTopItem(pTree, hItem->iShownIndex)); if (hItem->iShownIndex >= (pTree->hTop->iShownIndex + pTree->cFullVisible)) return(TV_SetTopItem(pTree, hItem->iShownIndex + 1 - pTree->cFullVisible)); return FALSE; } // ---------------------------------------------------------------------------- // Scroll window vertically and horizontally as needed to make given item // fully visible vertically and horizontally // ---------------------------------------------------------------------------- BOOL NEAR TV_ScrollIntoView(PTREE pTree, HTREEITEM hItem) { UINT iWidth, iOffset; BOOL fChange; fChange = TV_ScrollVertIntoView(pTree, hItem); // ensure that item's text is fully visible horizontally iWidth = pTree->cxImage + pTree->cxState + hItem->iWidth; if (iWidth > (UINT)pTree->cxWnd) iWidth = pTree->cxWnd; //hItem->iWidth; iOffset = ITEM_OFFSET(pTree, hItem); if ((int) (iOffset) < pTree->xPos) fChange |= TV_SetLeft(pTree, iOffset); else if ((iOffset + iWidth) > (UINT)(pTree->xPos + pTree->cxWnd)) fChange |= TV_SetLeft(pTree, iOffset + iWidth - pTree->cxWnd); return fChange; } // ---------------------------------------------------------------------------- // Sets position of horizontal scroll bar and scrolls window to match that // position // sets xPos // ---------------------------------------------------------------------------- BOOL NEAR TV_SetLeft(PTREE pTree, int x) { if (!pTree->fHorz || pTree->ci.style & (TVS_NOSCROLL | TVS_NOHSCROLL)) return(FALSE); if (x > (int) (pTree->cxMax - pTree->cxWnd)) x = (pTree->cxMax - pTree->cxWnd); if (x < 0) x = 0; if (x == pTree->xPos) return(FALSE); if (pTree->fRedraw) { #ifndef UNIX SMOOTHSCROLLINFO si = { sizeof(si), SSIF_MINSCROLL | SSIF_MAXSCROLLTIME, pTree->ci.hwnd, pTree->xPos - x, 0, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE, pTree->uMaxScrollTime, 1, 1 }; #else SMOOTHSCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SSIF_MINSCROLL | SSIF_MAXSCROLLTIME; si.hwnd = pTree->ci.hwnd; si.dx = pTree->xPos - x; si.dy = 0; si.lprcSrc = NULL; si.lprcClip = NULL; si.hrgnUpdate = NULL; si.lprcUpdate = NULL; si.fuScroll = SW_INVALIDATE | SW_ERASE; si.uMaxScrollTime = pTree->uMaxScrollTime; si.cxMinScroll = 1; si.cyMinScroll = 1; si.pfnScrollProc = NULL; #endif SmoothScrollWindow(&si); } pTree->xPos = (SHORT) x; SetScrollPos(pTree->ci.hwnd, SB_HORZ, x, TRUE); TV_UpdateToolTip(pTree); return(TRUE); } // ---------------------------------------------------------------------------- // Returns the tree's item that has the given shown index, NULL if no item // found with that index. // ---------------------------------------------------------------------------- HTREEITEM NEAR TV_GetShownIndexItem(HTREEITEM hItem, UINT wShownIndex) { HTREEITEM hWalk; if (hItem == NULL) return NULL; ASSERT((int)wShownIndex >= 0); for (hWalk = hItem; hWalk && (hWalk->iShownIndex <= wShownIndex); hWalk = hWalk->hNext) { hItem = hWalk; if (hWalk->iShownIndex + (UINT)hWalk->iIntegral > wShownIndex) return hWalk; } return TV_GetShownIndexItem(hItem->hKids, wShownIndex); } // ---------------------------------------------------------------------------- // Sets position of vertical scroll bar and scrolls window to match that // position // sets hTop // ---------------------------------------------------------------------------- BOOL NEAR TV_SmoothSetTopItem(PTREE pTree, UINT wNewTop, UINT uSmooth) { HTREEITEM hItem = pTree->hRoot->hKids; UINT wOldTop; if (!hItem) return FALSE; if ((pTree->ci.style & TVS_NOSCROLL) || (wNewTop == (UINT)-1) || (pTree->cShowing <= pTree->cFullVisible)) { // we've wrapped around (treat as a negative index) -- use min pos // or there aren't enough items to scroll wNewTop = 0; } else if (wNewTop > (UINT)(pTree->cShowing - pTree->cFullVisible)) { // we've gone too far down -- use max pos wNewTop = (pTree->cShowing - pTree->cFullVisible); } // if there's no room for anything to show. peg at the end if (wNewTop > 0 && wNewTop >= pTree->cShowing) { wNewTop = pTree->cShowing - 1; } hItem = TV_GetShownIndexItem(hItem, wNewTop); // BUGBUG (scotth): refreshing in regedit sometimes hits this case ASSERT(hItem); if (NULL == hItem || pTree->hTop == hItem) return FALSE; // need to refetch because wNewTop couldhave pointed to the middle of this item, // which is not allowed wNewTop = hItem->iShownIndex; wOldTop = pTree->hTop->iShownIndex; pTree->hTop = hItem; if (pTree->fRedraw) { #ifndef UNIX SMOOTHSCROLLINFO si = { sizeof(si), SSIF_MINSCROLL | SSIF_MAXSCROLLTIME, pTree->ci.hwnd, 0, (int) (wOldTop - wNewTop) * (int) pTree->cyItem, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE | uSmooth, pTree->uMaxScrollTime, 1, 1 }; #else SMOOTHSCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SSIF_MINSCROLL | SSIF_MAXSCROLLTIME; si.hwnd = pTree->ci.hwnd; si.dx = 0; si.dy = (int) (wOldTop - wNewTop) * (int) pTree->cyItem; si.lprcSrc = NULL; si.lprcClip = NULL; si.hrgnUpdate = NULL; si.lprcUpdate = NULL; si.fuScroll = SW_INVALIDATE | SW_ERASE | uSmooth; si.uMaxScrollTime = pTree->uMaxScrollTime; si.cxMinScroll = 1; si.cyMinScroll = 1; si.pfnScrollProc = NULL; #endif SmoothScrollWindow(&si); } SetScrollPos(pTree->ci.hwnd, SB_VERT, wNewTop, TRUE); TV_UpdateToolTip(pTree); return(TRUE); } // ---------------------------------------------------------------------------- // Computes the horizontal and vertical scroll bar ranges, pages, and // positions, adding or removing the scroll bars as needed. // sets fHorz, fVert // ---------------------------------------------------------------------------- BOOL NEAR TV_CalcScrollBars(PTREE pTree) { // UINT wMaxPos; BOOL fChange = FALSE; SCROLLINFO si; if (pTree->ci.style & TVS_NOSCROLL) return FALSE; si.cbSize = sizeof(SCROLLINFO); if (!(pTree->ci.style & TVS_NOHSCROLL)) { if ((SHORT)pTree->cxMax > (SHORT)pTree->cxWnd) { if (!pTree->fHorz) { fChange = TRUE; pTree->fHorz = TRUE; } si.fMask = SIF_PAGE | SIF_RANGE; si.nMin = 0; si.nMax = pTree->cxMax - 1; si.nPage = pTree->cxWnd; TV_SetLeft(pTree, (UINT)SetScrollInfo(pTree->ci.hwnd, SB_HORZ, &si, TRUE)); } else if (pTree->fHorz) { TV_SetLeft(pTree, 0); SetScrollRange(pTree->ci.hwnd, SB_HORZ, 0, 0, TRUE); pTree->fHorz = FALSE; fChange = TRUE; } } if (pTree->cShowing > pTree->cFullVisible) { if (!pTree->fVert) { pTree->fVert = TRUE; fChange = TRUE; } si.fMask = SIF_PAGE | SIF_RANGE; si.nMin = 0; si.nMax = pTree->cShowing - 1; si.nPage = pTree->cFullVisible; TV_SetTopItem(pTree, (UINT)SetScrollInfo(pTree->ci.hwnd, SB_VERT, &si, TRUE)); } else if (pTree->fVert) { TV_SetTopItem(pTree, 0); SetScrollRange(pTree->ci.hwnd, SB_VERT, 0, 0, TRUE); pTree->fVert = FALSE; fChange = TRUE; } if (fChange) TV_SizeWnd(pTree, 0, 0); return(TRUE); } // ---------------------------------------------------------------------------- // Handles horizontal scrolling. // ---------------------------------------------------------------------------- BOOL NEAR TV_HorzScroll(PTREE pTree, UINT wCode, UINT wNewPos) { BOOL fChanged; TV_DismissEdit(pTree, FALSE); switch (wCode) { case SB_BOTTOM: wNewPos = pTree->cxMax - pTree->cxWnd; break; case SB_ENDSCROLL: wNewPos = pTree->xPos; break; case SB_LINEDOWN: wNewPos = pTree->xPos + MAGIC_HORZLINE; break; case SB_LINEUP: wNewPos = pTree->xPos - MAGIC_HORZLINE; break; case SB_PAGEDOWN: wNewPos = pTree->xPos + (pTree->cxWnd - MAGIC_HORZLINE); break; case SB_PAGEUP: wNewPos = pTree->xPos - (pTree->cxWnd - MAGIC_HORZLINE); break; case SB_THUMBPOSITION: case SB_THUMBTRACK: break; case SB_TOP: wNewPos = 0; break; } if (fChanged = TV_SetLeft(pTree, wNewPos)) UpdateWindow(pTree->ci.hwnd); return(fChanged); } // ---------------------------------------------------------------------------- // Handles vertical scrolling. // ---------------------------------------------------------------------------- BOOL NEAR TV_VertScroll(PTREE pTree, UINT wCode, UINT wPos) { UINT wNewPos = 0; UINT wOldPos; BOOL fChanged; UINT uSmooth = 0; if (!pTree->hTop) return FALSE; wOldPos = pTree->hTop->iShownIndex; TV_DismissEdit(pTree, FALSE); switch (wCode) { case SB_BOTTOM: wNewPos = pTree->cShowing - pTree->cFullVisible; break; case SB_ENDSCROLL: wNewPos = wOldPos; break; case SB_LINEDOWN: wNewPos = wOldPos + pTree->hTop->iIntegral; break; case SB_LINEUP: wNewPos = wOldPos - 1; if (wNewPos > wOldPos) wNewPos = 0; break; case SB_PAGEDOWN: wNewPos = wOldPos + (pTree->cFullVisible - 1); break; case SB_PAGEUP: wNewPos = wOldPos - (pTree->cFullVisible - 1); if (wNewPos > wOldPos) wNewPos = 0; break; case SB_THUMBPOSITION: case SB_THUMBTRACK: uSmooth = SSW_EX_IMMEDIATE; wNewPos = wPos; break; case SB_TOP: wNewPos = 0; break; } if (fChanged = TV_SmoothSetTopItem(pTree, wNewPos, uSmooth)) UpdateWindow(pTree->ci.hwnd); return(fChanged); } #ifdef DEBUG static int nCompares; #endif typedef struct { LPTSTR lpstr; BOOL bCallBack; HTREEITEM hItem; } TVCOMPARE, FAR *LPTVCOMPARE; // Pointer comparision function for Sort and Search functions. // lParam is lParam passed to sort/search functions. Returns // -1 if p1 < p2, 0 if p1 == p2, and 1 if p1 > p2. int CALLBACK TV_DefCompare(LPTVCOMPARE sCmp1, LPTVCOMPARE sCmp2, LPARAM lParam) { #ifdef DEBUG ++nCompares; #endif return lstrcmpi(sCmp1->lpstr, sCmp2->lpstr); } int CALLBACK TV_CompareItems(LPTVCOMPARE sCmp1, LPTVCOMPARE sCmp2, LPARAM lParam) { TV_SORTCB FAR *pSortCB = (TV_SORTCB FAR *)lParam; #ifdef DEBUG ++nCompares; #endif return(pSortCB->lpfnCompare(sCmp1->hItem->lParam, sCmp2->hItem->lParam, pSortCB->lParam)); } UINT NEAR TV_CountKids(HTREEITEM hItem) { int cnt; for (cnt = 0, hItem = hItem->hKids; hItem; hItem = hItem->hNext) cnt++; return cnt; } // BUGBUG: bRecurse not implemented BOOL PASCAL TV_SortCB(PTREE pTree, TV_SORTCB FAR *pSortCB, BOOL bRecurse, PFNDPACOMPARE lpfnDPACompare) { HDPA dpaSort; HDSA dsaCmp; HTREEITEM hItem, hNext, hFirstMoved; LPTVCOMPARE psCompare, FAR *ppsCompare; int i, cKids; HTREEITEM hParent = pSortCB->hParent; #ifdef DEBUG DWORD dwTime = GetTickCount(); nCompares = 0; #endif if (!hParent || hParent == TVI_ROOT) hParent = pTree->hRoot; if (!ValidateTreeItem(hParent, FALSE)) return FALSE; // Invalid parameter // Code below assumes at least one kid cKids = TV_CountKids(hParent); if (!cKids) return FALSE; // Create a DSA for all the extra info we'll need dsaCmp = DSA_Create(sizeof(TVCOMPARE), cKids); if (!dsaCmp) goto Error1; // Create a DPA containing all the tree items dpaSort = DPA_Create(cKids); if (!dpaSort) goto Error2; for (hItem = hParent->hKids; hItem; hItem = hItem->hNext) { TVCOMPARE sCompare; int nItem; // If I can't sort all of them, I don't want to sort any of them // We want to cache the text callback for default processing if (!lpfnDPACompare && hItem->lpstr==LPSTR_TEXTCALLBACK) { TVITEMEX sItem; TCHAR szTemp[MAX_PATH]; sItem.pszText = szTemp; sItem.cchTextMax = ARRAYSIZE(szTemp); TV_GetItem(pTree, hItem, TVIF_TEXT, &sItem); sCompare.lpstr = NULL; sCompare.bCallBack = TRUE; Str_Set(&sCompare.lpstr, sItem.pszText); if (!sCompare.lpstr) { goto Error3; } } else { sCompare.lpstr = hItem->lpstr; sCompare.bCallBack = FALSE; } // Create the pointer for this guy and add it to the DPA list sCompare.hItem = hItem; nItem = DSA_AppendItem(dsaCmp, &sCompare); if (nItem < 0) { if (sCompare.bCallBack) { Str_Set(&sCompare.lpstr, NULL); } goto Error3; } if (DPA_AppendPtr(dpaSort, DSA_GetItemPtr(dsaCmp, nItem)) < 0) { goto Error3; } } // Sort the DPA, then stick them back under the parent in the new order DPA_Sort(dpaSort, lpfnDPACompare ? (PFNDPACOMPARE)lpfnDPACompare : (PFNDPACOMPARE) TV_DefCompare, (LPARAM)pSortCB); // Look for the first moved item, so we can invalidate a smaller area ppsCompare = (LPTVCOMPARE FAR *)DPA_GetPtrPtr(dpaSort); if (hParent->hKids != (*ppsCompare)->hItem) { hParent->hKids = (*ppsCompare)->hItem; hFirstMoved = hParent->hKids; } else { hFirstMoved = NULL; } // We do n-1 iterations here for (i = DPA_GetPtrCount(dpaSort) - 1; i > 0; --i, ++ppsCompare) { hNext = (*(ppsCompare+1))->hItem; if ((*ppsCompare)->hItem->hNext != hNext && !hFirstMoved) { hFirstMoved = hNext; } (*ppsCompare)->hItem->hNext = hNext; } (*ppsCompare)->hItem->hNext = NULL; TV_UpdateShownIndexes(pTree, hParent); if ((pSortCB->hParent == TVI_ROOT) || !hParent) { if (pTree->cShowing < pTree->cFullVisible) { pTree->hTop = pTree->hRoot->hKids; } } if (hFirstMoved && (hParent->state & TVIS_EXPANDED)) { RECT rcUpdate; TV_GetItemRect(pTree, hFirstMoved, &rcUpdate, FALSE); if (hParent->hNext) { RECT rcTemp; TV_GetItemRect(pTree, hParent->hNext, &rcTemp, FALSE); rcUpdate.bottom = rcTemp.bottom; } else { RECT rcClient; GetClientRect(pTree->ci.hwnd, &rcClient); // Set to maximal positive number, so the whole rest of // the treeview gets invalidated rcUpdate.bottom = rcClient.bottom; } if (pTree->fRedraw) InvalidateRect(pTree->ci.hwnd, &rcUpdate, TRUE); } Error3: DPA_Destroy(dpaSort); Error2: for (i = DSA_GetItemCount(dsaCmp) - 1; i >= 0; --i) { psCompare = DSA_GetItemPtr(dsaCmp, i); if (psCompare->bCallBack) { Str_Set(&(psCompare->lpstr), NULL); } } DSA_Destroy(dsaCmp); Error1: #ifdef DEBUG TraceMsg(TF_TREEVIEW, "tv.sort: %ld ms; %d cmps", GetTickCount()-dwTime, nCompares); #endif { int wNewPos; // restore the scroll position if (GetWindowStyle(pTree->ci.hwnd) & WS_VSCROLL) { SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; wNewPos = 0; if (GetScrollInfo(pTree->ci.hwnd, SB_VERT, &si)) { wNewPos = si.nPos; } } else { wNewPos = 0; } if (TV_SetTopItem(pTree, wNewPos)) UpdateWindow(pTree->ci.hwnd); } // if the caret is the child of the thing that was sorted, make sure it's // visible (but if we're sorting something completely unrelated, don't bother if (pTree->hCaret) { hItem = pTree->hCaret; do { // do this first. if hParent is hCaret, we don't want to ensure visible... // only if it's an eventual child hItem = hItem->hParent; if (hParent == hItem) { TV_EnsureVisible(pTree, pTree->hCaret); } } while(hItem && hItem != pTree->hRoot); } // The items in the view may have moved around; let apps know // Do this last because this call might yield MyNotifyWinEvent(EVENT_OBJECT_REORDER, pTree->ci.hwnd, OBJID_CLIENT, 0); return TRUE; } BOOL NEAR TV_SortChildrenCB(PTREE pTree, LPTV_SORTCB pSortCB, BOOL bRecurse) { return(TV_SortCB(pTree, pSortCB, bRecurse, (PFNDPACOMPARE)TV_CompareItems)); } BOOL NEAR TV_SortChildren(PTREE pTree, HTREEITEM hParent, BOOL bRecurse) { TV_SORTCB sSortCB; sSortCB.hParent = hParent; return(TV_SortCB(pTree, &sSortCB, bRecurse, NULL)); }