//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: A C T R E E . C P P // // Contents: Functions related to the Advanced Configuration dialog // tree view control // // Notes: // // Author: danielwe 3 Dec 1997 // //---------------------------------------------------------------------------- #include "pch.h" #pragma hdrstop #include "netcon.h" #include "netconp.h" #include "acsheet.h" #include "acbind.h" #include "ncnetcfg.h" #include "lancmn.h" #include "ncui.h" #include "ncsetup.h" #include "ncperms.h" DWORD GetDepthSpecialCase ( INetCfgBindingPath* pPath) { HRESULT hr; DWORD dwDepth; hr = pPath->GetDepth (&dwDepth); if (SUCCEEDED(hr)) { INetCfgComponent* pLast; hr = HrGetLastComponentAndInterface ( pPath, &pLast, NULL); if (SUCCEEDED(hr)) { DWORD dwCharacteristics; // If the last component in the bindpath is one which // doesn't expose its lower bindings, then compsensate by // returning a depth that thinks it does. This special case // is only for this code which was written for the origianl // binding engine but needed to be quickly adapted to the new // binding engine which doesn't return 'fake' bindpaths. // hr = pLast->GetCharacteristics (&dwCharacteristics); if (SUCCEEDED(hr) && (dwCharacteristics & NCF_DONTEXPOSELOWER)) { PWSTR pszInfId; hr = pLast->GetId (&pszInfId); if (S_OK == hr) { if (0 == lstrcmpW (pszInfId, L"ms_nwnb")) { dwDepth += 2; } else if (0 == lstrcmpW (pszInfId, L"ms_nwipx")) { dwDepth += 1; } CoTaskMemFree (pszInfId); } } ReleaseObj (pLast); } } return dwDepth; } //+--------------------------------------------------------------------------- // // Function: FreeBindPathInfoList // // Purpose: Frees the given list of BIND_PATH_INFO structures // // Arguments: // listbpip [in, ref] Reference to list to be freed // // Returns: Nothing // // Author: danielwe 26 Nov 1997 // // Notes: // VOID FreeBindPathInfoList(BPIP_LIST &listbpip) { BPIP_LIST::iterator iterBpip; for (iterBpip = listbpip.begin(); iterBpip != listbpip.end(); iterBpip++) { BIND_PATH_INFO * pbpi = *iterBpip; ReleaseObj(pbpi->pncbp); delete pbpi; } listbpip.erase(listbpip.begin(), listbpip.end()); } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnTreeItemChanged // // Purpose: Called in response to the TVN_SELCHANGED message // // Arguments: // idCtrl [] // pnmh [] // bHandled [] // // Returns: // // Author: danielwe 26 Nov 1997 // // Notes: // LRESULT CBindingsDlg::OnTreeItemChanged(int idCtrl, LPNMHDR pnmh, BOOL& bHandled) { NM_TREEVIEW * pnmtv = reinterpret_cast(pnmh); Assert(pnmtv); #ifdef ENABLETRACE WCHAR szBuffer[265]; pnmtv->itemNew.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT; pnmtv->itemNew.pszText = szBuffer; pnmtv->itemNew.cchTextMax = celems(szBuffer); TreeView_GetItem(m_hwndTV, &pnmtv->itemNew); TREE_ITEM_DATA * ptid; ptid = reinterpret_cast(pnmtv->itemNew.lParam); Assert(ptid); TraceTag(ttidAdvCfg, "*-------------------------------------------------" "------------------------------*"); TraceTag(ttidAdvCfg, "Tree item %S selected", szBuffer); TraceTag(ttidAdvCfg, "-----------------------------------------"); TraceTag(ttidAdvCfg, "OnEnable list:"); TraceTag(ttidAdvCfg, "--------------"); BPIP_LIST::iterator iterBpip; for (iterBpip = ptid->listbpipOnEnable.begin(); iterBpip != ptid->listbpipOnEnable.end(); iterBpip++) { BIND_PATH_INFO * pbpi = *iterBpip; DbgDumpBindPath(pbpi->pncbp); } TraceTag(ttidAdvCfg, "-----------------------------------"); TraceTag(ttidAdvCfg, "OnDisable list:"); TraceTag(ttidAdvCfg, "--------------"); for (iterBpip = ptid->listbpipOnDisable.begin(); iterBpip != ptid->listbpipOnDisable.end(); iterBpip++) { BIND_PATH_INFO * pbpi = *iterBpip; DbgDumpBindPath(pbpi->pncbp); } TraceTag(ttidAdvCfg, "*-------------------------------------------------" "------------------------------*"); #endif // Assume both buttons are greyed initially ::EnableWindow(GetDlgItem(PSB_Binding_Up), FALSE); ::EnableWindow(GetDlgItem(PSB_Binding_Down), FALSE); if (TreeView_GetParent(m_hwndTV, pnmtv->itemNew.hItem)) { if (TreeView_GetNextSibling(m_hwndTV, pnmtv->itemNew.hItem)) { ::EnableWindow(GetDlgItem(PSB_Binding_Down), TRUE); } if (TreeView_GetPrevSibling(m_hwndTV, pnmtv->itemNew.hItem)) { ::EnableWindow(GetDlgItem(PSB_Binding_Up), TRUE); } } return 0; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnTreeDeleteItem // // Purpose: Called in response to the TVN_DELETEITEM message // // Arguments: // idCtrl [] // pnmh [] // bHandled [] // // Returns: Nothing useful // // Author: danielwe 26 Nov 1997 // // Notes: // LRESULT CBindingsDlg::OnTreeDeleteItem(int idCtrl, LPNMHDR pnmh, BOOL& bHandled) { NM_TREEVIEW * pnmtv = reinterpret_cast(pnmh); TREE_ITEM_DATA * ptid; Assert(pnmtv); ptid = reinterpret_cast(pnmtv->itemOld.lParam); // May be NULL if moving items around if (ptid) { ReleaseObj(ptid->pncc); FreeBindPathInfoList(ptid->listbpipOnEnable); FreeBindPathInfoList(ptid->listbpipOnDisable); delete ptid; } return FALSE; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnTreeItemExpanding // // Purpose: Called when the TVN_ITEMEXPANDING message is received // // Arguments: // idCtrl [] // pnmh [] // bHandled [] // // Returns: // // Author: danielwe 26 Nov 1997 // // Notes: // LRESULT CBindingsDlg::OnTreeItemExpanding(int idCtrl, LPNMHDR pnmh, BOOL& bHandled) { // This prevents all tree items from collapsing return TRUE; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnTreeKeyDown // // Purpose: Called when the TVN_KEYDOWN message is received // // Arguments: // idCtrl [] // pnmh [] // fHandled [] // // Returns: // // Author: danielwe 22 Dec 1997 // // Notes: // LRESULT CBindingsDlg::OnTreeKeyDown(int idCtrl, LPNMHDR pnmh, BOOL& fHandled) { TV_KEYDOWN * ptvkd = (TV_KEYDOWN*)pnmh; HTREEITEM hti = NULL; if (VK_SPACE == ptvkd->wVKey) { hti = TreeView_GetSelection(m_hwndTV); // if there is a selection if (hti) { ToggleCheckbox(hti); } } return TRUE; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::ToggleCheckbox // // Purpose: Called when the user toggles a checbox in the treeview // control. // // Arguments: // hti [in] HTREEITEM of item that was toggled // // Returns: Nothing // // Author: danielwe 26 Nov 1997 // // Notes: // VOID CBindingsDlg::ToggleCheckbox(HTREEITEM hti) { if (!FHasPermission(NCPERM_ChangeBindState)) { // do nothing return; } TV_ITEM tvi = {0}; TREE_ITEM_DATA * ptid; BOOL fEnable; tvi.mask = TVIF_PARAM | TVIF_HANDLE | TVIF_STATE; tvi.stateMask = TVIS_STATEIMAGEMASK; tvi.hItem = hti; TreeView_GetItem(m_hwndTV, &tvi); ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No tree item data??"); BPIP_LIST::iterator iterBpip; BPIP_LIST * plist; if (tvi.state & INDEXTOSTATEIMAGEMASK(SELS_CHECKED)) { // unchecking the box plist = &ptid->listbpipOnDisable; fEnable = FALSE; } else { // checking the box plist = &ptid->listbpipOnEnable; fEnable = TRUE; } TraceTag(ttidAdvCfg, "ToggleChecbox: %s the following binding path(s)", fEnable ? "Enabling" : "Disabling"); // Enable or disable each binding path in the appropriate list for (iterBpip = plist->begin(); iterBpip != plist->end(); iterBpip++) { BIND_PATH_INFO * pbpi = *iterBpip; (VOID)pbpi->pncbp->Enable(fEnable); DbgDumpBindPath(pbpi->pncbp); } SetCheckboxStates(); TraceTag(ttidAdvCfg, "Done!"); } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnClick // // Purpose: Called in response to the NM_CLICK message. // // Arguments: // idCtrl [] // pnmh [] // fHandled [] // // Returns: // // Author: danielwe 26 Nov 1997 // // Notes: // LRESULT CBindingsDlg::OnClick(int idCtrl, LPNMHDR pnmh, BOOL& fHandled) { return OnClickOrDoubleClick(idCtrl, pnmh, FALSE); } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnDoubleClick // // Purpose: Called in response to the NM_DBLCLK message. // // Arguments: // idCtrl [] // pnmh [] // fHandled [] // // Returns: // // Author: danielwe 16 Dec 1997 // // Notes: // LRESULT CBindingsDlg::OnDoubleClick(int idCtrl, LPNMHDR pnmh, BOOL& fHandled) { return OnClickOrDoubleClick(idCtrl, pnmh, TRUE); } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnClickOrDoubleClick // // Purpose: Handles clicks or double clicks in the treeview control // // Arguments: // idCtrl [in] ID of control // pnmh [in] Notification header // fDoubleClick [in] TRUE if double click, FALSE if single click // // Returns: // // Author: danielwe 16 Dec 1997 // // Notes: // LRESULT CBindingsDlg::OnClickOrDoubleClick(int idCtrl, LPNMHDR pnmh, BOOL fDoubleClick) { if (idCtrl == TVW_Bindings) { DWORD dwpts; RECT rc; TV_HITTESTINFO tvhti = {0}; HTREEITEM hti; // we have the location dwpts = GetMessagePos(); // translate it relative to the tree view ::GetWindowRect(m_hwndTV, &rc); tvhti.pt.x = LOWORD(dwpts) - rc.left; tvhti.pt.y = HIWORD(dwpts) - rc.top; // get currently selected item hti = TreeView_HitTest(m_hwndTV, &tvhti); if (hti) { if (tvhti.flags & TVHT_ONITEMSTATEICON) { ToggleCheckbox(hti); } else if ((tvhti.flags & TVHT_ONITEM) && fDoubleClick) { ToggleCheckbox(hti); } } } return FALSE; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnBindingUpDown // // Purpose: Handles the user moving a binding in the treeview up or down // // Arguments: // fUp [in] TRUE if moving up, FALSE if down // // Returns: Nothing // // Author: danielwe 3 Dec 1997 // // Notes: // VOID CBindingsDlg::OnBindingUpDown(BOOL fUp) { HRESULT hr = S_OK; TV_ITEM tvi = {0}; HTREEITEM htiSel; HTREEITEM htiDst; TREE_ITEM_DATA * ptidSel; TREE_ITEM_DATA * ptidDst; INetCfgComponentBindings * pnccb; htiSel = TreeView_GetSelection(m_hwndTV); AssertSz(htiSel, "No selection?"); if (fUp) { htiDst = TreeView_GetPrevSibling(m_hwndTV, htiSel); } else { htiDst = TreeView_GetNextSibling(m_hwndTV, htiSel); } AssertSz(htiDst, "No next item?!"); tvi.mask = TVIF_PARAM; tvi.hItem = htiSel; TreeView_GetItem(m_hwndTV, &tvi); ptidSel = reinterpret_cast(tvi.lParam); tvi.hItem = htiDst; TreeView_GetItem(m_hwndTV, &tvi); ptidDst = reinterpret_cast(tvi.lParam); BPIP_LIST::iterator iterlist; INetCfgBindingPath * pncbpDst; BIND_PATH_INFO * pbpiDst = NULL; BPIP_LIST::iterator posDst; BPIP_LIST::reverse_iterator posDstRev; INetCfgComponent * pnccDstOwner; if (fUp) { posDst = ptidDst->listbpipOnDisable.begin(); pbpiDst = *posDst; } else { posDstRev = ptidDst->listbpipOnDisable.rbegin(); pbpiDst = *posDstRev; } AssertSz(pbpiDst, "We never found a path to move before or after!"); pncbpDst = pbpiDst->pncbp; Assert(pncbpDst); hr = pncbpDst->GetOwner(&pnccDstOwner); if (SUCCEEDED(hr)) { hr = pnccDstOwner->QueryInterface(IID_INetCfgComponentBindings, reinterpret_cast(&pnccb)); if (SUCCEEDED(hr)) { for (iterlist = ptidSel->listbpipOnDisable.begin(); iterlist != ptidSel->listbpipOnDisable.end() && SUCCEEDED(hr); iterlist++) { // loop thru each item in the OnDisable list INetCfgBindingPath * pncbp; BIND_PATH_INFO * pbpi; pbpi = *iterlist; pncbp = pbpi->pncbp; #if DBG INetCfgComponent * pnccSrcOwner; if (SUCCEEDED(pncbp->GetOwner(&pnccSrcOwner))) { AssertSz(pnccSrcOwner == pnccDstOwner, "Source and " "dst path owners are not the same!?!"); ReleaseObj(pnccSrcOwner); } #endif if (fUp) { TraceTag(ttidAdvCfg, "Treeview: Moving..."); DbgDumpBindPath(pncbp); // Move this binding path before the tagret hr = pnccb->MoveBefore(pncbp, pncbpDst); TraceTag(ttidAdvCfg, "Treeview: before..."); DbgDumpBindPath(pncbpDst); } else { TraceTag(ttidAdvCfg, "Treeview: Moving..."); DbgDumpBindPath(pncbp); // Move this binding path after the tagret hr = pnccb->MoveAfter(pncbp, pncbpDst); TraceTag(ttidAdvCfg, "Treeview: after..."); DbgDumpBindPath(pncbpDst); } } ReleaseObj(pnccb); } ReleaseObj(pnccDstOwner); } if (SUCCEEDED(hr)) { HTREEITEM htiParent; htiParent = TreeView_GetParent(m_hwndTV, htiSel); // Now that the binding has been moved, move the tree view item to the // proper place. If moving if (fUp) { // If moving up, the "move after" item should be the previous // sibling's previous sibling. If that doesn't exist, use the // previous sibling's parent. That had better exist! htiDst = TreeView_GetPrevSibling(m_hwndTV, htiDst); if (!htiDst) { htiDst = htiParent; } } AssertSz(htiDst, "No destination to move after!"); SendDlgItemMessage(TVW_Bindings, WM_SETREDRAW, FALSE, 0); htiSel = HtiMoveTreeItemAfter(htiParent, htiDst, htiSel); TreeView_SelectItem(m_hwndTV, htiSel); SendDlgItemMessage(TVW_Bindings, WM_SETREDRAW, TRUE, 0); ::SetFocus(m_hwndTV); } } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnBindingUp // // Purpose: Called when the PSB_Binding_Up button is pressed // // Arguments: // wNotifyCode [] // wID [] // hWndCtl [] // bHandled [] // // Returns: // // Author: danielwe 3 Dec 1997 // // Notes: // LRESULT CBindingsDlg::OnBindingUp(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { OnBindingUpDown(TRUE); return 0; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::OnBindingDown // // Purpose: Called when the PSB_Binding_Down button is pressed // // Arguments: // wNotifyCode [] // wID [] // hWndCtl [] // bHandled [] // // Returns: // // Author: danielwe 3 Dec 1997 // // Notes: // LRESULT CBindingsDlg::OnBindingDown(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { OnBindingUpDown(FALSE); return 0; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::SetCheckboxStates // // Purpose: Sets the state of all checkboxes in the treeview. // // Arguments: // (none) // // Returns: Nothing // // Author: danielwe 26 Nov 1997 // // Notes: // VOID CBindingsDlg::SetCheckboxStates() { HRESULT hr = S_OK; CIterTreeView iterTV(m_hwndTV); HTREEITEM hti; TV_ITEM tvi = {0}; #ifdef ENABLETRACE WCHAR szBuffer[256]; #endif BOOL fHasPermission = FHasPermission(NCPERM_ChangeBindState); while ((hti = iterTV.HtiNext()) && SUCCEEDED(hr)) { TREE_ITEM_DATA * ptid; #ifdef ENABLETRACE tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT; tvi.pszText = szBuffer; tvi.cchTextMax = celems(szBuffer); #else tvi.mask = TVIF_HANDLE | TVIF_PARAM; #endif tvi.hItem = hti; TreeView_GetItem(m_hwndTV, &tvi); ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No tree item data??"); #ifdef ENABLETRACE TraceTag(ttidAdvCfg, "Setting checkbox state for item %S.", szBuffer); #endif BPIP_LIST::iterator iterBpip; DWORD cEnabled = 0; for (iterBpip = ptid->listbpipOnDisable.begin(); iterBpip != ptid->listbpipOnDisable.end(); iterBpip++) { BIND_PATH_INFO * pbpi = *iterBpip; if (S_OK == pbpi->pncbp->IsEnabled()) { cEnabled++; } } tvi.mask = TVIF_STATE; tvi.stateMask = TVIS_STATEIMAGEMASK; UINT iState; if (!fHasPermission) { iState = cEnabled ? SELS_FIXEDBINDING_ENABLED : SELS_FIXEDBINDING_DISABLED; } else { iState = cEnabled ? SELS_CHECKED : SELS_UNCHECKED; } tvi.state = INDEXTOSTATEIMAGEMASK(iState); TreeView_SetItem(m_hwndTV, &tvi); } } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::BuildBindingsList // // Purpose: Builds the contents of the Bindings treeview control // // Arguments: // pncc [in] INetCfgComponent of adapter upon which this list is based // // Returns: Nothing // // Author: danielwe 26 Nov 1997 // // Notes: // VOID CBindingsDlg::BuildBindingsList(INetCfgComponent *pncc) { HRESULT hr = S_OK; Assert(pncc); SBP_LIST listsbp; CIterNetCfgUpperBindingPath ncupbIter(pncc); INetCfgBindingPath * pncbp; CWaitCursor wc; SendDlgItemMessage(TVW_Bindings, WM_SETREDRAW, FALSE, 0); while (SUCCEEDED(hr) && S_OK == (hr = ncupbIter.HrNext(&pncbp))) { listsbp.push_back(CSortableBindPath(pncbp)); } if (SUCCEEDED(hr)) { SBP_LIST::iterator iterlist; // This sorts the list descending by depth listsbp.sort(); for (iterlist = listsbp.begin(); iterlist != listsbp.end() && SUCCEEDED(hr); iterlist++) { INetCfgBindingPath * pncbp; pncbp = (*iterlist).GetPath(); Assert(pncbp); hr = HrHandleSubpath(listsbp, pncbp); if (S_FALSE == hr) { hr = HrHandleTopLevel(pncbp); } } } #ifdef ENABLETRACE if (FALSE) { SBP_LIST::iterator iterlist; for (iterlist = listsbp.begin(); iterlist != listsbp.end(); iterlist++) { INetCfgBindingPath * pncbp; pncbp = (*iterlist).GetPath(); DWORD dwLen = GetDepthSpecialCase(pncbp); TraceTag(ttidAdvCfg, "Length is %ld.", dwLen); } } #endif if (SUCCEEDED(hr)) { hr = HrOrderDisableLists(); if (SUCCEEDED(hr)) { hr = HrOrderSubItems(); } } SendDlgItemMessage(TVW_Bindings, WM_SETREDRAW, TRUE, 0); // Select first item in the tree TreeView_SelectItem(m_hwndTV, TreeView_GetRoot(m_hwndTV)); { SBP_LIST::iterator iterlist; for (iterlist = listsbp.begin(); iterlist != listsbp.end() && SUCCEEDED(hr); iterlist++) { INetCfgBindingPath * pncbp; pncbp = (*iterlist).GetPath(); ReleaseObj(pncbp); } } TraceError("CBindingsDlg::BuildBindingsList", hr); } //+--------------------------------------------------------------------------- // // Function: BpiFindBindPathInList // // Purpose: Given a bind path and a list, finds the BIND_PATH_INFO item // that contains the given bind path. // // Arguments: // pncbp [in] Bind path to look for // listBpip [in, ref] List to search // // Returns: BIND_PATH_INFO of corresponding binding path, NULL if not // found // // Author: danielwe 4 Dec 1997 // // Notes: // BIND_PATH_INFO *BpiFindBindPathInList(INetCfgBindingPath *pncbp, BPIP_LIST &listBpip) { BPIP_LIST::iterator iterlist; for (iterlist = listBpip.begin(); iterlist != listBpip.end(); iterlist++) { BIND_PATH_INFO * pbpi; pbpi = *iterlist; if (S_OK == pncbp->IsSamePathAs(pbpi->pncbp)) { // Found the target path return pbpi; } } return NULL; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrOrderDisableList // // Purpose: Given a component's item data, orders the OnDisable list // based on the true binding order for the owning component // // Arguments: // ptid [in] Item data containing list // // Returns: S_OK if success, Win32 or OLE error code otherwise // // Author: danielwe 4 Dec 1997 // // Notes: // HRESULT CBindingsDlg::HrOrderDisableList(TREE_ITEM_DATA *ptid) { HRESULT hr = S_OK; INetCfgComponent * pnccOwner; BIND_PATH_INFO * pbpi; #if DBG size_t cItems = ptid->listbpipOnDisable.size(); #endif // Get the owning component of the first binding path in the list pbpi = *(ptid->listbpipOnDisable.begin()); hr = pbpi->pncbp->GetOwner(&pnccOwner); if (SUCCEEDED(hr)) { CIterNetCfgBindingPath ncbpIter(pnccOwner); INetCfgBindingPath * pncbp; BPIP_LIST::iterator posPncbp; BPIP_LIST::iterator posInsertAfter; // Start this at beginning posInsertAfter = ptid->listbpipOnDisable.begin(); while (SUCCEEDED(hr) && S_OK == (hr = ncbpIter.HrNext(&pncbp))) { pbpi = BpiFindBindPathInList(pncbp, ptid->listbpipOnDisable); if (pbpi) { BPIP_LIST::iterator posErase; posErase = find(ptid->listbpipOnDisable.begin(), ptid->listbpipOnDisable.end(), pbpi); AssertSz(posErase != ptid->listbpipOnDisable.end(), "It HAS" " to be in the list!"); // Found bind path in list // Remove it from present location and insert after next item ptid->listbpipOnDisable.splice(posInsertAfter, ptid->listbpipOnDisable, posErase); posInsertAfter++; } ReleaseObj(pncbp); } ReleaseObj(pnccOwner); } if (SUCCEEDED(hr)) { AssertSz(ptid->listbpipOnDisable.size() == cItems, "How come we don't" " have the same number of items in the list anymore??"); hr = S_OK; } TraceError("CBindingsDlg::HrOrderDisableList", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrOrderDisableLists // // Purpose: Orders the OnDisable lists of all tree view items according // to the true binding order // // Arguments: // (none) // // Returns: S_OK if success, Win32 or OLE error code otherwise // // Author: danielwe 4 Dec 1997 // // Notes: // HRESULT CBindingsDlg::HrOrderDisableLists() { HRESULT hr = S_OK; CIterTreeView iterHti(m_hwndTV); HTREEITEM hti; TV_ITEM tvi = {0}; // Loop thru each tree item, ordering the OnDisable lists to match the // owning component's true binding order while ((hti = iterHti.HtiNext()) && SUCCEEDED(hr)) { TREE_ITEM_DATA * ptid; tvi.mask = TVIF_PARAM; tvi.hItem = hti; TreeView_GetItem(m_hwndTV, &tvi); ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No item data?!"); hr = HrOrderDisableList(ptid); } if (SUCCEEDED(hr)) { hr = S_OK; } TraceError("CBindingsDlg::HrOrderDisableLists", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrOrderSubItems // // Purpose: Orders the sub items of the tree view to reflect the bind // order of the system // // Arguments: // (none) // // Returns: Nothing // // Author: danielwe 3 Dec 1997 // // Notes: // HRESULT CBindingsDlg::HrOrderSubItems() { HRESULT hr = S_OK; HTREEITEM htiTopLevel; htiTopLevel = TreeView_GetRoot(m_hwndTV); while (htiTopLevel) { HTREEITEM htiChild; TREE_ITEM_DATA * ptid; TV_ITEM tvi = {0}; tvi.mask = TVIF_PARAM; tvi.hItem = htiTopLevel; TreeView_GetItem(m_hwndTV, &tvi); ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No tree item data??"); CIterNetCfgBindingPath ncbpIter(ptid->pncc); INetCfgBindingPath * pncbp; HTREEITEM htiInsertAfter = NULL; while (SUCCEEDED(hr) && S_OK == (hr = ncbpIter.HrNext(&pncbp))) { BOOL fFound = FALSE; htiChild = TreeView_GetChild(m_hwndTV, htiTopLevel); while (htiChild && !fFound) { TREE_ITEM_DATA * ptidChild; tvi.mask = TVIF_PARAM; tvi.hItem = htiChild; TreeView_GetItem(m_hwndTV, &tvi); ptidChild = reinterpret_cast(tvi.lParam); AssertSz(ptidChild, "No tree item data??"); if (!ptidChild->fOrdered) { BIND_PATH_INFO * pbpi; pbpi = BpiFindBindPathInList(pncbp, ptidChild->listbpipOnDisable); if (pbpi) { htiInsertAfter = HtiMoveTreeItemAfter(htiTopLevel, htiInsertAfter, htiChild); ptidChild->fOrdered = TRUE; fFound = TRUE; // Go to next bind path break; } } htiChild = TreeView_GetNextSibling(m_hwndTV, htiChild); } ReleaseObj(pncbp); } htiTopLevel = TreeView_GetNextSibling(m_hwndTV, htiTopLevel); } if (SUCCEEDED(hr)) { hr = S_OK; } TraceError("CBindingsDlg::HrOrderSubItems", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HtiAddTreeViewItem // // Purpose: Addes a new tree item according to provided information // // Arguments: // pnccOwner [in] INetCfgComponent owner of component being added // htiParent [in] HTREEITEM of parent (NULL if top-level item) // // Returns: HTREEITEM of newly added item // // Author: danielwe 26 Nov 1997 // // Notes: // HTREEITEM CBindingsDlg::HtiAddTreeViewItem(INetCfgComponent * pnccOwner, HTREEITEM htiParent) { HRESULT hr = S_OK; HTREEITEM hti = NULL; SP_CLASSIMAGELIST_DATA cid; Assert(pnccOwner); // Get the class image list structure hr = HrSetupDiGetClassImageList(&cid); if (SUCCEEDED(hr)) { BSTR pszwName; hr = pnccOwner->GetDisplayName(&pszwName); if (SUCCEEDED(hr)) { GUID guidClass; hr = pnccOwner->GetClassGuid(&guidClass); if (SUCCEEDED(hr)) { INT nIndex; // Get the component's class image list index hr = HrSetupDiGetClassImageIndex(&cid, &guidClass, &nIndex); if (SUCCEEDED(hr)) { TV_INSERTSTRUCT tvis = {0}; TREE_ITEM_DATA * ptid; ptid = new TREE_ITEM_DATA; if (ptid) { AddRefObj(ptid->pncc = pnccOwner); ptid->fOrdered = FALSE; tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_STATE | TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvis.item.iImage = nIndex; tvis.item.iSelectedImage = nIndex; tvis.item.stateMask = TVIS_STATEIMAGEMASK | TVIS_EXPANDED; tvis.item.state = TVIS_EXPANDED | INDEXTOSTATEIMAGEMASK(SELS_CHECKED); tvis.item.pszText = pszwName; tvis.item.lParam = reinterpret_cast(ptid); tvis.hParent = htiParent; tvis.hInsertAfter = TVI_LAST; hti = TreeView_InsertItem(m_hwndTV, &tvis); TraceTag(ttidAdvCfg, "Adding%s treeview item: %S", htiParent ? " child" : "", tvis.item.pszText); CoTaskMemFree(pszwName); } else { hr = E_OUTOFMEMORY; } } } } (void) HrSetupDiDestroyClassImageList(&cid); } return hti; } //+--------------------------------------------------------------------------- // // Function: AddToListIfNotAlreadyAdded // // Purpose: Addes the given bind path info structure to the given list // // Arguments: // bpipList [in, ref] List to be added to // pbpi [in] BIND_PATH_INFO structure to add // // Returns: Nothing // // Author: danielwe 26 Nov 1997 // // Notes: If the item is not added to the list, it is deleted // VOID AddToListIfNotAlreadyAdded(BPIP_LIST &bpipList, BIND_PATH_INFO *pbpi) { BPIP_LIST::iterator iterBpip; BOOL fAlreadyInList = FALSE; for (iterBpip = bpipList.begin(); iterBpip != bpipList.end(); iterBpip++) { BIND_PATH_INFO * pbpiList = *iterBpip; if (S_OK == pbpiList->pncbp->IsSamePathAs(pbpi->pncbp)) { fAlreadyInList = TRUE; break; } } if (!fAlreadyInList) { bpipList.push_back(pbpi); } else { ReleaseObj(pbpi->pncbp); delete pbpi; } } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::AssociateBinding // // Purpose: Associates the given binding path with the given tree item // // Arguments: // pncbpThis [in] Bind path to associate // hti [in] HTREEITEM of item to associate the binding with // dwFlags [in] One or combination of: // ASSCF_ON_ENABLE - associate with OnEnable list // ASSCF_ON_DISABLE - associate with OnDisable list // ASSCF_ANCESTORS - associate this binding with all // ancestors of the given item as // well // // Returns: Nothing // // Author: danielwe 26 Nov 1997 // // Notes: If binding is already present in the given list, it is not // added again. // VOID CBindingsDlg::AssociateBinding(INetCfgBindingPath *pncbpThis, HTREEITEM hti, DWORD dwFlags) { TV_ITEM tvi = {0}; TREE_ITEM_DATA * ptid; #ifdef ENABLETRACE WCHAR szBuffer[256]; #endif //$ TODO (danielwe) 26 Nov 1997: Include ALL ancestors as well! AssertSz(dwFlags, "NO flags!"); #ifdef ENABLETRACE tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT; tvi.pszText = szBuffer; tvi.cchTextMax = celems(szBuffer); #else tvi.mask = TVIF_HANDLE | TVIF_PARAM; #endif tvi.hItem = hti; SideAssert(TreeView_GetItem(m_hwndTV, &tvi)); ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No tree item data??"); #ifdef ENABLETRACE TraceTag(ttidAdvCfg, "Associating the following binding path with tree " "item %S", szBuffer); #endif DbgDumpBindPath(pncbpThis); if (dwFlags & ASSCF_ON_ENABLE) { BIND_PATH_INFO * pbpi; pbpi = new BIND_PATH_INFO; if (pbpi) { AddRefObj(pbpi->pncbp = pncbpThis); // Note: (danielwe) 25 Nov 1997: Let's see if we need this. Until // then, set to 0 pbpi->dwLength = 0; AddToListIfNotAlreadyAdded(ptid->listbpipOnEnable, pbpi); } } if (dwFlags & ASSCF_ON_DISABLE) { BIND_PATH_INFO * pbpi; pbpi = new BIND_PATH_INFO; if (pbpi) { AddRefObj(pbpi->pncbp = pncbpThis); // Note: (danielwe) 25 Nov 1997: Let's see if we need this. Until // then, set to 0 pbpi->dwLength = 0; AddToListIfNotAlreadyAdded(ptid->listbpipOnDisable, pbpi); } } if (dwFlags & ASSCF_ANCESTORS) { // Now associate the same binding with my parent (this will recurse to // cover all ancestors) HTREEITEM htiParent = TreeView_GetParent(m_hwndTV, hti); if (htiParent) { AssociateBinding(pncbpThis, htiParent, dwFlags); } } } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrHandleSubpath // // Purpose: Handles the case of the given binding path being a subpath // of an already associated binding path // // Arguments: // listsbp [in, ref] Sorted list of binding paths to use in checking // pncbpSub [in] Binding path to compare // // Returns: S_OK if success, Win32 or OLE error code otherwise // // Author: danielwe 26 Nov 1997 // // Notes: // HRESULT CBindingsDlg::HrHandleSubpath(SBP_LIST &listsbp, INetCfgBindingPath *pncbpSub) { HRESULT hr = S_OK; BOOL fProcessed = FALSE; SBP_LIST::iterator iterlist; TraceTag(ttidAdvCfg, "---------------------------------------------------" "-------------------------"); DbgDumpBindPath(pncbpSub); TraceTag(ttidAdvCfg, "...is being compared to the following..."); for (iterlist = listsbp.begin(); iterlist != listsbp.end() && SUCCEEDED(hr); iterlist++) { INetCfgBindingPath * pncbp; INetCfgComponent * pnccOwner; pncbp = (*iterlist).GetPath(); Assert(pncbp); if (S_OK == pncbp->IsSamePathAs(pncbpSub)) { // Don't compare path to itself continue; } hr = pncbp->GetOwner(&pnccOwner); if (SUCCEEDED(hr)) { if (FIsHidden(pnccOwner)) { ReleaseObj(pnccOwner); continue; } else { ReleaseObj(pnccOwner); } DbgDumpBindPath(pncbp); hr = pncbpSub->IsSubPathOf(pncbp); if (S_OK == hr) { CIterTreeView iterTV(m_hwndTV); HTREEITEM hti; TV_ITEM tvi = {0}; #ifdef ENABLETRACE WCHAR szBuf[256] = {0}; #endif while ((hti = iterTV.HtiNext()) && SUCCEEDED(hr)) { TREE_ITEM_DATA * ptid; #ifdef ENABLETRACE tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE; tvi.pszText = szBuf; tvi.cchTextMax = celems(szBuf); #else tvi.mask = TVIF_PARAM | TVIF_HANDLE; #endif tvi.hItem = hti; TreeView_GetItem(m_hwndTV, &tvi); #ifdef ENABLETRACE TraceTag(ttidAdvCfg, "TreeView item: %S.", szBuf); #endif ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No tree item data??"); // Look for pncbp in OnEnable of this item BPIP_LIST::iterator iterBpip; for (iterBpip = ptid->listbpipOnEnable.begin(); iterBpip != ptid->listbpipOnEnable.end(); iterBpip++) { INetCfgBindingPath * pncbpIter; BIND_PATH_INFO * pbpi = *iterBpip; pncbpIter = pbpi->pncbp; AssertSz(pncbpIter, "No binding path?"); #ifdef ENABLETRACE TraceTag(ttidAdvCfg, "OnEnable bindpath is"); DbgDumpBindPath (pncbpIter); #endif if (S_OK == pncbpIter->IsSamePathAs(pncbp)) { hr = HrHandleSubItem(pncbpSub, pncbp, ptid, hti); fProcessed = TRUE; } } } } } } if (SUCCEEDED(hr)) { hr = fProcessed ? S_OK : S_FALSE; } TraceError("CBindingsDlg::HrHandleSubpath", (hr == S_FALSE) ? S_OK : hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HtiIsSubItem // // Purpose: Determines if the given component is already a sub item of // the given tree item // // Arguments: // pncc [in] Component to check // hti [in] HTREEITEM of item to check // // Returns: HTREEITEM of sub item, NULL if it is not a subitem // // Author: danielwe 26 Nov 1997 // // Notes: // HTREEITEM CBindingsDlg::HtiIsSubItem(INetCfgComponent *pncc, HTREEITEM hti) { HTREEITEM htiCur; htiCur = TreeView_GetChild(m_hwndTV, hti); while (htiCur) { TREE_ITEM_DATA * ptid; TV_ITEM tvi = {0}; tvi.hItem = htiCur; tvi.mask = TVIF_HANDLE | TVIF_PARAM; TreeView_GetItem(m_hwndTV, &tvi); ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No item data??"); // Note: (danielwe) 26 Nov 1997: Make sure pointer comparison is // ok. if (pncc == ptid->pncc) { return htiCur; } htiCur = TreeView_GetNextSibling(m_hwndTV, htiCur); } return NULL; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrHandleSubItem // // Purpose: Handles the case of a single sub-item existing in the tree // that matches the given binding path. // // Arguments: // pncbpThis [in] Binding path being evaluated // pncbpMatch [in] Binding path it is a subpath of // ptid [in] Tree item data for tree view item that pncbpMatch // is associated with // htiMatchItem [in] HTREEITEM of above // // Returns: S_OK if success, Win32 or OLE error code otherwise // // Author: danielwe 26 Nov 1997 // // Notes: // HRESULT CBindingsDlg::HrHandleSubItem(INetCfgBindingPath *pncbpThis, INetCfgBindingPath *pncbpMatch, TREE_ITEM_DATA *ptid, HTREEITEM htiMatchItem) { HRESULT hr = S_OK; DWORD dwThisLen; DWORD dwMatchLen; DWORD dLen; INetCfgComponent * pnccMatchItem; pnccMatchItem = ptid->pncc; Assert(pnccMatchItem); dwThisLen = GetDepthSpecialCase(pncbpThis); dwMatchLen = GetDepthSpecialCase(pncbpMatch); dLen = dwMatchLen - dwThisLen; if ((dwMatchLen - dwThisLen) == 1 || (S_OK == (hr = HrComponentIsHidden(pncbpMatch, dLen)))) { INetCfgComponent * pnccThisOwner; INetCfgComponent * pnccMatchOwner; hr = pncbpThis->GetOwner(&pnccThisOwner); if (SUCCEEDED(hr)) { hr = pncbpMatch->GetOwner(&pnccMatchOwner); if (SUCCEEDED(hr)) { if (!FIsHidden(pnccThisOwner) && !FDontExposeLower(pnccMatchOwner)) { hr = HrHandleValidSubItem(pncbpThis, pncbpMatch, pnccThisOwner, htiMatchItem, ptid); } ReleaseObj(pnccMatchOwner); } ReleaseObj(pnccThisOwner); } } AssociateBinding(pncbpThis, htiMatchItem, ASSCF_ON_ENABLE | ASSCF_ANCESTORS); TraceError("CBindingsDlg::HrHandleSubItem", (hr == S_FALSE) ? S_OK : hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrHandleValidSubItem // // Purpose: Handles the case of the given binding path being a sub item // of at least on item in the tree. // // Arguments: // pncbpThis [in] THIS binding path // pncbpMatch [in] MATCH binding path // pnccThisOwner [in] Owner of THIS binding path // htiMatchItem [in] HTREEITEM of match item // ptid [in] TREE item data for match item // // Returns: S_OK if success, Win32 or OLE error code otherwise // // Author: danielwe 1 Dec 1997 // // Notes: // HRESULT CBindingsDlg::HrHandleValidSubItem(INetCfgBindingPath *pncbpThis, INetCfgBindingPath *pncbpMatch, INetCfgComponent *pnccThisOwner, HTREEITEM htiMatchItem, TREE_ITEM_DATA *ptid) { HRESULT hr = S_OK; HTREEITEM htiNew = NULL; if (pnccThisOwner != ptid->pncc) { // Check if it is already present as a subitem htiNew = HtiIsSubItem(pnccThisOwner, htiMatchItem); if (!htiNew) { htiNew = HtiAddTreeViewItem(pnccThisOwner, htiMatchItem); } AssertSz(htiNew, "No new or existing tree item!?!"); AssociateBinding(pncbpMatch, htiNew, ASSCF_ON_ENABLE | ASSCF_ON_DISABLE); AssociateBinding(pncbpThis, htiNew, ASSCF_ON_ENABLE); } TraceError("CBindingsDlg::HrHandleComponent", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrComponentIsHidden // // Purpose: Determines if the Nth component of the given binding path is // hidden. // // Arguments: // pncbp [in] Binding path to check // iComp [in] Index of component to check for hidden characterstic // // Returns: S_OK if success, Win32 or OLE error code otherwise // // Author: danielwe 26 Nov 1997 // // Notes: // HRESULT CBindingsDlg::HrComponentIsHidden(INetCfgBindingPath *pncbp, DWORD iComp) { Assert(pncbp); HRESULT hr = S_OK; CIterNetCfgBindingInterface ncbiIter(pncbp); INetCfgBindingInterface * pncbi; // Convert from component count to interface count iComp--; AssertSz(iComp > 0, "We should never be looking for the first component!"); while (SUCCEEDED(hr) && iComp && S_OK == (hr = ncbiIter.HrNext(&pncbi))) { iComp--; if (!iComp) { INetCfgComponent * pnccLower; hr = pncbi->GetLowerComponent(&pnccLower); if (SUCCEEDED(hr)) { if (!FIsHidden(pnccLower)) { hr = S_FALSE; } ReleaseObj(pnccLower); } } ReleaseObj(pncbi); } TraceError("CBindingsDlg::HrComponentIsHidden", (hr == S_FALSE) ? S_OK : hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HrHandleTopLevel // // Purpose: Handles the case of the given binding path not being associated // with any existing tree view item // // Arguments: // pncbpThis [in] Binding path being evaluated // // Returns: S_OK if success, Win32 or OLE error code otherwise // // Author: danielwe 26 Nov 1997 // // Notes: // HRESULT CBindingsDlg::HrHandleTopLevel(INetCfgBindingPath *pncbpThis) { HRESULT hr = S_OK; INetCfgComponent * pnccOwner; BOOL fFound = FALSE; // Check if the owner of this path is already present in the tree hr = pncbpThis->GetOwner(&pnccOwner); if (SUCCEEDED(hr)) { CIterTreeView iterTV(m_hwndTV); HTREEITEM hti; TV_ITEM tvi = {0}; while ((hti = iterTV.HtiNext()) && SUCCEEDED(hr)) { TREE_ITEM_DATA * ptid; tvi.mask = TVIF_PARAM | TVIF_HANDLE; tvi.hItem = hti; TreeView_GetItem(m_hwndTV, &tvi); ptid = reinterpret_cast(tvi.lParam); AssertSz(ptid, "No tree item data??"); // Note: (danielwe) 25 Nov 1997: Pointer comparison may not // work. Use GUIDs if necessary. if (ptid->pncc == pnccOwner) { // Found match with THIS binding owner and an existing tree // item AssociateBinding(pncbpThis, hti, ASSCF_ON_ENABLE | ASSCF_ON_DISABLE); fFound = TRUE; break; } } if (SUCCEEDED(hr) && !fFound) { // Not found in the tree if (!FIsHidden(pnccOwner)) { DWORD dwLen; dwLen = GetDepthSpecialCase(pncbpThis); if (dwLen > 2) { HTREEITEM hti; hti = HtiAddTreeViewItem(pnccOwner, NULL); if (hti) { AssociateBinding(pncbpThis, hti, ASSCF_ON_ENABLE | ASSCF_ON_DISABLE); } } } } ReleaseObj(pnccOwner); } TraceError("CBindingsDlg::HrHandleTopLevel", (S_FALSE == hr) ? S_OK : hr); return hr; } //+--------------------------------------------------------------------------- // // Function: ChangeTreeItemParam // // Purpose: Helper function to change the lParam of a tree view item // // Arguments: // hwndTV [in] Tree view window // hitem [in] Handle to item to change // lparam [in] New value of lParam // // Returns: Nothing // // Author: danielwe 3 Dec 1997 // // Notes: // VOID ChangeTreeItemParam(HWND hwndTV, HTREEITEM hitem, LPARAM lparam) { TV_ITEM tvi; tvi.hItem = hitem; tvi.mask = TVIF_PARAM; tvi.lParam = lparam; TreeView_SetItem(hwndTV, &tvi); } //+--------------------------------------------------------------------------- // // Member: CBindingsDlg::HtiMoveTreeItemAfter // // Purpose: Moves the given tree view item and all its children after the // given treeview item // // Arguments: // htiParent [in] Parent treeview item // htiDest [in] Item to move after // htiSrc [in] Item to move // // Returns: Newly added treeview item // // Author: danielwe 3 Dec 1997 // // Notes: // HTREEITEM CBindingsDlg::HtiMoveTreeItemAfter(HTREEITEM htiParent, HTREEITEM htiDest, HTREEITEM htiSrc) { HTREEITEM htiNew; HTREEITEM htiChild; HTREEITEM htiNextChild; TV_INSERTSTRUCT tvis; WCHAR szText[256]; TraceTag(ttidAdvCfg, "Moving ..."); DbgDumpTreeViewItem(m_hwndTV, htiSrc); TraceTag(ttidAdvCfg, "... after ..."); DbgDumpTreeViewItem(m_hwndTV, htiDest); // retieve the items data tvis.item.hItem = htiSrc; tvis.item.mask = TVIF_IMAGE | TVIF_PARAM | TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_STATE; tvis.item.stateMask = TVIS_STATEIMAGEMASK; tvis.item.pszText = szText; tvis.item.cchTextMax = celems(szText); TreeView_GetItem(m_hwndTV, &tvis.item); if (NULL == htiDest) { tvis.hInsertAfter = TVI_LAST; } else { if (htiParent == htiDest) { tvis.hInsertAfter = TVI_FIRST; } else { tvis.hInsertAfter = htiDest; } } tvis.hParent = htiParent; // add our new one htiNew = TreeView_InsertItem(m_hwndTV, &tvis); // copy all children htiChild = TreeView_GetChild(m_hwndTV, htiSrc); while (htiChild) { htiNextChild = TreeView_GetNextSibling(m_hwndTV, htiChild); HtiMoveTreeItemAfter(htiNew, NULL, htiChild); htiChild = htiNextChild; } // set old location param to null, so when it is removed, // our lparam is not deleted by our remove routine ChangeTreeItemParam(m_hwndTV, htiSrc, NULL); // remove from old location TreeView_DeleteItem(m_hwndTV, htiSrc); return htiNew; } // // Treeview flat iterator // //+--------------------------------------------------------------------------- // // Member: CIterTreeView::HtiNext // // Purpose: Advances the iterator to the next treeview item // // Arguments: // (none) // // Returns: Next HTREEITEM in tree view // // Author: danielwe 26 Nov 1997 // // Notes: Uses a systematic iteration. First all children of first item // are returned, then all siblings, then next sibling and so on // HTREEITEM CIterTreeView::HtiNext() { HTREEITEM htiRet; HTREEITEM hti; if (m_stackHti.empty()) { return NULL; } htiRet = Front(); hti = TreeView_GetChild(m_hwndTV, htiRet); if (!hti) { PopAndDelete(); hti = TreeView_GetNextSibling(m_hwndTV, htiRet); if (hti) { PushAndAlloc(hti); } else { if (!m_stackHti.empty()) { hti = TreeView_GetNextSibling(m_hwndTV, Front()); PopAndDelete(); if (hti) { PushAndAlloc(hti); } } } } else { PushAndAlloc(hti); } return htiRet; }