windows-nt/Source/XPSP1/NT/shell/comctl32/v6/treeview.c
2020-09-26 16:20:57 +08:00

4358 lines
131 KiB
C

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