#include "ctlspriv.h" #include "treeview.h" #include "listview.h" // penwin.h is messed up; define local stuff for now #define HN_BEGINDIALOG 40 // Lens/EditText/garbage detection dialog is about // to come up on this hedit/bedit #define HN_ENDDIALOG 41 // Lens/EditText/garbage detection dialog has // just been destroyed //--------------------------------------------------------- #define IDT_SCROLLWAIT 43 //----------------------- // ToolTip stuff... // #define REPEATTIME SendMessage(pTree->hwndToolTips,TTM_GETDELAYTIME,(WPARAM)TTDT_RESHOW, 0) #define CHECKFOCUSTIME (REPEATTIME) #define IDT_TOOLTIPWAIT 2 #define IDT_FOCUSCHANGE 3 // in tooltips.c BOOL ChildOfActiveWindow(HWND hwnd); void TV_HandleStateIconClick(PTREE pTree, HTREEITEM hItem); HWND TV_EditLabel(PTREE pTree, HTREEITEM hItem, LPTSTR pszInitial); void TV_CancelEditTimer(PTREE pTree); BOOL TV_SetItem(PTREE pTree, LPCTVITEMEX ptvi); void TV_DeleteHotFonts(PTREE pTree); BOOL TV_IsShowing(HTREEITEM hItem); LRESULT TV_OnScroll(PTREE ptv, LPNMHDR pnm); #define TVBD_FROMWHEEL 0x0001 #define TVBD_WHEELFORWARD 0x0002 #define TVBD_WHEELBACK 0x0004 BOOL ValidateTreeItem(TREEITEM * hItem, UINT flags) { BOOL fValid = TRUE; /* * Check the values to make sure the new Win64-compatible values * are consistent with the old Win32 values. */ COMPILETIME_ASSERT( (DWORD)(ULONG_PTR)TVI_ROOT == 0xFFFF0000 && (DWORD)(ULONG_PTR)TVI_FIRST == 0xFFFF0001 && (DWORD)(ULONG_PTR)TVI_LAST == 0xFFFF0002 && (DWORD)(ULONG_PTR)TVI_SORT == 0xFFFF0003); if (hItem) { if (HIWORD64(hItem) == HIWORD64(TVI_ROOT)) { switch (LOWORD(hItem)) { case LOWORD(TVI_ROOT): case LOWORD(TVI_FIRST): case LOWORD(TVI_LAST): case LOWORD(TVI_SORT): break; default: AssertMsg(FALSE, TEXT("ValidateTreeItem() Invalid special item")); fValid = FALSE; break; } } else { __try { // Use "volatile" to force memory access at start of struct *(volatile void **)hItem; fValid = hItem->wSignature == TV_SIG; } __except(EXCEPTION_EXECUTE_HANDLER) { fValid = FALSE; } __endexcept } } else if (!flags) { // The only flag is VTI_NULLOK RIPMSG(FALSE, "ValidateTreeItem(): NULL HTREEITEM"); fValid = FALSE; } return fValid; } // ---------------------------------------------------------------------------- // // Initialize TreeView on library entry -- register SysTreeView class // // ---------------------------------------------------------------------------- BOOL TV_Init(HINSTANCE hinst) { WNDCLASS wc; wc.lpfnWndProc = TV_WndProc; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = NULL; wc.hInstance = hinst; wc.lpszClassName = c_szTreeViewClass; wc.hbrBackground = NULL; wc.style = CS_DBLCLKS | CS_GLOBALCLASS; wc.cbWndExtra = sizeof(PTREE); wc.cbClsExtra = 0; return (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS)); } // ---------------------------------------------------------------------------- // // If the tooltip bubble is up, then pop it. // // ---------------------------------------------------------------------------- void TV_PopBubble(PTREE pTree) { if (pTree->hwndToolTips && pTree->hToolTip) { pTree->hToolTip = NULL; SendMessage(pTree->hwndToolTips, TTM_POP, 0L, 0L); } } // ---------------------------------------------------------------------------- // // Sends a TVN_BEGINDRAG or TVN_BEGINRDRAG notification with information in the ptDrag and // itemNew fields of an NM_TREEVIEW structure // // ---------------------------------------------------------------------------- BOOL TV_SendBeginDrag(PTREE pTree, int code, TREEITEM * hItem, int x, int y) { NM_TREEVIEW nm; TV_PopBubble(pTree); // dismiss the infotip if we start to drag nm.itemNew.hItem = hItem; nm.itemNew.state = hItem->state; nm.itemNew.lParam = hItem->lParam; nm.itemNew.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM); nm.itemOld.mask = 0; nm.ptDrag.x = x; nm.ptDrag.y = y; return (BOOL)CCSendNotify(&pTree->ci, code, &nm.hdr); } // ---------------------------------------------------------------------------- // // Sends a TVN_ITEMEXPANDING or TVN_ITEMEXPANDED notification with information // in the action and itemNew fields of an NM_TREEVIEW structure // // Returns FALSE to allow processing to continue, or TRUE to stop. // // If the hItem is destroyed by the callback, then we always return TRUE. // // Note that the application cannot stop a TVN_ITEMEXPANDED, so the only // way a TVN_ITEMEXPANDED can return "Stop" is if the item got destroyed. // // ---------------------------------------------------------------------------- BOOL TV_SendItemExpand(PTREE pTree, int code, TREEITEM * hItem, WPARAM action) { NM_TREEVIEW nm; TVWATCHEDITEM wi; BOOL fResult; BOOL fWatched; ASSERT(code == TVN_ITEMEXPANDING || code == TVN_ITEMEXPANDED); nm.itemNew.mask = 0; nm.itemNew.hItem = hItem; if (hItem == TVI_ROOT) hItem = pTree->hRoot; nm.itemNew.state = hItem->state; nm.itemNew.lParam = hItem->lParam; nm.itemNew.iImage = hItem->iImage; nm.itemNew.iSelectedImage = hItem->iSelectedImage; switch(hItem->fKids) { case KIDS_CALLBACK: case KIDS_FORCE_YES: nm.itemNew.cChildren = 1; nm.itemNew.mask = TVIF_CHILDREN; break; case KIDS_FORCE_NO: nm.itemNew.cChildren = 0; nm.itemNew.mask = TVIF_CHILDREN; break; } nm.itemNew.mask |= (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE); nm.itemOld.mask = 0; nm.action = (UINT)(action & TVE_ACTIONMASK); // // Some apps will delete the item while it is being expanded, since // during expansion, they will realize, "Hey, the thing represented // by this item no longer exists, I'd better delete it." (E.g,. // Explorer.) So keep an eye on the item so we don't fault when // this happens. // // If we can't start a watch, then tough, just send the notification // the unsafe way. fWatched = TV_StartWatch(pTree, &wi, hItem); fResult = (BOOL)CCSendNotify(&pTree->ci, code, &nm.hdr); // The app return code from TVN_ITEMEXPANDED is ignored. // You can't stop a TVN_ITEMEXPANDED; it's already happened. if (code == TVN_ITEMEXPANDED) fResult = FALSE; // Continue processing if (fWatched) { if (!TV_IsWatchValid(pTree, &wi)) fResult = TRUE; // Oh no! Stop! TV_EndWatch(pTree, &wi); } return fResult; } // ---------------------------------------------------------------------------- // // Sends a TVN_SELCHANGING or TVN_SELCHANGED notification with information in // the itemOld and itemNew fields of an NM_TREEVIEW structure // // ---------------------------------------------------------------------------- BOOL TV_SendSelChange(PTREE pTree, int code, TREEITEM * hOldItem, TREEITEM * hNewItem, UINT action) { NM_TREEVIEW nm; nm.action = action; nm.itemNew.hItem = hNewItem; nm.itemNew.state = hNewItem ? hNewItem->state : 0; nm.itemNew.lParam = hNewItem ? hNewItem->lParam : 0; nm.itemNew.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM); nm.itemOld.hItem = hOldItem; nm.itemOld.state = hOldItem ? hOldItem->state : 0; nm.itemOld.lParam = hOldItem ? hOldItem->lParam : 0; nm.itemOld.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM); return (BOOL)CCSendNotify(&pTree->ci, code, &nm.hdr); } // ---------------------------------------------------------------------------- // // Returns the first visible item above the given item in the tree. // // ---------------------------------------------------------------------------- TREEITEM * TV_GetPrevVisItem(TREEITEM * hItem) { TREEITEM * hParent = hItem->hParent; TREEITEM * hWalk; DBG_ValidateTreeItem(hItem, 0); if (hParent->hKids == hItem) return VISIBLE_PARENT(hItem); for (hWalk = hParent->hKids; hWalk->hNext != hItem; hWalk = hWalk->hNext); checkKids: if (hWalk->hKids && (hWalk->state & TVIS_EXPANDED)) { for (hWalk = hWalk->hKids; hWalk->hNext; hWalk = hWalk->hNext); goto checkKids; } return(hWalk); } // ---------------------------------------------------------------------------- // // Returns the first visible item below the given item in the tree. // // ---------------------------------------------------------------------------- TREEITEM * TV_GetNextVisItem(TREEITEM * hItem) { DBG_ValidateTreeItem(hItem, 0); if (hItem->hKids && (hItem->state & TVIS_EXPANDED)) return hItem->hKids; checkNext: if (hItem->hNext) return(hItem->hNext); hItem = hItem->hParent; if (hItem) goto checkNext; return NULL; } // ---------------------------------------------------------------------------- // // Determine what part of what item is at the given (x,y) location in the // tree's client area. If the location is outside the client area, NULL is // returned with the TVHT_TOLEFT, TVHT_TORIGHT, TVHT_ABOVE, and/or TVHT_BELOW // flags set in the wHitCode as appropriate. If the location is below the // last item, NULL is returned with wHitCode set to TVHT_NOWHERE. Otherwise, // the item is returned with wHitCode set to either TVHT_ONITEMINDENT, // TVHT_ONITEMBUTTON, TVHT_ONITEMICON, TVHT_ONITEMLABEL, or TVHT_ONITEMRIGHT // // ---------------------------------------------------------------------------- TREEITEM * TV_CheckHit(PTREE pTree, int x, int y, UINT *wHitCode) { TREEITEM * hItem = pTree->hTop; int cxState; TVITEMEX sItem; *wHitCode = 0; if (x < 0) *wHitCode |= TVHT_TOLEFT; else if (x > (int) pTree->cxWnd) *wHitCode |= TVHT_TORIGHT; if (y < 0) *wHitCode |= TVHT_ABOVE; else if (y > (int) pTree->cyWnd) *wHitCode |= TVHT_BELOW; if (*wHitCode) return NULL; { int index = y / pTree->cyItem; while (hItem && index >= hItem->iIntegral) { index -= hItem->iIntegral; hItem = TV_GetNextVisItem(hItem); } } if (!hItem) { *wHitCode = TVHT_NOWHERE; return NULL; } x -= (pTree->cxBorder + (hItem->iLevel * pTree->cxIndent)); x += pTree->xPos; if ((pTree->ci.style & (TVS_HASLINES | TVS_HASBUTTONS)) && (pTree->ci.style &TVS_LINESATROOT)) { // Subtract some more to make up for the pluses at the root x -= pTree->cxIndent; } TV_GetItem(pTree, hItem, TVIF_CHILDREN, &sItem); cxState = TV_StateIndex(&sItem) ? pTree->cxState : 0; if (x <= (int) (hItem->iWidth + pTree->cxImage + cxState)) { if (x >= 0) { if (pTree->himlState && (x < cxState)) { *wHitCode = TVHT_ONITEMSTATEICON; } else if (pTree->hImageList && (x < (int) pTree->cxImage + cxState)) { *wHitCode = TVHT_ONITEMICON; } else { *wHitCode = TVHT_ONITEMLABEL; } } else if ((x >= -pTree->cxIndent) && sItem.cChildren && (pTree->ci.style & TVS_HASBUTTONS)) *wHitCode = TVHT_ONITEMBUTTON; else *wHitCode = TVHT_ONITEMINDENT; } else *wHitCode = TVHT_ONITEMRIGHT; return hItem; } // This is tricky because CheckForDragBegin yields and the app may have // destroyed the item we are thinking about dragging // // To give the app some feedback, we give the hItem the drop highlight // if it isn't already the caret. This also allows us to check if the // item got deleted behind our back - TV_DeleteItemRecurse makes sure // that deleted items are never the hCaret or hDropTarget. // // After TV_CheckForDragBegin, the caller must call TV_FinishCheckDrag // to clean up the UI changes that TV_CheckForDragBegin temporarily // performed. // BOOL TV_CheckForDragBegin(PTREE pTree, HTREEITEM hItem, int x, int y) { BOOL fDrag; // // If the item is not the caret, then make it the (temporary) // drop target so the user gets some feedback. // // Bug#94368 raymondc - If hItem == pTree->hCaret, it still might not // be visible if the control doesn't yet have focus and the treeview // is not marked showselalways. Maybe we should just always set // hItem to DROPHILITE. // if (hItem == pTree->hCaret) { pTree->hOldDrop = NULL; pTree->fRestoreOldDrop = FALSE; } else { pTree->hOldDrop = pTree->hDropTarget; pTree->fRestoreOldDrop = TRUE; TV_SelectItem(pTree, TVGN_DROPHILITE, hItem, 0, TVC_BYMOUSE); ASSERT(hItem == pTree->hDropTarget); } // // We are dragging the hItem if CheckForDragBegin says okay, // and TV_DeleteItemRecurse didn't wipe us out. // fDrag = CheckForDragBegin(pTree->ci.hwnd, x, y) && (hItem == pTree->hDropTarget || hItem == pTree->hCaret); return fDrag; } void TV_FinishCheckDrag(PTREE pTree) { // // Clean up our temporary UI changes that happened when we started // dragging. // if (pTree->fRestoreOldDrop) { HTREEITEM hOldDrop = pTree->hOldDrop; pTree->fRestoreOldDrop = FALSE; pTree->hOldDrop = NULL; TV_SelectItem(pTree, TVGN_DROPHILITE, hOldDrop, 0, TVC_BYMOUSE); } } void TV_SendRButtonDown(PTREE pTree, int x, int y) { BOOL fRet = FALSE; UINT wHitCode; TREEITEM * hItem = TV_CheckHit(pTree, x, y, &wHitCode); HWND hwnd = pTree->ci.hwnd; if (!TV_DismissEdit(pTree, FALSE)) // end any previous editing (accept it) return; // Something happened such that we should not process button down // // Need to see if the user is going to start a drag operation // GetMessagePosClient(pTree->ci.hwnd, &pTree->ptCapture); if (TV_CheckForDragBegin(pTree, hItem, x, y)) { // let them start dragging if (hItem) { pTree->htiDrag = hItem; TV_SendBeginDrag(pTree, TVN_BEGINRDRAG, hItem, x, y); } } else if (!IsWindow(hwnd)) { return; // bail! } else { SetFocus(pTree->ci.hwnd); // Activate this window like listview... fRet = !CCSendNotify(&pTree->ci, NM_RCLICK, NULL); } // Don't finish the CheckForDragBegin until after the NM_RCLICK // because apps want to display the context menu while the // temporary drag UI is still active. TV_FinishCheckDrag(pTree); if (fRet) SendMessage(pTree->ci.hwndParent, WM_CONTEXTMENU, (WPARAM)pTree->ci.hwnd, GetMessagePos()); } // ---------------------------------------------------------------------------- // // If the given item is visible in the client area, the rectangle that // surrounds that item is invalidated // // ---------------------------------------------------------------------------- void TV_InvalidateItem(PTREE pTree, TREEITEM * hItem, UINT fRedraw) { RECT rc; if (hItem && pTree->fRedraw && TV_GetItemRect(pTree, hItem, &rc, FALSE)) { RedrawWindow(pTree->ci.hwnd, &rc, NULL, fRedraw); } } // // Given an item, compute where the text of this item ends up being painted. // Basically, stare at TV_DrawItem and dutifully reproduce all the code that // messes with the x-coordinate. // int ITEM_OFFSET(PTREE pTree, HTREEITEM hItem) { int x = pTree->cxBorder + (hItem->iLevel * pTree->cxIndent); // state image if (pTree->himlState && TV_StateIndex(hItem)) x += pTree->cxState; // image if (pTree->hImageList) { // even if not drawing image, draw text in right place x += pTree->cxImage; } // "plus" at the front of the tree if ((pTree->ci.style & TVS_LINESATROOT) && (pTree->ci.style & (TVS_HASLINES | TVS_HASBUTTONS))) x += pTree->cxIndent; return x; } // ---------------------------------------------------------------------------- // // If the given item is visible in the client area, the rectangle that // surrounds that item is filled into lprc // // Returns TRUE if the item is shown, FALSE otherwise // // ---------------------------------------------------------------------------- BOOL TV_GetItemRect(PTREE pTree, TREEITEM * hItem, LPRECT lprc, BOOL bItemRect) { UINT iOffset; if (!hItem) return FALSE; DBG_ValidateTreeItem(hItem, 0); if (!ITEM_VISIBLE(hItem)) return FALSE; iOffset = hItem->iShownIndex - pTree->hTop->iShownIndex; if (bItemRect) { // Calculate where X position should start... lprc->left = -pTree->xPos + ITEM_OFFSET(pTree, hItem); lprc->right = lprc->left + hItem->iWidth; } else { lprc->left = 0; lprc->right = pTree->cxWnd; } lprc->top = iOffset * pTree->cyItem; lprc->bottom = lprc->top + (pTree->cyItem * hItem->iIntegral) ; return TRUE; } void TV_OnSetRedraw(PTREE pTree, BOOL fRedraw) { pTree->fRedraw = TRUE && fRedraw; if (pTree->fRedraw) { // This use to only refresh the items from hTop down, this is bad as if items are inserted // before the visible point within the tree then we would fail! if ( pTree->hRoot ) pTree->cShowing = TV_UpdateShownIndexes(pTree,pTree->hRoot); // Must force recalculation of all tree items to get the right cxMax. TV_ScrollBarsAfterSetWidth(pTree, NULL); InvalidateRect(pTree->ci.hwnd, NULL, TRUE); //REVIEW: could be smarter } } // Treeview item watching implementation // // You need to "watch" an item any time you hold onto its HTREEITEM // and then yield control to the application. If you didn't watch // the item, then if the app deletes the item, you end up with a // stale HTREEITEM pointer and fault. // // To begin watching an item, call TV_StartWatch with the item you // want to start watching. When finished watching, call TV_EndWatch. // // In between, you can call TV_IsWatchStale() which tells you if the // item has been deleted behind your back and you shouldn't use it. // Alternatively, use TV_IsWatchValid() which says if it's okay. // // Additional bonus behavior for enumeration: If the watched item // is deleted, we cache the hNext item so that you can step to the // item after the one that got deleted. Note that this works even // if the hNext item gets deleted before you get a chance to look, // because we just move the cached item to the hNext's hNext. // // Sample usage for watching: // // TVWATCHEDITEM wi; // if (TV_StartWatch(pTree, &wi, htiStartHere)) { // FunctionThatYields(); // if (TV_IsWatchValid(pTree, &wi)) { // KeepUsing(htiStartHere); // } else { // // item was deleted while we yielded; stop using it // } // TV_EndWatch(pTree, &wi); // } // // Sample usage for enumerating: // // TVWATCHEDITEM wi; // if (TV_StartWatch(pTree, &wi, htiFirst)) { // while (TV_GetWatchItem(pTree, &wi)) { // FunctionThatYields(TV_GetWatchItem(pTree, &wi)); // if (TV_IsWatchValid(pTree, &wi)) { // KeepUsing(htiStartHere); // } else { // // item was deleted while we yielded; stop using it // } // TV_NextWatchItem(pTree, &wi); // } // TV_EndWatch(pTree, &wi); // } // // // // // TV_StartWatch - Begin watching an item. // // Returns FALSE if out of memory. // BOOL TV_StartWatch(PTREE pTree, PTVWATCHEDITEM pwi, HTREEITEM htiStart) { pwi->hti = htiStart; pwi->fStale = FALSE; return DPA_AppendPtr(pTree->hdpaWatch, pwi) != -1; } // // TV_EndWatch - Remove the item from the watch list. // BOOL TV_EndWatch(PTREE pTree, PTVWATCHEDITEM pwi) { int i = DPA_GetPtrCount(pTree->hdpaWatch); while (--i >= 0) { PTVWATCHEDITEM pwiT = DPA_FastGetPtr(pTree->hdpaWatch, i); ASSERT(pwiT); if (pwi == pwiT) { DPA_DeletePtr(pTree->hdpaWatch, i); return TRUE; } } ASSERT(!"TV_EndWatch: Item not in list"); return FALSE; } // End of treeview item watching implementation void TV_SetItemRecurse(PTREE pTree, TREEITEM *hItem, LPTVITEMEX ptvi) { // Note: This code assumes nobody will try to delete an item // during a SetItem notification. while (hItem) { ptvi->hItem = hItem; TV_SetItem(pTree, ptvi); if (hItem->hKids) { TV_SetItemRecurse(pTree, hItem->hKids, ptvi); } hItem = hItem->hNext; } } BOOL TV_DoExpandRecurse(PTREE pTree, TREEITEM *hItem, BOOL fNotify) { TVWATCHEDITEM wi; BOOL fRc = FALSE; if (TV_StartWatch(pTree, &wi, hItem)) { while ((hItem = TV_GetWatchItem(pTree, &wi))) { // was the escape key pressed at any point since the last check? if (GetAsyncKeyState(VK_ESCAPE) & 0x1) goto failed; TV_Expand(pTree, TVE_EXPAND, hItem, fNotify); // yields if (TV_IsWatchValid(pTree, &wi)) { if (hItem->hKids) { if (!TV_DoExpandRecurse(pTree, hItem->hKids, fNotify)) goto failed; } } TV_NextWatchItem(pTree, &wi); } fRc = TRUE; failed: TV_EndWatch(pTree, &wi); } return fRc; } void TV_ExpandRecurse(PTREE pTree, TREEITEM *hItem, BOOL fNotify) { BOOL fRedraw = pTree->fRedraw; TV_OnSetRedraw(pTree, FALSE); // we're going to check this after each expand so clear it first GetAsyncKeyState(VK_ESCAPE); TV_Expand(pTree, TVE_EXPAND, hItem, fNotify); // Bug#94345 hItem may have gone bad during that TV_Expand TV_DoExpandRecurse(pTree, hItem->hKids, fNotify); TV_OnSetRedraw(pTree, fRedraw); } void TV_ExpandParents(PTREE pTree, TREEITEM *hItem, BOOL fNotify) { if (hItem == TVI_ROOT || hItem == NULL) // Root has no parents { return; } hItem = hItem->hParent; if (hItem) { TVWATCHEDITEM wi; if (TV_StartWatch(pTree, &wi, hItem)) { TV_ExpandParents(pTree, hItem, fNotify); // Item may have gone invalid during expansion if (TV_IsWatchValid(pTree, &wi) && // make sure this item is not in a collapsed branch !(hItem->state & TVIS_EXPANDED)) { TV_Expand(pTree, TVE_EXPAND, hItem, fNotify); } TV_EndWatch(pTree, &wi); } } } // makes sure an item is expanded and scrolled into view BOOL TV_EnsureVisible(PTREE pTree, TREEITEM * hItem) { TV_ExpandParents(pTree, hItem, TRUE); return TV_ScrollIntoView(pTree, hItem); } // // Walk up the tree towards the root until we find the item at level iLevel. // Note the cast to (char) because iLevel is a BYTE, so the root's level is // 0xFF. Casting to (char) turns 0xFF it into -1. // HTREEITEM TV_WalkToLevel(HTREEITEM hWalk, int iLevel) { int i; for (i = (char)hWalk->iLevel - iLevel; i > 0; i--) hWalk = hWalk->hParent; return hWalk; } // this is to handle single expand mode. // The new selection is toggled, and the old selection is collapsed // assume that parents of hNewSel are already fully expanded // to do this, we build a parent dpa for the old and new // then go through find the first parent node of the old selection that's not in // the new sel tree. and expand that. void TV_ExpandOnSelChange(PTREE pTree, TREEITEM *hNewSel, TREEITEM *hOldSel) { LRESULT dwAbort; NM_TREEVIEW nm; BOOL fCollapsing; TVWATCHEDITEM wiOld, wiNew; // Revalidate hNewSel and hOldSel since they may have been deleted // during all the notifications that occurred in the meantime. if (!ValidateTreeItem(hOldSel, VTI_NULLOK) || !ValidateTreeItem(hNewSel, VTI_NULLOK)) return; if (TV_StartWatch(pTree, &wiOld, hOldSel)) { if (TV_StartWatch(pTree, &wiNew, hNewSel)) { // Let the app clean up after itself nm.itemOld.hItem = hOldSel; if (hOldSel) nm.itemOld.lParam = hOldSel->lParam; nm.itemOld.mask = (TVIF_HANDLE | TVIF_PARAM); nm.itemNew.hItem = hNewSel; if (hNewSel) nm.itemNew.lParam = hNewSel->lParam; nm.itemNew.mask = (TVIF_HANDLE | TVIF_PARAM); dwAbort = CCSendNotify(&pTree->ci, TVN_SINGLEEXPAND, &nm.hdr); UpdateWindow(pTree->ci.hwnd); // Revalidate hNewSel and hOldSel since they may have been deleted // by that notification. if (!TV_IsWatchValid(pTree, &wiOld) || !TV_IsWatchValid(pTree, &wiNew)) goto cleanup; // Collapse if the NewSel currently expanded. fCollapsing = hNewSel && (hNewSel->state & TVIS_EXPANDED); // Note that Ctrl+select allows the user to suppress the collapse // of the old selection. if (!(dwAbort & TVNRET_SKIPOLD) && hOldSel && GetKeyState(VK_CONTROL) >= 0) { if (!(pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE) ) { // // Collapse parents until we reach the common ancestor between // hOldSel and hNewSel. Note carefully that we don't cache // any HTREEITEMs to avoid revalidation problems. // // // Find the common ancestor, which might be the tree root. // int iLevelCommon; if (!hNewSel) iLevelCommon = -1; // common ancestor is root else { HTREEITEM hItemO, hItemN; iLevelCommon = min((char)hOldSel->iLevel, (char)hNewSel->iLevel); hItemO = TV_WalkToLevel(hOldSel, iLevelCommon); hItemN = TV_WalkToLevel(hNewSel, iLevelCommon); while (iLevelCommon >= 0 && hItemO != hItemN) { iLevelCommon--; hItemO = hItemO->hParent; hItemN = hItemN->hParent; } } // // Now walk up the tree from hOldSel, collapsing everything // until we reach the common ancestor. Do not collapse the // common ancestor. // while ((char)hOldSel->iLevel > iLevelCommon) { TV_Expand(pTree, TVE_COLLAPSE, hOldSel, TRUE); if (!TV_IsWatchValid(pTree, &wiOld)) break; hOldSel = hOldSel->hParent; TV_RestartWatch(pTree, &wiOld, hOldSel); } } else if (hNewSel && hOldSel->hParent == hNewSel->hParent) // Only if are direct siblings { TV_Expand(pTree, TVE_COLLAPSE, hOldSel, TRUE); } } if ((!(dwAbort & TVNRET_SKIPNEW)) && hNewSel && TV_IsWatchValid(pTree, &wiNew) && (!(pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE) || !(hNewSel->state & TVIS_EXPANDED))) { WPARAM wParam = pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE ? TVE_EXPAND : TVE_TOGGLE; TV_Expand(pTree, wParam, hNewSel, TRUE); UpdateWindow(pTree->ci.hwnd); } cleanup: TV_EndWatch(pTree, &wiNew); } TV_EndWatch(pTree, &wiOld); } } // ---------------------------------------------------------------------------- // // Notify the parent that the selection is about to change. If the change is // accepted, de-select the current selected item and select the given item // // sets hCaret // // in: // hItem item to become selected // wType TVGN_ values (TVGN_CARET, TVGN_DROPHILIGHT are only valid values) // flags combination of flags // TVSIFI_NOTIFY - send notify to parent window // TVSIFI_UPDATENOW - do UpdateWindow() to force sync painting // TVSIFI_NOSINGLEEXPAND- don't do single-expand stuff // action action code to send identifying how selection is being made // // NOTE: Multiple Selection still needs to be added -- this multiplesel code // is garbage // // ---------------------------------------------------------------------------- BOOL TV_SelectItem(PTREE pTree, WPARAM wType, TREEITEM * hItem, UINT flags, UINT action) { UINT uRDWFlags = RDW_INVALIDATE; if (pTree->hImageList && (ImageList_GetBkColor(pTree->hImageList) == (COLORREF)-1)) uRDWFlags |= RDW_ERASE; if (!ValidateTreeItem(hItem, VTI_NULLOK)) return FALSE; // Invalid parameter switch (wType) { case TVGN_FIRSTVISIBLE: if (!hItem) return FALSE; TV_EnsureVisible(pTree, hItem); if (pTree->fVert) TV_SetTopItem(pTree, hItem->iShownIndex); break; case TVGN_DROPHILITE: ASSERT(hItem == NULL || ITEM_VISIBLE(hItem)); if (hItem != pTree->hDropTarget) { if (pTree->hDropTarget) { pTree->hDropTarget->state &= ~TVIS_DROPHILITED; TV_InvalidateItem(pTree, pTree->hDropTarget, uRDWFlags); } if (hItem) { hItem->state |= TVIS_DROPHILITED; TV_InvalidateItem(pTree, hItem, uRDWFlags); } pTree->hDropTarget = hItem; if (pTree->hCaret) { TV_InvalidateItem(pTree, pTree->hCaret, uRDWFlags); } if (flags & TVSIFI_UPDATENOW) UpdateWindow(pTree->ci.hwnd); } break; case TVGN_CARET: // REVIEW: we may want to scroll into view in this case // it's already the selected item, just return if (pTree->hCaret != hItem) { TREEITEM * hOldSel; if ((flags & TVSIFI_NOTIFY) && TV_SendSelChange(pTree, TVN_SELCHANGING, pTree->hCaret, hItem, action)) return FALSE; if (pTree->hCaret) { pTree->hCaret->state &= ~TVIS_SELECTED; TV_InvalidateItem(pTree, pTree->hCaret, uRDWFlags); } hOldSel = pTree->hCaret; pTree->hCaret = hItem; if (hItem) { hItem->state |= TVIS_SELECTED; // make sure this item is not in a collapsed branch TV_ExpandParents(pTree, hItem, (flags & TVSIFI_NOTIFY)); TV_InvalidateItem(pTree, hItem, uRDWFlags ); if (action == TVC_BYMOUSE) { // if selected by mouse, let's wait a doubleclick sec before scrolling SetTimer(pTree->ci.hwnd, IDT_SCROLLWAIT, GetDoubleClickTime(), NULL); pTree->fScrollWait = TRUE; } else if (pTree->fRedraw) TV_ScrollVertIntoView(pTree, hItem); } if (pTree->hwndToolTips) TV_Timer(pTree, IDT_TOOLTIPWAIT); if (flags & TVSIFI_NOTIFY) TV_SendSelChange(pTree, TVN_SELCHANGED, hOldSel, hItem, action); if ((pTree->ci.style & TVS_SINGLEEXPAND) && !(flags & TVSIFI_NOSINGLEEXPAND) && action != TVC_BYKEYBOARD && hOldSel) { TV_ExpandOnSelChange(pTree, pTree->hCaret, hOldSel); } if (flags & TVSIFI_UPDATENOW) UpdateWindow(pTree->ci.hwnd); NotifyWinEvent(EVENT_OBJECT_FOCUS, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); NotifyWinEvent(EVENT_OBJECT_SELECTION, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); } break; default: DebugMsg(DM_TRACE, TEXT("Invalid type passed to TV_SelectItem")); return FALSE; } return TRUE; // success } // remove all the children, but pretend they are still there BOOL TV_ResetItem(PTREE pTree, HTREEITEM hItem) { TV_DeleteItem(pTree, hItem, TVDI_CHILDRENONLY); hItem->state &= ~TVIS_EXPANDEDONCE; hItem->fKids = KIDS_FORCE_YES; // force children return TRUE; } // ---------------------------------------------------------------------------- // // Expand or collapse an item's children // Returns TRUE if any change took place and FALSE if unchanged // // ---------------------------------------------------------------------------- BOOL TV_Expand(PTREE pTree, WPARAM wCode, TREEITEM * hItem, BOOL fNotify) { WORD fOldState; UINT cntVisDescendants; TVITEMEX sItem; TREEITEM * hItemExpanding; // deal with the evil invisible root for multiple root trees. hItemExpanding = hItem; if ((hItem == NULL) || (hItem == TVI_ROOT)) hItem = pTree->hRoot; DBG_ValidateTreeItem(hItem, 0); TV_GetItem(pTree, hItem, TVIF_CHILDREN, &sItem); if (!(wCode & TVE_ACTIONMASK) || sItem.cChildren == 0) return FALSE; // no children to expand or collapse if ((wCode & TVE_ACTIONMASK) == TVE_TOGGLE) { wCode = (wCode & ~TVE_ACTIONMASK); // if it's not expaned, or not fully expanded, expand now wCode |= (((!(hItem->state & TVIS_EXPANDED)) || hItem->state & TVIS_EXPANDPARTIAL) ? TVE_EXPAND : TVE_COLLAPSE); } if (((wCode & TVE_ACTIONMASK) == TVE_EXPAND) && !(hItem->state & TVIS_EXPANDEDONCE)) { // if its the first expand, ALWAYS notify the parent fNotify = TRUE; } // at this point the children may be added if they aren't already there (callback) if (fNotify && TV_SendItemExpand(pTree, TVN_ITEMEXPANDING, hItemExpanding, wCode)) return FALSE; // if (!hItem->hKids && (hItem->fKids == KIDS_FORCE_NO)) // this may be right, but I don't // have proof now. if (!hItem->hKids) { // kids we removed, or never there TV_InvalidateItem(pTree, hItem, RDW_INVALIDATE); return FALSE; } fOldState = hItem->state; if (hItem->hParent) // never turn off TVIS_EXPANED for the invisible root { if ((wCode & TVE_ACTIONMASK) == TVE_EXPAND) hItem->state |= TVIS_EXPANDED; else hItem->state &= ~(TVIS_EXPANDED | TVIS_EXPANDPARTIAL); if (wCode & TVE_EXPANDPARTIAL) { hItem->state |= TVIS_EXPANDPARTIAL; } else { hItem->state &= ~(TVIS_EXPANDPARTIAL); } } // if we're not changing the expanded state // check to see if we're supposed to collapse reset if (!(fOldState & TVIS_EXPANDED) && !(hItem->state & TVIS_EXPANDED)) { if ((wCode & (TVE_ACTIONMASK | TVE_COLLAPSERESET)) == (TVE_COLLAPSE | TVE_COLLAPSERESET)) { TV_ResetItem(pTree, hItem); } return FALSE; } // if we changed expaneded states, recalc the scrolling if ((fOldState ^ hItem->state) & TVIS_EXPANDED) { cntVisDescendants = TV_ScrollBelow(pTree, hItem, TRUE, hItem->state & TVIS_EXPANDED); if (hItem->state & TVIS_EXPANDED) { UINT wNewTop, wTopOffset, wLastKid; TV_ScrollBarsAfterExpand(pTree, hItem); wNewTop = pTree->hTop->iShownIndex; wTopOffset = hItem->iShownIndex - wNewTop; wLastKid = wTopOffset + cntVisDescendants + 1; if (wLastKid > pTree->cFullVisible) { wNewTop += min(wLastKid - pTree->cFullVisible, wTopOffset); TV_SetTopItem(pTree, wNewTop); } } else { TV_ScrollBarsAfterCollapse(pTree, hItem); TV_ScrollVertIntoView(pTree, hItem); // If we collapsed the subtree that contains the caret, then // pop the caret back to the last visible ancestor // Pass TVIS_NOSINGLEEXPAND so we won't expand an item right // after we collapsed it (d'oh!) if (pTree->hCaret) { TREEITEM * hWalk = TV_WalkToLevel(pTree->hCaret, hItem->iLevel); if (hWalk == hItem) { TV_SelectItem(pTree, TVGN_CARET, hItem, (fNotify ? TVSIFI_NOTIFY : 0) | TVSIFI_UPDATENOW | TVSIFI_NOSINGLEEXPAND, TVC_UNKNOWN); } } } } else if ((fOldState ^ hItem->state) & TVIS_EXPANDPARTIAL) { // we didn't change the expanded state, only the expand partial TV_InvalidateItem(pTree, hItem, RDW_INVALIDATE); } if (fNotify && TV_SendItemExpand(pTree, TVN_ITEMEXPANDED, hItem, wCode)) return FALSE; hItem->state |= TVIS_EXPANDEDONCE; if ((wCode & (TVE_ACTIONMASK | TVE_COLLAPSERESET)) == (TVE_COLLAPSE | TVE_COLLAPSERESET)) { TV_ResetItem(pTree, hItem); } // Bug#94368 raymondc v6 we generate a notification even if nothing happened, // which confuses accessibility. E.g., app tried to expand something // that was already expanded. Explorer Band does this when you navigate. NotifyWinEvent(EVENT_OBJECT_STATECHANGE, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); return TRUE; } BOOL BetweenItems(PTREE pTree, HTREEITEM hItem, HTREEITEM hItemStart, HTREEITEM hItemEnd) { if (hItemStart) { while ((hItemStart = TV_GetNextVisItem(hItemStart)) && (hItemEnd != hItemStart)) { if (hItem == hItemStart) return TRUE; } } return FALSE; } // Now only Korean version is interested in incremental search with composition string. #define FREE_COMP_STRING(pszCompStr) LocalFree((HLOCAL)(pszCompStr)) BOOL TV_OnImeComposition(PTREE pTree, WPARAM wParam, LPARAM lParam) { LPTSTR lpsz; int iCycle = 0; HTREEITEM hItem; TCHAR szTemp[MAXLABELTEXT]; TVITEMEX ti; LPTSTR lpszAlt = NULL; // use only if SameChar int iLen; HIMC hImc; TCHAR *pszCompStr; BOOL fRet = TRUE; if (hImc = ImmGetContext(pTree->ci.hwnd)) { if (lParam & GCS_RESULTSTR) { fRet = FALSE; pszCompStr = GET_COMP_STRING(hImc, GCS_RESULTSTR); if (pszCompStr) { IncrementSearchImeCompStr(&pTree->is, FALSE, pszCompStr, &lpsz); FREE_COMP_STRING(pszCompStr); } } if (lParam & GCS_COMPSTR) { fRet = TRUE; pszCompStr = GET_COMP_STRING(hImc, GCS_COMPSTR); if (pszCompStr) { if (IncrementSearchImeCompStr(&pTree->is, TRUE, pszCompStr, &lpsz)) { if (pTree->hCaret) { pTree->htiSearch = pTree->hCaret; } else if (pTree->hRoot && pTree->hRoot->hKids) { pTree->htiSearch = pTree->hRoot->hKids; } else return fRet; } if (!lpsz || !*lpsz || !pTree->hRoot || !pTree->hRoot->hKids) return fRet; hItem = pTree->htiSearch; ti.cchTextMax = sizeof(szTemp); iLen = lstrlen(lpsz); if (iLen > 1 && SameChars(lpsz, lpsz[0])) lpszAlt = lpsz + iLen - 1; do { ti.pszText = szTemp; hItem = TV_GetNextVisItem(hItem); if (!hItem) { iCycle++; hItem = pTree->hRoot->hKids; } TV_GetItem(pTree, hItem, TVIF_TEXT, &ti); if ((ti.pszText != LPSTR_TEXTCALLBACK) && HIWORD64(ti.pszText)) { // DebugMsg(DM_TRACE, "treesearch %d %s %s", (LPSTR)lpsz, (LPSTR)lpsz, (LPSTR)ti.pszText); if (IntlStrEqNI(lpsz, ti.pszText, iLen) || (lpszAlt && IntlStrEqNI(lpszAlt, ti.pszText, 1) && BetweenItems(pTree, hItem, pTree->hCaret, pTree->htiSearch))) { DebugMsg(DM_TRACE, TEXT("Selecting")); TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYKEYBOARD); //notify of navigation key usage CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS); return fRet; } } } while(iCycle < 2); // if they hit the same key twice in a row at the beginning of // the search, and there was no item found, they likely meant to // retstart the search if (lpszAlt) { // first clear out the string so that we won't recurse again IncrementSearchString(&pTree->is, 0, NULL); TV_OnImeComposition(pTree, wParam, lParam); } else { IncrementSearchBeep(&pTree->is); } //notify of navigation key usage CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS); FREE_COMP_STRING(pszCompStr); } } ImmReleaseContext(pTree->ci.hwnd, hImc); } return fRet; } void TV_OnChar(PTREE pTree, UINT ch, int cRepeat) { LPTSTR lpsz = NULL; int iCycle = 0; HTREEITEM hItem; TCHAR szTemp[MAXLABELTEXT]; TVITEMEX ti; LPTSTR lpszAlt = NULL; // use only if SameChar int iLen; if (IncrementSearchString(&pTree->is, ch, &lpsz) || !pTree->htiSearch) { if (pTree->hCaret) { pTree->htiSearch = pTree->hCaret; } else if (pTree->hRoot && pTree->hRoot->hKids) { pTree->htiSearch = pTree->hRoot->hKids; } else return; } if (!lpsz || !*lpsz || !pTree->hRoot || !pTree->hRoot->hKids) return; hItem = pTree->htiSearch; ti.cchTextMax = ARRAYSIZE(szTemp); iLen = lstrlen(lpsz); if (iLen > 1 && SameChars(lpsz, lpsz[0])) lpszAlt = lpsz + iLen - 1; do { ti.pszText = szTemp; hItem = TV_GetNextVisItem(hItem); if (!hItem) { iCycle++; hItem = pTree->hRoot->hKids; } TV_GetItem(pTree, hItem, TVIF_TEXT, &ti); if ((ti.pszText != LPSTR_TEXTCALLBACK) && HIWORD64(ti.pszText)) { // DebugMsg(DM_TRACE, TEXT("treesearch %d %s %s"), (LPTSTR)lpsz, (LPTSTR)lpsz, (LPTSTR)ti.pszText); if (IntlStrEqNI(lpsz, ti.pszText, iLen) || (lpszAlt && IntlStrEqNI(lpszAlt, ti.pszText, 1) && BetweenItems(pTree, hItem, pTree->hCaret, pTree->htiSearch))) { DebugMsg(DM_TRACE, TEXT("Selecting")); TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYKEYBOARD); //notify of navigation key usage CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS); return; } } } while(iCycle < 2); // if they hit the same key twice in a row at the beginning of // the search, and there was no item found, they likely meant to // retstart the search if (lpszAlt) { // first clear out the string so that we won't recurse again IncrementSearchString(&pTree->is, 0, NULL); TV_OnChar(pTree, ch, cRepeat); } else { IncrementSearchBeep(&pTree->is); } //notify of navigation key usage CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS); } // ---------------------------------------------------------------------------- // // Handle WM_KEYDOWN messages // If control key is down, treat keys as scroll codes; otherwise, treat keys // as caret position changes. // // ---------------------------------------------------------------------------- BOOL TV_KeyDown(PTREE pTree, WPARAM wKey, LPARAM dwKeyData) { TREEITEM * hItem; UINT wShownIndex; TV_KEYDOWN nm; BOOL fPuntChar; BOOL ret = TRUE; // Notify nm.wVKey = (WORD)wKey; fPuntChar = (BOOL)CCSendNotify(&pTree->ci, TVN_KEYDOWN, &nm.hdr); wKey = RTLSwapLeftRightArrows(&pTree->ci, wKey); if (GetKeyState(VK_CONTROL) < 0) { // control key is down UINT wScrollCode; switch (wKey) { case VK_LEFT: TV_HorzScroll(pTree, SB_LINEUP, 0); break; case VK_RIGHT: TV_HorzScroll(pTree, SB_LINEDOWN, 0); break; case VK_PRIOR: wScrollCode = SB_PAGEUP; goto kdVertScroll; case VK_HOME: wScrollCode = SB_TOP; goto kdVertScroll; case VK_NEXT: wScrollCode = SB_PAGEDOWN; goto kdVertScroll; case VK_END: wScrollCode = SB_BOTTOM; goto kdVertScroll; case VK_UP: wScrollCode = SB_LINEUP; goto kdVertScroll; case VK_DOWN: wScrollCode = SB_LINEDOWN; kdVertScroll: TV_VertScroll(pTree, wScrollCode, 0); break; default: ret = FALSE; } } else { switch (wKey) { case VK_RETURN: fPuntChar = (BOOL)CCSendNotify(&pTree->ci, NM_RETURN, NULL); break; case VK_PRIOR: if (pTree->hCaret && (pTree->hCaret->iShownIndex > (pTree->cFullVisible - 1))) { wShownIndex = pTree->hCaret->iShownIndex - (pTree->cFullVisible - 1); goto selectIndex; } // fall thru case VK_HOME: wShownIndex = 0; goto selectIndex; case VK_NEXT: if (!pTree->hCaret) { wShownIndex = 0; goto selectIndex; } wShownIndex = pTree->hCaret->iShownIndex + (pTree->cFullVisible - 1); if (wShownIndex < pTree->cShowing) goto selectIndex; // fall thru case VK_END: wShownIndex = pTree->cShowing - 1; selectIndex: hItem = TV_GetShownIndexItem(pTree->hRoot->hKids, wShownIndex); goto kdSetCaret; break; case VK_SUBTRACT: if (pTree->hCaret) { fPuntChar = TRUE; TV_Expand(pTree, TVE_COLLAPSE, pTree->hCaret, TRUE); } break; case VK_ADD: if (pTree->hCaret) { fPuntChar = TRUE; TV_Expand(pTree, TVE_EXPAND, pTree->hCaret, TRUE); } break; case VK_MULTIPLY: if (pTree->hCaret) { fPuntChar = TRUE; TV_ExpandRecurse(pTree, pTree->hCaret, TRUE); } break; case VK_LEFT: if (pTree->hCaret && (pTree->hCaret->state & TVIS_EXPANDED)) { TV_Expand(pTree, TVE_COLLAPSE, pTree->hCaret, TRUE); break; } else if (pTree->hCaret) { hItem = VISIBLE_PARENT(pTree->hCaret); goto kdSetCaret; } break; case VK_BACK: // get the parent, avoiding the root item fPuntChar = TRUE; if (pTree->hCaret) { hItem = VISIBLE_PARENT(pTree->hCaret); goto kdSetCaret; } break; case VK_UP: if (pTree->hCaret) hItem = TV_GetPrevVisItem(pTree->hCaret); else hItem = pTree->hRoot->hKids; goto kdSetCaret; break; case VK_RIGHT: if (pTree->hCaret && !(pTree->hCaret->state & TVIS_EXPANDED)) { TV_Expand(pTree, TVE_EXPAND, pTree->hCaret, TRUE); break; } // else fall through case VK_DOWN: if (pTree->hCaret) hItem = TV_GetNextVisItem(pTree->hCaret); else hItem = pTree->hRoot->hKids; kdSetCaret: if (hItem) TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYKEYBOARD); break; case VK_SPACE: if ((pTree->ci.style & TVS_CHECKBOXES) && pTree->hCaret) { TV_HandleStateIconClick(pTree, pTree->hCaret); fPuntChar = TRUE; // don't beep } break; default: ret = FALSE; } } if (fPuntChar) { pTree->iPuntChar++; } else if (pTree->iPuntChar){ // this is tricky... if we want to punt the char, just increment the // count. if we do NOT, then we must clear the queue of WM_CHAR's // this is to preserve the iPuntChar to mean "punt the next n WM_CHAR messages MSG msg; while((pTree->iPuntChar > 0) && PeekMessage(&msg, pTree->ci.hwnd, WM_CHAR, WM_CHAR, PM_REMOVE)) { pTree->iPuntChar--; } //ASSERT(!pTree->iPuntChar); } if ( VK_MENU!=wKey ) { //notify of navigation key usage CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS); } return ret; } // ---------------------------------------------------------------------------- // // Sets the tree's indent width per hierarchy level and recompute widths. // // sets cxIndent // // ---------------------------------------------------------------------------- void TV_SetIndent(PTREE pTree, WPARAM cxIndent) { if (pTree->hImageList) { if ((SHORT)cxIndent < pTree->cxImage) cxIndent = pTree->cxImage; } if ((SHORT)cxIndent < pTree->cyText) cxIndent = pTree->cyText; if (cxIndent < MAGIC_MININDENT) cxIndent = MAGIC_MININDENT; pTree->cxIndent = (SHORT)cxIndent; TV_CreateIndentBmps(pTree); TV_ScrollBarsAfterSetWidth(pTree, NULL); } // ---------------------------------------------------------------------------- // // Sets the tree's item height to be the maximum of the image height and text // height. Then recompute the tree's full visible count. // // sets cyItem, cFullVisible // // ---------------------------------------------------------------------------- void TV_SetItemHeight(PTREE pTree) { // height MUST be even with TVS_HASLINES -- go ahead and make it always even if (!pTree->fCyItemSet) pTree->cyItem = (max(pTree->cyImage, pTree->cyText) + 1); // height not always even not, only on haslines style. if (pTree->cyItem <= 1) { pTree->cyItem = 1; // Don't let it go zero or negative! } else if (!(pTree->ci.style & TVS_NONEVENHEIGHT)) pTree->cyItem &= ~1; pTree->cFullVisible = pTree->cyWnd / pTree->cyItem; TV_CreateIndentBmps(pTree); TV_CalcScrollBars(pTree); } HIMAGELIST TV_SetImageList(PTREE pTree, HIMAGELIST hImage, int iImageIndex) { HIMAGELIST hImageOld = NULL; int cx; int cy; switch (iImageIndex) { case TVSIL_STATE: hImageOld = pTree->himlState; pTree->himlState = hImage; if (hImage) { CCGetIconSize(&pTree->ci, hImage, &pTree->cxState , &pTree->cyState); } else { pTree->cxState = 0; } break; case TVSIL_NORMAL: hImageOld = pTree->hImageList; if (hImage && CCGetIconSize(&pTree->ci, hImage, &cx, &cy)) { pTree->cxNativeImage = (SHORT)cx; pTree->cyNativeImage = (SHORT)cy; pTree->cxImage = (SHORT)((pTree->cxNativeImage) + MAGIC_INDENT); pTree->cyImage = (SHORT)(pTree->cyNativeImage); if (pTree->cxIndent < pTree->cxImage) TV_SetIndent(pTree, pTree->cxImage); pTree->hImageList = hImage; if (!hImageOld && pTree->ci.style & TVS_CHECKBOXES) { TV_InitCheckBoxes(pTree); } } else { pTree->cxImage = 0; pTree->cyImage = 0; pTree->cxNativeImage = 0; pTree->cyNativeImage = 0; pTree->hImageList = NULL; } break; default: DebugMsg(DM_TRACE, TEXT("sh TR - TVM_SETIMAGELIST: unrecognized iImageList")); break; } TV_ScrollBarsAfterSetWidth(pTree, NULL); TV_SetItemHeight(pTree); return hImageOld; } // Bug#94345: does not deal with hfont == NULL void TV_OnSetFont(PTREE pTree, HFONT hNewFont, BOOL fRedraw) { HDC hdc; HFONT hfontSel; TCHAR c = TEXT('J'); // for bog SIZE size; if (pTree->fCreatedFont && pTree->hFont) { DeleteObject(pTree->hFont); pTree->fCreatedFont = FALSE; } if (hNewFont == NULL) { LOGFONT lf; HRESULT hr = E_FAIL; // Create identical theme font. Although theme manager APIs will be used for drawing text, // create a copy of the normal state font. The control will be sized based on this font's dimensions if (pTree->hTheme) { ZeroMemory(&lf, SIZEOF(lf)); hr = GetThemeFont(pTree->hTheme, NULL, TVP_TREEITEM, TREIS_NORMAL, TMT_FONT, &lf); } if (FAILED(hr)) { SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE); } hNewFont = CreateFontIndirect(&lf); pTree->fCreatedFont = TRUE; // make sure we delete it } hdc = GetDC(pTree->ci.hwnd); hfontSel = hNewFont ? SelectObject(hdc, hNewFont) : NULL; // Office9 Setup had a bug where they installed a bogus font, // which created okay but all APIs against it (e.g., GetTextExtentPoint) // failed! Protect against failure by pre-setting the value to something // non-garbage. size.cy = 0; GetTextExtentPoint(hdc, &c, 1, &size); pTree->cyText = (SHORT)(size.cy + (g_cyBorder * 2)); if (hfontSel) SelectObject(hdc, hfontSel); ReleaseDC(pTree->ci.hwnd, hdc); pTree->hFont = hNewFont; if (pTree->hFontBold) TV_CreateBoldFont(pTree); pTree->ci.uiCodePage = GetCodePageForFont(hNewFont); TV_DeleteHotFonts(pTree); TV_SetIndent(pTree, 16 /*g_cxSmIcon*/ + MAGIC_INDENT); TV_SetImageList(pTree, pTree->hImageList, TVSIL_NORMAL); TV_ScrollBarsAfterSetWidth(pTree, NULL); TV_SetItemHeight(pTree); if (pTree->hwndToolTips) SendMessage(pTree->hwndToolTips, WM_SETFONT, (WPARAM)pTree->hFont, (LPARAM)TRUE); // REVIEW: does this happen as a result of the above? // if (fRedraw) // RedrawWindow(pTree->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); } void TV_CreateBoldFont(PTREE pTree) { LOGFONT lf; if (pTree->hFontBold) DeleteObject (pTree->hFontBold); GetObject(pTree->hFont, sizeof (lf), &lf); CCAdjustForBold(&lf); pTree->hFontBold = CreateFontIndirect(&lf); } // ---------------------------------------------------------------------------- // // Gets the item with the described relationship to the given item, NULL if // no item can be found with that relationship. // // ---------------------------------------------------------------------------- TREEITEM * TV_GetNextItem(PTREE pTree, TREEITEM * hItem, WPARAM wGetCode) { switch (wGetCode) { case TVGN_ROOT: return pTree->hRoot->hKids; case TVGN_DROPHILITE: return pTree->hDropTarget; case TVGN_CARET: return pTree->hCaret; case TVGN_FIRSTVISIBLE: return pTree->hTop; case TVGN_LASTVISIBLE: return TV_GetShownIndexItem(pTree->hRoot->hKids, pTree->cShowing-1); case TVGN_CHILD: if (!hItem || (hItem == TVI_ROOT)) return pTree->hRoot->hKids; break; } // all of these require a valid hItem if (!ValidateTreeItem(hItem, 0)) return NULL; switch (wGetCode) { case TVGN_NEXTVISIBLE: return TV_GetNextVisItem(hItem); case TVGN_PREVIOUSVISIBLE: return TV_GetPrevVisItem(hItem); case TVGN_NEXT: return hItem->hNext; case TVGN_PREVIOUS: if (hItem->hParent->hKids == hItem) return NULL; else { TREEITEM * hWalk; for (hWalk = hItem->hParent->hKids; hWalk->hNext != hItem; hWalk = hWalk->hNext); return hWalk; } case TVGN_PARENT: return VISIBLE_PARENT(hItem); case TVGN_CHILD: return hItem->hKids; } return NULL; } // ---------------------------------------------------------------------------- // // Returns the number of items (including the partially visible item at the // bottom based on the given flag) that fit in the tree's client window. // // ---------------------------------------------------------------------------- LRESULT TV_GetVisCount(PTREE pTree, BOOL fIncludePartial) { int i; if (!fIncludePartial) return(MAKELRESULTFROMUINT(pTree->cFullVisible)); i = pTree->cFullVisible; if (pTree->cyWnd - (i * pTree->cyItem)) i++; return i; } void TV_InvalidateInsertMarkRect(PTREE pTree, BOOL fErase) { RECT rc; if (TV_GetInsertMarkRect(pTree, &rc)) InvalidateRect(pTree->ci.hwnd, &rc, fErase); } // ---------------------------------------------------------------------------- // // recomputes tree's fields that rely on the tree's client window size // // sets cxWnd, cyWnd, cFullVisible // // ---------------------------------------------------------------------------- BOOL TV_SizeWnd(PTREE pTree, UINT cxWnd, UINT cyWnd) { RECT rc; UINT cxOld = pTree->cxWnd; if (!cxWnd || !cyWnd) { GetClientRect(pTree->ci.hwnd, &rc); cxWnd = rc.right; cyWnd = rc.bottom; } pTree->cxWnd = (SHORT)cxWnd; pTree->cyWnd = (SHORT)cyWnd; pTree->cFullVisible = cyWnd / pTree->cyItem; if (pTree->ci.style & TVS_NOSCROLL) pTree->cxMax = (WORD) cxWnd; TV_CalcScrollBars(pTree); if (pTree->cxBorder) { rc.top = 0; rc.bottom = cyWnd; rc.right = cxOld; rc.left = cxOld - pTree->cxBorder; if (rc.left < (int)cxWnd) { // invalidate so clipping happens on right on size. InvalidateRect(pTree->ci.hwnd, &rc, TRUE); } } TV_InvalidateInsertMarkRect(pTree, TRUE); return TRUE; } void TV_HandleStateIconClick(PTREE pTree, HTREEITEM hItem) { TVITEMEX tvi; int iState; tvi.stateMask = TVIS_STATEIMAGEMASK; TV_GetItem(pTree, hItem, TVIF_STATE, &tvi); iState = STATEIMAGEMASKTOINDEX(tvi.state & tvi.stateMask); iState %= (ImageList_GetImageCount(pTree->himlState) - 1); iState++; tvi.mask = TVIF_STATE; tvi.state = INDEXTOSTATEIMAGEMASK(iState); tvi.hItem = hItem; TV_SetItem(pTree, &tvi); } // // Eudora is a piece of work. // // When they get a NM_DBLCLK notification from a treeview, they say, // "Oh, I know that treeview allocates its NMHDR from the stack, and // there's this local variable on Treeview's stack I'm really interested // in, so I'm going to hard-code an offset from the pnmhdr and read the // DWORD at that location so I can get at the local variable. I will then // stop working if this value is zero." // // The conversion to UNICODE changed our stack layout enough that they // end up always getting zero -- it's the NULL parameter which is the // final argument to CCSendNotify. Since all this stack layout stuff is // sensitive to how the compiler's optimizer feels today, we create a // special notify structure Just For Eudora which mimics the stack layout // they expected to see in Win95. // typedef struct NMEUDORA { NMHDR nmhdr; BYTE Padding[48]; DWORD MustBeNonzero; // Eudora fails to install if this is zero } NMEUDORA; // ---------------------------------------------------------------------------- // // WM_LBUTTONDBLCLK message -- toggle expand/collapse state of item's children // WM_LBUTTONDOWN message -- on item's button, do same as WM_LBUTTONDBLCLK, // otherwise select item and ensure that item is fully visible // // ---------------------------------------------------------------------------- void TV_ButtonDown(PTREE pTree, UINT wMsg, UINT wFlags, int x, int y, UINT TVBD_flags) { UINT wHitCode; TREEITEM * hItem; HWND hwndTree; LRESULT lResult; #ifdef _X86_ NMEUDORA nmeu; nmeu.MustBeNonzero = 1; COMPILETIME_ASSERT(FIELD_OFFSET(NMEUDORA, MustBeNonzero) == 0x3C); #endif GetMessagePosClient(pTree->ci.hwnd, &pTree->ptCapture); if (!TV_DismissEdit(pTree, FALSE)) // end any previous editing (accept it) return; // Something happened such that we should not process button down hItem = TV_CheckHit(pTree, x, y, &wHitCode); // Excel likes to destroy the entire tree when it gets a double-click // so we need to watch the item in case it vanishes behind our back. hwndTree = pTree->ci.hwnd; if (wMsg == WM_LBUTTONDBLCLK) { // // Cancel any name editing that might happen. // TV_CancelEditTimer(pTree); if (wHitCode & (TVHT_ONITEM | TVHT_ONITEMBUTTON)) { goto ExpandItem; } // // Collapses node above the line double clicked on // else if ((pTree->ci.style & TVS_HASLINES) && (wHitCode & TVHT_ONITEMINDENT) && (abs(x % pTree->cxIndent - pTree->cxIndent/2) <= g_cxDoubleClk)) { int i; for (i = hItem->iLevel - x/pTree->cxIndent + ((pTree->ci.style & TVS_LINESATROOT)?1:0); i > 1; i--) hItem = hItem->hParent; ExpandItem: #ifdef _X86_ lResult = CCSendNotify(&pTree->ci, wFlags & MK_RBUTTON ? NM_RDBLCLK : NM_DBLCLK, &nmeu.nmhdr); #else lResult = CCSendNotify(&pTree->ci, wFlags & MK_RBUTTON ? NM_RDBLCLK : NM_DBLCLK, NULL); #endif if (!IsWindow(hwndTree)) goto bail; if (!lResult) { // don't auto expand this if we're in single expand mode because the first click did it already if (!(pTree->ci.style & TVS_SINGLEEXPAND)) TV_Expand(pTree, TVE_TOGGLE, hItem, TRUE); } } pTree->fScrollWait = FALSE; } else { // WM_LBUTTONDOWN if (wHitCode == TVHT_ONITEMBUTTON) { if (!CCSendNotify(&pTree->ci, NM_CLICK, NULL)) { if (TVBD_flags & TVBD_FROMWHEEL) TV_Expand(pTree, (TVBD_flags & TVBD_WHEELFORWARD) ? TVE_EXPAND : TVE_COLLAPSE, hItem, TRUE); else TV_Expand(pTree, TVE_TOGGLE, hItem, TRUE); } } else if (wHitCode & TVHT_ONITEM || ((pTree->ci.style & TVS_FULLROWSELECT) && (wHitCode & (TVHT_ONITEMRIGHT | TVHT_ONITEMINDENT)))) { BOOL fSameItem, bDragging; ASSERT(hItem); fSameItem = (hItem == pTree->hCaret); if (TVBD_flags & TVBD_FROMWHEEL) bDragging = FALSE; else if (pTree->ci.style & TVS_DISABLEDRAGDROP) bDragging = FALSE; else { bDragging = TV_CheckForDragBegin(pTree, hItem, x, y); TV_FinishCheckDrag(pTree); } if (bDragging) { pTree->htiDrag = hItem; TV_SendBeginDrag(pTree, TVN_BEGINDRAG, hItem, x, y); return; } if (!CCSendNotify(&pTree->ci, NM_CLICK, NULL)) { if (wHitCode == TVHT_ONITEMSTATEICON && (pTree->ci.style & TVS_CHECKBOXES)) { TV_HandleStateIconClick(pTree, hItem); } else { // Only set the caret (selection) if not dragging TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYMOUSE); if (fSameItem && (wHitCode & TVHT_ONITEMLABEL) && pTree->fFocus && !(pTree->ci.style & TVS_SINGLEEXPAND)) { // // The item and window are currently selected and user clicked // on label. Try to enter into name editing mode. // SetTimer(pTree->ci.hwnd, IDT_NAMEEDIT, GetDoubleClickTime(), NULL); pTree->fNameEditPending = TRUE; } if (fSameItem && (pTree->ci.style & TVS_SINGLEEXPAND) && (!(pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE) || !(hItem->state & TVIS_EXPANDED))) { WPARAM wParam = pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE ? TVE_EXPAND : TVE_TOGGLE; TV_Expand(pTree, wParam, pTree->hCaret, TRUE); } } } } else { CCSendNotify(&pTree->ci, NM_CLICK, NULL); } } if (!pTree->fFocus) SetFocus(pTree->ci.hwnd); bail:; } // ---------------------------------------------------------------------------- // // Gets the item's text, data, and/or image. // // ---------------------------------------------------------------------------- BOOL TV_OnGetItem(PTREE pTree, LPTVITEMEX ptvi) { if (!ptvi) return FALSE; if (!ValidateTreeItem(ptvi->hItem, 0)) return FALSE; // Invalid parameter TV_GetItem(pTree, ptvi->hItem, ptvi->mask, ptvi); return TRUE; // success } BOOL TV_OnGetItemA(PTREE pTree, LPTVITEMEXA ptvi) { BOOL bRet; LPSTR pszA = NULL; LPWSTR pszW = NULL; //HACK Alert! This code assumes that TVITEMA is exactly the same // as TVITEMW except for the text pointer in the TVITEM ASSERT(sizeof(TVITEMA) == sizeof(TVITEMW)); if (!IsFlagPtr(ptvi) && (ptvi->mask & TVIF_TEXT) && !IsFlagPtr(ptvi->pszText)) { pszA = ptvi->pszText; pszW = LocalAlloc(LMEM_FIXED, ptvi->cchTextMax * sizeof(WCHAR)); if (pszW == NULL) { return FALSE; } ptvi->pszText = (LPSTR)pszW; } bRet = TV_OnGetItem(pTree, (LPTVITEMEXW)ptvi); if (pszA) { if (bRet && ptvi->cchTextMax) ConvertWToAN(pTree->ci.uiCodePage, pszA, ptvi->cchTextMax, (LPWSTR)(ptvi->pszText), -1); LocalFree(pszW); ptvi->pszText = pszA; } return bRet; } // ---------------------------------------------------------------------------- // // Sets the item's text, data, and/or image. // // ---------------------------------------------------------------------------- BOOL TV_SetItemA(PTREE pTree, LPTVITEMEXA ptvi) { LPSTR pszA = NULL; BOOL lRet; //HACK Alert! This code assumes that TVITEMA is exactly the same // as TVITEMW except for the text pointer in the TVITEM ASSERT(sizeof(TVITEMA) == sizeof(TVITEMW)); if (!IsFlagPtr(ptvi) && (ptvi->mask & TVIF_TEXT) && !IsFlagPtr(ptvi->pszText)) { pszA = ptvi->pszText; ptvi->pszText = (LPSTR)ProduceWFromA(pTree->ci.uiCodePage, pszA); if (ptvi->pszText == NULL) { ptvi->pszText = pszA; return -1; } } lRet = TV_SetItem(pTree, (LPCTVITEMEX)ptvi); if (pszA) { FreeProducedString(ptvi->pszText); ptvi->pszText = pszA; } return lRet; } BOOL TV_SetItem(PTREE pTree, LPCTVITEMEX ptvi) { UINT uRDWFlags = RDW_INVALIDATE; BOOL fEraseIfTransparent = FALSE; HTREEITEM hItem; BOOL bActualChange = FALSE; // HACK: We want to keep track of which // attributes were changed from CALLBACK to // "real", and don't invalidate if those were // the only changes int iIntegralPrev; BOOL fName = FALSE; BOOL fFocusSel = FALSE; BOOL fRecalcWidth = FALSE; BOOL fStateImageChange = FALSE; if (!ptvi) return FALSE; hItem = ptvi->hItem; // deal with the evil invisible root for multiple root trees. if (hItem == TVI_ROOT) { hItem = pTree->hRoot; } if (!ValidateTreeItem(hItem, 0)) return FALSE; iIntegralPrev = hItem->iIntegral; // Bug#94368: send ITEMCHANING and ITEMCHANGED msgs if (ptvi->mask & TVIF_TEXT) { uRDWFlags |= RDW_ERASE; bActualChange = TRUE; if (!ptvi->pszText) { Str_Set(&hItem->lpstr, LPSTR_TEXTCALLBACK); } else { #ifdef DEBUG ASSERTMSG(!pTree->fInTextCallback, "set item (text) getting called while we are in text callback!"); #endif if (!Str_Set(&hItem->lpstr, ptvi->pszText)) { // // Memory allocation failed - The best we can do now // is to set the item back to callback, and hope that // the top level program can handle it. // DebugMsg(DM_ERROR, TEXT("TreeView: Out of memory")); hItem->lpstr = LPSTR_TEXTCALLBACK; } } fRecalcWidth = TRUE; fName = TRUE; } if (ptvi->mask & TVIF_PARAM) { bActualChange = TRUE; hItem->lParam = ptvi->lParam; } if (ptvi->mask & TVIF_IMAGE) { if (hItem->iImage != (WORD)I_IMAGECALLBACK) { bActualChange = TRUE; fEraseIfTransparent = TRUE; if (pTree->hImageList && (ImageList_GetBkColor(pTree->hImageList) == (COLORREF)-1)) uRDWFlags |= RDW_ERASE; } hItem->iImage = (SHORT)ptvi->iImage; } if (ptvi->mask & TVIF_SELECTEDIMAGE) { if (hItem->iSelectedImage != (WORD)I_IMAGECALLBACK) bActualChange = TRUE; hItem->iSelectedImage = (SHORT)ptvi->iSelectedImage; } if (ptvi->mask & TVIF_CHILDREN) { uRDWFlags |= RDW_ERASE; if (hItem->fKids != KIDS_CALLBACK) bActualChange = TRUE; switch (ptvi->cChildren) { case I_CHILDRENAUTO: hItem->fKids = KIDS_COMPUTE; break; case I_CHILDRENCALLBACK: hItem->fKids = KIDS_CALLBACK; break; case 0: hItem->fKids = KIDS_FORCE_NO; break; default: hItem->fKids = KIDS_FORCE_YES; break; } // // If this item currently has no kid, reset the item. // if ((ptvi->cChildren == I_CHILDRENCALLBACK) && (hItem->hKids == NULL)) { hItem->state &= ~TVIS_EXPANDEDONCE; if (hItem->hParent) hItem->state &= ~TVIS_EXPANDED; } } if (ptvi->mask & TVIF_INTEGRAL) { if (LOWORD(ptvi->iIntegral) > 0) hItem->iIntegral = LOWORD(ptvi->iIntegral); } if (ptvi->mask & TVIF_STATE) { // don't & ptvi->state with TVIS_ALL because win95 didn't // and setting TVIS_FOCUS was retrievable even though we don't use it UINT change = (hItem->state ^ ptvi->state) & ptvi->stateMask; if (change) { // Bug#94368: (TVIS_SELECTED | TVIS_DROPHILITED) changes // should effect tree state hItem->state ^= change; bActualChange = TRUE; fEraseIfTransparent = TRUE; if (hItem->state & TVIS_BOLD) { if (!pTree->hFontBold) TV_CreateBoldFont(pTree); } if (change & TVIS_BOLD){ // do this because changing the boldness uRDWFlags |= RDW_ERASE; fRecalcWidth = TRUE; } fStateImageChange = change & TVIS_STATEIMAGEMASK; if (fStateImageChange) { uRDWFlags |= RDW_ERASE; // Adding/removing a state image changes the ITEM_OFFSET // If old image was 0, then we are adding. // If new image is 0, then we are removing. // (If old=new, then we don't get into this code path, so we // don't have to worry about that case.) if (!(hItem->state & TVIS_STATEIMAGEMASK) || // new !((hItem->state ^ change) & TVIS_STATEIMAGEMASK)) { // old fRecalcWidth = TRUE; } } fFocusSel = ((change & TVIS_SELECTED) != 0); } } if (fRecalcWidth) { hItem->iWidth = 0; // Invalidate old width if (TV_IsShowing(hItem)) { TV_ScrollBarsAfterSetWidth(pTree, hItem); } } // force a redraw if something changed AND if we are not // inside of a paint of this guy (callbacks will set the // item on the paint callback to implement lazy data schemes) if (bActualChange && (pTree->hItemPainting != hItem)) { if (fEraseIfTransparent) { if (pTree->hImageList) { if (ImageList_GetBkColor(pTree->hImageList) == CLR_NONE) { uRDWFlags |= RDW_ERASE; } } } // If item height changed, then we've got a lot of cleaning up // to do. if (hItem->iIntegral != iIntegralPrev) { TV_ScrollBarsAfterResize(pTree, hItem, iIntegralPrev, uRDWFlags); } else { TV_InvalidateItem(pTree, hItem, uRDWFlags); } // REVIEW: we might need to update the scroll bars if the // text length changed! } if (bActualChange) { if (fName) NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); if (fFocusSel) { NotifyWinEvent(EVENT_OBJECT_FOCUS, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); NotifyWinEvent(((hItem->state & TVIS_SELECTED) ? EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE), pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); } if (fStateImageChange) NotifyWinEvent(EVENT_OBJECT_STATECHANGE, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); } return TRUE; } // ---------------------------------------------------------------------------- // // Calls TV_CheckHit to get the hit test results and then package it in a // structure back to the app. // // ---------------------------------------------------------------------------- HTREEITEM TV_OnHitTest(PTREE pTree, LPTV_HITTESTINFO lptvh) { if (!lptvh) return 0; //Bug#94345: Validate LPTVHITTEST lptvh->hItem = TV_CheckHit(pTree, lptvh->pt.x, lptvh->pt.y, &lptvh->flags); return lptvh->hItem; } BOOL TV_IsItemTruncated(PTREE pTree, TREEITEM *hItem, LPRECT lprc) { if (TV_GetItemRect(pTree,hItem,lprc,TRUE)) { lprc->left -= g_cxEdge; lprc->top -= g_cyBorder; if ((lprc->left + hItem->iWidth) > pTree->cxWnd) { return TRUE; } } return FALSE; } BOOL TV_HandleTTNShow(PTREE pTree, LPNMHDR lpnm) { if (pTree->hToolTip && pTree->fPlaceTooltip) { LPNMTTSHOWINFO psi = (LPNMTTSHOWINFO)lpnm; RECT rc; TVITEMEX item; // Now get the text associated with that item item.stateMask = TVIS_BOLD; TV_GetItem(pTree, pTree->hToolTip, TVIF_STATE, &item); SendMessage(pTree->hwndToolTips, WM_SETFONT, (WPARAM)((item.state & TVIS_BOLD) ? pTree->hFontBold : pTree->hFont), 0); TV_GetItemRect(pTree, pTree->hToolTip, &rc, TRUE); MapWindowRect(pTree->ci.hwnd, HWND_DESKTOP, &rc); // We draw the text with margins, so take those into account too. // These values come from TV_DrawItem... rc.top += g_cyBorder; rc.left += g_cxLabelMargin; // // At this point, (rc.left, rc.top) are the coordinates we pass // to DrawText. Ask the tooltip how we should position it so the // tooltip text shows up in precisely the same location. // // Bug#94368 raymondc v6: wrong coordinates if app has used TVM_SETITEMHEIGHT SendMessage(pTree->hwndToolTips, TTM_ADJUSTRECT, TRUE, (LPARAM)&rc); SetWindowPos(pTree->hwndToolTips, NULL, rc.left, rc.top,0,0, SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER); // This is an inplace tooltip, so disable animation. psi->dwStyle |= TTS_NOANIMATE; // handled! return TRUE; } return FALSE; } // // Copy the font from the treeview item into the tooltip so the tooltip // shows up in the correct font. // BOOL TV_HandleTTCustomDraw(PTREE pTree, LPNMTTCUSTOMDRAW pnm) { if (pTree->hToolTip && pTree->fPlaceTooltip && (pnm->nmcd.dwDrawStage == CDDS_PREPAINT || pnm->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)) { // // Set up the customdraw DC to match the font of the TV item. // TVFAKEDRAW tvfd; DWORD dwCustom = 0; TreeView_BeginFakeCustomDraw(pTree, &tvfd); dwCustom = TreeView_BeginFakeItemDraw(&tvfd, pTree->hToolTip); // If client changed the font, then transfer the font // from our private hdc into the tooltip's HDC. We use // a private HDC because we only want to let the app change // the font, not the colors or anything else. if (dwCustom & CDRF_NEWFONT) { SelectObject(pnm->nmcd.hdc, GetCurrentObject(tvfd.nmcd.nmcd.hdc, OBJ_FONT)); } TreeView_EndFakeItemDraw(&tvfd); TreeView_EndFakeCustomDraw(&tvfd); // Don't return other wacky flags to TT, since all we // did was change the font (if even that) return dwCustom & CDRF_NEWFONT; } return CDRF_DODEFAULT; } BOOL TV_SetToolTipTarget(PTREE pTree, HTREEITEM hItem) { // update the item we're showing the bubble for... if (pTree->hToolTip != hItem) { // the hide will keep us from flashing ShowWindow(pTree->hwndToolTips, SW_HIDE); UpdateWindow(pTree->hwndToolTips); pTree->hToolTip = hItem; SendMessage(pTree->hwndToolTips, TTM_UPDATE, 0, 0); return TRUE; } return FALSE; } TREEITEM* TV_ItemAtCursor(PTREE pTree, LPRECT prc) { RECT rc; UINT wHitCode; TREEITEM* hItem; GetCursorPos((LPPOINT)&rc); ScreenToClient(pTree->ci.hwnd, (LPPOINT)&rc); hItem = TV_CheckHit(pTree,rc.left,rc.top,&wHitCode); if (prc) *prc = rc; if (!(wHitCode & TVHT_ONITEM)) hItem = NULL; return hItem; } BOOL TV_UpdateToolTipTarget(PTREE pTree) { RECT rc; TREEITEM *hItem = TV_ItemAtCursor(pTree, &rc); if (!(pTree->ci.style & TVS_NOTOOLTIPS) && !TV_IsItemTruncated(pTree, hItem, &rc) && !(pTree->ci.style & TVS_INFOTIP)) hItem = NULL; // else if (!(pTree->ci.style & TVS_NOTOOLTIPS) // || (pTree->ci.style & TVS_INFOTIP)) return TV_SetToolTipTarget(pTree, hItem); } BOOL TV_UpdateToolTip(PTREE pTree) { if (pTree->hwndToolTips && pTree->fRedraw) return (TV_UpdateToolTipTarget(pTree)); return TRUE; } BOOL TV_SetInsertMark(PTREE pTree, HTREEITEM hItem, BOOL fAfter) { if (!ValidateTreeItem(hItem, VTI_NULLOK)) // NULL means remove insert mark return FALSE; TV_InvalidateInsertMarkRect(pTree, TRUE); // Make sure the old one gets erased pTree->fInsertAfter = BOOLIFY(fAfter); pTree->htiInsert = hItem; TV_InvalidateInsertMarkRect(pTree, FALSE); // Make sure the new one gets drawn return TRUE; } BOOL TV_GetInfoTip(PTREE pTree, LPTOOLTIPTEXT lpttt, HTREEITEM hti, LPTSTR szBuf, int cch) { NMTVGETINFOTIP git; szBuf[0] = 0; git.pszText = szBuf; git.cchTextMax = cch; git.hItem = hti; git.lParam = hti->lParam; // for folded items pszText is prepopulated with the // item text, clients should append to this CCSendNotify(&pTree->ci, TVN_GETINFOTIP, &git.hdr); CCSetInfoTipWidth(pTree->ci.hwnd, pTree->hwndToolTips); Str_Set(&pTree->pszTip, git.pszText); lpttt->lpszText = pTree->pszTip; if(pTree->ci.style & TVS_RTLREADING) { lpttt->uFlags |= TTF_RTLREADING; } return lpttt->lpszText && lpttt->lpszText[0]; } void TV_HandleNeedText(PTREE pTree, LPTOOLTIPTEXT lpttt) { TVITEMEX tvItem; TCHAR szBuf[INFOTIPSIZE]; RECT rc; HTREEITEM hItem; // No distracting tooltips while in-place editing, please if (pTree->htiEdit) { return; } // If the cursor isn't over anything, then stop hItem = TV_ItemAtCursor(pTree, &rc); if (!hItem) return; // If the item has an infotip, then use it if (pTree->ci.style & TVS_INFOTIP) { if (hItem && TV_GetInfoTip(pTree, lpttt, hItem, szBuf, ARRAYSIZE(szBuf))) { pTree->fPlaceTooltip = FALSE; pTree->hToolTip = hItem; return; } } // Else it isn't an infotip CCResetInfoTipWidth(pTree->ci.hwnd, pTree->hwndToolTips); // If the item is not truncated, then no need for a tooltip if (!TV_IsItemTruncated(pTree, hItem, &rc)) { tvItem.hItem = NULL; return; } // Display an in-place tooltip for the item pTree->fPlaceTooltip = TRUE; pTree->hToolTip = hItem; tvItem.hItem = hItem; tvItem.mask = TVIF_TEXT | TVIF_STATE; tvItem.pszText = szBuf; tvItem.stateMask = TVIS_DROPHILITED | TVIS_SELECTED; COMPILETIME_ASSERT(MAXLABELTEXT <= ARRAYSIZE(szBuf)); tvItem.cchTextMax = MAXLABELTEXT; TV_OnGetItem(pTree,&tvItem); Str_Set(&pTree->pszTip, tvItem.pszText); lpttt->lpszText = pTree->pszTip; DebugMsg(DM_TRACE, TEXT("TV_HandleNeedText for %d returns %s"), tvItem.hItem, lpttt->szText); } // // Visual Studio 5.0 Books Online (part of VB 5.0) subclasses // us and responds NFR_ANSI, so we end up getting TTN_NEEDTEXTA // instead of TTN_NEEDTEXTW. We can't risk forcing the tooltip // to UNICODE because some other apps may have done this on purpose // (because they intend to intercept TTN_NEEDTEXTA and do custom tooltips). // So support the ANSI tooltip notification so VB stays happy. // Note: This doesn't have to be efficient, as it's an error case anyway. // void TV_HandleNeedTextA(PTREE pTree, LPTOOLTIPTEXTA lptttA) { TOOLTIPTEXT ttt; ttt.szText[0] = TEXT('\0'); ttt.hdr = lptttA->hdr; ttt.lpszText = ttt.szText; ttt.hinst = lptttA->hinst; ttt.uFlags = lptttA->uFlags; ttt.lParam = lptttA->lParam; TV_HandleNeedText(pTree, &ttt); if (pTree->pszTipA) LocalFree(pTree->pszTipA); pTree->pszTipA = ProduceAFromW(pTree->ci.uiCodePage, ttt.lpszText); lptttA->lpszText = pTree->pszTipA; lptttA->uFlags = ttt.uFlags; } // ---------------------------------------------------------------------------- // // TV_Timer // // Checks to see if it is our name editing timer. If so it calls of to // do name editing // // ---------------------------------------------------------------------------- LRESULT TV_Timer(PTREE pTree, UINT uTimerId) { switch (uTimerId) { case IDT_NAMEEDIT: // Kill the timer as we wont need any more messages from it. KillTimer(pTree->ci.hwnd, IDT_NAMEEDIT); if (pTree->fNameEditPending) { // And start name editing mode. if (!TV_EditLabel(pTree, pTree->hCaret, NULL)) { TV_DismissEdit(pTree, FALSE); } // remove the flag... pTree->fNameEditPending = FALSE; } break; case IDT_SCROLLWAIT: KillTimer(pTree->ci.hwnd, IDT_SCROLLWAIT); if (pTree->fScrollWait) { if (pTree->hCaret) { TV_ScrollVertIntoView(pTree, pTree->hCaret); } pTree->fScrollWait = FALSE; } break; } return 0; } // ---------------------------------------------------------------------------- // // TV_Command // // Process the WM_COMMAND. See if it is an input from our edit windows. // if so we may want to dismiss it, and or set it is being dirty... // // ---------------------------------------------------------------------------- void TV_Command(PTREE pTree, int id, HWND hwndCtl, UINT codeNotify) { if ((pTree != NULL) && (hwndCtl == pTree->hwndEdit)) { switch (codeNotify) { case EN_UPDATE: // We will use the ID of the window as a Dirty flag... SetWindowID(pTree->hwndEdit, 1); TV_SetEditSize(pTree); break; case EN_KILLFOCUS: // We lost focus, so dismiss edit and save changes // (Note that the owner might reject the change and restart // edit mode, which traps the user. Owners need to give the // user a way to get out.) // // Fix horrible undocumented hanging problem: LVN_ENDLABELEDIT // is sent in response to EN_KILLFOCUS, which is send in response // to WM_KILLFOCUS, and it is undocumented that you cannot display // UI during WM_KILLFOCUS when a journal record hook is active, // because the presence of a hook forces serialization of activation, // and so when you put up UI, you generate activation changes, which // get stuck because you haven't finished responding to the previous // WM_KILLFOCUS message yet. // // See NT bug 414634. // if (InSendMessage()) ReplyMessage(0); if (!TV_DismissEdit(pTree, FALSE)) return; break; case HN_BEGINDIALOG: // penwin is bringing up a dialog ASSERT(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system pTree->fNoDismissEdit = TRUE; break; case HN_ENDDIALOG: // penwin has destroyed dialog ASSERT(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system pTree->fNoDismissEdit = FALSE; break; } // Forward edit control notifications up to parent // if (IsWindow(hwndCtl)) FORWARD_WM_COMMAND(pTree->ci.hwndParent, id, hwndCtl, codeNotify, SendMessage); } } HIMAGELIST CreateCheckBoxImagelist(HIMAGELIST himl, BOOL fTree, BOOL fUseColorKey, BOOL fMirror); void TV_CreateToolTips(PTREE pTree); void TV_InitCheckBoxes(PTREE pTree) { HIMAGELIST himl; TVITEMEX ti; himl = CreateCheckBoxImagelist(pTree->hImageList, TRUE, TRUE, IS_WINDOW_RTL_MIRRORED(pTree->ci.hwnd)); if (pTree->hImageList) { COLORREF cr = ImageList_GetBkColor(pTree->hImageList); ImageList_SetBkColor(himl, IsUsingCleartype()? (CLR_NONE) : (cr)); } TV_SetImageList(pTree, himl, TVSIL_STATE); ti.mask = TVIF_STATE; ti.state = INDEXTOSTATEIMAGEMASK(1); ti.stateMask = TVIS_STATEIMAGEMASK; TV_SetItemRecurse(pTree, pTree->hRoot, &ti); } void TV_OnStyleChanged(PTREE pTree, WPARAM gwl, LPSTYLESTRUCT pinfo) { // Style changed: redraw everything... // // try to do this smartly, avoiding unnecessary redraws if (gwl == GWL_STYLE) { DWORD changeFlags; DWORD styleNew; TV_DismissEdit(pTree, FALSE); // You cannot combine TVS_HASLINES and TVS_FULLROWSELECT // because it doesn't work styleNew = pinfo->styleNew; if (styleNew & TVS_HASLINES) { if (styleNew & TVS_FULLROWSELECT) { RIPMSG(FALSE, "TV_OnStyleChanged(): Cannot combine TVS_HASLINES and TVS_FULLROWSELECT"); } styleNew &= ~TVS_FULLROWSELECT; } changeFlags = pTree->ci.style ^ styleNew; // those that changed pTree->ci.style = styleNew; // change our version pTree->ci.style &= ~TVS_RTLREADING; pTree->ci.style |= (pinfo->styleNew & TVS_RTLREADING); if (changeFlags & (TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT)) TV_CreateIndentBmps(pTree); if (changeFlags & TVS_CHECKBOXES) { if (pTree->ci.style & TVS_CHECKBOXES) { TV_InitCheckBoxes(pTree); } } if (changeFlags & TVS_NOTOOLTIPS) { if (pTree->ci.style & TVS_NOTOOLTIPS) { DestroyWindow(pTree->hwndToolTips); pTree->hwndToolTips = NULL; } else { TV_CreateToolTips(pTree); } } if (changeFlags & TVS_TRACKSELECT) { if (!(pTree->ci.style & TVS_TRACKSELECT)) { if (pTree->hHot) { TV_InvalidateItem(pTree, pTree->hHot, RDW_INVALIDATE | RDW_ERASE); pTree->hHot = NULL; } } } // Checkboxes and stuff may have changed width - go recompute TV_ScrollBarsAfterSetWidth(pTree, NULL); } else if (gwl == GWL_EXSTYLE) { DWORD changeFlags; changeFlags = (pinfo->styleNew & WS_EX_RTLREADING) ?TVS_RTLREADING :0; if (changeFlags ^ (pTree->ci.style & TVS_RTLREADING)) { pTree->ci.style ^= TVS_RTLREADING; TV_DismissEdit(pTree, FALSE); // Cancels edits DestroyWindow(pTree->hwndToolTips); pTree->hwndToolTips = NULL; TV_CreateToolTips(pTree); } } } void TV_OnMouseMove(PTREE pTree, DWORD dwPos, WPARAM wParam) { if (pTree->ci.style & TVS_TRACKSELECT) { POINT pt; HTREEITEM hHot; UINT wHitCode; pt.x = GET_X_LPARAM(dwPos); pt.y = GET_Y_LPARAM(dwPos); hHot = TV_CheckHit(pTree,pt.x,pt.y,&wHitCode); if (!(pTree->ci.style & TVS_FULLROWSELECT) && !(wHitCode & TVHT_ONITEM)) { hHot = NULL; } if (hHot != pTree->hHot) { TV_InvalidateItem(pTree, pTree->hHot, RDW_INVALIDATE); TV_InvalidateItem(pTree, hHot, RDW_INVALIDATE); pTree->hHot = hHot; // update now so that we won't have an invalid area // under the tooltips UpdateWindow(pTree->ci.hwnd); } } if (pTree->hwndToolTips) { if (!TV_UpdateToolTip(pTree)) { RelayToToolTips(pTree->hwndToolTips, pTree->ci.hwnd, WM_MOUSEMOVE, wParam, dwPos); } } } void TV_OnWinIniChange(PTREE pTree, WPARAM wParam) { if (!wParam || (wParam == SPI_SETNONCLIENTMETRICS) || (wParam == SPI_SETICONTITLELOGFONT)) { if (pTree->fCreatedFont) TV_OnSetFont(pTree, NULL, TRUE); if (!pTree->fIndentSet) { // this will validate against the minimum TV_SetIndent(pTree, 0); } } } void TV_OnSetBkColor(PTREE pTree, COLORREF clr) { if (pTree->clrBk != (COLORREF)-1) { DeleteObject(pTree->hbrBk); } pTree->clrBk = clr; if (clr != (COLORREF)-1) { pTree->hbrBk = CreateSolidBrush(clr); } TV_CreateIndentBmps(pTree); // This also invalidates pTree->clrBkNonTheme = clr; } DWORD TV_SetExtendedStyle(PTREE pTree, DWORD dwNewStyle, DWORD dwExMask) { DWORD dwOldStyle = pTree->dwExStyle; if (dwExMask) dwNewStyle = (pTree->dwExStyle & ~ dwExMask) | (dwNewStyle & dwExMask); // do validation of the new flags here... pTree->dwExStyle = dwNewStyle; // if ((dwOldStyle ^ dwNewStyle) & TVS_EX_NOSINGLECOLLAPSE) ... do whatever (no need to invalidate rect, this is behavior style) return dwOldStyle; } BOOL TV_TranslateAccelerator(HWND hwnd, LPMSG lpmsg) { if (!lpmsg) return FALSE; if (GetFocus() != hwnd) return FALSE; switch (lpmsg->message) { case WM_KEYUP: case WM_KEYDOWN: if (GetKeyState(VK_CONTROL) < 0) { switch (lpmsg->wParam) { case VK_LEFT: case VK_RIGHT: case VK_PRIOR: case VK_HOME: case VK_NEXT: case VK_END: case VK_UP: case VK_DOWN: TranslateMessage(lpmsg); DispatchMessage(lpmsg); return TRUE; } } else { switch (lpmsg->wParam) { case VK_RETURN: case VK_PRIOR: case VK_HOME: case VK_NEXT: case VK_END: case VK_SUBTRACT: case VK_ADD: case VK_MULTIPLY: case VK_LEFT: case VK_BACK: case VK_UP: case VK_RIGHT: case VK_DOWN: case VK_SPACE: TranslateMessage(lpmsg); DispatchMessage(lpmsg); return TRUE; } } break; } return FALSE; } HTREEITEM TV_FindAccId(HTREEITEM hItem, DWORD dwAccId) { HTREEITEM hKid; HTREEITEM hNext; if (hItem->dwAccId == dwAccId) return hItem; for (hKid = hItem->hKids; hKid; hKid = hNext) { HTREEITEM hItemFound; hNext = hKid->hNext; // recurse on each child hItemFound = TV_FindAccId(hKid, dwAccId); if (hItemFound) return hItemFound; } return NULL; } LRESULT TV_MapAccIDToHTREEITEM(PTREE pTree, DWORD dwAccId) { return (LRESULT)TV_FindAccId(pTree->hRoot, dwAccId); } LRESULT TV_MapHTREEITEMToAccID(PTREE pTree, HTREEITEM hItem) { DBG_ValidateTreeItem(hItem, 0); return (LRESULT)(hItem->dwAccId); } // ---------------------------------------------------------------------------- // // TV_WndProc // // Take a guess. // // ---------------------------------------------------------------------------- LRESULT CALLBACK TV_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { PTREE pTree = (PTREE)GetWindowPtr(hwnd, 0); if (pTree) { if ((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST) && (pTree->ci.style & TVS_TRACKSELECT) && !pTree->fTrackSet) { TRACKMOUSEEVENT tme; pTree->fTrackSet = TRUE; tme.cbSize = sizeof(tme); tme.hwndTrack = pTree->ci.hwnd; tme.dwFlags = TME_LEAVE; TrackMouseEvent(&tme); } else if (uMsg == g_uDragImages) { return TV_GenerateDragImage(pTree, (SHDRAGIMAGE*)lParam); } else if (uMsg == WM_THEMECHANGED) // Check for theme changes { HTHEME hTheme; if (pTree->hTheme) CloseThemeData(pTree->hTheme); pTree->hTheme = NULL; hTheme = OpenThemeData(pTree->ci.hwnd, L"TreeView"); // Reset cached brushes if (hTheme) { TV_InitThemeMetrics(pTree, hTheme); } else { // Background color (system) SendMessage(pTree->ci.hwnd, TVM_SETBKCOLOR, 0, pTree->clrBkNonTheme); // Line color (system) SendMessage(pTree->ci.hwnd, TVM_SETLINECOLOR, 0, pTree->clrLineNonTheme); } pTree->hTheme = hTheme; InvalidateRect(pTree->ci.hwnd, NULL, TRUE); } } else { if (uMsg == WM_CREATE) return TV_OnCreate(hwnd, (LPCREATESTRUCT)lParam); goto DoDefault; } switch (uMsg) { case WM_MOUSELEAVE: pTree->fTrackSet = FALSE; TV_InvalidateItem(pTree, pTree->hHot, RDW_INVALIDATE); pTree->hHot = NULL; TV_PopBubble(pTree); break; case TVMP_CALCSCROLLBARS: TV_CalcScrollBars(pTree); break; case TVM_MAPACCIDTOHTREEITEM: return TV_MapAccIDToHTREEITEM(pTree, (DWORD)wParam); break; case TVM_MAPHTREEITEMTOACCID: return TV_MapHTREEITEMToAccID(pTree, (HTREEITEM)wParam); break; case TVM_GETITEMSTATE: { TVITEMEX tvi; tvi.mask = TVIF_STATE; tvi.stateMask = (UINT) lParam; tvi.hItem = (HTREEITEM)wParam; if (!TV_OnGetItem(pTree, &tvi)) return 0; return tvi.state; } case TVM_SETBKCOLOR: { LRESULT lres = (LRESULT)pTree->clrBk; TV_OnSetBkColor(pTree, (COLORREF)lParam); return lres; } case TVM_SETTEXTCOLOR: { LRESULT lres = (LRESULT)pTree->clrText; pTree->clrText = (COLORREF)lParam; if (pTree->hbrText && pTree->hbrText != g_hbrWindowText) { DeleteObject(pTree->hbrText); } if (pTree->clrText == (COLORREF)-1) { pTree->hbrText = NULL; } else { pTree->hbrText = CreateSolidBrush(pTree->clrText); } if (!pTree->hbrText) pTree->hbrText = g_hbrWindowText; TV_CreateIndentBmps(pTree); // This also invalidates return lres; } case TVM_GETBKCOLOR: return (LRESULT)pTree->clrBk; case TVM_GETTEXTCOLOR: return (LRESULT)pTree->clrText; case TVM_GETSCROLLTIME: return (LRESULT)pTree->uMaxScrollTime; case TVM_SETSCROLLTIME: { UINT u = pTree->uMaxScrollTime; pTree->uMaxScrollTime = (UINT)wParam; return (LRESULT)u; } case TVM_INSERTITEMA: if (!lParam) return 0; return (LRESULT)TV_InsertItemA(pTree, (LPTV_INSERTSTRUCTA)lParam); case TVM_GETITEMA: if (!lParam) return 0; return (LRESULT)TV_OnGetItemA(pTree, (LPTVITEMEXA)lParam); case TVM_SETITEMA: if (!lParam) return 0; return (LRESULT)TV_SetItemA(pTree, (LPTVITEMEXA)lParam); case TVM_INSERTITEM: return (LRESULT)TV_InsertItem(pTree, (LPTV_INSERTSTRUCT)lParam); case TVM_DELETEITEM: // Assume if items are being deleted that name editing is invalid. TV_DismissEdit(pTree, TRUE); return TV_DeleteItem(pTree, (TREEITEM *)lParam, TVDI_NORMAL); case TVM_GETNEXTITEM: return (LRESULT)TV_GetNextItem(pTree, (TREEITEM *)lParam, wParam); case TVM_GETITEMRECT: // lParam points to hItem to get rect from on input if (!lParam) return 0; if (!ValidateTreeItem(*(HTREEITEM *)lParam, 0)) return 0; // Invalid parameter return (LRESULT)TV_GetItemRect(pTree, *(HTREEITEM *)lParam, (LPRECT)lParam, (BOOL)wParam); case TVM_GETITEM: return (LRESULT)TV_OnGetItem(pTree, (LPTVITEMEX)lParam); case TVM_SETITEM: return (LRESULT)TV_SetItem(pTree, (LPCTVITEMEX)lParam); case TVM_ENSUREVISIBLE: if (!ValidateTreeItem((HTREEITEM)lParam, 0)) return 0; return TV_EnsureVisible(pTree, (TREEITEM *)lParam); case TVM_SETIMAGELIST: return (LRESULT)(ULONG_PTR)TV_SetImageList(pTree, (HIMAGELIST)lParam, (int)wParam); case TVM_EXPAND: if (!ValidateTreeItem((HTREEITEM)lParam, 0)) return FALSE; // invalid parameter return TV_Expand(pTree, wParam, (TREEITEM *)lParam, FALSE); case TVM_HITTEST: return (LRESULT)TV_OnHitTest(pTree, (LPTV_HITTESTINFO)lParam); case TVM_GETCOUNT: return MAKELRESULTFROMUINT(pTree->cItems); case TVM_GETIMAGELIST: switch (wParam) { case TVSIL_NORMAL: return MAKELRESULTFROMUINT(pTree->hImageList); case TVSIL_STATE: return MAKELRESULTFROMUINT(pTree->himlState); default: return 0; } case TVM_GETISEARCHSTRINGA: if (GetFocus() == pTree->ci.hwnd) return (LRESULT)GetIncrementSearchStringA(&pTree->is, pTree->ci.uiCodePage, (LPSTR)lParam); else return 0; case TVM_GETISEARCHSTRING: if (GetFocus() == pTree->ci.hwnd) return (LRESULT)GetIncrementSearchString(&pTree->is, (LPTSTR)lParam); else return 0; case TVM_EDITLABELA: { LPWSTR lpEditString = NULL; HWND hRet; if (wParam) { lpEditString = ProduceWFromA(pTree->ci.uiCodePage, (LPSTR)wParam); } hRet = TV_EditLabel(pTree, (HTREEITEM)lParam, lpEditString); if (lpEditString) { FreeProducedString(lpEditString); } return MAKELRESULTFROMUINT(hRet); } case TVM_EDITLABEL: return MAKELRESULTFROMUINT(TV_EditLabel(pTree, (HTREEITEM)lParam, (LPTSTR)wParam)); case TVM_GETVISIBLECOUNT: return TV_GetVisCount(pTree, (BOOL) wParam); case TVM_SETINDENT: TV_SetIndent(pTree, wParam); pTree->fIndentSet = TRUE; break; case TVM_GETINDENT: return MAKELRESULTFROMUINT(pTree->cxIndent); case TVM_CREATEDRAGIMAGE: return MAKELRESULTFROMUINT(TV_CreateDragImage(pTree, (TREEITEM *)lParam)); case TVM_GETEDITCONTROL: return (LRESULT)(ULONG_PTR)pTree->hwndEdit; case TVM_SORTCHILDREN: return TV_SortChildren(pTree, (TREEITEM *)lParam, (BOOL)wParam); case TVM_SORTCHILDRENCB: return TV_SortChildrenCB(pTree, (TV_SORTCB *)lParam, (BOOL)wParam); case TVM_SELECTITEM: // wParam: separate action flags (TVGN) and select item flags (TVSIF) return TV_SelectItem(pTree, (wParam & TVGN_VALID), (TREEITEM *)lParam, TVSIFI_NOTIFY | TVSIFI_UPDATENOW | ((wParam & TVSI_NOSINGLEEXPAND) ? TVSIFI_NOSINGLEEXPAND : 0), TVC_UNKNOWN); case TVM_ENDEDITLABELNOW: return TV_DismissEdit(pTree, (BOOL)wParam); case TVM_GETTOOLTIPS: return (LRESULT)(ULONG_PTR)pTree->hwndToolTips; case TVM_SETTOOLTIPS:{ HWND hwndOld = pTree->hwndToolTips; pTree->hwndToolTips = (HWND)wParam; return (LRESULT)(ULONG_PTR)hwndOld; } case TVM_GETITEMHEIGHT: return pTree->cyItem; case TVM_SETITEMHEIGHT: { int iOld = pTree->cyItem; pTree->fCyItemSet = (wParam != (WPARAM)-1); pTree->cyItem = (SHORT)wParam; // must be even TV_SetItemHeight(pTree); return iOld; } case TVM_SETBORDER: { int cyOld = pTree->cyBorder , cxOld = pTree->cxBorder; if (wParam & TVSBF_YBORDER) pTree->cyBorder = HIWORD(lParam); if (wParam & TVSBF_XBORDER) pTree->cxBorder = LOWORD(lParam); TV_CalcScrollBars(pTree); return MAKELONG(cxOld, cyOld); } case TVM_GETBORDER: return MAKELONG(pTree->cxBorder, pTree->cyBorder); case TVM_SETINSERTMARK: return TV_SetInsertMark(pTree, (TREEITEM *)lParam, (BOOL) wParam); case TVM_SETINSERTMARKCOLOR: { LRESULT lres = (LRESULT)pTree->clrim; pTree->clrim = (COLORREF) lParam; TV_InvalidateInsertMarkRect(pTree, FALSE); // Repaint in new color return lres; } case TVM_GETINSERTMARKCOLOR: return pTree->clrim; case TVM_TRANSLATEACCELERATOR: return TV_TranslateAccelerator(hwnd, (LPMSG)lParam); case TVM_SETLINECOLOR: { LRESULT lres = (LRESULT)pTree->clrLine; pTree->clrLineNonTheme = (COLORREF)lParam; pTree->clrLine = (COLORREF)lParam; if (pTree->hbrLine && pTree->hbrLine != g_hbrGrayText) { DeleteObject(pTree->hbrLine); } if (pTree->clrLine == CLR_DEFAULT) { pTree->hbrLine = NULL; } else { pTree->hbrLine = CreateSolidBrush(pTree->clrLine); } if (!pTree->hbrLine) pTree->hbrLine = g_hbrGrayText; TV_CreateIndentBmps(pTree); // This also invalidates return lres; } case TVM_GETLINECOLOR: return (LRESULT)pTree->clrLine; case TVM_SETEXTENDEDSTYLE: return TV_SetExtendedStyle(pTree, (DWORD)lParam, (DWORD)wParam); case TVM_GETEXTENDEDSTYLE: return pTree->dwExStyle; case WM_IME_COMPOSITION: // Now only Korean version is interested in incremental search with composition string. if (g_fDBCSInputEnabled) { if (((ULONG_PTR)GetKeyboardLayout(0L) & 0xF000FFFFL) == 0xE0000412L) { if (TV_OnImeComposition(pTree, wParam, lParam)) { lParam &= ~GCS_RESULTSTR; goto DoDefault; } else break; } } goto DoDefault; case WM_CHAR: if (pTree->iPuntChar) { pTree->iPuntChar--; return TRUE; } else { return HANDLE_WM_CHAR(pTree, wParam, lParam, TV_OnChar); } case WM_DESTROY: TV_DestroyTree(pTree); break; case WM_SETCURSOR: { NMMOUSE nm; HTREEITEM hItem; nm.dwHitInfo = lParam; hItem = TV_ItemAtCursor(pTree, NULL); if(hItem) { nm.dwItemSpec = (ULONG_PTR)hItem; nm.dwItemData = (ULONG_PTR)(hItem->lParam); } else { nm.dwItemSpec = 0; nm.dwItemData = 0; } if (CCSendNotify(&pTree->ci, NM_SETCURSOR, &nm.hdr)) { return 0; } } if (pTree->ci.style & TVS_TRACKSELECT) { if (pTree->hHot) { if (!pTree->hCurHot) { pTree->hCurHot = LoadCursor(NULL, IDC_HAND); } SetCursor(pTree->hCurHot); return TRUE; } } goto DoDefault; break; case WM_WININICHANGE: TV_OnWinIniChange(pTree, wParam); break; case WM_STYLECHANGED: TV_OnStyleChanged(pTree, wParam, (LPSTYLESTRUCT)lParam); break; case WM_SETREDRAW: TV_OnSetRedraw(pTree, (BOOL)wParam); break; case WM_PRINTCLIENT: case WM_PAINT: TV_Paint(pTree, (HDC)wParam); break; case WM_NCPAINT: if (pTree->hTheme && pTree->ci.dwExStyle & WS_EX_CLIENTEDGE) { HRGN hrgn = (wParam != 1) ? (HRGN)wParam : NULL; if (CCDrawNonClientTheme(pTree->hTheme, hwnd, hrgn, pTree->hbrBk, 0, 0)) { return TRUE; } } goto DoDefault; case WM_ERASEBKGND: { RECT rc; TV_GetBackgroundBrush(pTree, (HDC) wParam); GetClipBox((HDC) wParam, &rc); FillRect((HDC)wParam, &rc, pTree->hbrBk); } return TRUE; case WM_GETDLGCODE: return (LRESULT) (DLGC_WANTARROWS | DLGC_WANTCHARS); case WM_HSCROLL: TV_HorzScroll(pTree, GET_WM_HSCROLL_CODE(wParam, lParam), GET_WM_HSCROLL_POS(wParam, lParam)); break; case WM_VSCROLL: TV_VertScroll(pTree, GET_WM_VSCROLL_CODE(wParam, lParam), GET_WM_VSCROLL_POS(wParam, lParam)); break; case WM_KEYDOWN: if (TV_KeyDown(pTree, wParam, lParam)) IncrementSearchString(&pTree->is, 0, NULL); goto DoDefault; case WM_LBUTTONDBLCLK: case WM_LBUTTONDOWN: TV_ButtonDown(pTree, uMsg, (UINT) wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); break; case WM_KILLFOCUS: // Reset wheel scroll amount gcWheelDelta = 0; pTree->fFocus = FALSE; if (pTree->hCaret) { TV_InvalidateItem(pTree, pTree->hCaret, RDW_INVALIDATE); UpdateWindow(pTree->ci.hwnd); } CCSendNotify(&pTree->ci, NM_KILLFOCUS, NULL); IncrementSearchString(&pTree->is, 0, NULL); break; case WM_SETFOCUS: ASSERT(gcWheelDelta == 0); pTree->fFocus = TRUE; if (pTree->hCaret) { TV_InvalidateItem(pTree, pTree->hCaret, RDW_INVALIDATE); NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, TV_GetAccId(pTree->hCaret)); } else TV_SelectItem(pTree, TVGN_CARET, pTree->hTop, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_INTERNAL); CCSendNotify(&pTree->ci, NM_SETFOCUS, NULL); break; case WM_GETFONT: return MAKELRESULTFROMUINT(pTree->hFont); case WM_SETFONT: TV_OnSetFont(pTree, (HFONT) wParam, (BOOL) lParam); break; case WM_SIZE: TV_SizeWnd(pTree, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); break; case WM_ENABLE: // HACK: we don't get WM_STYLECHANGE on EnableWindow() if (wParam) pTree->ci.style &= ~WS_DISABLED; // enabled else pTree->ci.style |= WS_DISABLED; // disabled TV_CreateIndentBmps(pTree); // This invalidates the whole window! break; case WM_SYSCOLORCHANGE: InitGlobalColors(); TV_CreateIndentBmps(pTree); // This invalidates the whole window! break; case WM_RBUTTONDOWN: TV_SendRButtonDown(pTree, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); break; case WM_TIMER: TV_Timer(pTree, (UINT) wParam); break; case WM_MOUSEMOVE: TV_OnMouseMove(pTree, (DWORD) lParam, wParam); break; case WM_COMMAND: TV_Command(pTree, (int)GET_WM_COMMAND_ID(wParam, lParam), GET_WM_COMMAND_HWND(wParam, lParam), (UINT)GET_WM_COMMAND_CMD(wParam, lParam)); break; case WM_NOTIFY: { LPNMHDR lpnm = (LPNMHDR)lParam; if ((lpnm->code <= PGN_FIRST) && (PGN_LAST <= lpnm->code)) { LRESULT TV_OnPagerControlNotify(PTREE pTree, LPNMHDR pnm); return TV_OnPagerControlNotify(pTree, lpnm); } if (lpnm->hwndFrom == pTree->hwndToolTips) { switch (lpnm->code) { case TTN_NEEDTEXT: TV_HandleNeedText(pTree, (LPTOOLTIPTEXT)lpnm); break; case TTN_NEEDTEXTA: TV_HandleNeedTextA(pTree, (LPTOOLTIPTEXTA)lpnm); break; case TTN_SHOW: return TV_HandleTTNShow(pTree, lpnm); case NM_CUSTOMDRAW: return TV_HandleTTCustomDraw(pTree, (LPNMTTCUSTOMDRAW)lpnm); } } break; } case WM_NOTIFYFORMAT: return CIHandleNotifyFormat(&pTree->ci, lParam); case WM_MBUTTONDOWN: SetFocus(hwnd); goto DoDefault; case WM_GETOBJECT: if( lParam == OBJID_QUERYCLASSNAMEIDX ) return MSAA_CLASSNAMEIDX_TREEVIEW; goto DoDefault; case WM_UPDATEUISTATE: { DWORD dwUIStateMask = MAKEWPARAM(0xFFFF, UISF_HIDEFOCUS); if (CCOnUIState(&(pTree->ci), WM_UPDATEUISTATE, wParam & dwUIStateMask, lParam)) if (pTree->hCaret) TV_InvalidateItem(pTree, pTree->hCaret, TRUE); goto DoDefault; } case WM_SYSKEYDOWN: TV_KeyDown(pTree, wParam, lParam); //fall through default: // Special handling of magellan mouse message if (uMsg == g_msgMSWheel) { DWORD dwStyle; int cScrollLines; int cPage; int pos; int cDetants; int iWheelDelta = (int)(short)HIWORD(wParam); BOOL fScroll = !(wParam & (MK_SHIFT | MK_CONTROL)); BOOL fDataZoom = (BOOL) (wParam & MK_SHIFT); // Update count of scroll amount gcWheelDelta -= iWheelDelta; cDetants = gcWheelDelta / WHEEL_DELTA; if (cDetants != 0) { gcWheelDelta %= WHEEL_DELTA; } if (fScroll) { if ( g_ucScrollLines > 0 && cDetants != 0 && (WS_VSCROLL | WS_HSCROLL) & (dwStyle = GetWindowStyle(hwnd))) { if (dwStyle & WS_VSCROLL) { cPage = max(1, (pTree->cFullVisible - 1)); cScrollLines = cDetants * min(g_ucScrollLines, (UINT) cPage); pos = max(0, pTree->hTop->iShownIndex + cScrollLines); TV_VertScroll(pTree, SB_THUMBPOSITION, pos); } else { cPage = max(MAGIC_HORZLINE, (pTree->cxWnd - MAGIC_HORZLINE)) / MAGIC_HORZLINE; cScrollLines = cDetants * (int) min((ULONG) cPage, g_ucScrollLines) * MAGIC_HORZLINE; pos = max(0, pTree->xPos + cScrollLines); TV_HorzScroll(pTree, SB_THUMBPOSITION, pos); } } return 1; } else if (fDataZoom) { UINT wHitCode; POINT pt; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ScreenToClient(hwnd, &pt); // If we are rolling forward and hit an item then navigate into that // item or expand tree (simulate lbuttondown which will do it). We // also need to handle rolling backwards over the ITEMBUTTON so // that we can collapse the tree in that case. Otherwise // just fall through so it isn't handled. In that case if we // are being hosted in explorer it will do a backwards // history navigation. if (TV_CheckHit(pTree, pt.x, pt.y, &wHitCode) && (wHitCode & (TVHT_ONITEM | TVHT_ONITEMBUTTON))) { UINT uFlags = TVBD_FROMWHEEL; uFlags |= (iWheelDelta > 0) ? TVBD_WHEELFORWARD : TVBD_WHEELBACK; if ((uFlags & TVBD_WHEELFORWARD) || (wHitCode == TVHT_ONITEMBUTTON)) { TV_ButtonDown(pTree, WM_LBUTTONDOWN, 0, pt.x, pt.y, uFlags); return 1; } } // else fall through } } else { LRESULT lres; if (CCWndProc(&pTree->ci, uMsg, wParam, lParam, &lres)) return lres; } DoDefault: return(DefWindowProc(hwnd, uMsg, wParam, lParam)); } return(0L); } // NOTE: there is very similar code in the listview // // Totally disgusting hack in order to catch VK_RETURN // before edit control gets it. // LRESULT CALLBACK TV_EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { PTREE pTree = (PTREE)GetWindowInt(GetParent(hwnd), 0); ASSERT(pTree); if (!pTree) return 0L; // wierd cases can get here... switch (msg) { case WM_KEYDOWN: switch (wParam) { case VK_RETURN: TV_DismissEdit(pTree, FALSE); return 0L; case VK_ESCAPE: TV_DismissEdit(pTree, TRUE); return 0L; } break; case WM_CHAR: switch (wParam) { case VK_RETURN: // Eat the character, so edit control wont beep! return 0L; } // 97903: Localization says we need this for DBCS chars case WM_IME_CHAR: msg = WM_CHAR; break; } return CallWindowProc(pTree->pfnEditWndProc, hwnd, msg, wParam, lParam); } void TV_SetEditSize(PTREE pTree) { RECT rcLabel; UINT seips; if (pTree->htiEdit == NULL) return; TV_GetItemRect(pTree, pTree->htiEdit, &rcLabel, TRUE); // get exact the text bounds (acount for borders used when drawing) InflateRect(&rcLabel, -g_cxLabelMargin, -g_cyBorder); seips = 0; #ifdef DEBUG // If we are in one of the no-scroll modes then it's possible for the // resulting rectangle not to be visible. Similarly, if the item itself // isn't visible, then the resulting rectangle is definitely not visible. // Tell SetEditInPlaceSize not to get upset in those cases. if ((pTree->ci.style & (TVS_NOSCROLL | TVS_NOHSCROLL)) || !ITEM_VISIBLE(pTree->htiEdit)) seips |= SEIPS_NOSCROLL; #endif SetEditInPlaceSize(pTree->hwndEdit, &rcLabel, (HFONT)SendMessage(pTree->hwndEdit, WM_GETFONT, 0, 0), seips); } void TV_CancelEditTimer(PTREE pTree) { if (pTree->fNameEditPending) { KillTimer(pTree->ci.hwnd, IDT_NAMEEDIT); pTree->fNameEditPending = FALSE; } } HWND TV_EditLabel(PTREE pTree, HTREEITEM hItem, LPTSTR pszInitial) { TCHAR szLabel[MAXLABELTEXT]; TV_DISPINFO nm; if (!(pTree->ci.style & TVS_EDITLABELS)) return NULL; if (!ValidateTreeItem(hItem, 0)) return NULL; TV_DismissEdit(pTree, FALSE); // Now get the text associated with that item nm.item.pszText = szLabel; nm.item.cchTextMax = ARRAYSIZE(szLabel); nm.item.stateMask = TVIS_BOLD; // this cast is ok as long as TVIF_INTEGRAL or anything past it isn't asked for TV_GetItem(pTree, hItem, TVIF_TEXT | TVIF_STATE, (LPTVITEMEX)&nm.item); // Must subtract one from ARRAYSIZE(szLabel) because Edit_LimitText // doesn't include the terminating NULL pTree->hwndEdit = CreateEditInPlaceWindow(pTree->ci.hwnd, pszInitial? pszInitial : nm.item.pszText, ARRAYSIZE(szLabel) - 1, WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD | ES_LEFT | ES_AUTOHSCROLL, (nm.item.state & TVIS_BOLD) ? pTree->hFontBold : pTree->hFont); if (pTree->hwndEdit) { if (pszInitial) // if initialized, it's dirty. SetWindowID(pTree->hwndEdit, 1); // // Now notify the parent of this window and see if they want it. // We do it after we cretae the window, but before we show it // such that our parent can query for it and do things like limit // the number of characters that are input nm.item.hItem = hItem; nm.item.state = hItem->state; nm.item.lParam = hItem->lParam; nm.item.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM | TVIF_TEXT); if ((BOOL)CCSendNotify(&pTree->ci, TVN_BEGINLABELEDIT, &nm.hdr)) { DestroyWindow(pTree->hwndEdit); pTree->hwndEdit = NULL; return NULL; } TV_PopBubble(pTree); TV_ScrollIntoView(pTree, hItem); pTree->pfnEditWndProc = SubclassWindow(pTree->hwndEdit, TV_EditWndProc); pTree->htiEdit = hItem; TV_SetEditSize(pTree); // Show the window and set focus to it. Do this after setting the // size so we don't get flicker. SetFocus(pTree->hwndEdit); ShowWindow(pTree->hwndEdit, SW_SHOW); TV_InvalidateItem(pTree, hItem, RDW_INVALIDATE | RDW_ERASE); RescrollEditWindow(pTree->hwndEdit); } return pTree->hwndEdit; } BOOL TV_DismissEdit(PTREE pTree, BOOL fCancel) { HWND hwndEdit; BOOL fOkToContinue = TRUE; HTREEITEM htiEdit; if (pTree->fNoDismissEdit) return FALSE; hwndEdit = pTree->hwndEdit; if (!hwndEdit) { // Also make sure there are no pending edits... TV_CancelEditTimer(pTree); return TRUE; } // Assume that if we are not visible that the window is in the // process of being destroyed and we should not process the // editing of the window... if (!IsWindowVisible(pTree->ci.hwnd)) fCancel = TRUE; // // We are using the Window ID of the control as a BOOL to // state if it is dirty or not. switch (GetWindowID(hwndEdit)) { case 0: // The edit control is not dirty so act like cancel. fCancel = TRUE; // FALL THROUGH case 1: // The edit control is dirty so continue. SetWindowID(hwndEdit, 2); // Don't recurse break; case 2: // We are in the process of processing an update now, bail out return TRUE; } // TV_DeleteItemRecurse will set htiEdit to NULL if the program // deleted the items out from underneath us (while we are waiting // for the edit timer). htiEdit = pTree->htiEdit; if (htiEdit != NULL) { TV_DISPINFO nm; TCHAR szLabel[MAXLABELTEXT]; DBG_ValidateTreeItem(htiEdit, 0); // Initialize notification message. nm.item.hItem = htiEdit; nm.item.lParam = htiEdit->lParam; nm.item.mask = 0; if (fCancel) nm.item.pszText = NULL; else { Edit_GetText(hwndEdit, szLabel, ARRAYSIZE(szLabel)); nm.item.pszText = szLabel; nm.item.cchTextMax = ARRAYSIZE(szLabel); nm.item.mask |= TVIF_TEXT; } // Make sure the text redraws properly TV_InvalidateItem(pTree, htiEdit, RDW_INVALIDATE | RDW_ERASE); pTree->fNoDismissEdit = TRUE; // this is so that we don't recurse due to killfocus ShowWindow(hwndEdit, SW_HIDE); pTree->fNoDismissEdit = FALSE; // // Notify the parent that we the label editing has completed. // We will use the LV_DISPINFO structure to return the new // label in. The parent still has the old text available by // calling the GetItemText function. // fOkToContinue = (BOOL)CCSendNotify(&pTree->ci, TVN_ENDLABELEDIT, &nm.hdr); if (fOkToContinue && !fCancel) { // Bug#94368 raymondc: The caller might have deleted the item in // response to the edit. We should revalidate here (or make // delete item invalidate our edit item). Treat a deletion // as if it were a rejected edit. // // If the item has the text set as CALLBACK, we will let the // ower know that they are supposed to set the item text in // their own data structures. Else we will simply update the // text in the actual view. // // Note: The callee may have set the handle to null to tell // us that the handle to item is no longer valid. if (nm.item.hItem != NULL) { if (htiEdit->lpstr != LPSTR_TEXTCALLBACK) { // Set the item text (everything's set up in nm.item) // nm.item.mask = TVIF_TEXT; TV_SetItem(pTree, (LPTVITEMEX)&nm.item); } else { CCSendNotify(&pTree->ci, TVN_SETDISPINFO, &nm.hdr); } } } } // If we did not reenter edit mode before now reset the edit state // variables to NULL if (hwndEdit == pTree->hwndEdit) { pTree->htiEdit = NULL; pTree->hwndEdit = NULL; // so we don't get reentered on the kill focus } // done with the edit control DestroyWindow(hwndEdit); return fOkToContinue; } LRESULT TV_OnCalcSize(PTREE pTree, LPNMHDR pnm) { LPNMPGCALCSIZE pcalcsize = (LPNMPGCALCSIZE)pnm; switch(pcalcsize->dwFlag) { case PGF_CALCHEIGHT: pcalcsize->iHeight = pTree->cShowing * pTree->cyItem; TraceMsg(TF_WARNING, "tv.PGF_CALCHEIGHT: cShow=%d cShow*cyItem=%d AWR()=%d", pTree->cShowing, pTree->cShowing * pTree->cyItem, pcalcsize->iHeight); break; case PGF_CALCWIDTH: break; } return 0L; } LRESULT TV_OnPagerControlNotify(PTREE pTree, LPNMHDR pnm) { switch(pnm->code) { case PGN_SCROLL: return TV_OnScroll(pTree, pnm); break; case PGN_CALCSIZE: return TV_OnCalcSize(pTree, pnm); break; } return 0L; } LRESULT TV_OnScroll(PTREE pTree, LPNMHDR pnm) { LPNMPGSCROLL pscroll = (LPNMPGSCROLL)pnm; RECT rc = pscroll->rcParent; RECT rcTemp; int iDir = pscroll->iDir; int dyScroll = pscroll->iScroll; TREEITEM * hItem; UINT uCode; int parentsize; TREEITEM * hPrevItem; TREEITEM * hNextItem; int y; POINT pt = {pscroll->iXpos, pscroll->iYpos}; POINT ptTemp = pt; TREEITEM * hCurrentItem = TV_CheckHit(pTree, pt.x + 1, pt.y + 1 , &uCode); switch(iDir) { case PGF_SCROLLUP: //Check if any Item is partially visible at the left/top. if so then set the bottom // of that Item to be our current offset and then scroll. This avoids skipping over // certain Items when partial Items are displayed at the left or top y = pt.y; TV_GetItemRect(pTree,hCurrentItem,&rcTemp, TRUE); if (rcTemp.top < y-1) { hCurrentItem =TV_GetNextItem(pTree,hCurrentItem,TVGN_NEXTVISIBLE); } // Now do the calculation parentsize = RECTHEIGHT(rc); //if the control key is down and we have more than parentsize size of child window // then scroll by that amount if ((pscroll->fwKeys & PGK_CONTROL) && ((pt.y - parentsize) > 0)) { dyScroll = parentsize; } else if ((pt.y - pTree->cyItem) > 0) { // we dont have control key down so scroll by one buttonsize dyScroll = pTree->cyItem; } else { pscroll->iScroll = pt.y; return 0L; } ptTemp.y -= dyScroll; hItem = TV_CheckHit(pTree, ptTemp.x, ptTemp.y, &uCode); if (hItem) { // if the hit test gives us the same Item as our CurrentItem then set the Item // to one Item to the top/left of the CurrentItem hPrevItem = TV_GetNextItem(pTree,hCurrentItem, TVGN_PREVIOUSVISIBLE); if ((hItem == hCurrentItem) && ( hPrevItem != NULL)) { hItem = hPrevItem; } //When scrolling left if we end up in the middle of some Item then we align it to the //right of that Item this is to avoid scrolling more than the pager window width but if the // Item happens to be the left Item of our current Item then we end up in not scrolling //if thats the case then move one more Item to the left. if (hItem == hPrevItem) { hItem = TV_GetNextItem(pTree, hItem, TVGN_PREVIOUSVISIBLE); if(!hItem) { dyScroll = pt.y; break; } } TV_GetItemRect(pTree,hItem,&rcTemp, TRUE); dyScroll = pt.y - rcTemp.bottom; } break; case PGF_SCROLLDOWN: { RECT rcChild; int childsize; GetWindowRect(pTree->ci.hwnd, &rcChild); childsize = RECTHEIGHT(rcChild); parentsize = RECTHEIGHT(rc); //if the control key is down and we have more than parentsize size of child window // then scroll by that amount if ((pscroll->fwKeys & PGK_CONTROL) && ((childsize - pt.y - parentsize) > parentsize)) { dyScroll = parentsize; } else if ( (childsize - pt.y - parentsize) > (pTree->cyItem * hCurrentItem->iIntegral) ) { // we dont have control key down so scroll by one buttonsize dyScroll = pTree->cyItem * hCurrentItem->iIntegral; } else { pscroll->iScroll = childsize - pt.y - parentsize; return 0L; } ptTemp.y += dyScroll; hItem = TV_CheckHit(pTree, ptTemp.x, ptTemp.y, &uCode); if (hItem) { if ((hItem == hCurrentItem) && ((hNextItem = TV_GetNextItem(pTree,hItem,TVGN_NEXTVISIBLE)) != NULL)) { hItem = hNextItem; } TV_GetItemRect(pTree, hItem, &rcTemp, TRUE); dyScroll = rcTemp.top - pt.y ; } break; } } //Set the scroll value pscroll->iScroll = dyScroll; return 0L; }