mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2339 lines
70 KiB
2339 lines
70 KiB
//---------------------------------------------------------------------------
|
|
// Handle the tree and stuff.
|
|
//
|
|
// History:
|
|
// 03-22-93 SatoNa Calling GetContextMenuOf() for context menus
|
|
//---------------------------------------------------------------------------
|
|
|
|
//---------------------------------------------------------------------------
|
|
#include "cabinet.h"
|
|
#include "onetree.h"
|
|
|
|
#include "tree.h"
|
|
#include "rcids.h"
|
|
#include "drivlist.h"
|
|
#include <shellp.h>
|
|
|
|
typedef struct {
|
|
HWND hwndTree;
|
|
LPCSHITEMID szID;
|
|
} STRSEARCH;
|
|
|
|
|
|
HIMAGELIST g_himlSysSmall = NULL;
|
|
HIMAGELIST g_himlSysLarge = NULL;
|
|
void Tree_SortChildren(PFileCabinet pfc, HTREEITEM htiParent, LPOneTreeNode lpn);
|
|
void FileCabinet_GetViewRect(PFileCabinet this, RECT * prc);
|
|
void Tree_TrimInvisible(HWND hwndTree, HTREEITEM hItem);
|
|
|
|
//BUGBUG make this near after tree is sucked out of cabinet.
|
|
LPOneTreeNode Tree_GetFCTreeData(HWND hwndTree, HTREEITEM hItem)
|
|
{
|
|
TV_ITEM ti;
|
|
|
|
if (!hItem) {
|
|
// if hItem is false, GETITEM will fail, but we always store the OT node
|
|
// data in here, so we'll just return the root's node.
|
|
// change this if we start storing something else in the dwData
|
|
return NULL;
|
|
}
|
|
|
|
ti.mask = TVIF_PARAM;
|
|
ti.hItem = hItem;
|
|
ti.lParam = 0;
|
|
|
|
TreeView_GetItem(hwndTree, &ti);
|
|
return (LPOneTreeNode)ti.lParam;
|
|
}
|
|
|
|
void Tree_SetItemState(PFileCabinet pfc, HTREEITEM hti, UINT stateMask, UINT state)
|
|
{
|
|
|
|
if (hti) {
|
|
TV_ITEM tvi;
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.stateMask = stateMask;
|
|
tvi.hItem = hti;
|
|
tvi.state = state;
|
|
TreeView_SetItem(pfc->hwndTree, &tvi);
|
|
}
|
|
|
|
}
|
|
|
|
void Tree_NukeCutState(PFileCabinet pfc)
|
|
{
|
|
Tree_SetItemState(pfc, pfc->htiCut, TVIS_CUT, 0);
|
|
pfc->htiCut = NULL;
|
|
|
|
ChangeClipboardChain(pfc->hwndMain, pfc->hwndNextViewer);
|
|
pfc->hwndNextViewer = NULL;
|
|
}
|
|
|
|
LPSHELLFOLDER Tree_BindToFolder(HWND hwndTree, HTREEITEM hItem)
|
|
{
|
|
LPOneTreeNode lpn;
|
|
IShellFolder * psf;
|
|
|
|
lpn = Tree_GetFCTreeData(hwndTree, hItem);
|
|
psf = OTBindToFolder(lpn);
|
|
|
|
return psf;
|
|
}
|
|
|
|
LPCITEMIDLIST Tree_GetFolderID(HWND hwndTree, HTREEITEM hItem)
|
|
{
|
|
LPOneTreeNode lpn = Tree_GetFCTreeData(hwndTree, hItem);
|
|
if (lpn) {
|
|
return OTGetFolderID(lpn);
|
|
}
|
|
return NULL;
|
|
}
|
|
/*
|
|
*/
|
|
void Tree_CompleteCallbacks(PFileCabinet pfc, HTREEITEM hItem)
|
|
{
|
|
TV_ITEM ti;
|
|
LPOneTreeNode lpn = Tree_GetFCTreeData(pfc->hwndTree, hItem);
|
|
|
|
ti.mask = TVIF_CHILDREN | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
|
|
ti.hItem = hItem;
|
|
OTNodeFillTV_ITEM(lpn, &ti);
|
|
|
|
TreeView_SetItem(pfc->hwndTree, &ti);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Get the text of the requested item;
|
|
void Tree_GetItemText(HWND hwndTree, HTREEITEM hti, LPTSTR lpszText, int cbText)
|
|
{
|
|
TV_ITEM tvi;
|
|
|
|
tvi.mask = TVIF_TEXT;
|
|
tvi.hItem = hti;
|
|
tvi.pszText = lpszText;
|
|
tvi.cchTextMax = cbText;
|
|
|
|
TreeView_GetItem(hwndTree, &tvi);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Get the text of the requested item;
|
|
UINT Tree_GetItemState(HWND hwndTree, HTREEITEM hti)
|
|
{
|
|
TV_ITEM tvi;
|
|
|
|
tvi.mask = TVIF_STATE;
|
|
tvi.hItem = hti;
|
|
tvi.stateMask = TVIS_ALL;
|
|
|
|
TreeView_GetItem(hwndTree, &tvi);
|
|
return tvi.state;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// If the given pt is within an items text or icon area then return that
|
|
// item.
|
|
HTREEITEM Tree_HitTest(HWND hwndTree, POINT pt, DWORD *pdwFlags)
|
|
{
|
|
HTREEITEM hti;
|
|
TV_HITTESTINFO tvht;
|
|
tvht.pt = pt;
|
|
|
|
hti = TreeView_HitTest(hwndTree, &tvht);
|
|
if (pdwFlags)
|
|
*pdwFlags = tvht.flags;
|
|
return hti;
|
|
}
|
|
|
|
|
|
LRESULT Tree_ContextMenu(PFileCabinet pfc, LPPOINT ppt)
|
|
{
|
|
HTREEITEM hti;
|
|
POINT ptPopup; // in screen coordinate
|
|
|
|
if (ppt)
|
|
{
|
|
//
|
|
// Mouse-driven: Pick the treeitem from the position.
|
|
//
|
|
ptPopup = *ppt;
|
|
ScreenToClient(pfc->hwndTree, ppt);
|
|
hti = Tree_HitTest(pfc->hwndTree, *ppt, NULL);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Keyboard-driven: Get the popup position from the selected item.
|
|
//
|
|
hti = TreeView_GetSelection(pfc->hwndTree);
|
|
if (hti)
|
|
{
|
|
RECT rc;
|
|
//
|
|
// Note that TV_GetItemRect returns it in client coordinate!
|
|
//
|
|
TreeView_GetItemRect(pfc->hwndTree, hti, &rc, TRUE);
|
|
ptPopup.x = (rc.left+rc.right)/2;
|
|
ptPopup.y = (rc.top+rc.bottom)/2;
|
|
MapWindowPoints(pfc->hwndTree, HWND_DESKTOP, &ptPopup, 1);
|
|
}
|
|
}
|
|
|
|
if (hti != NULL)
|
|
{
|
|
IShellFolder * psf;
|
|
HTREEITEM htiParent = TreeView_GetParent(pfc->hwndTree, hti);
|
|
|
|
psf = Tree_BindToFolder(pfc->hwndTree, htiParent);
|
|
|
|
if (psf)
|
|
{
|
|
LPCITEMIDLIST pidl = Tree_GetFolderID(pfc->hwndTree, hti);
|
|
if ( pidl )
|
|
{
|
|
IContextMenu * pcm;
|
|
HRESULT hres;
|
|
|
|
//
|
|
// Note: We should pass pfc->hwndMain instead of pfc->hwndTree
|
|
// because we send this hwnd random messages, and tree may
|
|
// fault on these messages intended for hwndMain.
|
|
// in general, this hwnd needs to be something that can support
|
|
// GETISHELLBROWSESR
|
|
//
|
|
hres = psf->lpVtbl->GetUIObjectOf(psf, pfc->hwndMain,
|
|
1, &pidl, &IID_IContextMenu, NULL, &pcm);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
HMENU hmenu = CreatePopupMenu();
|
|
|
|
if (hmenu)
|
|
{
|
|
UINT idCmd;
|
|
|
|
//
|
|
// Step 2: Let the object handler add menu items for verbs.
|
|
//
|
|
pcm->lpVtbl->QueryContextMenu(pcm, hmenu, 0, 1, 0x7fff,
|
|
CMF_EXPLORE|CMF_CANRENAME);
|
|
|
|
Assert(!pfc->pcmTree);
|
|
pfc->pcmTree = pcm;
|
|
|
|
//
|
|
// Pop up the menu and let the user select an item.
|
|
//
|
|
idCmd = TrackPopupMenu(hmenu,
|
|
TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
|
|
ptPopup.x, ptPopup.y, 0, pfc->hwndMain, NULL);
|
|
|
|
pfc->pcmTree = NULL;
|
|
|
|
if (idCmd)
|
|
{
|
|
TCHAR szCommandString[20];
|
|
BOOL fHandled = FALSE;
|
|
BOOL fCutting = FALSE;
|
|
HRESULT hres;
|
|
|
|
// We need to special case the rename command
|
|
hres = pcm->lpVtbl->GetCommandString(pcm,
|
|
idCmd-1, GCS_VERB, NULL,
|
|
(LPSTR)szCommandString,
|
|
ARRAYSIZE(szCommandString));
|
|
#ifdef UNICODE
|
|
if (FAILED(hres) || *szCommandString == TEXT('\0'))
|
|
{
|
|
CHAR szCommandAnsi[20];
|
|
hres = pcm->lpVtbl->GetCommandString(pcm,
|
|
idCmd-1, GCS_VERB, NULL,
|
|
szCommandAnsi,
|
|
ARRAYSIZE(szCommandAnsi));
|
|
MultiByteToWideChar(CP_ACP, 0,
|
|
szCommandAnsi, -1,
|
|
szCommandString,
|
|
ARRAYSIZE(szCommandString));
|
|
}
|
|
#endif
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
if (lstrcmpi(szCommandString, c_szRename)==0) {
|
|
TreeView_EditLabel(pfc->hwndTree, hti);
|
|
fHandled = TRUE;
|
|
} else if (!lstrcmpi(szCommandString, c_szMove)) {
|
|
if (hti) {
|
|
// For cut-effect after InvokeCommand.
|
|
fCutting = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fHandled)
|
|
{
|
|
|
|
//
|
|
// Call InvokeCommand (-1 is from 1,0x7fff)
|
|
//
|
|
CMINVOKECOMMANDINFOEX ici = {
|
|
SIZEOF(CMINVOKECOMMANDINFOEX),
|
|
0L,
|
|
pfc->hwndMain,
|
|
(LPSTR)MAKEINTRESOURCE(idCmd - 1),
|
|
NULL, NULL,
|
|
SW_NORMAL,
|
|
};
|
|
|
|
hres = pcm->lpVtbl->InvokeCommand(pcm,
|
|
(LPCMINVOKECOMMANDINFO)&ici);
|
|
if (fCutting && SUCCEEDED(hres))
|
|
{
|
|
Tree_SetItemState(pfc, hti, TVIS_CUT, TVIS_CUT);
|
|
Assert(!pfc->hwndNextViewer);
|
|
pfc->hwndNextViewer = SetClipboardViewer(pfc->hwndMain);
|
|
DebugMsg(DM_TRACE, TEXT("CABINET: Set ClipboardViewer %d %d"), pfc->hwndMain, pfc->hwndNextViewer);
|
|
pfc->htiCut = hti;
|
|
}
|
|
}
|
|
|
|
//
|
|
// REVIEW: I don't see any reason we should call Tree_BuildPath here.
|
|
//
|
|
//
|
|
// We need to update the tree view if the selected
|
|
// item is deleted.
|
|
// BUGBUG: It assumes a file system directory.
|
|
//
|
|
//Tree_BuildPath(pfc->hwndTree, hti, szPath, TRUE);
|
|
}
|
|
DestroyMenu(hmenu);
|
|
}
|
|
pcm->lpVtbl->Release(pcm);
|
|
}
|
|
}
|
|
psf->lpVtbl->Release(psf);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// This function handles a right click on a tree item.
|
|
//
|
|
// REVIEW UNDONE
|
|
//
|
|
LRESULT Tree_HandleRClick(PFileCabinet pfc)
|
|
{
|
|
DWORD dwPos;
|
|
POINT pt;
|
|
|
|
dwPos = GetMessagePos();
|
|
pt.x = LOWORD(dwPos);
|
|
pt.y = HIWORD(dwPos);
|
|
|
|
return Tree_ContextMenu(pfc, &pt);
|
|
}
|
|
|
|
BOOL Tree_ValidateNode(PFileCabinet pfc, HTREEITEM hti)
|
|
{
|
|
BOOL fRefreshed = FALSE;
|
|
if (hti) {
|
|
|
|
LPOneTreeNode lpnd = Tree_GetFCTreeData(pfc->hwndTree, hti);
|
|
if (lpnd && OTIsRemovableRoot(lpnd)) {
|
|
|
|
// if we did this on the current selection, do a full refresh
|
|
if (hti == TreeView_GetSelection(pfc->hwndTree)) {
|
|
|
|
PostMessage(pfc->hwndMain, WM_COMMAND, MAKEWPARAM(FCIDM_REFRESH, 0), (LPARAM) (pfc->hwndMain));
|
|
|
|
} else {
|
|
// we hit a removeable drive. invalidate and refresh this
|
|
DoInvalidateAll(lpnd, -1);
|
|
Tree_TrimInvisible(pfc->hwndTree, hti);
|
|
Tree_InvalidateItemInfo(pfc->hwndTree, hti);
|
|
Tree_RefreshOneLevel(pfc, hti, TRUE);
|
|
}
|
|
fRefreshed = TRUE;
|
|
|
|
}
|
|
}
|
|
return fRefreshed;
|
|
}
|
|
|
|
void Tree_HandleClick(PFileCabinet pfc)
|
|
{
|
|
DWORD dwPos;
|
|
POINT pt;
|
|
HTREEITEM hti;
|
|
DWORD dwFlags;
|
|
|
|
dwPos = GetMessagePos();
|
|
pt.x = LOWORD(dwPos);
|
|
pt.y = HIWORD(dwPos);
|
|
|
|
ScreenToClient(pfc->hwndTree, &pt);
|
|
hti = Tree_HitTest(pfc->hwndTree, pt, &dwFlags);
|
|
if (dwFlags & TVHT_ONITEM)
|
|
Tree_ValidateNode(pfc, hti);
|
|
}
|
|
|
|
void Tree_ValidateCurrentSelection(PFileCabinet pfc)
|
|
{
|
|
HTREEITEM hti;
|
|
hti = TreeView_GetSelection(pfc->hwndTree);
|
|
Tree_ValidateNode(pfc, hti);
|
|
}
|
|
|
|
//
|
|
// Returns the absolute pidl to the specified tree node.
|
|
//
|
|
LPITEMIDLIST Tree_GetAbsolutePidl(HWND hwndTree, HTREEITEM hti)
|
|
{
|
|
LPCITEMIDLIST pidlT = Tree_GetFolderID(hwndTree, hti);
|
|
LPITEMIDLIST pidlAbs;
|
|
if (pidlT && (NULL != (pidlAbs=ILClone(pidlT))))
|
|
{
|
|
while(NULL != (hti = TreeView_GetParent(hwndTree, hti)))
|
|
{
|
|
pidlT = Tree_GetFolderID(hwndTree, hti);
|
|
if (pidlT)
|
|
{
|
|
LPITEMIDLIST pidlNew = ILCombine(pidlT, pidlAbs);
|
|
ILFree(pidlAbs);
|
|
if (pidlNew)
|
|
{
|
|
pidlAbs = pidlNew;
|
|
}
|
|
else
|
|
{
|
|
// Out of memory
|
|
pidlAbs = NULL;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Something is going wrong.
|
|
ILFree(pidlAbs);
|
|
pidlAbs=NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pidlAbs;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// hwnd -- Specifies the owner window for message box/dialog box
|
|
//
|
|
void Tree_HandleBeginDrag(HWND hwndOwner, BOOL fShowMenu, LPNM_TREEVIEW lpnmhdr)
|
|
{
|
|
HWND hwndTree = lpnmhdr->hdr.hwndFrom;
|
|
HTREEITEM hti = lpnmhdr->itemNew.hItem;
|
|
HTREEITEM htiParent = TreeView_GetParent(hwndTree, hti);
|
|
LPSHELLFOLDER psfParent = Tree_BindToFolder(hwndTree, htiParent);
|
|
|
|
if (psfParent)
|
|
{
|
|
LPCITEMIDLIST pidl = Tree_GetFolderID(hwndTree, hti);
|
|
if (pidl)
|
|
{
|
|
HRESULT hres;
|
|
LPDATAOBJECT pdtobj;
|
|
|
|
//
|
|
// First let's ask the parent to create the IDataObject.
|
|
//
|
|
// BUGBUG: Use IID_IShellDataObject since we can't marshal arbitrary IDataObject
|
|
//
|
|
hres = psfParent->lpVtbl->GetUIObjectOf(psfParent, hwndOwner, 1, &pidl, &IID_IDataObject, NULL, &pdtobj);
|
|
|
|
//
|
|
// If it failed, we create a default one.
|
|
//
|
|
if (FAILED(hres))
|
|
{
|
|
LPITEMIDLIST pidlAbs = Tree_GetAbsolutePidl(hwndTree, htiParent);
|
|
if (pidlAbs)
|
|
{
|
|
hres = CIDLData_CreateFromIDArray(pidlAbs, 1, &pidl, &pdtobj);
|
|
ILFree(pidlAbs);
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
HIMAGELIST himlDrag = TreeView_CreateDragImage(hwndTree, lpnmhdr->itemNew.hItem);
|
|
DWORD dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
|
|
psfParent->lpVtbl->GetAttributesOf(psfParent, 1, &pidl, &dwEffect);
|
|
dwEffect &= DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
|
|
|
|
if (himlDrag) {
|
|
if (DAD_SetDragImage(himlDrag, NULL))
|
|
{
|
|
SHDoDragDrop(hwndOwner, pdtobj, NULL, dwEffect, &dwEffect);
|
|
|
|
DAD_SetDragImage((HIMAGELIST)-1, NULL);
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("sh ER - Tree_HandleBeginDrag DAD_SetDragImage failed"));
|
|
Assert(0);
|
|
}
|
|
ImageList_Destroy(himlDrag);
|
|
}
|
|
|
|
pdtobj->lpVtbl->Release(pdtobj);
|
|
}
|
|
|
|
}
|
|
psfParent->lpVtbl->Release(psfParent);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Tree_RealHandleSelChange
|
|
//
|
|
// This function returns TRUE, only if we were able to switch the view
|
|
// to the selected item.
|
|
//---------------------------------------------------------------------------
|
|
|
|
BOOL Tree_RealHandleSelChange(PFileCabinet pfc)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
HTREEITEM hti;
|
|
RECT rcTo, rcFrom, ItemRect, ClientRect;
|
|
LPOneTreeNode lpn;
|
|
|
|
//
|
|
// Kill the timer
|
|
//
|
|
KillTimer(pfc->hwndMain, pfc->nSelChangeTimer);
|
|
pfc->nSelChangeTimer = 0;
|
|
|
|
//
|
|
// This function is not re-entrant.
|
|
//
|
|
if (pfc->fChangingFolder)
|
|
{
|
|
Assert(0); // fatal problem (i.e., a bug in our code).
|
|
return FALSE;
|
|
}
|
|
|
|
hti = TreeView_GetSelection(pfc->hwndTree);
|
|
|
|
if (hti == NULL)
|
|
{
|
|
// This can happen if we get reentered at a bad time,
|
|
// such as doing a refresh when viewing a network resource
|
|
return(FALSE);
|
|
}
|
|
|
|
lpn = Tree_GetFCTreeData(pfc->hwndTree, hti);
|
|
if (lpn == pfc->lpndOpen)
|
|
return TRUE; // we scooted around and came back to where we started.
|
|
|
|
GetWindowRect(pfc->hwndView, &rcTo);
|
|
MapWindowPoints(NULL, pfc->hwndMain, (LPPOINT)&rcTo.left, 2);
|
|
|
|
// Clip the ItemRect to the TreeView bounds.
|
|
TreeView_GetItemRect(pfc->hwndTree, hti, &ItemRect, TRUE);
|
|
GetClientRect(pfc->hwndTree, &ClientRect);
|
|
IntersectRect(&rcFrom, &ClientRect, &ItemRect);
|
|
MapWindowPoints(pfc->hwndTree, pfc->hwndMain, (LPPOINT)&rcFrom.left, 2);
|
|
|
|
DrawAnimatedRects(pfc->hwndMain, IDANI_OPEN, &rcFrom, &rcTo);
|
|
|
|
//
|
|
// Notes: The fully-qualified path name in szPath will be used to
|
|
// maintain the MRU list of tree items.
|
|
//
|
|
{
|
|
LPITEMIDLIST pidl = OTCreateIDListFromNode(lpn);
|
|
if (pidl)
|
|
{
|
|
//
|
|
// WARNING:
|
|
//
|
|
// We should return FALSE, if Cabinet_ChangeView returns
|
|
// S_FALSE (SETPATH is posted). That's why we don't use
|
|
// SUCCEEDED macro here.
|
|
//
|
|
DebugMsg(DM_TRACE, TEXT("ca TR - Tree_RealHandleSelChange calling Cabinet_ChangeView..."));
|
|
fRet = (Cabinet_ChangeView(pfc, lpn, pidl, FALSE)==NOERROR);
|
|
DebugMsg(DM_TRACE, TEXT("ca TR - Tree_RealHandleSelChange Cabinet_ChangeView returned %d"), fRet);\
|
|
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If any of FSNotify message is ignored during this function,
|
|
// update the tree now.
|
|
//
|
|
if (pfc->fUpdateTree) {
|
|
Tree_RefreshAll(pfc);
|
|
pfc->fUpdateTree = FALSE;
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
DWORD g_tmMsg = 0;
|
|
DWORD g_tmStart = 0;
|
|
DWORD g_tmReceived = 0;
|
|
#endif
|
|
|
|
void CALLBACK Tree_TimerProc(HWND hWnd, UINT uMessage, UINT wTimer, DWORD dwTime)
|
|
{
|
|
PFileCabinet pfc = GetPFC(hWnd); // BUGBUG: we aren't gaurenteed this is our HWND!
|
|
#ifdef DEBUG
|
|
g_tmReceived = GetTickCount();
|
|
#endif
|
|
|
|
Tree_RealHandleSelChange(pfc);
|
|
|
|
#ifdef DEBUG
|
|
DebugMsg(DM_TRACE, TEXT("ca TR - Tree_TimerProc (msg=%d, timer=%d, selchange=%d)"),
|
|
g_tmStart-g_tmMsg, g_tmReceived-g_tmStart, GetTickCount()-g_tmReceived);
|
|
#endif
|
|
}
|
|
|
|
|
|
void Tree_HandleSelChange(PFileCabinet pfc, BOOL fDelayed)
|
|
{
|
|
// We don't want to change selection right away in case the user is
|
|
// just scrolling through the list. We'll wait 3/4 second for keyboard
|
|
// and no time for mouse (we get strange flashing if we do it right
|
|
// away) and then do it.
|
|
if (pfc->nSelChangeTimer)
|
|
{
|
|
KillTimer(pfc->hwndMain, pfc->nSelChangeTimer); // BUGBUG: using wrong timer ID?
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
g_tmMsg = GetMessageTime();
|
|
g_tmStart = GetTickCount();
|
|
#endif
|
|
pfc->nSelChangeTimer = SetTimer(pfc->hwndMain, 1,
|
|
fDelayed ? (GetDoubleClickTime()*3/2) : 1, Tree_TimerProc);
|
|
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Find an child of the given item with the given name,
|
|
// Returns null if the item can't be found or there are no children.
|
|
// REVIEW HACK TV_FINDTEM will do this.
|
|
HTREEITEM Tree_FindChildItem(HWND hwndTree, HTREEITEM htiParent, LPCSHITEMID pmkid)
|
|
{
|
|
HTREEITEM hti = NULL;
|
|
LPSHELLFOLDER psf;
|
|
LPITEMIDLIST pidlFirst;
|
|
HRESULT hres;
|
|
|
|
pidlFirst = ILCloneFirst((LPITEMIDLIST)pmkid);
|
|
if (!pidlFirst)
|
|
return NULL;
|
|
|
|
psf = Tree_BindToFolder(hwndTree, htiParent);
|
|
if (psf) {
|
|
|
|
|
|
for (hti = TreeView_GetChild(hwndTree, htiParent);
|
|
hti;
|
|
hti = TreeView_GetNextSibling(hwndTree, hti))
|
|
{
|
|
|
|
LPCITEMIDLIST pidl2 = Tree_GetFolderID(hwndTree, hti);
|
|
hres = psf->lpVtbl->CompareIDs(psf, 0, pidlFirst, pidl2);
|
|
if (SUCCEEDED(hres) && (hres == ResultFromShort(0)))
|
|
break;
|
|
}
|
|
|
|
psf->lpVtbl->Release(psf);
|
|
}
|
|
|
|
ILFree(pidlFirst);
|
|
return hti;
|
|
}
|
|
|
|
|
|
// Pointer comparision function for Sort and Search functions.
|
|
// lParam is lParam passed to sort/search functions. Returns
|
|
// -1 if p1 < p2, 0 if p1 == p2, and 1 if p1 > p2.
|
|
//
|
|
int CALLBACK _export HTIList_FolderIDCompare(HTREEITEM hItem1, HTREEITEM hItem2, LPARAM lParam)
|
|
{
|
|
LPOneTreeNode lpData1, lpData2;
|
|
LPCSHITEMID lpszID1, lpszID2;
|
|
STRSEARCH *pss = (STRSEARCH *)lParam;
|
|
|
|
// HACK: If the item is on our stack, it is just the pointer we want
|
|
// to compare
|
|
if (hItem1)
|
|
{
|
|
lpData1 = Tree_GetFCTreeData(pss->hwndTree, hItem1);
|
|
if (!lpData1)
|
|
{
|
|
// This should only happen when we are building the list
|
|
// of roots.
|
|
return(0);
|
|
}
|
|
lpszID1 = &OTGetFolderID(lpData1)->mkid;
|
|
}
|
|
else
|
|
{
|
|
lpszID1 = pss->szID;
|
|
}
|
|
|
|
if (hItem2)
|
|
{
|
|
lpData2 = Tree_GetFCTreeData(pss->hwndTree, hItem2);
|
|
if (!lpData2)
|
|
{
|
|
// This should only happen when we are building the list
|
|
// of roots.
|
|
return(0);
|
|
}
|
|
lpszID2 = &OTGetFolderID(lpData2)->mkid;
|
|
}
|
|
else
|
|
{
|
|
Assert(FALSE);
|
|
}
|
|
|
|
return memcmp(lpszID1, lpszID2, lpszID1->cb);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Create a DPA list of HTREEITEMS, one for each child of the given node
|
|
// in the given tree.
|
|
|
|
// this HTIList stuff gets punted when OneTree works.
|
|
HDPA HTIList_CreateFromTree(HWND hwndTree, HTREEITEM htiParent)
|
|
{
|
|
HTREEITEM htiChild;
|
|
HDPA hdpa;
|
|
STRSEARCH ss;
|
|
|
|
htiChild = TreeView_GetChild(hwndTree, htiParent);
|
|
if (htiChild)
|
|
{
|
|
// Has children.
|
|
hdpa = DPA_Create(8);
|
|
if (!hdpa)
|
|
{
|
|
return(NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
while (htiChild)
|
|
{
|
|
DPA_InsertPtr(hdpa, 0, htiChild);
|
|
htiChild = TreeView_GetNextSibling(hwndTree, htiChild);
|
|
}
|
|
|
|
ss.hwndTree = hwndTree;
|
|
ss.szID = NULL;
|
|
DPA_Sort(hdpa, HTIList_FolderIDCompare, (LPARAM)(LPTSTR)&ss);
|
|
|
|
return(hdpa);
|
|
}
|
|
|
|
void SetTreeItemData(HWND hwndTree, HTREEITEM hti, LPARAM lParam)
|
|
{
|
|
TV_ITEM ti;
|
|
LPOneTreeNode lpnd;
|
|
|
|
ti.hItem = hti;
|
|
ti.mask = TVIF_PARAM;
|
|
ti.lParam = lParam;
|
|
|
|
|
|
lpnd = Tree_GetFCTreeData(hwndTree, hti);
|
|
if (lpnd) {
|
|
OTRelease(lpnd);
|
|
} else {
|
|
// this likely shouldn't ever be null... REVIEW this.
|
|
Assert(0);
|
|
}
|
|
|
|
TreeView_SetItem(hwndTree, &ti);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Delete all the items from the tree that are in the given list.
|
|
BOOL HTIList_DeleteItemsFromTree(HDPA hdpa, HWND hwndTree)
|
|
{
|
|
int i, cItems;
|
|
HTREEITEM htiTmp;
|
|
BOOL fDeleted = FALSE;
|
|
|
|
if (hdpa)
|
|
{
|
|
// Delete all non-null items from the tree.
|
|
cItems = DPA_GetPtrCount(hdpa);
|
|
for (i=0; i<cItems; i++)
|
|
{
|
|
htiTmp = DPA_FastGetPtr(hdpa, i);
|
|
if (htiTmp) {
|
|
|
|
TreeView_DeleteItem(hwndTree, htiTmp);
|
|
fDeleted = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return fDeleted;
|
|
}
|
|
|
|
|
|
int HTIList_FindChildItem(HWND hwndTree, HDPA hdpa, LPCSHITEMID szID)
|
|
{
|
|
STRSEARCH ss;
|
|
|
|
if (!hdpa)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
ss.hwndTree = hwndTree;
|
|
ss.szID = szID;
|
|
|
|
return(DPA_Search(hdpa, NULL, 0, HTIList_FolderIDCompare,
|
|
(LPARAM)(LPTSTR)&ss, DPAS_SORTED));
|
|
}
|
|
|
|
|
|
void Tree_InvalidateItemInfo(HWND hwndTree, HTREEITEM hItem)
|
|
{
|
|
TV_ITEM ti;
|
|
|
|
ti.mask = TVIF_CHILDREN | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
|
|
ti.hItem = hItem;
|
|
ti.cChildren = I_CHILDRENCALLBACK;
|
|
ti.iImage = I_IMAGECALLBACK;
|
|
ti.iSelectedImage = I_IMAGECALLBACK;
|
|
ti.pszText = LPSTR_TEXTCALLBACK;
|
|
TreeView_SetItem(hwndTree, &ti);
|
|
}
|
|
|
|
void Tree_InsertItem(PFileCabinet pfc, HTREEITEM htiParent, LPOneTreeNode lpnKid)
|
|
{
|
|
TV_INSERTSTRUCT tii;
|
|
|
|
// use callbacks for the expensive fields
|
|
DebugDumpNode(lpnKid, TEXT("Tree_InsertItem"));
|
|
Assert(lpnKid);
|
|
|
|
tii.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_CHILDREN;
|
|
tii.hParent = htiParent;
|
|
tii.hInsertAfter = TVI_FIRST;
|
|
tii.item.iImage = I_IMAGECALLBACK;
|
|
tii.item.iSelectedImage = I_IMAGECALLBACK;
|
|
|
|
tii.item.pszText = LPSTR_TEXTCALLBACK;
|
|
tii.item.cChildren = I_CHILDRENCALLBACK; // OTHasSubFolders(lpnKid);
|
|
tii.item.lParam = (LPARAM)lpnKid;
|
|
|
|
TreeView_InsertItem(pfc->hwndTree, &tii);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Tidyup.
|
|
#define HTIList_Destroy(hdpa) if (hdpa) DPA_Destroy(hdpa)
|
|
|
|
BOOL Tree_FillOneLevel(PFileCabinet pfc, HTREEITEM htiParent, BOOL bInvalOld, BOOL fInteractive)
|
|
{
|
|
LPOneTreeNode lpn, lpnKid;
|
|
int iSubNodes, i;
|
|
HDPA hdpa;
|
|
BOOL bTreeChanged = FALSE;
|
|
int iChild;
|
|
BOOL fSuccess;
|
|
DECLAREWAITCURSOR;
|
|
|
|
// Tree_GetFCTreeData will deal right if htiParent is null meaning root
|
|
lpn = Tree_GetFCTreeData(pfc->hwndTree, htiParent);
|
|
if (!lpn)
|
|
{
|
|
// We did everything that can be done, so return TRUE
|
|
return(TRUE);
|
|
}
|
|
|
|
PushRecursion(pfc);
|
|
SetWaitCursor();
|
|
|
|
// Build list of all the items
|
|
hdpa = HTIList_CreateFromTree(pfc->hwndTree, htiParent);
|
|
|
|
fSuccess = OTSubNodeCount(pfc->hwndMain, lpn, pfc, &iSubNodes, fInteractive);
|
|
|
|
for ( i = 0; i < iSubNodes; i++)
|
|
{
|
|
MSG msg;
|
|
LPCITEMIDLIST pidlSubFolder;
|
|
lpnKid = OTGetNthSubNode(pfc->hwndMain, lpn, i);
|
|
|
|
//
|
|
// iSubNodes could be out-of-sync and larger than the real value.
|
|
// In such a case, we should simply exit from this loop.
|
|
//
|
|
if (!lpnKid ||
|
|
pfc->fPostCloseLater ||
|
|
PeekMessage(&msg, NULL, WM_CLOSE, WM_CLOSE, PM_NOREMOVE)) {
|
|
break;
|
|
}
|
|
|
|
pidlSubFolder = OTGetFolderID(lpnKid);
|
|
|
|
iChild = HTIList_FindChildItem(pfc->hwndTree, hdpa, &pidlSubFolder->mkid);
|
|
|
|
// already in tree?
|
|
if (iChild >= 0) {
|
|
// Yep, Remove it from the list of things to delete.
|
|
if (hdpa)
|
|
{
|
|
if (bInvalOld) {
|
|
Tree_InvalidateItemInfo(pfc->hwndTree, DPA_FastGetPtr(hdpa, iChild));
|
|
}
|
|
DPA_DeletePtr(hdpa, iChild);
|
|
}
|
|
} else {
|
|
|
|
bTreeChanged = TRUE;
|
|
Tree_InsertItem(pfc, htiParent, lpnKid);
|
|
}
|
|
}
|
|
|
|
if (HTIList_DeleteItemsFromTree(hdpa, pfc->hwndTree))
|
|
bTreeChanged = TRUE;;
|
|
HTIList_Destroy(hdpa);
|
|
|
|
|
|
if (bTreeChanged)
|
|
{
|
|
Tree_SortChildren(pfc, htiParent, lpn);
|
|
}
|
|
|
|
ResetWaitCursor();
|
|
PopRecursion(pfc);
|
|
return fSuccess;
|
|
}
|
|
|
|
void Tree_SortChildren(PFileCabinet pfc, HTREEITEM htiParent, LPOneTreeNode lpn)
|
|
{
|
|
IShellFolder *psf;
|
|
|
|
// Make sure the parent folder is currently cached
|
|
psf = OTBindToFolder(lpn);
|
|
if (psf)
|
|
{
|
|
TV_SORTCB sSortCB;
|
|
|
|
sSortCB.hParent = htiParent;
|
|
sSortCB.lpfnCompare = OTTreeViewCompare;
|
|
sSortCB.lParam = (LPARAM)psf;
|
|
|
|
TreeView_SortChildrenCB(pfc->hwndTree, &sSortCB, FALSE);
|
|
psf->lpVtbl->Release(psf);
|
|
}
|
|
else
|
|
{
|
|
// We'll just do default sorting
|
|
TreeView_SortChildren(pfc->hwndTree, htiParent, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Build a tree for the given path.
|
|
// REVIEW UNDONE - To facilitate doing the drag drop we could do with
|
|
// markers in the tree to indicate special objects
|
|
// NB Given the path "C:\foo\bar\fred" we build complete sub-trees for
|
|
// c:\ and c:\foo and c:\foo\bar and then set the focus to c:\foo\bar\fred
|
|
// NB We don't build bit's of the tree that have already been built
|
|
|
|
HTREEITEM Tree_Build(PFileCabinet pfc,
|
|
LPCITEMIDLIST pidlFull,
|
|
BOOL bExpand, BOOL bDontFail)
|
|
{
|
|
HTREEITEM hti;
|
|
LPCITEMIDLIST pidl;
|
|
|
|
Assert(pfc->hwndTree);
|
|
Assert(pidlFull);
|
|
|
|
// Get the "root" node; insert it if necessary
|
|
hti = TreeView_GetChild(pfc->hwndTree, NULL);
|
|
if (!hti)
|
|
{
|
|
Tree_InsertItem(pfc, NULL, OTGetRootNode());
|
|
|
|
hti = TreeView_GetChild(pfc->hwndTree, NULL);
|
|
if (!hti)
|
|
{
|
|
return(NULL); // Really bad! No item in the tree.
|
|
}
|
|
}
|
|
|
|
for (pidl=pidlFull ; !ILIsEmpty(pidl) && hti; pidl = ILGetNext(pidl))
|
|
{
|
|
HTREEITEM htiPrev = hti; // store it away for error case
|
|
|
|
TreeView_Expand(pfc->hwndTree, hti, TVE_EXPAND);
|
|
hti = Tree_FindChildItem(pfc->hwndTree, hti, &pidl->mkid);
|
|
|
|
if (!hti)
|
|
{
|
|
LPITEMIDLIST pidlChild;
|
|
// If this is a network case, we know the item should exist, but
|
|
// was not found, which is a semi-normal condition as network
|
|
// enumeration is not an exact science. As such we we should
|
|
// force the item in to the tree.
|
|
//
|
|
LPOneTreeNode lpn = Tree_GetFCTreeData(pfc->hwndTree, htiPrev);
|
|
|
|
// Now try to add the item to the list
|
|
if (lpn && (NULL != (pidlChild = ILCloneFirst(pidl))))
|
|
{
|
|
OTAddSubFolder(lpn, pidlChild, FALSE, NULL);
|
|
Tree_FillOneLevel(pfc, htiPrev, FALSE, FALSE);
|
|
hti = Tree_FindChildItem(pfc->hwndTree, htiPrev, &pidl->mkid);
|
|
ILFree(pidlChild);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Firewall : Check if we could find the specified item in the tree.
|
|
//
|
|
if (hti == NULL)
|
|
{
|
|
// No, Go back to the previous selection.
|
|
hti = TreeView_GetSelection(pfc->hwndTree);
|
|
DebugMsg(DM_WARNING, TEXT("sh Firewall - Tree_Build: Can't find the item"));
|
|
|
|
// Check if we have any selection
|
|
if (hti == NULL && bDontFail)
|
|
{
|
|
// Go back to the root
|
|
hti = TreeView_GetRoot(pfc->hwndTree);
|
|
Assert(hti); // should not hit.
|
|
}
|
|
}
|
|
|
|
if (hti)
|
|
{
|
|
// Expand one more level and set the selection...
|
|
if (bExpand)
|
|
TreeView_Expand(pfc->hwndTree, hti, TVE_EXPAND);
|
|
|
|
TreeView_SelectItem(pfc->hwndTree, hti);
|
|
}
|
|
|
|
return hti;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Given a new split position - move everything around.
|
|
BOOL Tree_MoveSplit(HWND hwndCabinet, UINT x)
|
|
{
|
|
PFileCabinet pfc = GetPFC(hwndCabinet);
|
|
|
|
pfc->TreeSplit = x;
|
|
|
|
// BUGBUG: this is bogus
|
|
PostMessage(hwndCabinet, WM_SIZE, SIZE_RESTORED, 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// Stolen (essentially) from COMMCTRL
|
|
HBITMAP CreateDitherBitmap(COLORREF crFG, COLORREF crBG)
|
|
{
|
|
PBITMAPINFO pbmi;
|
|
HBITMAP hbm;
|
|
HDC hdc;
|
|
int i;
|
|
long patGray[8];
|
|
DWORD rgb;
|
|
|
|
pbmi = (PBITMAPINFO)LocalAlloc(LPTR, SIZEOF(BITMAPINFOHEADER) + (SIZEOF(RGBQUAD) * 16));
|
|
if (!pbmi)
|
|
return NULL;
|
|
|
|
pbmi->bmiHeader.biSize = SIZEOF(BITMAPINFOHEADER);
|
|
pbmi->bmiHeader.biWidth = 8;
|
|
pbmi->bmiHeader.biHeight = 8;
|
|
pbmi->bmiHeader.biPlanes = 1;
|
|
pbmi->bmiHeader.biBitCount = 1;
|
|
pbmi->bmiHeader.biCompression = BI_RGB;
|
|
|
|
rgb = crBG;
|
|
pbmi->bmiColors[0].rgbBlue = GetBValue(rgb);
|
|
pbmi->bmiColors[0].rgbGreen = GetGValue(rgb);
|
|
pbmi->bmiColors[0].rgbRed = GetRValue(rgb);
|
|
pbmi->bmiColors[0].rgbReserved = 0;
|
|
|
|
rgb = crFG;
|
|
pbmi->bmiColors[1].rgbBlue = GetBValue(rgb);
|
|
pbmi->bmiColors[1].rgbGreen = GetGValue(rgb);
|
|
pbmi->bmiColors[1].rgbRed = GetRValue(rgb);
|
|
pbmi->bmiColors[1].rgbReserved = 0;
|
|
|
|
|
|
/* initialize the brushes */
|
|
|
|
for (i = 0; i < 8; i++)
|
|
if (i & 1)
|
|
patGray[i] = 0xAAAA5555L; // 0x11114444L; // lighter gray
|
|
else
|
|
patGray[i] = 0x5555AAAAL; // 0x11114444L; // lighter gray
|
|
|
|
hdc = GetDC(NULL);
|
|
|
|
// REVIEW: We cast am array of long to (BYTE const *). Is it ok for Win32?
|
|
hbm = CreateDIBitmap(hdc, &pbmi->bmiHeader, CBM_INIT,
|
|
(BYTE const *)patGray, pbmi, DIB_RGB_COLORS);
|
|
|
|
ReleaseDC(NULL, hdc);
|
|
|
|
LocalFree(pbmi);
|
|
|
|
return hbm;
|
|
}
|
|
|
|
|
|
// Stolen (essentially) from COMMCTRL
|
|
HBRUSH CreateDitherBrush(void)
|
|
{
|
|
HBITMAP hbmGray;
|
|
HBRUSH hbrRet = NULL;
|
|
|
|
hbmGray = CreateDitherBitmap(RGB(255, 255, 255), RGB(0, 0, 0));
|
|
if (hbmGray)
|
|
{
|
|
hbrRet = CreatePatternBrush(hbmGray);
|
|
DeleteObject(hbmGray);
|
|
}
|
|
|
|
return(hbrRet);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Handle dragging the split bar thing.
|
|
LRESULT Tree_HandleSplitDrag(PFileCabinet pfc, int x)
|
|
{
|
|
MSG msg;
|
|
int y, dx, dy;
|
|
int nAccel = 2;
|
|
RECT rc;
|
|
HDC hdc;
|
|
LONG lStyle;
|
|
HWND hwndCabinet = pfc->hwndMain;
|
|
HBRUSH hbrDither, hbrOld;
|
|
|
|
if (IsIconic(hwndCabinet))
|
|
return 0;
|
|
|
|
lStyle = GetWindowLong(hwndCabinet, GWL_STYLE);
|
|
lStyle &= ~WS_CLIPCHILDREN;
|
|
SetWindowLong(hwndCabinet, GWL_STYLE, lStyle);
|
|
|
|
dx = g_cxSizeFrame;
|
|
GetClientRect(pfc->hwndTree, &rc);
|
|
MapWindowRect(pfc->hwndTree, hwndCabinet, &rc);
|
|
|
|
// Add some for the scroll bar
|
|
if (GetWindowLong(pfc->hwndTree, GWL_STYLE) & WS_HSCROLL)
|
|
{
|
|
rc.bottom += GetSystemMetrics(SM_CYHSCROLL);
|
|
}
|
|
|
|
// Add some for the title window
|
|
if (IsWindowVisible(pfc->hwndTreeTitle))
|
|
{
|
|
RECT rcTemp;
|
|
|
|
GetClientRect(pfc->hwndTreeTitle, &rcTemp);
|
|
MapWindowRect(pfc->hwndTreeTitle, hwndCabinet, &rcTemp);
|
|
UnionRect(&rc, &rc, &rcTemp);
|
|
}
|
|
|
|
y = rc.top;
|
|
dy = rc.bottom - rc.top;
|
|
// We need this to limit the split drag.
|
|
GetClientRect(hwndCabinet, &rc);
|
|
|
|
// Stop the split going right off either end.
|
|
rc.left += g_cxIcon;
|
|
rc.right -= g_cxIcon;
|
|
// We assume the window cannot be less than one icon wide
|
|
if (rc.right < rc.left)
|
|
{
|
|
rc.right = rc.left;
|
|
}
|
|
|
|
// Convert rect to screen coords.
|
|
MapWindowRect(hwndCabinet, NULL, &rc);
|
|
|
|
hdc = GetDC(hwndCabinet);
|
|
hbrDither = CreateDitherBrush();
|
|
if (hbrDither)
|
|
{
|
|
hbrOld = SelectObject(hdc, hbrDither);
|
|
}
|
|
|
|
// split bar loop...
|
|
PatBlt(hdc, x - dx / 2, y, dx, dy, PATINVERT);
|
|
SetCapture(hwndCabinet);
|
|
while (GetMessage(&msg, NULL, 0, 0))
|
|
{
|
|
if (msg.message == WM_LBUTTONUP || msg.message == WM_LBUTTONDOWN
|
|
|| msg.message == WM_RBUTTONDOWN)
|
|
break;
|
|
|
|
if ( GetCapture() != hwndCabinet) {
|
|
msg.message = WM_RBUTTONDOWN; // treat as cancel
|
|
break;
|
|
}
|
|
|
|
if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN ||
|
|
(msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST))
|
|
{
|
|
if (msg.message == WM_KEYDOWN)
|
|
{
|
|
#ifdef DOACCEL
|
|
if (msg.lParam & (1L << 30L))
|
|
++nAccel;
|
|
else
|
|
nAccel = 2;
|
|
#else
|
|
nAccel = 4;
|
|
#endif
|
|
|
|
if (msg.wParam == VK_LEFT)
|
|
{
|
|
msg.message = WM_MOUSEMOVE;
|
|
msg.pt.x -= nAccel/2;
|
|
}
|
|
else if (msg.wParam == VK_RIGHT)
|
|
{
|
|
msg.message = WM_MOUSEMOVE;
|
|
msg.pt.x += nAccel/2;
|
|
}
|
|
else if (msg.wParam == VK_RETURN ||
|
|
msg.wParam == VK_ESCAPE)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (msg.pt.x > rc.right)
|
|
msg.pt.x = rc.right;
|
|
if (msg.pt.x < rc.left)
|
|
msg.pt.x = rc.left;
|
|
SetCursorPos(msg.pt.x, msg.pt.y);
|
|
}
|
|
if (msg.message == WM_MOUSEMOVE)
|
|
{
|
|
int lo, hi;
|
|
|
|
if (msg.pt.x > rc.right)
|
|
msg.pt.x = rc.right;
|
|
if (msg.pt.x < rc.left)
|
|
msg.pt.x = rc.left;
|
|
ScreenToClient(hwndCabinet, &msg.pt);
|
|
|
|
// Clip out the parts we don't want so
|
|
// that we do a single PatBlt (less
|
|
// flicker for small movements).
|
|
if (x < msg.pt.x)
|
|
{
|
|
lo = x;
|
|
hi = msg.pt.x;
|
|
}
|
|
else
|
|
{
|
|
lo = msg.pt.x;
|
|
hi = x;
|
|
}
|
|
lo -= dx / 2;
|
|
hi -= dx / 2;
|
|
if (hi < lo+dx)
|
|
{
|
|
ExcludeClipRect(hdc, hi, y, lo+dx, y+dy);
|
|
}
|
|
else
|
|
{
|
|
ExcludeClipRect(hdc, lo+dx, y, hi, y+dy);
|
|
}
|
|
|
|
// Erase the old and draw the new in one draw.
|
|
PatBlt(hdc, lo, y, hi-lo+dx, dy, PATINVERT);
|
|
SelectClipRgn(hdc, NULL);
|
|
|
|
x = msg.pt.x;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
ReleaseCapture();
|
|
|
|
// erase old
|
|
PatBlt(hdc, x - dx / 2, y, dx, dy, PATINVERT);
|
|
|
|
if (hbrDither)
|
|
{
|
|
if (hbrOld)
|
|
{
|
|
SelectObject(hdc, hbrOld);
|
|
}
|
|
DeleteObject(hbrDither);
|
|
}
|
|
ReleaseDC(hwndCabinet, hdc);
|
|
|
|
lStyle |= WS_CLIPCHILDREN;
|
|
SetWindowLong(hwndCabinet, GWL_STYLE, lStyle);
|
|
|
|
if (msg.wParam != VK_ESCAPE && msg.message != WM_RBUTTONDOWN && msg.message != WM_CAPTURECHANGED)
|
|
{
|
|
Tree_MoveSplit(hwndCabinet, x - (dx/2));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Rebuild a bit of the tree by comparing what's already in the tree with
|
|
// what the view says should be in the tree.
|
|
//
|
|
// Returns: TRUE, if the tree is successfully refreshed.
|
|
// FALSE, otherwise.
|
|
//
|
|
// Notes: This function will change the selection if the specified item does
|
|
// not exist. In such a case, this function returns FALSE.
|
|
//
|
|
BOOL Tree_HandleItemRefresh(PFileCabinet pfc, HTREEITEM hti, BOOL bInvalOld, BOOL fInteractive)
|
|
{
|
|
LPOneTreeNode lpn = Tree_GetFCTreeData(pfc->hwndTree, hti);
|
|
|
|
return Tree_FillOneLevel(pfc, hti, bInvalOld, fInteractive);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Process Treeview begin of label editing
|
|
|
|
LRESULT Tree_HandleBeginLabelEdit(PFileCabinet pfc, TV_DISPINFO *ptvdi)
|
|
{
|
|
HTREEITEM htiParent;
|
|
BOOL fCantRename = TRUE;
|
|
|
|
htiParent = TreeView_GetParent(pfc->hwndTree, ptvdi->item.hItem);
|
|
|
|
//
|
|
// Top level guys are always inconvincible (just like Microsoft).
|
|
//
|
|
if (htiParent)
|
|
{
|
|
LPSHELLFOLDER psfParent = Tree_BindToFolder(pfc->hwndTree, htiParent);
|
|
if (psfParent)
|
|
{
|
|
LPOneTreeNode lpn = Tree_GetFCTreeData(pfc->hwndTree, ptvdi->item.hItem);
|
|
LPCITEMIDLIST pidl = OTGetFolderID(lpn);
|
|
if (pidl) {
|
|
DWORD dwAttribs = SFGAO_CANRENAME;
|
|
psfParent->lpVtbl->GetAttributesOf(psfParent, 1, &pidl, &dwAttribs);
|
|
if (dwAttribs & SFGAO_CANRENAME) {
|
|
fCantRename = FALSE;
|
|
}
|
|
}
|
|
psfParent->lpVtbl->Release(psfParent);
|
|
}
|
|
}
|
|
|
|
if (fCantRename)
|
|
MessageBeep(0);
|
|
else
|
|
pfc->hMainAccel = NULL; // so we don't steal accelerators from the edit field
|
|
|
|
return fCantRename;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Process Treeview end of label editing
|
|
|
|
LRESULT Tree_HandleEndLabelEdit(PFileCabinet pfc, TV_DISPINFO *ptvdi)
|
|
{
|
|
HTREEITEM htiParent;
|
|
LPSHELLFOLDER psfParent;
|
|
|
|
// reload our accelerator table
|
|
pfc->hMainAccel = LoadAccelerators(hinstCabinet, MAKEINTRESOURCE(ACCEL_MERGE));
|
|
|
|
// See if the user cancelled
|
|
if (ptvdi->item.pszText == NULL)
|
|
return TRUE; // Nothing to do here.
|
|
|
|
Assert(ptvdi->item.hItem);
|
|
htiParent = TreeView_GetParent(pfc->hwndTree, ptvdi->item.hItem);
|
|
Assert(htiParent);
|
|
|
|
psfParent = Tree_BindToFolder(pfc->hwndTree, htiParent);
|
|
|
|
if (psfParent)
|
|
{
|
|
LPOneTreeNode lpn = Tree_GetFCTreeData(pfc->hwndTree, ptvdi->item.hItem);
|
|
LPCITEMIDLIST pidl = OTGetFolderID(lpn);
|
|
if (pidl)
|
|
{
|
|
UINT cch = lstrlen(ptvdi->item.pszText)+1;
|
|
LPOLESTR pwsz = (LPOLESTR)LocalAlloc(LPTR, cch * SIZEOF(OLECHAR));
|
|
if (pwsz)
|
|
{
|
|
StrToOleStrN(pwsz, cch, ptvdi->item.pszText, -1);
|
|
if (SUCCEEDED(psfParent->lpVtbl->SetNameOf(psfParent, pfc->hwndMain,
|
|
pidl, pwsz, 0, NULL)))
|
|
{
|
|
//
|
|
// we need to update the display of everything...
|
|
//
|
|
// Rebuild the parent
|
|
//
|
|
SHChangeNotifyHandleEvents();
|
|
|
|
// NOTES: pidl is no longer valid here.
|
|
|
|
//
|
|
// Set the handle to NULL in the notification to let
|
|
// the system know that the pointer is probably not
|
|
// valid anymore.
|
|
//
|
|
ptvdi->item.hItem = NULL;
|
|
}
|
|
else
|
|
{
|
|
SendMessage(pfc->hwndTree, TVM_EDITLABEL,
|
|
(WPARAM)ptvdi->item.pszText, (LPARAM)ptvdi->item.hItem);
|
|
}
|
|
LocalFree((HLOCAL)pwsz);
|
|
}
|
|
}
|
|
psfParent->lpVtbl->Release(psfParent);
|
|
}
|
|
|
|
return 0; // We always return 0, "we handled it".
|
|
}
|
|
|
|
|
|
#ifdef SETALLVIS
|
|
void Tree_SetAllVisItemInfos(PFileCabinet pfc)
|
|
{
|
|
HTREEITEM hItem;
|
|
int nVisible;
|
|
HWND hwndTree = pfc->hwndTree;
|
|
|
|
// Now set the info for all visible items
|
|
hItem = TreeView_GetFirstVisible(hwndTree);
|
|
nVisible = TreeView_GetVisibleCount(hwndTree);
|
|
for ( ; nVisible > 0 && hItem; --nVisible)
|
|
{
|
|
Tree_CompleteCallbacks(pfc, hItem);
|
|
hItem = TreeView_GetNextVisible(hwndTree, hItem);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void Tree_GetDispInfo(PFileCabinet pfc, TV_DISPINFO *lpnm)
|
|
{
|
|
LPOneTreeNode lpn;
|
|
TV_ITEM ti;
|
|
|
|
// if the dwData is null, we likely are in the process of deleting it.
|
|
lpn = (LPOneTreeNode)lpnm->item.lParam;
|
|
if (!lpn)
|
|
return;
|
|
|
|
ti.hItem = lpnm->item.hItem;
|
|
ti.mask = 0;
|
|
|
|
// Use this as a flag as to whether we have set the image and kids
|
|
// Only set them if we are being asked for them
|
|
if (lpnm->item.mask & (TVIF_CHILDREN | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE))
|
|
{
|
|
|
|
ti.mask = lpnm->item.mask & (TVIF_CHILDREN | TVIF_IMAGE | TVIF_SELECTEDIMAGE);
|
|
|
|
OTNodeFillTV_ITEM(lpn, &ti);
|
|
// on start, removable root nodes have the +/-
|
|
if (OTIsRemovableRoot(lpn))
|
|
lpnm->item.cChildren = TRUE;
|
|
|
|
// Return the right values, so it gets painted correctly this time
|
|
if (lpnm->item.mask & TVIF_CHILDREN)
|
|
lpnm->item.cChildren = ti.cChildren;
|
|
if (lpnm->item.mask & TVIF_IMAGE) {
|
|
lpnm->item.iImage = ti.iImage;
|
|
ti.mask |= TVIF_STATE;
|
|
lpnm->item.mask |= TVIF_STATE;
|
|
ti.state = lpnm->item.state = (OTIsShared(lpn) ? INDEXTOOVERLAYMASK(IDOI_SHARE) : 0);
|
|
ti.stateMask = lpnm->item.stateMask = TVIS_OVERLAYMASK;
|
|
}
|
|
if (lpnm->item.mask & TVIF_SELECTEDIMAGE)
|
|
lpnm->item.iSelectedImage = ti.iSelectedImage;
|
|
|
|
|
|
#ifdef SETALLVIS
|
|
// Now set the info for all visible items
|
|
Tree_SetAllVisItemInfos(lpnm->hdr.hwndFrom);
|
|
#endif
|
|
}
|
|
|
|
if (lpnm->item.mask & TVIF_TEXT) {
|
|
LPTSTR lpsz;
|
|
ti.mask |= TVIF_TEXT;
|
|
DebugDumpNode(lpn, TEXT("InTreeGetDispInfo"));
|
|
|
|
lpsz = lpn->lpText;
|
|
|
|
if (!lpsz) {
|
|
Assert(0);
|
|
lpsz = (LPTSTR)c_szNULL;
|
|
}
|
|
ti.pszText = lpsz;
|
|
lpnm->item.pszText = lpsz; // BUGBUG: OTGetNodeName()?
|
|
|
|
}
|
|
|
|
//TreeView_SetItem(pfc->hwndTree, &ti);
|
|
lpnm->item.mask |= TVIF_DI_SETITEM;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// BUGBUG: replace with DPA_Search()
|
|
|
|
int DPA_FindString(HDPA hdpa, LPCTSTR lpszText)
|
|
{
|
|
UINT j, cItems;
|
|
|
|
cItems = DPA_GetPtrCount(hdpa);
|
|
for (j=0; j<cItems; j++)
|
|
{
|
|
if (lstrcmpi(lpszText, DPA_FastGetPtr(hdpa, j)) == 0)
|
|
return j;
|
|
}
|
|
|
|
// Didn't find it.
|
|
return -1;
|
|
}
|
|
|
|
|
|
HTREEITEM Tree_GetItemFromIDList(HWND hwndTree, LPCITEMIDLIST pidlFull)
|
|
{
|
|
LPCITEMIDLIST pidl;
|
|
HTREEITEM hti;
|
|
|
|
Assert(hwndTree);
|
|
Assert(pidlFull);
|
|
|
|
// the first parent is child of 0.
|
|
// this is new with the desktop being the root, and the mycomputer/network/etc
|
|
// being the child of the desktop node in the tree.
|
|
hti = TreeView_GetChild(hwndTree, 0);
|
|
|
|
for (pidl = pidlFull; !ILIsEmpty(pidl) && hti; pidl = ILGetNext(pidl))
|
|
{
|
|
hti = Tree_FindChildItem(hwndTree, hti, &pidl->mkid);
|
|
}
|
|
return hti;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Cause the specified folder to be refreshed.
|
|
void Tree_Refresh(PFileCabinet pfc, LPCITEMIDLIST pidl)
|
|
{
|
|
HTREEITEM hti;
|
|
Assert(pidl);
|
|
hti = Tree_GetItemFromIDList(pfc->hwndTree, pidl);
|
|
|
|
if (hti)
|
|
{
|
|
Tree_InvalidateItemInfo(pfc->hwndTree, hti);
|
|
Tree_HandleItemRefresh(pfc, hti, TRUE, FALSE);
|
|
|
|
// WARNING: hti may no longer be valid here...
|
|
}
|
|
// We don't need to update the tree which is not yet opened.
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Do a Free() on all the pointers in the given DPA.
|
|
// BUGBUG: replace with DPA_DeleteAllPtrs()
|
|
|
|
void DPA_FreePtrs(HDPA hdpa)
|
|
{
|
|
UINT j, cItems;
|
|
LPVOID lp;
|
|
|
|
cItems = DPA_GetPtrCount(hdpa);
|
|
for (j = 0; j < cItems; j++) {
|
|
lp = DPA_FastGetPtr(hdpa, j);
|
|
if (lp)
|
|
Free(lp);
|
|
}
|
|
}
|
|
|
|
|
|
void Tree_UpdateHasKidsButton(PFileCabinet pfc, LPNM_TREEVIEW lpnmtv)
|
|
{
|
|
TV_ITEM ti;
|
|
int i;
|
|
int j;
|
|
int cChildren;
|
|
|
|
OTSubNodeCount(pfc->hwndMain, (LPOneTreeNode)lpnmtv->itemNew.lParam, pfc, &cChildren, FALSE);
|
|
j = (cChildren != 0);
|
|
i = (lpnmtv->itemNew.cChildren != 0);
|
|
|
|
// if they don't agree, set it.
|
|
if ( i ^ j ) {
|
|
ti.mask = TVIF_CHILDREN;
|
|
ti.hItem = lpnmtv->itemNew.hItem;
|
|
ti.cChildren = cChildren;
|
|
|
|
TreeView_SetItem(pfc->hwndTree, &ti);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
LRESULT Tree_HandleExpanding(PFileCabinet pfc, LPNM_TREEVIEW lpnmtv)
|
|
{
|
|
BOOL fSuccess = TRUE; // assume no error.
|
|
|
|
if (lpnmtv->action != TVE_EXPAND)
|
|
return FALSE;
|
|
|
|
// We may have "reset" this item by removing all its children, so we
|
|
// need to refresh it
|
|
|
|
if (!(lpnmtv->itemNew.state & TVIS_EXPANDEDONCE))
|
|
{
|
|
//
|
|
// If we are expanding the currently selected item, don't be
|
|
// interactive to avoid duplicated dialog boxes.
|
|
//
|
|
BOOL fInteractive = (lpnmtv->itemNew.hItem != TreeView_GetSelection(pfc->hwndTree));
|
|
LPOneTreeNode lpnd = Tree_GetFCTreeData(pfc->hwndTree, lpnmtv->itemNew.hItem);
|
|
if (lpnd && OTIsRemovableRoot(lpnd))
|
|
DoInvalidateAll(lpnd, -1);
|
|
|
|
pfc->fExpandingItem = TRUE;
|
|
fSuccess = Tree_HandleItemRefresh(pfc, lpnmtv->itemNew.hItem, FALSE, fInteractive);
|
|
pfc->fExpandingItem = FALSE;
|
|
|
|
//
|
|
// Don't call Tree_UpdateHasKidsButton if enumeration failed.
|
|
// Otherwise, we end up enumerating it again.
|
|
//
|
|
if (fSuccess)
|
|
{
|
|
Tree_UpdateHasKidsButton(pfc, lpnmtv);
|
|
}
|
|
}
|
|
|
|
return !fSuccess;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// This function updates the drives in the tree
|
|
//
|
|
// this is only called by Tree.c where we know a hwndTree already exists
|
|
void _UpdateDrives(PFileCabinet pfc)
|
|
{
|
|
// Refresh the list of drives in the tree
|
|
LPITEMIDLIST pidl = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
|
|
if (pidl)
|
|
{
|
|
HTREEITEM hDrives = Tree_GetItemFromIDList(pfc->hwndTree, pidl);
|
|
if (hDrives)
|
|
{
|
|
// desktop.c takes care of this and anyway invalidating all drives is bad
|
|
// InvalidateDriveType(-1);
|
|
Tree_RefreshOneLevel(pfc, hDrives, FALSE);
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
void Tree_InvalidateImageIndex(HWND hwndTree, HTREEITEM hItem, int iImage)
|
|
{
|
|
HTREEITEM hChild;
|
|
TV_ITEM tvi;
|
|
|
|
if (hItem) {
|
|
tvi.mask = TVIF_SELECTEDIMAGE | TVIF_IMAGE;
|
|
tvi.hItem = hItem;
|
|
|
|
TreeView_GetItem(hwndTree, &tvi);
|
|
if (iImage == -1 ||
|
|
tvi.iImage == iImage ||
|
|
tvi.iSelectedImage == iImage) {
|
|
Tree_InvalidateItemInfo(hwndTree, hItem);
|
|
}
|
|
}
|
|
|
|
hChild = TreeView_GetChild(hwndTree, hItem);
|
|
if (!hChild)
|
|
return;
|
|
|
|
for ( ; hChild; hChild = TreeView_GetNextSibling(hwndTree, hChild))
|
|
{
|
|
Tree_InvalidateImageIndex(hwndTree, hChild, iImage);
|
|
}
|
|
}
|
|
|
|
void CancelMenuMode(PFileCabinet this)
|
|
{
|
|
// make sure we're not in menu mode because all this is going to nuke the menu
|
|
if (GetForegroundWindow() == this->hwndMain)
|
|
SendMessage(this->hwndMain, WM_CANCELMODE, 0, 0);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Processes a FSNotify messages
|
|
//
|
|
|
|
LRESULT Tree_HandleFileSysChange(PFileCabinet this, LPNMOTFSEINFO lpnm)
|
|
{
|
|
LPOneTreeNode lpnKid;
|
|
LPOneTreeNode lpnRoot;
|
|
LPITEMIDLIST pidl, pidlExtra;
|
|
LONG lNotification = lpnm->lEvent;
|
|
HDPA htilist;
|
|
HTREEITEM hti, hti2, htiParent;
|
|
|
|
//
|
|
// If we are in the middle of Cabinet_ChangeView function,
|
|
// ignore this event and update the entire tree later.
|
|
//
|
|
if (this->uRecurse != 1) {
|
|
this->fUpdateTree = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Note that renames between directories are changed to
|
|
// create/delete pairs by SHChangeNotify.
|
|
//
|
|
pidl = lpnm->pidl ? ILClone(lpnm->pidl) : NULL;
|
|
lpnRoot = OTGetRootNode();
|
|
DebugMsg(DM_TRACE, TEXT("sh TR - Tree_HandleFileSysChange called (fse = %x)"), lNotification);
|
|
|
|
switch(lNotification)
|
|
{
|
|
case SHCNE_ASSOCCHANGED:
|
|
OTInvalidateAll();
|
|
Tree_RefreshAll(this);
|
|
break;
|
|
|
|
case SHCNE_UPDATEITEM:
|
|
{
|
|
if (pidl)
|
|
{
|
|
ILRemoveLastID(pidl);
|
|
hti = Tree_GetItemFromIDList(this->hwndTree, pidl);
|
|
}
|
|
else
|
|
{
|
|
hti = NULL;
|
|
}
|
|
|
|
hti = Tree_FindChildItem(this->hwndTree, hti, &(ILFindLastID(lpnm->pidl)->mkid));
|
|
if (hti) {
|
|
lpnKid = OTGetNodeFromIDList(lpnm->pidl, OTGNF_TRYADD);
|
|
if (lpnKid)
|
|
{
|
|
LPOneTreeNode lpndParent;
|
|
BOOL fSelected = (hti == TreeView_GetSelection(this->hwndTree));
|
|
|
|
SetTreeItemData(this->hwndTree, hti, (LPARAM)lpnKid);
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti);
|
|
|
|
if (fSelected)
|
|
{
|
|
Tree_HandleSelChange(this, FALSE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SHCNE_RENAMEFOLDER:
|
|
if (pidl)
|
|
{
|
|
ILRemoveLastID(pidl);
|
|
hti = Tree_GetItemFromIDList(this->hwndTree, pidl);
|
|
}
|
|
else
|
|
{
|
|
hti = NULL;
|
|
}
|
|
|
|
pidlExtra = lpnm->pidlExtra ? ILClone(lpnm->pidlExtra) : NULL;
|
|
|
|
// we need to invalidate both if these are in different dirs
|
|
if (pidlExtra)
|
|
{
|
|
ILRemoveLastID(pidlExtra);
|
|
hti2 = Tree_GetItemFromIDList(this->hwndTree, pidlExtra);
|
|
}
|
|
else
|
|
{
|
|
hti2 = NULL;
|
|
}
|
|
|
|
// are they different??
|
|
if (hti != hti2) {
|
|
// yes, then it was a move
|
|
|
|
// delete one
|
|
if (hti) {
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti);
|
|
hti = Tree_FindChildItem(this->hwndTree, hti, &(ILFindLastID(lpnm->pidl)->mkid));
|
|
if (hti)
|
|
TreeView_DeleteItem(this->hwndTree, hti);
|
|
}
|
|
|
|
// add the other if it does not already exist
|
|
if (hti2) {
|
|
BOOL bRootChild = (hti2 == TreeView_GetChild(this->hwndTree, NULL));
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti2);
|
|
|
|
if ((bRootChild || (Tree_GetItemState(this->hwndTree, hti2) & TVIS_EXPANDEDONCE))
|
|
&& !Tree_FindChildItem(this->hwndTree, hti2, &(ILFindLastID(lpnm->pidlExtra)->mkid)))
|
|
{
|
|
lpnKid = OTGetNodeFromIDList(lpnm->pidlExtra, OTGNF_TRYADD);
|
|
if (lpnKid) {
|
|
LPOneTreeNode lpndParent;
|
|
Tree_InsertItem(this, hti2, lpnKid);
|
|
lpndParent = OTGetParent(lpnKid);
|
|
if (lpndParent) {
|
|
Tree_SortChildren(this, TreeView_GetParent(this->hwndTree, hti2), lpndParent);
|
|
OTRelease(lpndParent);
|
|
}
|
|
|
|
if (bRootChild) {
|
|
// make sure the root item is expanded
|
|
// it's confusing if you add an item in and
|
|
// don't expand the root node because we don't show lines
|
|
// at root, so you might not know that something was added
|
|
TreeView_Expand(this->hwndTree, hti2, TVE_EXPAND);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (hti) {
|
|
|
|
// no, it was a rename
|
|
|
|
hti = Tree_FindChildItem(this->hwndTree, hti, &(ILFindLastID(lpnm->pidl)->mkid));
|
|
if (hti) {
|
|
lpnKid = OTGetNodeFromIDList(lpnm->pidlExtra, OTGNF_TRYADD);
|
|
if (lpnKid)
|
|
{
|
|
LPOneTreeNode lpndParent;
|
|
BOOL fSelected = (hti == TreeView_GetSelection(this->hwndTree));
|
|
|
|
SetTreeItemData(this->hwndTree, hti, (LPARAM)lpnKid);
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti);
|
|
lpndParent = OTGetParent(lpnKid);
|
|
if (lpndParent) {
|
|
Tree_SortChildren(this, TreeView_GetParent(this->hwndTree, hti), lpndParent);
|
|
OTRelease(lpndParent);
|
|
}
|
|
|
|
if (fSelected)
|
|
{
|
|
Tree_HandleSelChange(this, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pidlExtra)
|
|
{
|
|
ILFree(pidlExtra);
|
|
}
|
|
break;
|
|
|
|
case SHCNE_RMDIR:
|
|
if (!pidl)
|
|
{
|
|
break;
|
|
}
|
|
|
|
hti = Tree_GetItemFromIDList(this->hwndTree, pidl);
|
|
if (hti) {
|
|
if (Tree_GetFCTreeData(this->hwndTree, hti) == lpnRoot) {
|
|
// user deleted the root node...
|
|
// do a full refresh
|
|
Tree_RefreshAll(this);
|
|
htiParent = NULL;
|
|
} else {
|
|
htiParent = TreeView_GetParent(this->hwndTree, hti);
|
|
TreeView_DeleteItem(this->hwndTree, hti);
|
|
}
|
|
} else {
|
|
// it's possible that we haven't expanded to this child, only
|
|
// to the parent
|
|
ILRemoveLastID(pidl);
|
|
htiParent = Tree_GetItemFromIDList(this->hwndTree, pidl);
|
|
}
|
|
|
|
// to ge tthe +/- right
|
|
if (htiParent)
|
|
Tree_InvalidateItemInfo(this->hwndTree, htiParent);
|
|
|
|
break;
|
|
|
|
case SHCNE_MKDIR:
|
|
if (!pidl)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ILRemoveLastID(pidl);
|
|
hti = Tree_GetItemFromIDList(this->hwndTree, pidl);
|
|
if (hti)
|
|
{
|
|
LPOneTreeNode lpndParent;
|
|
|
|
lpnKid = OTGetNodeFromIDList(lpnm->pidl, OTGNF_TRYADD);
|
|
if (lpnKid) {
|
|
|
|
Tree_InsertItem(this, hti, lpnKid);
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti);
|
|
lpndParent = OTGetParent(lpnKid);
|
|
if (lpndParent) {
|
|
Tree_SortChildren(this, hti, lpndParent);
|
|
OTRelease(lpndParent);
|
|
|
|
}
|
|
if (lpndParent == lpnRoot) {
|
|
// make sure the root item is expanded
|
|
// it's confusing if you add an item in and
|
|
// don't expand the root node because we don't show lines
|
|
// at root, so you might not know that something was added
|
|
TreeView_Expand(this->hwndTree, hti, TVE_EXPAND);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
// bugbug, this won't work for the root
|
|
case SHCNE_UPDATEDIR:
|
|
if (!pidl)
|
|
{
|
|
break;
|
|
}
|
|
|
|
hti = Tree_GetItemFromIDList(this->hwndTree, pidl);
|
|
if (hti)
|
|
{
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti);
|
|
}
|
|
// if we found the hti or if the pidl pointed to root
|
|
if (hti || ILIsEmpty(pidl)) {
|
|
|
|
// If the pidlExtra==NULL, this is NOT a recursive
|
|
// update. if pidlExtra IS filled in, it points to
|
|
// where the recursion should begin. (However, the
|
|
// only code that ever sets pidlExtra for UPDATEDIR
|
|
// is in fsnotify.c, and it always sets to whatever
|
|
// pidl is, so we don't need to refigure where the
|
|
// recursion should start...we already know!)
|
|
BOOL bRecurse = (lpnm->pidlExtra != NULL);
|
|
|
|
// only recurse if this is updatedir
|
|
Tree_RefreshOneLevel(this, hti, bRecurse);
|
|
}
|
|
break;
|
|
|
|
case SHCNE_NETSHARE:
|
|
case SHCNE_NETUNSHARE:
|
|
if (!pidl)
|
|
{
|
|
break;
|
|
}
|
|
|
|
hti=Tree_GetItemFromIDList(this->hwndTree, pidl);
|
|
if (hti)
|
|
{
|
|
// declare from cabwnd.c
|
|
HTREEITEM htiSel, htiParent;
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti);
|
|
//
|
|
// Update the icon on the title bar, if this item is selected.
|
|
//
|
|
htiSel = TreeView_GetSelection(this->hwndTree);
|
|
if (htiSel == hti) {
|
|
_SetCabinetIcons(this, this->lpndOpen, -1);
|
|
}
|
|
|
|
// BUGBUG: code was here to update the view window if
|
|
// it was viewing :drives. but that was broken
|
|
// because the tree might not be up... it was there because
|
|
// the defview for :drives didn't listen to these events.
|
|
// we should fix that instead of hacking it in here. (chee)
|
|
|
|
//
|
|
// Updates the drive list, if this item is in the list.
|
|
//
|
|
for (htiParent = htiSel; htiParent;
|
|
htiParent = TreeView_GetParent(this->hwndTree, htiParent))
|
|
{
|
|
if (htiParent == hti) {
|
|
LPOneTreeNode lpn = Tree_GetFCTreeData(this->hwndTree, htiSel);
|
|
if (lpn) {
|
|
OTAddRef(lpn);
|
|
this->lpndOpen = lpn;
|
|
if (this->hwndDrives && IsWindowVisible(this->hwndDrives))
|
|
DriveList_UpdatePath(this, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_DRIVEREMOVED:
|
|
case SHCNE_MEDIAREMOVED:
|
|
case SHCNE_DRIVEADD:
|
|
case SHCNE_MEDIAINSERTED:
|
|
_UpdateDrives(this);
|
|
break;
|
|
|
|
case SHCNE_SERVERDISCONNECT:
|
|
if (pidl && (NULL != (hti = Tree_GetItemFromIDList(this->hwndTree, pidl))))
|
|
{
|
|
htilist = HTIList_CreateFromTree(this->hwndTree, hti);
|
|
HTIList_DeleteItemsFromTree(htilist, this->hwndTree);
|
|
Tree_InvalidateItemInfo(this->hwndTree, hti);
|
|
if (hti == TreeView_GetSelection(this->hwndTree)) {
|
|
TreeView_SelectItem(this->hwndTree, TreeView_GetParent(this->hwndTree, hti));
|
|
Tree_RealHandleSelChange(this);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_UPDATEIMAGE:
|
|
{
|
|
LPSHChangeDWORDAsIDList pImage;
|
|
int iOldImage;
|
|
|
|
pImage = (LPSHChangeDWORDAsIDList)lpnm->pidl;
|
|
iOldImage = pImage->dwItem1;
|
|
Tree_InvalidateImageIndex(this->hwndTree, NULL, iOldImage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pidl)
|
|
{
|
|
ILFree(pidl);
|
|
}
|
|
|
|
OTRelease(lpnRoot);
|
|
return 0L;
|
|
}
|
|
|
|
void Tree_KeyboardContextMenu(PFileCabinet pfc)
|
|
{
|
|
Tree_ContextMenu(pfc, NULL);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Handle notification from the tree view.
|
|
LRESULT Tree_OnNotify(PFileCabinet pfc, LPNMHDR lpnmhdr)
|
|
{
|
|
LPOneTreeNode lpnd;
|
|
|
|
Assert(pfc->hwndTree); // this must be true...
|
|
|
|
// now the ones to handle only if a tree is there.
|
|
switch (lpnmhdr->code) {
|
|
|
|
case NM_SETFOCUS:
|
|
CFileCabinet_OnFocusChange(pfc, FOCUS_TREE);
|
|
break;
|
|
|
|
case NM_KILLFOCUS:
|
|
break;
|
|
|
|
case NM_RCLICK:
|
|
Tree_HandleRClick(pfc);
|
|
return 1;
|
|
|
|
case NM_DBLCLK:
|
|
//
|
|
// In case of double click, force the delayed selection now.
|
|
// Otherwise, we can't prevent double logon dialog problems
|
|
// described below.
|
|
//
|
|
if (!Tree_RealHandleSelChange(pfc))
|
|
{
|
|
//
|
|
// If it failed (canceled, out-of-memory, not accessible),
|
|
// return TRUE (no further action).
|
|
// This code will prevent second logon dialog box when
|
|
// the first one is canceled.
|
|
//
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case NM_RETURN:
|
|
Tree_ValidateCurrentSelection(pfc);
|
|
return TRUE;
|
|
|
|
case NM_CLICK:
|
|
Tree_HandleClick(pfc);
|
|
break;
|
|
|
|
case NM_CUSTOMDRAW:
|
|
#ifdef WINNT
|
|
{
|
|
LPNMTVCUSTOMDRAW lpCD = (LPNMTVCUSTOMDRAW)lpnmhdr;
|
|
|
|
switch (lpCD->nmcd.dwDrawStage) {
|
|
|
|
case CDDS_PREPAINT:
|
|
if (g_fShowCompColor) {
|
|
return CDRF_NOTIFYITEMDRAW;
|
|
} else {
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
break;
|
|
|
|
case CDDS_ITEMPREPAINT:
|
|
|
|
if (!(lpCD->nmcd.uItemState & CDIS_FOCUS)) {
|
|
LPOneTreeNode lpn = (LPOneTreeNode)lpCD->nmcd.lItemlParam;
|
|
|
|
if (OTIsCompressed(lpn)) {
|
|
lpCD->clrText = g_crAltColor;
|
|
}
|
|
}
|
|
|
|
return CDRF_DODEFAULT;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
return CDRF_DODEFAULT;
|
|
|
|
case TVN_BEGINDRAG:
|
|
case TVN_BEGINRDRAG:
|
|
if (!pfc->fExpandingItem)
|
|
{
|
|
Tree_HandleBeginDrag(pfc->hwndMain, lpnmhdr->code == TVN_BEGINRDRAG, (LPNM_TREEVIEW)lpnmhdr);
|
|
}
|
|
else
|
|
{
|
|
MessageBeep(0);
|
|
}
|
|
break;
|
|
|
|
case TVN_ITEMEXPANDING:
|
|
if (!pfc->fExpandingItem)
|
|
{
|
|
return Tree_HandleExpanding(pfc, (LPNM_TREEVIEW) lpnmhdr);
|
|
}
|
|
else
|
|
{
|
|
MessageBeep(0);
|
|
}
|
|
break;
|
|
|
|
case TVN_SELCHANGING:
|
|
if (pfc->fExpandingItem)
|
|
{
|
|
MessageBeep(0);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case TVN_SELCHANGED:
|
|
Tree_HandleSelChange(pfc, ((NM_TREEVIEW*)lpnmhdr)->action != TVC_BYMOUSE);
|
|
break;
|
|
|
|
case TVN_GETDISPINFO:
|
|
Tree_GetDispInfo(pfc, (TV_DISPINFO *)lpnmhdr);
|
|
break;
|
|
|
|
case TVN_BEGINLABELEDIT:
|
|
return Tree_HandleBeginLabelEdit(pfc, (TV_DISPINFO *)lpnmhdr);
|
|
|
|
case TVN_ENDLABELEDIT:
|
|
return Tree_HandleEndLabelEdit(pfc, (TV_DISPINFO *)lpnmhdr);
|
|
|
|
case TVN_DELETEITEM:
|
|
lpnd = (LPOneTreeNode)((NM_TREEVIEW*)lpnmhdr)->itemOld.lParam;
|
|
if (lpnd == pfc->lpndOpen)
|
|
CancelMenuMode(pfc);
|
|
DebugDumpNode(lpnd, TEXT("TVN_DeleteItem"));
|
|
OTRelease(lpnd);
|
|
if (((NM_TREEVIEW*)lpnmhdr)->itemOld.hItem == pfc->htiCut) {
|
|
pfc->htiCut = NULL;
|
|
Tree_NukeCutState(pfc);
|
|
}
|
|
break;
|
|
|
|
}
|
|
return 0L;
|
|
}
|
|
|
|
void Tree_RefreshOneLevel(PFileCabinet pfc, HTREEITEM hItem, BOOL bRecurse)
|
|
{
|
|
HTREEITEM hChild;
|
|
LPOneTreeNode lpn;
|
|
|
|
// we might want a flag to not traverse through unexpanded
|
|
// branches, but failing out all the time might be wrong
|
|
hChild = TreeView_GetChild(pfc->hwndTree, hItem);
|
|
if (!hChild)
|
|
return;
|
|
|
|
lpn = Tree_GetFCTreeData(pfc->hwndTree, hItem);
|
|
if (lpn) // it could have been root.
|
|
OTInvalidateNode(lpn);
|
|
|
|
// Don't recurse if Tree_FillOneLevel failed.
|
|
if (Tree_FillOneLevel(pfc, hItem, TRUE, FALSE) && bRecurse)
|
|
{
|
|
// Make sure we start of at the first child after filling...
|
|
hChild = TreeView_GetChild(pfc->hwndTree, hItem);
|
|
|
|
for ( ; hChild; hChild = TreeView_GetNextSibling(pfc->hwndTree, hChild))
|
|
{
|
|
Tree_RefreshOneLevel(pfc, hChild, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Tree_TrimInvisible(HWND hwndTree, HTREEITEM hItem)
|
|
{
|
|
HTREEITEM hChild;
|
|
|
|
hChild = TreeView_GetChild(hwndTree, hItem);
|
|
if (!hChild)
|
|
return;
|
|
|
|
// Check if the child is visible to see if the level is
|
|
// currently expanded, and if the folder still exists; delete
|
|
// the child and siblings if not
|
|
// Note that the root level is always expanded
|
|
if (hItem && TreeView_GetNextVisible(hwndTree, hItem) != hChild)
|
|
{
|
|
TreeView_Expand(hwndTree, hItem, TVE_COLLAPSE | TVE_COLLAPSERESET);
|
|
}
|
|
else
|
|
{
|
|
for ( ; hChild; hChild = TreeView_GetNextSibling(hwndTree, hChild))
|
|
{
|
|
Tree_TrimInvisible(hwndTree, hChild);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOL Tree_RefreshAll(PFileCabinet pfc)
|
|
{
|
|
HTREEITEM htiOld; // before Tree_RefreshOneLevel
|
|
HTREEITEM htiNew; // after Tree_RefreshOneLevel
|
|
BOOL fRet = FALSE;
|
|
|
|
Assert(!pfc->fChangingFolder);
|
|
|
|
htiOld = TreeView_GetSelection(pfc->hwndTree);
|
|
Tree_TrimInvisible(pfc->hwndTree, NULL);
|
|
Tree_RefreshOneLevel(pfc, NULL, TRUE);
|
|
htiNew = TreeView_GetSelection(pfc->hwndTree);
|
|
|
|
//
|
|
// If hti!=htiNew, it means the node is removed.
|
|
//
|
|
if (htiOld!=htiNew)
|
|
{
|
|
LPOneTreeNode lpnNew;
|
|
|
|
// if our selection got lost, it could have been because the network
|
|
// enumeration isn't reliable.. try forcing it in
|
|
lpnNew = OTGetNodeFromIDList(pfc->pidl, OTGNF_TRYADD);
|
|
if (lpnNew) {
|
|
htiNew = Tree_Build(pfc, pfc->pidl, FALSE, FALSE);
|
|
if (htiNew)
|
|
TreeView_SelectItem(pfc->hwndTree, htiNew);
|
|
} else {
|
|
|
|
DebugMsg(DM_TRACE, TEXT("ca TR - Tree_RefreshAll hti,htiNew=%x,%x fExp=%x"), htiOld, htiNew,
|
|
Tree_GetItemState(pfc->hwndTree, htiNew) & TVIS_EXPANDEDONCE);
|
|
|
|
lpnNew = Tree_GetFCTreeData(pfc->hwndTree, htiNew);
|
|
|
|
//
|
|
// Check if the new node is invalidated or not.
|
|
//
|
|
if (lpnNew && OTIsInvalidated(lpnNew))
|
|
{
|
|
//
|
|
// It is invalidated. It means EnumObjects on this node failed.
|
|
// Go to the parent (to avoid the same error on right-side).
|
|
//
|
|
HTREEITEM htiParent = TreeView_GetParent(pfc->hwndTree, htiNew);
|
|
DebugMsg(DM_TRACE, TEXT("ca TR - Tree_RefreshAll lpnNew is invalid"));
|
|
TreeView_SelectItem(pfc->hwndTree, htiParent);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Handle the selection change synchronously to avoid refreshing
|
|
// invalid right pane.
|
|
//
|
|
if (htiOld != htiNew)
|
|
Tree_RealHandleSelChange(pfc);
|
|
|
|
fRet = TRUE;
|
|
}
|
|
|
|
|
|
#ifdef SETALLVIS
|
|
// We want to make sure the following UpdateWindow does the job
|
|
// completely
|
|
Tree_SetAllVisItemInfos(pfc->hwndTree);
|
|
#endif
|
|
UpdateWindow(pfc->hwndTree);
|
|
return fRet;
|
|
}
|