/****************************************************************************/ /* */ /* TREECTL.C - */ /* */ /* Windows Directory Tree Window Proc Routines */ /* */ /****************************************************************************/ #define PUBLIC // avoid collision with shell.h #include "winfile.h" #include "treectl.h" #include "lfn.h" #include "winnet.h" #include "wfcopy.h" #define WS_TREESTYLE (WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | WS_HSCROLL | LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | LBS_DISABLENOSCROLL) WORD cNodes; // BOOL bCancelTree; .......... moved to winfile.c VOID RectTreeItem(HWND hwndLB, register INT iItem, BOOL bFocusOn); VOID GetTreePathIndirect(PDNODE pNode, register LPSTR szDest); VOID GetTreePath(PDNODE pNode, register LPSTR szDest); VOID ScanDirLevel(PDNODE pParentNode, LPSTR szPath, DWORD view); INT InsertDirectory(HWND hwndTreeCtl, PDNODE pParentNode, WORD iParentNode, LPSTR szName, PDNODE *ppNode); BOOL ReadDirLevel(HWND hwndTreeCtl, PDNODE pParentNode, LPSTR szPath, WORD nLevel, INT iParentNode, DWORD dwAttribs, BOOL bFullyExpand, LPSTR szAutoExpand); VOID FillTreeListbox(HWND hwndTreeCtl, LPSTR szDefaultDir, BOOL bFullyExpand, BOOL bDontSteal); WORD FindItemFromPath(HWND hwndLB, LPSTR lpszPath, BOOL bReturnParent, PDNODE *ppNode); VOID APIENTRY CheckEscapes(LPSTR); /*--------------------------------------------------------------------------*/ /* */ /* GetTreePathIndirect() - */ /* */ /* build a complete path for a given node in the tree by recursivly */ /* traversing the tree structure */ /* */ /*--------------------------------------------------------------------------*/ VOID GetTreePathIndirect( PDNODE pNode, register LPSTR szDest ) { register PDNODE pParent; pParent = pNode->pParent; if (pParent) GetTreePathIndirect(pParent, szDest); lstrcat(szDest, pNode->szName); if (pParent) lstrcat(szDest, "\\"); } /*--------------------------------------------------------------------------*/ /* */ /* GetTreePath() - */ /* */ /* build a complete path for a given node in the tree */ /* */ /*--------------------------------------------------------------------------*/ VOID GetTreePath( PDNODE pNode, register LPSTR szDest ) { szDest[0] = 0L; GetTreePathIndirect(pNode, szDest); /* Remove the last backslash (unless it is the root directory). */ if (pNode->pParent) szDest[lstrlen(szDest)-1] = 0L; } /*--------------------------------------------------------------------------*/ /* */ /* ScanDirLevel() - */ /* */ /* look down to see if this node has any sub directories */ /* /*--------------------------------------------------------------------------*/ // szPath is ANSI VOID ScanDirLevel( PDNODE pParentNode, LPSTR szPath, DWORD view ) { BOOL bFound; LFNDTA lfndta; ENTER("ScanDirLevel"); /* Add '*.*' to the current path. */ lstrcpy(szMessage, szPath); AddBackslash(szMessage); lstrcat(szMessage, szStarDotStar); /* Search for the first subdirectory on this level. */ // FixAnsiPathForDos(szMessage); bFound = WFFindFirst(&lfndta, szMessage, ATTR_DIR | view); while (bFound) { /* Is this not a '.' or '..' directory? */ if ((lfndta.fd.cFileName[0] != '.') && (lfndta.fd.dwFileAttributes & ATTR_DIR)) { pParentNode->wFlags |= TF_HASCHILDREN; bFound = FALSE; } else /* Search for the next subdirectory. */ bFound = WFFindNext(&lfndta); } WFFindClose(&lfndta); LEAVE("ScanDirLevel"); } // wizzy cool recursive path compare routine // // p1 and p2 must be on the same level (p1->nLevels == p2->nLevels) INT ComparePath( PDNODE p1, PDNODE p2 ) { INT ret; if (p1 == p2) { return 0; // equal (base case) } else { ret = ComparePath(p1->pParent, p2->pParent); if (ret == 0) { // parents are equal ret = lstrcmp(p1->szName, p2->szName); #if 0 { CHAR buf[200]; wsprintf(buf, "Compare(%s, %s) -> %d\r\n", (LPSTR)p1->szName, (LPSTR)p2->szName, ret); OutputDebugString(buf); } #endif } // not equal parents, propagate up the call tree return ret; } } INT CompareNodes( PDNODE p1, PDNODE p2 ) { PDNODE p1save, p2save; INT ret; ENTER("CompareNodes"); ASSERT(p1 && p2); PRINT(BF_PARMTRACE, "IN: p1=%s", p1->szName); PRINT(BF_PARMTRACE, "IN: p2=%s", p2->szName); p1save = p1; p2save = p2; // get p1 and p2 to the same level while (p1->nLevels > p2->nLevels) p1 = p1->pParent; while (p2->nLevels > p1->nLevels) p2 = p2->pParent; // compare those paths ret = ComparePath(p1, p2); if (ret == 0) ret = (INT)p1save->nLevels - (INT)p2save->nLevels; LEAVE("CompareNodes"); return ret; } // // InsertDirectory() // // wizzy quick n log n binary insert code! // // creates and inserts a new node in the tree, this also sets // the TF_LASTLEVELENTRY bits to mark a branch as being the last // for a given level as well as marking parents with // TF_HASCHILDREN | TF_EXPANDED to indicate they have been expanded // and have children. // // Returns iNode and fills ppNode with pNode. // INT InsertDirectory( HWND hwndTreeCtl, PDNODE pParentNode, WORD iParentNode, LPSTR szName, PDNODE *ppNode ) { WORD len, x; PDNODE pNode, pMid; HWND hwndLB; INT iMin; INT iMax; INT iMid; ENTER("InsertDirectory"); PRINT(BF_PARMTRACE, "IN: pParentNode=%lx", pParentNode); PRINT(BF_PARMTRACE, "IN: iParentNode=%d", iParentNode); PRINT(BF_PARMTRACE, "IN: szName=%s", szName); len = (WORD)lstrlen(szName); pNode = (PDNODE)LocalAlloc(LPTR, sizeof(DNODE)+len); if (!pNode) { if (ppNode) { *ppNode = NULL; } return 0; } pNode->pParent = pParentNode; pNode->nLevels = pParentNode ? (pParentNode->nLevels + (BYTE)1) : (BYTE)0; pNode->wFlags = (BYTE)NULL; pNode->iNetType = -1; if (IsLFN(szName)) { pNode->wFlags |= TF_LFN; } lstrcpy(pNode->szName, szName); if (pParentNode) pParentNode->wFlags |= TF_HASCHILDREN | TF_EXPANDED; // mark the parent hwndLB = GetDlgItem(hwndTreeCtl, IDCW_TREELISTBOX); // computing the real text extent is too slow so we aproximate // with the following (note, we don't keep this on a per tree // basis so it is kinda bogus anyway) x = (WORD)(len + 2 * pNode->nLevels) * (WORD)dxText; if (x > xTreeMax) { xTreeMax = x; } iMax = (INT)SendMessage(hwndLB, LB_GETCOUNT, 0, 0L); if (iMax > 0) { // do a binary insert iMin = iParentNode + 1; iMax--; // last index do { iMid = (iMax + iMin) / 2; SendMessage(hwndLB, LB_GETTEXT, iMid, (LPARAM)&pMid); if (CompareNodes(pNode, pMid) > 0) iMin = iMid + 1; else iMax = iMid - 1; } while (iMax > iMin); SendMessage(hwndLB, LB_GETTEXT, iMax, (LPARAM)&pMid); if (CompareNodes(pNode, pMid) > 0) iMax++; // insert after this one } // now reset the TF_LASTLEVEL flags as appropriate // look for the first guy on our level above us and turn off // his TF_LASTLEVELENTRY flag so he draws a line down to us iMid = iMax - 1; while (iMid >= 0) { SendMessage(hwndLB, LB_GETTEXT, iMid--, (LPARAM)&pMid); if (pMid->nLevels == pNode->nLevels) { pMid->wFlags &= ~TF_LASTLEVELENTRY; break; } else if (pMid->nLevels < pNode->nLevels) break; } // if no one below me or the level of the guy below is less, then // this is the last entry for this level if (((INT)SendMessage(hwndLB, LB_GETTEXT, iMax, (LPARAM)&pMid) == LB_ERR) || (pMid->nLevels < pNode->nLevels)) pNode->wFlags |= TF_LASTLEVELENTRY; SendMessage(hwndLB, LB_INSERTSTRING, iMax, (LPARAM)pNode); if (ppNode) { *ppNode = pNode; } LEAVE("InsertDirectory"); return iMax; } // this yeilds control to other apps and allows us to process // messages and user input. to avoid overrunning the stack // from multiple tree reads being initiated at the same time // we check how much space we have on the stack before we yield extern WORD end; // C compiler end of static data symbol extern WORD pStackTop; WORD StackAvail(VOID) { #ifdef LATER _asm mov ax,sp _asm sub ax,pStackTop if (0) return 0; // get rid of warning, optimized out #endif return 0x7fff; // Hack. shouldn't really matter. StackAvail in NT is a NOP } VOID APIENTRY wfYield() { MSG msg; #ifdef LATER WORD free_stack; free_stack = StackAvail(); #endif #if 0 { CHAR buf[30]; wsprintf(buf, "free stack: %d\r\n", free_stack); OutputDebugString(buf); } #endif #if LATER if (free_stack < 1024*4) { CHAR buf[40]; wsprintf(buf, "not enough stack %d\r\n", free_stack); OutputDebugString(buf); return; } #endif while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (!TranslateMDISysAccel(hwndMDIClient, &msg) && (!hwndFrame || !TranslateAccelerator(hwndFrame, hAccel, &msg))) { TranslateMessage(&msg); DispatchMessage(&msg); } } } // INT iReadLevel = 0; ...............Moved to winfile.c //-------------------------------------------------------------------------- // // ReadDirLevel() - // // this does a depth first search of the dir tree. note, a bredth // first implementation did not perform any better. // // szPath a directory path that MUST EXIST long enough // to hold the full path to the largest directory // that will be found (MAXPATHLEN). this is an // ANSI string. (ie C:\ and C:\FOO are valid) // nLevel level in the tree // iParentNode index of parent node // dwAttribs attributes to filter with // bFullyExpand TRUE means expand this node fully // szAutoExpand list of directories to autoexpand ANSI // (eg. for "C:\foo\bar\stuff" // "foo" NULL "bar" NULL "stuff" NULL NULL) // // returns: // TRUE tree read sucessful // FALSE user abort or bogus tree read //-------------------------------------------------------------------------- BOOL ReadDirLevel( HWND hwndTreeCtl, PDNODE pParentNode, LPSTR szPath, WORD nLevel, INT iParentNode, DWORD dwAttribs, BOOL bFullyExpand, LPSTR szAutoExpand ) { LPSTR szEndPath; LFNDTA lfndta; INT iNode; BOOL bFound; PDNODE pNode; BOOL bAutoExpand; BOOL bResult = TRUE; WORD view; HWND hwndParent; HWND hwndDir; HANDLE hDTA; LPMYDTA lpmydta; INT count; RECT rc; ENTER("ReadDirLevel"); PRINT(BF_PARMTRACE, "IN: szPath=%s", szPath); PRINT(BF_PARMTRACE, "IN: nLevel=%d", (LPSTR)nLevel); PRINT(BF_PARMTRACE, "IN: bFullyExpand=%d", IntToPtr(bFullyExpand)); PRINT(BF_PARMTRACE, "IN: szAutoExpand=%s", szAutoExpand); if (StackAvail() < 1024*2) return(TRUE); hwndParent = GetParent(hwndTreeCtl); view = (WORD)GetWindowLong(hwndParent, GWL_VIEW); // we optimize the tree read if we are not adding pluses and // we find a directory window that already has read all the // directories for the path we are about to search. in this // case we look through the DTA structure in the dir window // to get all the directories (instead of calling FindFirst/FindNext). // in this case we have to disable yielding since the user could // potentialy close the dir window that we are reading, or change // directory. hDTA = NULL; if (!(view & VIEW_PLUSES)) { if ((hwndDir = HasDirWindow(hwndParent)) && (GetWindowLong(hwndParent, GWL_ATTRIBS) & ATTR_DIR)) { SendMessage(hwndDir, FS_GETDIRECTORY, sizeof(szMessage), (LPARAM)szMessage); StripBackslash(szMessage); if (!lstrcmpi(szMessage, szPath)) { SendMessage(hwndDir, FS_GETFILESPEC, sizeof(szMessage), (LPARAM)szMessage); if (!lstrcmp(szMessage, szStarDotStar)) { hDTA = (HANDLE)GetWindowLongPtr(hwndDir, GWLP_HDTA); lpmydta = (LPMYDTA)LocalLock(hDTA); count = (INT)lpmydta->my_nFileSizeLow; // holds number of entries, NOT size. } } } } SetWindowLong(hwndTreeCtl, GWL_READLEVEL, GetWindowLong(hwndTreeCtl, GWL_READLEVEL) + 1); iReadLevel++; // global for menu code szEndPath = (LPSTR)(szPath + lstrlen(szPath)); /* Add '\*.*' to the current path. */ AddBackslash(szPath); lstrcat(szPath, szStarDotStar); if (hDTA) { // steal the entry from the dir window lpmydta = GETDTAPTR(lpmydta, lpmydta->wSize); // search for any "real" directories while (count > 0 && (!(lpmydta->my_dwAttrs & ATTR_DIR) || (lpmydta->my_dwAttrs & ATTR_PARENT))) { lpmydta = GETDTAPTR(lpmydta, lpmydta->wSize); count--; } if (count > 0) { bFound = TRUE; memcpy(&(lfndta.fd.dwFileAttributes), &(lpmydta->my_dwAttrs), IMPORTANT_DTA_SIZE); lstrcpy(lfndta.fd.cFileName, lpmydta->my_cFileName); } else bFound = FALSE; } else { // get first file from DOS lstrcpy(szMessage, szPath); FixAnsiPathForDos(szMessage); bFound = WFFindFirst(&lfndta, szMessage, dwAttribs); } // for net drive case where we can't actually see what is in these // direcotries we will build the tree automatically if (!bFound && *szAutoExpand) { LPSTR p; p = szAutoExpand; szAutoExpand += lstrlen(szAutoExpand) + 1; iNode = InsertDirectory(hwndTreeCtl, pParentNode, (WORD)iParentNode, p, &pNode); pParentNode->wFlags |= TF_DISABLED; /* Construct the path to this new subdirectory. */ *szEndPath = 0; // remove old stuff AddBackslash(szPath); lstrcat(szPath, p); if (pNode) ReadDirLevel(hwndTreeCtl, pNode, szPath, (WORD)(nLevel+1), iNode, dwAttribs, bFullyExpand, szAutoExpand); } while (bFound) { wfYield(); if (bCancelTree) { bResult = FALSE; if (bCancelTree == 2) PostMessage(hwndFrame, WM_COMMAND, IDM_EXIT, 0L); goto DONE; } /* Is this not a '.' or '..' directory? */ if ((lfndta.fd.cFileName[0] != '.') && (lfndta.fd.dwFileAttributes & ATTR_DIR)) { if (!hDTA) OemToAnsi(lfndta.fd.cFileName, lfndta.fd.cFileName); // we will try to auto expand this node if it matches if (*szAutoExpand && !lstrcmpi(szAutoExpand, lfndta.fd.cFileName)) { bAutoExpand = TRUE; szAutoExpand += lstrlen(szAutoExpand) + 1; } else { bAutoExpand = FALSE; } iNode = InsertDirectory(hwndTreeCtl, pParentNode, (WORD)iParentNode, lfndta.fd.cFileName, &pNode); if (bStatusBar && ((cNodes % 7) == 0)) { // make sure we are the active window before we // update the status bar if (hwndParent == (HWND)SendMessage(hwndMDIClient, WM_MDIGETACTIVE, 0, 0L)) { wsprintf(szStatusTree, szDirsRead, cNodes); // stomp over the status bar! GetClientRect(hwndFrame, &rc); rc.top = rc.bottom - dyStatus; InvalidateRect(hwndFrame, &rc, FALSE); // force the paint because we don't yield UpdateWindow(hwndFrame); } } cNodes++; /* Construct the path to this new subdirectory. */ *szEndPath = 0L; AddBackslash(szPath); lstrcat(szPath, lfndta.fd.cFileName); // cFileName is ANSI now // either recurse or add pluses if (pNode) { if (bFullyExpand || bAutoExpand) { if (!ReadDirLevel(hwndTreeCtl, pNode, szPath, (WORD)(nLevel+1), iNode, dwAttribs, bFullyExpand, szAutoExpand)) { bResult = FALSE; goto DONE; } } else if (view & VIEW_PLUSES) { ScanDirLevel(pNode, szPath, dwAttribs & ATTR_HS); } } } if (hDTA) { // short cut, steal data from dir window count--; lpmydta = GETDTAPTR(lpmydta, lpmydta->wSize); while (count > 0 && (!(lpmydta->my_dwAttrs & ATTR_DIR) || (lpmydta->my_dwAttrs & ATTR_PARENT))) { lpmydta = GETDTAPTR(lpmydta, lpmydta->wSize); count--; } if (count > 0) { bFound = TRUE; memcpy(&(lfndta.fd.dwFileAttributes), &(lpmydta->my_dwAttrs), IMPORTANT_DTA_SIZE); lstrcpy(lfndta.fd.cFileName, lpmydta->my_cFileName); } else bFound = FALSE; } else { bFound = WFFindNext(&lfndta); // get it from dos } } *szEndPath = 0L; // clean off any stuff we left on the end of the path DONE: if (!hDTA) { WFFindClose(&lfndta); } else { LocalUnlock(hDTA); } SetWindowLong(hwndTreeCtl, GWL_READLEVEL, GetWindowLong(hwndTreeCtl, GWL_READLEVEL) - 1); iReadLevel--; LEAVE("ReadDirLevel"); return bResult; } // this is used by StealTreeData() to avoid alias problems where // the nodes in one tree point to parents in the other tree. // basically, as we are duplicating the tree data structure we // have to find the parent node that coorisponds with the parent // of the tree we are copying from in the tree that we are building. // since the tree is build in order we run up the listbox, looking // for the parent (matched by it's level being one smaller than // the level of the node being inserted). when we find that we // return the pointer to that node. PDNODE FindParent( INT iLevelParent, INT iStartInd, HWND hwndLB ) { PDNODE pNode; while (TRUE) { if (SendMessage(hwndLB, LB_GETTEXT, iStartInd, (LPARAM)&pNode) == LB_ERR) return NULL; if (pNode->nLevels == (BYTE)iLevelParent) { SendMessage(hwndLB, LB_GETTEXT, iStartInd, (LPARAM)&pNode); return pNode; } iStartInd--; } } BOOL StealTreeData( HWND hwndTC, HWND hwndLB, LPSTR szDir ) { HWND hwndSrc, hwndT; CHAR szSrc[MAXPATHLEN]; WORD wView; DWORD dwAttribs; ENTER("StealTreeData"); // we need to match on these attributes as well as the name wView = (WORD)(GetWindowLong(GetParent(hwndTC), GWL_VIEW) & VIEW_PLUSES); dwAttribs = (DWORD)GetWindowLong(GetParent(hwndTC), GWL_ATTRIBS) & ATTR_HS; // get the dir of this new window for compare below for (hwndSrc = GetWindow(hwndMDIClient, GW_CHILD); hwndSrc; hwndSrc = GetWindow(hwndSrc, GW_HWNDNEXT)) { // avoid finding ourselves, make sure has a tree // and make sure the tree attributes match if ((hwndT = HasTreeWindow(hwndSrc)) && (hwndT != hwndTC) && !GetWindowLong(hwndT, GWL_READLEVEL) && (wView == (WORD)(GetWindowLong(hwndSrc, GWL_VIEW) & VIEW_PLUSES)) && (dwAttribs == (DWORD)(GetWindowLong(hwndSrc, GWL_ATTRIBS) & ATTR_HS))) { SendMessage(hwndSrc, FS_GETDIRECTORY, sizeof(szSrc), (LPARAM)szSrc); StripBackslash(szSrc); if (!lstrcmpi(szDir, szSrc)) // are they the same? break; // yes, do stuff below } } if (hwndSrc) { HWND hwndLBSrc; PDNODE pNode, pNewNode, pLastParent; INT i; hwndLBSrc = GetDlgItem(hwndT, IDCW_TREELISTBOX); // don't seal from a tree that hasn't been read yet! if ((INT)SendMessage(hwndLBSrc, LB_GETCOUNT, 0, 0L) == 0) { LEAVE("StealTreeData"); return FALSE; } pLastParent = NULL; for (i = 0; SendMessage(hwndLBSrc, LB_GETTEXT, i, (LPARAM)&pNode) != LB_ERR; i++) { if (pNewNode = (PDNODE)LocalAlloc(LPTR, sizeof(DNODE)+lstrlen(pNode->szName))) { *pNewNode = *pNode; // dup the node lstrcpy(pNewNode->szName, pNode->szName); // and the name // accelerate the case where we are on the same level to avoid // slow linear search! if (pLastParent && pLastParent->nLevels == (pNode->nLevels - (BYTE)1)) { pNewNode->pParent = pLastParent; } else { pNewNode->pParent = pLastParent = FindParent(pNode->nLevels-1, i-1, hwndLB); } PRINT(BF_PARMTRACE, "(stolen)Inserting...0x%lx", pNewNode); PRINT(BF_PARMTRACE, " at %d", IntToPtr(i)); SendMessage(hwndLB, LB_INSERTSTRING, i, (LPARAM)pNewNode); ASSERT((PDNODE)SendMessage(hwndLB, LB_GETITEMDATA, i, 0L) == pNewNode); } } LEAVE("StealTreeData"); return TRUE; // successful steal } LEAVE("StealTreeData"); return FALSE; } VOID FreeAllTreeData( HWND hwndLB ) { INT nIndex; PDNODE pNode; ENTER("FreeAllTreeData"); // Free up the old tree (if any) nIndex = (INT)SendMessage(hwndLB, LB_GETCOUNT, 0, 0L); while (--nIndex >= 0) { SendMessage(hwndLB, LB_GETTEXT, nIndex, (LPARAM)&pNode); LocalFree((HANDLE)pNode); } SendMessage(hwndLB, LB_RESETCONTENT, 0, 0L); LEAVE("FreeAllTreeData"); } /*--------------------------------------------------------------------------*/ /* */ /* FillTreeListbox() - */ /* */ /*--------------------------------------------------------------------------*/ // szDefaultDir is ANSI VOID FillTreeListbox( HWND hwndTC, LPSTR szDefaultDir, BOOL bFullyExpand, BOOL bDontSteal ) { PDNODE pNode; INT iNode; DWORD dwAttribs; CHAR szTemp[MAXPATHLEN]; CHAR szExpand[MAXPATHLEN]; LPSTR p; HWND hwndLB; ENTER("FillTreeListbox"); hwndLB = GetDlgItem(hwndTC, IDCW_TREELISTBOX); FreeAllTreeData(hwndLB); SendMessage(hwndLB, WM_SETREDRAW, FALSE, 0L); bDontSteal = TRUE; // Force recalc for now if (bDontSteal || bFullyExpand || !StealTreeData(hwndTC, hwndLB, szDefaultDir)) { wsprintf(szTemp, "\\", DRIVEID(szDefaultDir) + 'A'); iNode = InsertDirectory(hwndTC, NULL, 0, szTemp, &pNode); if (pNode) { dwAttribs = ATTR_DIR | (GetWindowLong(GetParent(hwndTC), GWL_ATTRIBS) & ATTR_HS); cNodes = 0; bCancelTree = FALSE; if (szDefaultDir) { lstrcpy(szExpand, szDefaultDir+3); // skip "X:\" p = szExpand; while (*p) { // null out all slashes while (*p && *p != '\\') p = AnsiNext(p); if (*p) *p++ = 0L; } p++; *p = 0L; // double null terminated } else *szExpand = 0; if (!ReadDirLevel(hwndTC, pNode, szTemp, 1, 0, dwAttribs, bFullyExpand, szExpand)) { lFreeSpace = -2L; } } } SendMessage(hwndLB, LB_SETHORIZONTALEXTENT, xTreeMax, 0L); if (szDefaultDir) { FindItemFromPath(hwndLB, szDefaultDir, FALSE, &pNode); } SendMessage(hwndLB, LB_SELECTSTRING, -1, (LPARAM)pNode); UpdateStatus(GetParent(hwndTC)); // Redraw the Status Bar SendMessage(hwndLB, WM_SETREDRAW, TRUE, 0L); InvalidateRect(hwndLB, NULL, TRUE); UpdateWindow(hwndLB); // make this look a bit better LEAVE("FillTreeListbox"); } // // FindItemFromPath() // // find the PDNODE and LBIndex for a given path // // in: // hwndLB listbox of tree // lpszPath path to search for (ANSI) // bReturnParent TRUE if you want the parent, not the node // // // returns: // listbox index (0xFFFF if not found) // *ppNode is filled with pNode of node, or pNode of parent if bReturnParent is TRUE // WORD FindItemFromPath( HWND hwndLB, LPSTR lpszPath, BOOL bReturnParent, PDNODE *ppNode ) { register WORD i; register LPSTR p; PDNODE pNode; PDNODE pPreviousNode; CHAR szElement[1+MAXFILENAMELEN+1]; ENTER("FindItemFromPath"); if (lstrlen(lpszPath) < 3) { LEAVE("FindItemFromPath"); return -1; } if (IsDBCSLeadByte( lpszPath[0] ) || lpszPath[1] != ':') { LEAVE("FindItemFromPath"); return -1; } i = 0; pPreviousNode = NULL; while (*lpszPath) { /* NULL out szElement[1] so the backslash hack isn't repeated with * a first level directory of length 1. */ szElement[1] = 0L; /* Copy the next section of the path into 'szElement' */ p = szElement; while (*lpszPath && *lpszPath != '\\') { *p++ = *lpszPath; if (IsDBCSLeadByte( *lpszPath )) *p++ = lpszPath[1]; // copy 2nd byte of DBCS char. lpszPath = AnsiNext( lpszPath ); } /* Add a backslash for the Root directory. */ if ( !IsDBCSLeadByte( szElement[0] ) && szElement[1] == ':' ) *p++ = '\\'; /* NULL terminate 'szElement' */ *p = 0L; /* Skip over the path's next Backslash. */ if (*lpszPath) lpszPath = AnsiNext(lpszPath); else if (bReturnParent) { /* We're at the end of a path which includes a filename. Return * the previously found parent. */ if (ppNode) { *ppNode = pPreviousNode; } LEAVE("FindItemFromPath"); return i; } while (TRUE) { /* Out of LB items? Not found. */ if (SendMessage(hwndLB, LB_GETTEXT, i, (LPARAM)&pNode) == LB_ERR) return -1; if (pNode->pParent == pPreviousNode) { if (!lstrcmpi(szElement, pNode->szName)) { /* We've found the element... */ pPreviousNode = pNode; break; } } i++; } } if (ppNode) { *ppNode = pPreviousNode; } LEAVE("FindItemFromPath"); return i; } /*--------------------------------------------------------------------------*/ /* */ /* RectTreeItem() - */ /* */ /*--------------------------------------------------------------------------*/ VOID RectTreeItem( HWND hwndLB, INT iItem, BOOL bFocusOn ) { INT dx; INT len; HDC hdc; RECT rc; RECT rcClip; BOOL bSel; WORD wColor; PDNODE pNode; HBRUSH hBrush; HFONT hOld; CHAR szPath[MAXPATHLEN]; ENTER("RectTreeItem"); if (iItem == -1) { LEAVE("RectTreeItem"); return; } /* Are we over ourselves? (i.e. a selected item in the source listbox) */ bSel = (BOOL)SendMessage(hwndLB, LB_GETSEL, iItem, 0L); if (bSel && (hwndDragging == hwndLB)) { LEAVE("RectTreeItem"); return; } SendMessage(hwndLB, LB_GETTEXT, iItem, (LPARAM)&pNode); SendMessage(hwndLB, LB_GETITEMRECT, iItem, (LPARAM)&rc); hdc = GetDC(hwndLB); len = lstrlen(pNode->szName); lstrcpy(szPath, pNode->szName); if ((wTextAttribs & TA_LOWERCASE) && !(pNode->wFlags & TF_LFN)) AnsiLower(szPath); hOld = SelectObject(hdc, hFont); MGetTextExtent(hdc, szPath, len, &dx, NULL); dx += dyBorder; if (hOld) SelectObject(hdc, hOld); rc.left = pNode->nLevels * dxText * 2; rc.right = rc.left + dxFolder + dx + 4 * dyBorderx2; GetClientRect(hwndLB, &rcClip); IntersectRect(&rc, &rc, &rcClip); if (bFocusOn) { if (bSel) { wColor = COLOR_WINDOW; InflateRect(&rc, -dyBorder, -dyBorder); } else wColor = COLOR_WINDOWFRAME; if (hBrush = CreateSolidBrush(GetSysColor(wColor))) { FrameRect(hdc, &rc, hBrush); DeleteObject(hBrush); } } else { InvalidateRect(hwndLB, &rc, TRUE); UpdateWindow(hwndLB); } ReleaseDC(hwndLB, hdc); LEAVE("RectTreeItem"); } // return the drive of the first window to respond to the FS_GETDRIVE // message. this usually starts from the source or dest of a drop // and travels up until we find a drive or hit the MDI client INT APIENTRY GetDrive( HWND hwnd, POINT pt ) { CHAR chDrive; chDrive = 0L; while (hwnd && (hwnd != hwndMDIClient)) { chDrive = (CHAR)SendMessage(hwnd, FS_GETDRIVE, 0, MAKELONG((WORD)pt.x, (WORD)pt.y)); if (chDrive) return chDrive; hwnd = GetParent(hwnd); // try the next higher up } return 0; } BOOL IsNetPath( PDNODE pNode ) { CHAR szPath[MAXPATHLEN]; INT i; if (pNode->iNetType == -1) { GetTreePath(pNode, szPath); if (WNetGetDirectoryType((LPSTR)szPath, (LPDWORD)&i, TRUE) == WN_SUCCESS) pNode->iNetType = i; else pNode->iNetType = 0; } return pNode->iNetType; } VOID TCWP_DrawItem( LPDRAWITEMSTRUCT lpLBItem, HWND hwndLB, HWND hWnd ) { INT x, y, dx, dy; INT nLevel; HDC hdc; WORD len; RECT rc; BOOL bHasFocus, bDrawSelected; PDNODE pNode, pNTemp; DWORD rgbText; DWORD rgbBackground; HBRUSH hBrush, hOld; INT iBitmap; WORD view; CHAR szPath[MAXPATHLEN]; ENTER("TCWP_DrawItem"); if (lpLBItem->itemID == (DWORD)-1) { return; } hdc = lpLBItem->hDC; pNode = (PDNODE)lpLBItem->itemData; lstrcpy(szPath, pNode->szName); if ((wTextAttribs & TA_LOWERCASE) && !(pNode->wFlags & TF_LFN)) AnsiLower(szPath); len = (WORD)lstrlen(szPath); MGetTextExtent(hdc, szPath, len, &dx, NULL); dx += dyBorder; rc = lpLBItem->rcItem; rc.left = pNode->nLevels * dxText * 2; rc.right = rc.left + dxFolder + dx + 4 * dyBorderx2; if (lpLBItem->itemAction & (ODA_DRAWENTIRE | ODA_SELECT)) { // draw the branches of the tree first nLevel = pNode->nLevels; x = (nLevel * dxText * 2) - dxText + dyBorderx2; dy = lpLBItem->rcItem.bottom - lpLBItem->rcItem.top; y = lpLBItem->rcItem.top + (dy/2); if (hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOWTEXT))) { hOld = SelectObject(hdc, hBrush); if (pNode->pParent) { /* Draw the horizontal line over to the (possible) folder. */ PatBlt(hdc, x, y, dyText, dyBorder, PATCOPY); /* Draw the top part of the vertical line. */ PatBlt(hdc, x, lpLBItem->rcItem.top, dyBorder, dy/2, PATCOPY); /* If not the end of a node, draw the bottom part... */ if (!(pNode->wFlags & TF_LASTLEVELENTRY)) PatBlt(hdc, x, y+dyBorder, dyBorder, dy/2, PATCOPY); /* Draw the verticals on the left connecting other nodes. */ pNTemp = pNode->pParent; while (pNTemp) { nLevel--; if (!(pNTemp->wFlags & TF_LASTLEVELENTRY)) PatBlt(hdc, (nLevel * dxText * 2) - dxText + dyBorderx2, lpLBItem->rcItem.top, dyBorder,dy, PATCOPY); pNTemp = pNTemp->pParent; } } if (hOld) SelectObject(hdc, hOld); DeleteObject(hBrush); } bDrawSelected = (lpLBItem->itemState & ODS_SELECTED); bHasFocus = (GetFocus() == lpLBItem->hwndItem); // draw text with the proper background or rect if (bHasFocus && bDrawSelected) { rgbText = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); rgbBackground = SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT)); } ExtTextOut(hdc, x + dxText + dxFolder + 2 * dyBorderx2, y-(dyText/2), ETO_OPAQUE, &rc, szPath, len, NULL); // draw the bitmaps as needed // HACK: Don't draw the bitmap when moving if (fShowSourceBitmaps || (hwndDragging != hwndLB) || !bDrawSelected) { // Blt the proper folder bitmap view = (WORD)GetWindowLong(GetParent(hWnd), GWL_VIEW); if (bNetAdmin && IsNetPath(pNode)) { // we need this bitmap from lisa if (bDrawSelected) iBitmap = BM_IND_OPENDFS; else iBitmap = BM_IND_CLOSEDFS; } else if (!(view & VIEW_PLUSES) || !(pNode->wFlags & TF_HASCHILDREN)) { if (bDrawSelected) iBitmap = BM_IND_OPEN; else iBitmap = BM_IND_CLOSE; } else { if (pNode->wFlags & TF_EXPANDED) { if (bDrawSelected) iBitmap = BM_IND_OPENMINUS; else iBitmap = BM_IND_CLOSEMINUS; } else { if (bDrawSelected) iBitmap = BM_IND_OPENPLUS; else iBitmap = BM_IND_CLOSEPLUS; } } BitBlt(hdc, x + dxText + dyBorder, y-(dyFolder/2), dxFolder, dyFolder, hdcMem, iBitmap * dxFolder, (bHasFocus && bDrawSelected) ? dyFolder : 0, SRCCOPY); } // restore text stuff and draw rect as required if (bDrawSelected) { if (bHasFocus) { SetTextColor(hdc, rgbText); SetBkColor(hdc, rgbBackground); } else { HBRUSH hbr; if (hbr = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT))) { FrameRect(hdc, &rc, hbr); DeleteObject(hbr); } } } } if (lpLBItem->itemAction == ODA_FOCUS) DrawFocusRect(hdc, &rc); } /* A helper for both ExpandLevel and TreeCtlWndProc.TC_COLLAPSELEVEL. * Code moved from TreeCtlWndProc to be shared. EDH 13 Oct 91 */ VOID CollapseLevel( HWND hwndLB, PDNODE pNode, INT nIndex ) { DWORD_PTR dwTemp; PDNODE pParentNode = pNode; INT nIndexT = nIndex; /* Disable redrawing early. */ SendMessage(hwndLB, WM_SETREDRAW, FALSE, 0L); nIndexT++; /* Remove all subdirectories. */ while (TRUE) { /* Make sure we don't run off the end of the listbox. */ if (SendMessage(hwndLB, LB_GETTEXT, nIndexT, (LPARAM)&dwTemp) == LB_ERR) break; pNode = (PDNODE)dwTemp; if (pNode->nLevels <= pParentNode->nLevels) break; LocalFree((HANDLE)pNode); SendMessage(hwndLB, LB_DELETESTRING, nIndexT, 0L); } pParentNode->wFlags &= ~TF_EXPANDED; SendMessage(hwndLB, WM_SETREDRAW, TRUE, 0L); InvalidateRect(hwndLB, NULL, TRUE); } VOID ExpandLevel( HWND hWnd, WORD wParam, INT nIndex, PSTR szPath ) { HWND hwndLB; DWORD_PTR dwTemp; PDNODE pNode; INT iNumExpanded; INT iBottomIndex; INT iTopIndex; INT iNewTopIndex; INT iExpandInView; INT iCurrentIndex; RECT rc; if (GetWindowLong(hWnd, GWL_READLEVEL)) return; hwndLB = GetDlgItem(hWnd, IDCW_TREELISTBOX); if (nIndex == -1) if ((nIndex = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L)) == LB_ERR) return; SendMessage(hwndLB, LB_GETTEXT, nIndex, (LPARAM)&dwTemp); pNode = (PDNODE)dwTemp; // collapse the current contents so we avoid doubling existing "plus" dirs if (pNode->wFlags & TF_EXPANDED) { if (wParam) CollapseLevel(hwndLB, pNode, nIndex); else return; } GetTreePath(pNode, szPath); StripBackslash(szPath); // remove the slash cNodes = 0; bCancelTree = FALSE; SendMessage(hwndLB, WM_SETREDRAW, FALSE, 0L); // Disable redrawing. iCurrentIndex = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); iNumExpanded = (INT)SendMessage(hwndLB, LB_GETCOUNT, 0, 0L); iTopIndex = (INT)SendMessage(hwndLB, LB_GETTOPINDEX, 0, 0L); GetClientRect(hwndLB, &rc); iBottomIndex = iTopIndex + (rc.bottom+1) / dyFileName; if (IsTheDiskReallyThere(hWnd, szPath, FUNC_EXPAND)) ReadDirLevel(hWnd, pNode, szPath, (WORD)(pNode->nLevels + 1), nIndex, (DWORD)(ATTR_DIR | (GetWindowLong(GetParent(hWnd), GWL_ATTRIBS) & ATTR_HS)), (BOOL)wParam, szNULL); // this is how many will be in view iExpandInView = (iBottomIndex - (INT)iCurrentIndex); iNumExpanded = (INT)SendMessage(hwndLB, LB_GETCOUNT, 0, 0L) - iNumExpanded; if (iNumExpanded >= iExpandInView) { iNewTopIndex = min((INT)iCurrentIndex, iTopIndex + iNumExpanded - iExpandInView + 1); SendMessage(hwndLB, LB_SETTOPINDEX, (WORD)iNewTopIndex, 0L); } SendMessage(hwndLB, LB_SETHORIZONTALEXTENT, xTreeMax, 0L); SendMessage(hwndLB, WM_SETREDRAW, TRUE, 0L); if (iNumExpanded) InvalidateRect(hwndLB, NULL, TRUE); // Redraw the Status Bar UpdateStatus(GetParent(hWnd)); } /*--------------------------------------------------------------------------*/ /* */ /* TreeControlWndProc() - */ /* */ /*--------------------------------------------------------------------------*/ /* WndProc for the directory tree control. */ INT_PTR APIENTRY TreeControlWndProc( register HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam ) { WORD iSel; INT i, j; WPARAM nIndex; DWORD dwTemp; PDNODE pNode, pNodeNext; HWND hwndLB; CHAR szPath[MAXPATHLEN]; STKCHK(); hwndLB = GetDlgItem(hWnd, IDCW_TREELISTBOX); switch (wMsg) { case FS_GETDRIVE: MSG("TreeControlWndProc", "FS_GETDRIVE"); return (GetWindowLong(GetParent(hWnd), GWL_TYPE) + 'A'); case TC_COLLAPSELEVEL: MSG("TreeControlWndProc", "TC_COLLAPSELEVEL"); { PDNODE pParentNode; if (wParam) nIndex = wParam; else { nIndex = SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); if (nIndex == LB_ERR) break; } SendMessage(hwndLB, LB_GETTEXT, nIndex, (LPARAM)&pParentNode); // short circuit if we are already in this state if (!(pParentNode->wFlags & TF_EXPANDED)) break; CollapseLevel(hwndLB, pParentNode, (int)nIndex); break; } case TC_EXPANDLEVEL: MSG("TreeControlWndProc", "TC_EXPANDLEVEL"); ExpandLevel(hWnd, (WORD)wParam, (INT)-1, szPath); break; case TC_TOGGLELEVEL: MSG("TreeControlWndProc", "TC_TOGGLELEVEL"); // don't do anything while the tree is being built if (GetWindowLong(hWnd, GWL_READLEVEL)) return 1; SendMessage(hwndLB, LB_GETTEXT, (WPARAM)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L), (LPARAM)&pNode); if (pNode->wFlags & TF_EXPANDED) wMsg = TC_COLLAPSELEVEL; else wMsg = TC_EXPANDLEVEL; SendMessage(hWnd, wMsg, FALSE, 0L); break; case TC_GETDIR: // get a full path for a particular dir // wParam is the listbox index of path to get // lParam LOWORD is PSTR to buffer to fill in MSG("TreeControlWndProc", "TC_GETDIR"); SendMessage(hwndLB, LB_GETTEXT, wParam, (LPARAM)&pNode); GetTreePath(pNode, (LPSTR)lParam); break; case TC_SETDIRECTORY: MSG("TreeControlWndProc", "TC_SETDIRECTORY"); // set the selection in the tree to that for a given path { INT i; i = (INT)FindItemFromPath(hwndLB, (LPSTR)lParam, wParam ? TRUE : FALSE, NULL); if (i != -1) SendMessage(hwndLB, LB_SETCURSEL, i, 0L); break; } case TC_SETDRIVE: #define fFullyExpand LOBYTE(wParam) #define fDontSteal HIBYTE(wParam) #define szDir (LPSTR)lParam // NULL -> default == window text. MSG("TreeControlWndProc", "TC_SETDRIVE"); { RECT rc; if (GetWindowLong(hWnd, GWL_READLEVEL)) break; // is the drive/dir specified? if (szDir) { lstrcpy(szPath, szDir); // yes, use it } else { SendMessage(GetParent(hWnd), FS_GETDIRECTORY, sizeof(szPath), (LPARAM)szPath); // no, use current StripBackslash(szPath); } AnsiUpperBuff(szPath, 1); // make sure SetWindowLong(GetParent(hWnd), GWL_TYPE, 2); // resize for new vol label GetClientRect(GetParent(hWnd), &rc); SendMessage(GetParent(hWnd), WM_SIZE, SIZENOMDICRAP, MAKELONG(rc.right, rc.bottom)); // ensure the disk is available if the whole dir structure is // to be expanded if (!fFullyExpand || IsTheDiskReallyThere(hWnd, szPath, FUNC_EXPAND)) FillTreeListbox(hWnd, szPath, fFullyExpand, fDontSteal); // and force the dir half to update with a fake SELCHANGE message SendMessage(hWnd, WM_COMMAND, GET_WM_COMMAND_MPS(IDCW_TREELISTBOX, hWnd, LBN_SELCHANGE)); break; #undef fFullyExpand #undef fDontSteal #undef szDir } case WM_CHARTOITEM: MSG("TreeControlWndProc", "WM_CHARTOITEM"); { WORD w; CHAR szB[2]; INT cItems; CHAR ch; PDNODE pNode; if (GET_WM_CHARTOITEM_CHAR(wParam, lParam) == '\\') // backslash means the root return 0L; cItems = (INT)SendMessage(hwndLB, LB_GETCOUNT, 0, 0L); i = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); ch = GET_WM_CHARTOITEM_CHAR(wParam, lParam); if (i < 0 || ch <= ' ') // filter all other control chars return -2L; szB[1] = 0L; ch &= 255; for (j=1; j < cItems; j++) { SendMessage(hwndLB, LB_GETTEXT, (i+j) % cItems, (LPARAM)&pNode); szB[0] = pNode->szName[0]; /* Do it this way to be case insensitive. */ w = ch; if (!lstrcmpi((LPSTR)&w, szB)) break; } if (j == cItems) return -2L; SendMessage(hwndLB, LB_SETTOPINDEX, (i+j) % cItems, 0L); return((i+j) % cItems); } case WM_DESTROY: MSG("TreeControlWndProc", "WM_DESTROY"); if (hwndLB == GetFocus()) { HWND hwnd; if (hwnd = HasDirWindow(GetParent(hWnd))) SetFocus(hwnd); else SetFocus(HasDrivesWindow(GetParent(hWnd))); } FreeAllTreeData(hwndLB); break; case WM_CREATE: TRACE(BF_WM_CREATE, "TreeControlWndProc - WM_CREATE"); // create the owner draw list box for the tree { HWND hwnd; hwnd = CreateWindowEx(0L, szListbox, NULL, WS_TREESTYLE | WS_BORDER, 0, 0, 0, 0, hWnd, (HMENU)IDCW_TREELISTBOX, hAppInstance, NULL); if (!hwnd) return -1L; SendMessage(hwnd, WM_SETFONT, (WPARAM)hFont, 0L); SetWindowLong(hWnd, GWL_READLEVEL, 0); break; } case WM_DRAWITEM: MSG("TreeControlWndProc", "WM_DRAWITEM"); TCWP_DrawItem((LPDRAWITEMSTRUCT)lParam, hwndLB, hWnd); break; case WM_FILESYSCHANGE: MSG("TreeControlWndProc", "WM_FILESYSCHANGE"); { HWND hwndParent; PDNODE pNodePrev; PDNODE pNodeT; if (!lParam || wParam == FSC_REFRESH) break; nIndex = FindItemFromPath(hwndLB, (LPSTR)lParam, wParam == FSC_MKDIR, &pNode); if (nIndex == 0xFFFF) /* Did we find it? */ break; lstrcpy(szPath, (LPSTR)lParam); StripPath(szPath); switch (wParam) { case FSC_MKDIR: // auto expand the branch so they can see the new // directory just created if (!(pNode->wFlags & TF_EXPANDED) && (nIndex == (WPARAM)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L))) SendMessage(hWnd, TC_EXPANDLEVEL, FALSE, 0L); // make sure this node isn't already here if (FindItemFromPath(hwndLB, (LPSTR)lParam, FALSE, NULL) != 0xFFFF) break; // Insert it into the tree listbox dwTemp = InsertDirectory(hWnd, pNode, (WORD)nIndex, szPath, &pNodeT); // Add a plus if necessary hwndParent = GetParent(hWnd); if (GetWindowLong(hwndParent, GWL_VIEW) & VIEW_PLUSES) { lstrcpy(szPath, (LPSTR)lParam); ScanDirLevel((PDNODE)pNodeT, szPath, ATTR_DIR | (GetWindowLong(hwndParent, GWL_ATTRIBS) & ATTR_HS)); // Invalidate the window so the plus gets drawn if needed if (((PDNODE)pNodeT)->wFlags & TF_HASCHILDREN) InvalidateRect(hWnd, NULL, FALSE); } // if we are inserting before or at the current selection // push the current selection down nIndex = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); if ((INT)LOWORD(dwTemp) <= nIndex) { SendMessage(hwndLB, LB_SETCURSEL, nIndex + 1, 0L); } break; case FSC_RMDIR: if (nIndex == 0) /* NEVER delete the Root Dir! */ break; if (pNode->wFlags & TF_LASTLEVELENTRY) { // We are deleting the last subdirectory. // If there are previous sibling directories, mark one // as the last, else mark the parent as empty and unexpanded. // It is necessary to do these checks if this bit // is set, since if it isn't, there is another sibling // with TF_LASTLEVELENTRY set, and so the parent is nonempty. // // Find the previous entry which has a level not deeper than // the level of that being deleted. i = (int)nIndex; do { SendMessage(hwndLB, LB_GETTEXT, --i, (LPARAM)&pNodePrev); } while (pNodePrev->nLevels > pNode->nLevels); if (pNodePrev->nLevels == pNode->nLevels) { // The previous directory is a sibling... it becomes // the new last level entry. pNodePrev->wFlags |= TF_LASTLEVELENTRY; } else { // In order to find this entry, the parent must have // been expanded, so if the parent of the deleted dir // has no listbox entries under it, it may be assumed that // the directory has no children. pNodePrev->wFlags &= ~(TF_HASCHILDREN | TF_EXPANDED); } } // Are we deleting the current selection? // if so we move the selection to the item above the current. // this should work in all cases because you can't delete // the root. if ((WPARAM)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L) == nIndex) { SendMessage(hwndLB, LB_SETCURSEL, nIndex - 1, 0L); SendMessage(hWnd, WM_COMMAND, GET_WM_COMMAND_MPS(0, 0, LBN_SELCHANGE)); } SendMessage(hWnd, TC_COLLAPSELEVEL, nIndex, 0L); SendMessage(hwndLB, LB_DELETESTRING, nIndex, 0L); LocalFree((HANDLE)pNode); break; } break; } case WM_COMMAND: { WORD id; id = GET_WM_COMMAND_ID(wParam, lParam); switch (GET_WM_COMMAND_CMD(wParam, lParam)) { case LBN_SELCHANGE: MSG("TreeControlWndProc", "LBN_SELCHANGE"); { HWND hwndParent; HWND hwndDir; INT CurSel; hwndParent = GetParent(hWnd); CurSel = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); SendMessage(hWnd, TC_GETDIR, CurSel,(LPARAM)szPath); AddBackslash(szPath); SendMessage(hwndParent, FS_GETFILESPEC, sizeof(szPath) - lstrlen(szPath), (LPARAM)szPath+lstrlen(szPath)); if (hwndDir = HasDirWindow(hwndParent)) { // update the dir window id = CD_PATH; // don't allow abort on first or last directories if (CurSel > 0 && CurSel != ((INT)SendMessage(hwndLB, LB_GETCOUNT, 0, 0L) - 1)) { id = CD_PATH | CD_ALLOWABORT; } SendMessage(hwndDir, FS_CHANGEDISPLAY, id, (LPARAM)szPath); } else { SetMDIWindowText(hwndParent, szPath); } UpdateStatus(hwndParent); break; } case LBN_DBLCLK: MSG("TreeControlWndProc", "LBN_DBLCLK"); SendMessage(hwndFrame, WM_COMMAND, GET_WM_COMMAND_MPS(IDM_OPEN, 0, 0)); break; case LBN_SETFOCUS: MSG("TreeControlWndProc", "LBN_SETFOCUS"); SetWindowLongPtr(GetParent(hWnd), GWLP_LASTFOCUS, (LPARAM)GET_WM_COMMAND_HWND(wParam, lParam)); UpdateSelection(GET_WM_COMMAND_HWND(wParam, lParam)); UpdateStatus(GetParent(hWnd)); // update the status bar break; case LBN_KILLFOCUS: MSG("TreeControlWndProc", "LBN_KILLFOCUS"); SetWindowLongPtr(GetParent(hWnd), GWLP_LASTFOCUS, 0); UpdateSelection(GET_WM_COMMAND_HWND(wParam, lParam)); SetWindowLongPtr(GetParent(hWnd), GWLP_LASTFOCUS, (LPARAM)GET_WM_COMMAND_HWND(wParam, lParam)); break; } } break; case WM_LBTRACKPOINT: MSG("TreeControlWndProc", "WM_LBTRACKPOINT"); // wParam is the listbox index that we are over // lParam is the mouse point /* Return 0 to do nothing, 1 to abort everything, or 2 to abort just dblclicks. */ { HDC hdc; INT dx; INT xNode; MSG msg; RECT rc; HFONT hOld; POINT pt; DRAGOBJECTDATA dodata; /* Someone clicked somewhere in the listbox. */ // don't do anything while the tree is being built if (GetWindowLong(hWnd, GWL_READLEVEL)) return 1; /* Get the node they clicked on. */ SendMessage(hwndLB, LB_GETTEXT, wParam, (LPARAM)&pNode); lstrcpy(szPath, pNode->szName); if ((wTextAttribs & TA_LOWERCASE) && !(pNode->wFlags & TF_LFN)) AnsiLower(szPath); // if (pNode->wFlags | TF_DISABLED) // return 2L; // too FAR to the left? i = LOWORD(lParam); xNode = pNode->nLevels * dxText * 2; if (i < xNode) return 2; // yes, get out now // too FAR to the right? hdc = GetDC(hwndLB); hOld = SelectObject(hdc, hFont); MGetTextExtent(hdc, szPath, lstrlen(szPath), &dx, NULL); dx += (dyBorderx2*2); if (hOld) SelectObject(hdc, hOld); ReleaseDC(hwndLB, hdc); if (i > xNode + dxFolder + dx + 4 * dyBorderx2) return 2; // yes // Emulate a SELCHANGE notification and notify our parent SendMessage(hwndLB, LB_SETCURSEL, wParam, 0L); SendMessage(hWnd, WM_COMMAND, GET_WM_COMMAND_MPS(0, hwndLB, LBN_SELCHANGE)); // make sure mouse still down if (!(GetKeyState(VK_LBUTTON) & 0x8000)) return 1; MPOINT2POINT(MAKEMPOINT(lParam), pt); ClientToScreen(hwndLB, (LPPOINT)&pt); ScreenToClient(hWnd, (LPPOINT)&pt); SetRect(&rc, pt.x - dxClickRect, pt.y - dyClickRect, pt.x + dxClickRect, pt.y + dyClickRect); SetCapture(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); if (msg.message == WM_LBUTTONUP) break; MPOINT2POINT(MAKEMPOINT(msg.lParam), pt); if (GetCapture() != hWnd) { msg.message = WM_LBUTTONUP; break; } if ((msg.message == WM_MOUSEMOVE) && !(PtInRect(&rc, pt))) break; } ReleaseCapture(); /* Did the guy NOT drag anything? */ if (msg.message == WM_LBUTTONUP) return 1; /* Enter Danger Mouse's BatCave. */ SendMessage(GetParent(hWnd), FS_GETDIRECTORY, sizeof(szPath), (LPARAM)szPath); StripBackslash(szPath); hwndDragging = hwndLB; iCurDrag = SINGLECOPYCURSOR; dodata.pch = szPath; dodata.hMemGlobal = 0; DragObject(hwndMDIClient, hWnd, (UINT)DOF_DIRECTORY, (DWORD)(ULONG_PTR)&dodata, LoadCursor(hAppInstance, MAKEINTRESOURCE(iCurDrag))); hwndDragging = NULL; fShowSourceBitmaps = TRUE; InvalidateRect(hwndLB, NULL, FALSE); return 2; } case WM_DRAGSELECT: MSG("TreeControlWndProc", "WM_DRAGSELECT"); /* WM_DRAGSELECT is sent whenever a new window returns TRUE to a * QUERYDROPOBJECT. */ iSelHilite = LOWORD(((LPDROPSTRUCT)lParam)->dwControlData); RectTreeItem(hwndLB, iSelHilite, (BOOL)wParam); break; case WM_DRAGMOVE: MSG("TreeControlWndProc", "WM_DRAGMOVE"); /* WM_DRAGMOVE is sent when two consequetive TRUE QUERYDROPOBJECT * messages come from the same window. */ /* Get the subitem we are over. */ iSel = LOWORD(((LPDROPSTRUCT)lParam)->dwControlData); /* Is it a new one? */ if (iSel == (WORD)iSelHilite) break; /* Yup, un-select the old item. */ RectTreeItem(hwndLB, iSelHilite, FALSE); /* Select the new one. */ iSelHilite = iSel; RectTreeItem(hwndLB, iSel, TRUE); break; case WM_DRAGLOOP: MSG("TreeControlWndProc", "WM_DRAGLOOP"); // wParam TRUE on dropable target // FALSE not dropable target // lParam lpds { BOOL bCopy; #define lpds ((LPDROPSTRUCT)lParam) /* Are we over a drop-able sink? */ if (wParam) { if (GetKeyState(VK_CONTROL) < 0) // CTRL bCopy = TRUE; else if (GetKeyState(VK_MENU)<0 || GetKeyState(VK_SHIFT)<0) // ALT || SHIFT bCopy = FALSE; else bCopy = (GetDrive(lpds->hwndSink, lpds->ptDrop) != GetDrive(lpds->hwndSource, lpds->ptDrop)); } else { bCopy = TRUE; } if (bCopy != fShowSourceBitmaps) { RECT rc; fShowSourceBitmaps = bCopy; iSel = (WORD)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); if (!(BOOL)SendMessage(hwndLB, LB_GETITEMRECT, iSel, (LPARAM)&rc)) break; InvalidateRect(hwndLB, &rc, FALSE); UpdateWindow(hwndLB); // hack, set the cursor to match the move/copy state if (wParam) SetCursor(GetMoveCopyCursor()); } break; } case WM_QUERYDROPOBJECT: MSG("TreeControlWndProc", "WM_QUERYDROPOBJECT"); // wParam TRUE on NC area // FALSE on client area // lParam lpds // Do nothing return(FALSE); #define lpds ((LPDROPSTRUCT)lParam) /* Check for valid format. */ switch (lpds->wFmt) { case DOF_EXECUTABLE: case DOF_DOCUMENT: case DOF_DIRECTORY: case DOF_MULTIPLE: if (fShowSourceBitmaps) i = iCurDrag | 1; // copy else i = iCurDrag & 0xFFFE; break; default: return FALSE; } /* Must be dropping on the listbox client area. */ if (lpds->hwndSink != hwndLB) return FALSE; if (LOWORD(lpds->dwControlData) == 0xFFFF) return FALSE; return (INT_PTR)GetMoveCopyCursor(); case WM_DROPOBJECT: // tree being dropped on do your thing #define lpds ((LPDROPSTRUCT)lParam) // BUG: WM_DROPOBJECT structure packing! // Do nothing return(TRUE); MSG("TreeControlWndProc", "WM_DROPOBJECT"); // dir (search) drop on tree: // HIWORD(dwData) 0 // LOWORD(dwData) LPSTR to files being dragged // // tree drop on tree: // HIWORD(dwData) index of source drag // LOWORD(dwData) LPSTR to path { LPSTR pFrom; nIndex = LOWORD(lpds->dwControlData); pFrom = (LPSTR)(((LPDRAGOBJECTDATA)(lpds->dwData))->pch); // Get the destination SendMessage(hWnd, TC_GETDIR, nIndex, (LPARAM)szPath); CheckEscapes(szPath); // if source and dest are the same make this a NOP if (!lstrcmpi(szPath, pFrom)) return TRUE; AddBackslash(szPath); lstrcat(szPath, szStarDotStar); DMMoveCopyHelper(pFrom, szPath, fShowSourceBitmaps); RectTreeItem(hwndLB, (int)nIndex, FALSE); } return TRUE; #undef lpds case WM_MEASUREITEM: MSG("TreeControlWndProc", "WM_MEASUREITEM"); #define pLBMItem ((LPMEASUREITEMSTRUCT)lParam) pLBMItem->itemHeight = (WORD)dyFileName; break; case WM_VKEYTOITEM: MSG("TreeControlWndProc", "WM_VKEYTOITEM"); if (wParam == VK_ESCAPE) { bCancelTree = TRUE; return -2L; } i = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); if (i < 0) return -2L; j = 1; SendMessage(hwndLB, LB_GETTEXT, i, (LPARAM)&pNode); switch (GET_WM_VKEYTOITEM_ITEM(wParam, lParam)) { case VK_LEFT: while (SendMessage(hwndLB, LB_GETTEXT, --i, (LPARAM)&pNodeNext) != LB_ERR) { if (pNode == pNode->pParent) return(i); } goto SameSelection; case VK_RIGHT: if ((SendMessage(hwndLB, LB_GETTEXT, i+1, (LPARAM)&pNodeNext) == LB_ERR) || (pNodeNext->pParent != pNode)) { goto SameSelection; } return(i+1); case VK_UP: j = -1; /** FALL THRU ***/ case VK_DOWN: /* If the control key is not down, use default behavior. */ if (GetKeyState(VK_CONTROL) >= 0) return(-1L); while (SendMessage(hwndLB, LB_GETTEXT, i += j, (LPARAM)&pNodeNext) != LB_ERR) { if (pNodeNext->pParent == pNode->pParent) return(i); } SameSelection: MessageBeep(0); return(-2L); case VK_F6: // like excel case VK_TAB: { HWND hwndDir, hwndDrives; BOOL bDir; GetTreeWindows(GetParent(hWnd), NULL, &hwndDir, &hwndDrives); // Check to see if we can change to the directory window if (hwndDir) { HWND hwndLB; /* Local scope ONLY */ hwndLB = GetDlgItem (hwndDir,IDCW_LISTBOX); if (hwndLB) { SendMessage (hwndLB,LB_GETTEXT,0, (LPARAM) &pNode); bDir = pNode ? TRUE : FALSE; } } if (GetKeyState(VK_SHIFT) < 0) SetFocus(hwndDrives); else if (bDir) SetFocus (hwndDir); else SetFocus (hwndDrives); return -2L; // I dealt with this! } case VK_BACK: { BYTE nStartLevel; if (i <= 0) return -2L; // root case nStartLevel = pNode->nLevels; do { SendMessage(hwndLB, LB_GETTEXT, --i, (LPARAM)&pNodeNext); } while (i > 0 && pNodeNext->nLevels >= nStartLevel); return i; } default: if (GetKeyState(VK_CONTROL) < 0) return SendMessage(GetDlgItem(GetParent(hWnd), IDCW_DRIVES), wMsg, wParam, lParam); return -1L; } break; case WM_SETFOCUS: case WM_LBUTTONDOWN: MSG("TreeControlWndProc", "WM_LBUTTONDOWN"); SetFocus(hwndLB); break; case WM_SIZE: MSG("TreeControlWndProc", "WM_SIZE"); if (!IsIconic(GetParent(hWnd))) { INT iMax; MoveWindow(hwndLB, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE); iMax = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L); if (iMax >= 0) { RECT rc; INT top, bottom; GetClientRect(hwndLB, &rc); top = (INT)SendMessage(hwndLB, LB_GETTOPINDEX, 0, 0L); bottom = top + rc.bottom / dyFileName; if (iMax < top || iMax > bottom) SendMessage(hwndLB, LB_SETTOPINDEX, iMax - ((bottom - top) / 2), 0L); } } break; default: DEFMSG("TreeControlWndProc", (WORD)wMsg); return DefWindowProc(hWnd, wMsg, wParam, lParam); } return 0L; }