mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2930 lines
87 KiB
2930 lines
87 KiB
#include "ctlspriv.h"
|
|
#include "treeview.h"
|
|
#include "listview.h"
|
|
|
|
// BUGBUG -- penwin.h is screwy; 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 NEAR PASCAL ChildOfActiveWindow(HWND hwnd);
|
|
|
|
HWND NEAR TV_EditLabel(PTREE pTree, HTREEITEM hItem, LPTSTR pszInitial);
|
|
void NEAR TV_CancelEditTimer(PTREE pTree);
|
|
void FAR PASCAL RescrollEditWindow(HWND hwndEdit);
|
|
void TV_InitCheckBoxes(PTREE pTree);
|
|
BOOL NEAR TV_SetItem(PTREE pTree, const TV_ITEM FAR *ptvi);
|
|
void TV_DeleteHotFonts(PTREE pTree);
|
|
|
|
#define TVBD_FROMWHEEL 0x0001
|
|
#define TVBD_WHEELFORWARD 0x0002
|
|
#define TVBD_WHEELBACK 0x0004
|
|
|
|
#ifdef DEBUG
|
|
void NEAR ValidateTreeItem(TREEITEM FAR * hItem, BOOL bNullOk)
|
|
{
|
|
if (hItem) {
|
|
if (HIWORD(hItem) == 0xFFFF) {
|
|
switch (LOWORD(hItem)) {
|
|
#pragma warning(disable:4309)
|
|
case LOWORD(TVI_ROOT):
|
|
case LOWORD(TVI_FIRST):
|
|
case LOWORD(TVI_LAST):
|
|
case LOWORD(TVI_SORT):
|
|
#pragma warning(default:4309)
|
|
break;
|
|
|
|
default:
|
|
DebugMsg(DM_ERROR, TEXT("ValidateTreeItem() Invalid special item"));
|
|
DebugBreak();
|
|
}
|
|
} else if (hItem->dbg_sig != DEBUG_SIG) {
|
|
DebugMsg(DM_ERROR, TEXT("ValidateTreeItem(): BOGUS HTREEITEM %x"), hItem);
|
|
DebugBreak();
|
|
}
|
|
|
|
} else if (!bNullOk) {
|
|
DebugMsg(DM_ERROR, TEXT("ValidateTreeItem(): NULL HTREEITEM"));
|
|
DebugBreak();
|
|
}
|
|
}
|
|
#else
|
|
#define ValidateTreeItem(hItem, bNullOk)
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Initialize TreeView on library entry -- register SysTreeView class
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#pragma code_seg(CODESEG_INIT)
|
|
|
|
BOOL FAR TV_Init(HINSTANCE hinst)
|
|
{
|
|
WNDCLASS wc;
|
|
|
|
if (!GetClassInfo(hinst, c_szTreeViewClass, &wc)) {
|
|
#ifndef WIN32
|
|
//
|
|
// Use stab WndProc to avoid loading segment on init.
|
|
//
|
|
LRESULT CALLBACK _TV_WndProc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
|
|
wc.lpfnWndProc = _TV_WndProc;
|
|
#else
|
|
wc.lpfnWndProc = TV_WndProc;
|
|
#endif
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hIcon = NULL;
|
|
wc.lpszMenuName = NULL;
|
|
wc.hInstance = hinst;
|
|
wc.lpszClassName = c_szTreeViewClass;
|
|
wc.hbrBackground = NULL;
|
|
wc.style = CS_DBLCLKS | CS_GLOBALCLASS;
|
|
wc.cbWndExtra = sizeof(PTREE);
|
|
wc.cbClsExtra = 0;
|
|
|
|
return RegisterClass(&wc);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#pragma code_seg()
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Sends a TVN_BEGINDRAG or TVN_BEGINRDRAG notification with information in the ptDrag and
|
|
// itemNew fields of an NM_TREEVIEW structure
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL NEAR TV_SendBeginDrag(PTREE pTree, int code, TREEITEM FAR * hItem, int x, int y)
|
|
{
|
|
NM_TREEVIEW nm;
|
|
|
|
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)SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, code, &nm.hdr, pTree->ci.bUnicode);
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Sends a TVN_ITEMEXPANDING or TVN_ITEMEXPANDED notification with information
|
|
// in the action and itemNew fields of an NM_TREEVIEW structure
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL NEAR TV_SendItemExpand(PTREE pTree, int code, TREEITEM FAR * hItem, UINT action)
|
|
{
|
|
NM_TREEVIEW nm;
|
|
|
|
nm.itemNew.mask = 0;
|
|
nm.itemNew.hItem = hItem;
|
|
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 = action & TVE_ACTIONMASK;
|
|
|
|
return (BOOL)SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, code, &nm.hdr, pTree->ci.bUnicode);
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Sends a TVN_SELCHANGING or TVN_SELCHANGED notification with information in
|
|
// the itemOld and itemNew fields of an NM_TREEVIEW structure
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL NEAR TV_SendSelChange(PTREE pTree, int code, TREEITEM FAR * hOldItem, TREEITEM FAR * 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)SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, code, &nm.hdr, pTree->ci.bUnicode);
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Returns the first visible item above the given item in the tree.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
TREEITEM FAR * NEAR TV_GetPrevVisItem(TREEITEM FAR * hItem)
|
|
{
|
|
TREEITEM FAR * hParent = hItem->hParent;
|
|
TREEITEM FAR * hWalk;
|
|
|
|
ValidateTreeItem(hItem, FALSE);
|
|
|
|
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 FAR * NEAR TV_GetNextVisItem(TREEITEM FAR * hItem)
|
|
{
|
|
ValidateTreeItem(hItem, FALSE);
|
|
|
|
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 FAR * NEAR TV_CheckHit(PTREE pTree, int x, int y, UINT FAR *wHitCode)
|
|
{
|
|
int index;
|
|
TREEITEM FAR * hItem = pTree->hTop;
|
|
int cxState;
|
|
|
|
TV_ITEM 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;
|
|
|
|
index = y / pTree->cyItem;
|
|
|
|
while (hItem && index--)
|
|
hItem = TV_GetNextVisItem(hItem);
|
|
|
|
if (!hItem)
|
|
{
|
|
*wHitCode = TVHT_NOWHERE;
|
|
return NULL;
|
|
}
|
|
|
|
x -= 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;
|
|
}
|
|
|
|
|
|
void NEAR TV_SendRButtonDown(PTREE pTree, int x, int y)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
UINT wHitCode;
|
|
TREEITEM FAR * hItem = TV_CheckHit(pTree, x, y, &wHitCode);
|
|
TREEITEM FAR * hOldDrop = pTree->hDropTarget;
|
|
|
|
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
|
|
//
|
|
|
|
// Show drop highlight to give some indication the item was clicked
|
|
if (hItem != pTree->hCaret)
|
|
{
|
|
TV_SelectItem(pTree, TVGN_DROPHILITE, hItem, FALSE, TRUE, TVC_BYMOUSE);
|
|
}
|
|
|
|
if (CheckForDragBegin(pTree->ci.hwnd, x, y))
|
|
{
|
|
// let them start dragging
|
|
if (hItem)
|
|
TV_SendBeginDrag(pTree, TVN_BEGINRDRAG, hItem, x, y);
|
|
}
|
|
else
|
|
{
|
|
SetFocus(pTree->ci.hwnd); // Activate this window like listview...
|
|
fRet = !SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, NM_RCLICK, NULL, pTree->ci.bUnicode);
|
|
}
|
|
|
|
// Reset the drop highlight before continuing if it's still what we changed
|
|
// it to
|
|
if (pTree->hDropTarget == hItem)
|
|
{
|
|
TV_SelectItem(pTree, TVGN_DROPHILITE, hOldDrop, FALSE, TRUE, TVC_BYMOUSE);
|
|
}
|
|
|
|
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 NEAR TV_InvalidateItem(PTREE pTree, TREEITEM FAR * hItem, UINT fRedraw)
|
|
{
|
|
RECT rc;
|
|
|
|
if (hItem && pTree->fRedraw && TV_GetItemRect(pTree, hItem, &rc, FALSE))
|
|
{
|
|
RedrawWindow(pTree->ci.hwnd, &rc, NULL, fRedraw);
|
|
}
|
|
}
|
|
|
|
int FAR PASCAL ITEM_OFFSET(PTREE pTree, HTREEITEM hItem)
|
|
{
|
|
int i = ((pTree->cxIndent * hItem->iLevel) + pTree->cxImage + pTree->cxState);
|
|
|
|
if ((pTree->ci.style & (TVS_HASLINES |TVS_LINESATROOT)) == (TVS_HASLINES | TVS_LINESATROOT))
|
|
i += pTree->cxIndent;
|
|
|
|
return i;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// 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 NEAR TV_GetItemRect(PTREE pTree, TREEITEM FAR * hItem, LPRECT lprc, BOOL bItemRect)
|
|
{
|
|
UINT iOffset;
|
|
|
|
if (!hItem)
|
|
return FALSE;
|
|
|
|
ValidateTreeItem(hItem, FALSE);
|
|
|
|
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);
|
|
if (pTree->himlState) {
|
|
// if this particular one doesn't have an image, subtract off cxState;
|
|
TV_ITEM sItem;
|
|
|
|
TV_GetItem(pTree, hItem, 0, &sItem);
|
|
if (!TV_StateIndex(&sItem))
|
|
lprc->left -= pTree->cxState;
|
|
}
|
|
|
|
if (!pTree->hImageList)
|
|
lprc->left -= pTree->cxImage;
|
|
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;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void NEAR 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
|
|
}
|
|
}
|
|
|
|
void NEAR TV_SetItemRecurse(PTREE pTree, TREEITEM FAR *hItem, TV_ITEM FAR *ptvi)
|
|
{
|
|
while (hItem) {
|
|
ptvi->hItem = hItem;
|
|
TV_SetItem(pTree, ptvi);
|
|
if (hItem->hKids) {
|
|
TV_SetItemRecurse(pTree, hItem->hKids, ptvi);
|
|
}
|
|
|
|
hItem = hItem->hNext;
|
|
}
|
|
}
|
|
|
|
void NEAR TV_DoExpandRecurse(PTREE pTree, TREEITEM FAR *hItem, BOOL fNotify)
|
|
{
|
|
while (hItem) {
|
|
TV_Expand(pTree, TVE_EXPAND, hItem, fNotify);
|
|
if (hItem->hKids) {
|
|
TV_DoExpandRecurse(pTree, hItem->hKids, fNotify);
|
|
}
|
|
hItem = hItem->hNext;
|
|
}
|
|
}
|
|
|
|
|
|
void NEAR TV_ExpandRecurse(PTREE pTree, TREEITEM FAR *hItem, BOOL fNotify)
|
|
{
|
|
BOOL fRedraw = pTree->fRedraw;
|
|
|
|
TV_OnSetRedraw(pTree, FALSE);
|
|
TV_Expand(pTree, TVE_EXPAND, hItem, fNotify);
|
|
TV_DoExpandRecurse(pTree, hItem->hKids, fNotify);
|
|
TV_OnSetRedraw(pTree, fRedraw);
|
|
}
|
|
|
|
|
|
void NEAR TV_ExpandParents(PTREE pTree, TREEITEM FAR *hItem, BOOL fNotify)
|
|
{
|
|
|
|
if (hItem->hParent) {
|
|
TV_ExpandParents(pTree, hItem->hParent, fNotify);
|
|
|
|
// make sure this item is not in a collapsed branch
|
|
if (!(hItem->hParent->state & TVIS_EXPANDED)) {
|
|
TV_Expand(pTree, TVE_EXPAND, hItem->hParent, fNotify);
|
|
}
|
|
}
|
|
}
|
|
|
|
// makes sure an item is expanded and scrolled into view
|
|
|
|
BOOL NEAR TV_EnsureVisible(PTREE pTree, TREEITEM FAR * hItem)
|
|
{
|
|
TV_ExpandParents(pTree, hItem, TRUE);
|
|
return TV_ScrollIntoView(pTree, hItem);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// 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)
|
|
// fNotify send notify to parent window
|
|
// fUpdate do UpdateWindow() to force sync painting
|
|
// 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 NEAR TV_SelectItem(PTREE pTree, UINT wType, TREEITEM FAR * hItem, BOOL fNotify,
|
|
BOOL fUpdateNow, UINT action)
|
|
{
|
|
UINT uRDWFlags = RDW_INVALIDATE;
|
|
|
|
if (pTree->hImageList && (ImageList_GetBkColor(pTree->hImageList) == (COLORREF)-1))
|
|
uRDWFlags |= RDW_ERASE;
|
|
|
|
ValidateTreeItem(hItem, TRUE);
|
|
|
|
switch (wType) {
|
|
|
|
case TVGN_FIRSTVISIBLE:
|
|
if (!hItem)
|
|
return FALSE;
|
|
|
|
Assert(ITEM_VISIBLE(hItem));
|
|
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 (fUpdateNow)
|
|
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 FAR * hOldSel;
|
|
|
|
if (fNotify && 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, fNotify);
|
|
|
|
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 (fNotify)
|
|
TV_SendSelChange(pTree, TVN_SELCHANGED, hOldSel, hItem, action);
|
|
|
|
if (fUpdateNow)
|
|
UpdateWindow(pTree->ci.hwnd);
|
|
|
|
#ifdef ACTIVE_ACCESSIBILITY
|
|
MyNotifyWinEvent(EVENT_OBJECT_FOCUS, pTree->ci.hwnd, OBJID_CLIENT,
|
|
(long)hItem);
|
|
MyNotifyWinEvent(EVENT_OBJECT_SELECTION, pTree->ci.hwnd, OBJID_CLIENT,
|
|
(long)hItem);
|
|
#endif
|
|
}
|
|
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 NEAR 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 NEAR TV_Expand(PTREE pTree, UINT wCode, TREEITEM FAR * hItem, BOOL fNotify)
|
|
{
|
|
WORD fOldState;
|
|
UINT cntVisDescendants;
|
|
TV_ITEM sItem;
|
|
|
|
ValidateTreeItem(hItem, FALSE);
|
|
|
|
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, hItem, 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 & TVIS_EXPANDED);
|
|
|
|
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 (fOldState == (hItem->state & 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 == TVIS_EXPANDED) &&
|
|
(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 (pTree->hCaret)
|
|
{
|
|
TREEITEM FAR * hWalk = pTree->hCaret;
|
|
int i = hWalk->iLevel - hItem->iLevel;
|
|
|
|
for (i = hWalk->iLevel - hItem->iLevel; i > 0; i--)
|
|
hWalk = hWalk->hParent;
|
|
|
|
if (hWalk == hItem)
|
|
TV_SelectItem(pTree, TVGN_CARET, hItem, fNotify, TRUE, TVC_UNKNOWN);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (fNotify)
|
|
TV_SendItemExpand(pTree, TVN_ITEMEXPANDED, hItem, wCode);
|
|
|
|
hItem->state |= TVIS_EXPANDEDONCE;
|
|
|
|
if ((wCode & (TVE_ACTIONMASK | TVE_COLLAPSERESET)) == (TVE_COLLAPSE | TVE_COLLAPSERESET))
|
|
{
|
|
TV_ResetItem(pTree, hItem);
|
|
}
|
|
|
|
#ifdef ACTIVE_ACCESSIBILITY
|
|
MyNotifyWinEvent(EVENT_OBJECT_STATECHANGE, pTree->ci.hwnd, OBJID_CLIENT,
|
|
(long)hItem);
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL PASCAL BetweenItems(PTREE pTree, HTREEITEM hItem, HTREEITEM hItemStart, HTREEITEM hItemEnd)
|
|
{
|
|
while ((hItemStart = TV_GetNextVisItem(hItemStart)) && (hItemEnd != hItemStart))
|
|
{
|
|
if (hItem == hItemStart)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef FE_IME
|
|
// Now only Korean version is interested in incremental search with composition string.
|
|
#define GET_COMP_STRING(hImc, dwFlags, pszCompStr) \
|
|
{ \
|
|
int iNumComp; \
|
|
(pszCompStr) = (PSTR)LocalAlloc(LPTR, 1); \
|
|
if (iNumComp = (int)ImmGetCompositionString((hImc), (dwFlags), NULL, 0)) \
|
|
if ((pszCompStr) = (PSTR)LocalReAlloc((pszCompStr), iNumComp+1, LMEM_MOVEABLE)) \
|
|
{ \
|
|
ImmGetCompositionString((hImc), (dwFlags), (pszCompStr), iNumComp+1); \
|
|
(pszCompStr)[iNumComp] = '\0'; \
|
|
} \
|
|
}
|
|
|
|
#define FREE_COMP_STRING(pszCompStr) LocalFree((HLOCAL)(pszCompStr))
|
|
|
|
BOOL NEAR TV_OnImeComposition(PTREE pTree, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LPSTR lpsz;
|
|
int iCycle = 0;
|
|
static HTREEITEM hItemStart = NULL;
|
|
HTREEITEM hItem;
|
|
char szTemp[MAX_PATH];
|
|
TV_ITEM ti;
|
|
LPSTR lpszAlt = NULL; // use only if SameChar
|
|
int iLen;
|
|
HIMC hImc;
|
|
char *pszCompStr;
|
|
BOOL fRet = TRUE;
|
|
|
|
if (hImc = ImmGetContext(pTree->ci.hwnd))
|
|
{
|
|
if (lParam & GCS_RESULTSTR)
|
|
{
|
|
fRet = FALSE;
|
|
GET_COMP_STRING(hImc, GCS_RESULTSTR, pszCompStr);
|
|
if (pszCompStr)
|
|
{
|
|
IncrementSearchImeCompStr(FALSE, pszCompStr, &lpsz);
|
|
FREE_COMP_STRING(pszCompStr);
|
|
}
|
|
}
|
|
if (lParam & GCS_COMPSTR)
|
|
{
|
|
fRet = TRUE;
|
|
GET_COMP_STRING(hImc, GCS_COMPSTR, pszCompStr);
|
|
if (pszCompStr)
|
|
{
|
|
if (IncrementSearchImeCompStr(TRUE, pszCompStr, &lpsz)) {
|
|
if (pTree->hCaret) {
|
|
hItemStart = pTree->hCaret;
|
|
} else if (pTree->hRoot && pTree->hRoot->hKids) {
|
|
hItemStart = pTree->hRoot->hKids;
|
|
} else
|
|
return fRet;
|
|
}
|
|
|
|
if (!lpsz || !*lpsz || !pTree->hRoot || !pTree->hRoot->hKids)
|
|
return fRet;
|
|
|
|
hItem = hItemStart;
|
|
ti.cchTextMax = sizeof(szTemp);
|
|
iLen = lstrlen(lpsz);
|
|
if (iLen > 2 && SameDBCSChars(lpsz, (WORD)((BYTE)lpsz[0] << 8 | (BYTE)lpsz[1])))
|
|
lpszAlt = lpsz + iLen - 2;
|
|
|
|
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) &&
|
|
HIWORD(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, 2) &&
|
|
BetweenItems(pTree, hItem, pTree->hCaret, hItemStart)))
|
|
{
|
|
DebugMsg(DM_TRACE, "Selecting");
|
|
TV_SelectItem(pTree, TVGN_CARET, hItem, TRUE, TRUE, TVC_BYKEYBOARD);
|
|
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(0, NULL);
|
|
TV_OnImeComposition(pTree, wParam, lParam);
|
|
} else {
|
|
if (!g_iIncrSearchFailed)
|
|
MessageBeep(0);
|
|
g_iIncrSearchFailed++;
|
|
}
|
|
FREE_COMP_STRING(pszCompStr);
|
|
}
|
|
}
|
|
ImmReleaseContext(pTree->ci.hwnd, hImc);
|
|
}
|
|
return fRet;
|
|
}
|
|
#endif
|
|
|
|
|
|
void NEAR TV_OnChar(PTREE pTree, UINT ch, int cRepeat)
|
|
{
|
|
LPTSTR lpsz;
|
|
int iCycle = 0;
|
|
static HTREEITEM hItemStart = NULL;
|
|
HTREEITEM hItem;
|
|
TCHAR szTemp[MAX_PATH];
|
|
TV_ITEM ti;
|
|
LPTSTR lpszAlt = NULL; // use only if SameChar
|
|
int iLen;
|
|
|
|
if (IncrementSearchString(ch, &lpsz)) {
|
|
if (pTree->hCaret) {
|
|
hItemStart = pTree->hCaret;
|
|
} else if (pTree->hRoot && pTree->hRoot->hKids) {
|
|
hItemStart = pTree->hRoot->hKids;
|
|
} else
|
|
return;
|
|
}
|
|
|
|
if (!lpsz || !*lpsz || !pTree->hRoot || !pTree->hRoot->hKids)
|
|
return;
|
|
|
|
hItem = hItemStart;
|
|
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) &&
|
|
HIWORD(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, hItemStart)))
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("Selecting"));
|
|
TV_SelectItem(pTree, TVGN_CARET, hItem, TRUE, TRUE, TVC_BYKEYBOARD);
|
|
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(0, NULL);
|
|
TV_OnChar(pTree, ch, cRepeat);
|
|
} else {
|
|
if (!g_iIncrSearchFailed)
|
|
MessageBeep(0);
|
|
g_iIncrSearchFailed++;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Handle WM_KEYDOWN messages
|
|
// If control key is down, treat keys as scroll codes; otherwise, treat keys
|
|
// as caret position changes.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL NEAR TV_KeyDown(PTREE pTree, UINT wKey, DWORD dwKeyData)
|
|
{
|
|
TREEITEM FAR * hItem;
|
|
UINT wShownIndex;
|
|
TV_KEYDOWN nm;
|
|
BOOL fPuntChar;
|
|
BOOL ret = TRUE;
|
|
|
|
// Notify
|
|
nm.wVKey = wKey;
|
|
fPuntChar = (BOOL)SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, TVN_KEYDOWN, &nm.hdr, pTree->ci.bUnicode);
|
|
|
|
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)SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, NM_RETURN, NULL, pTree->ci.bUnicode);
|
|
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, TRUE, TRUE, TVC_BYKEYBOARD);
|
|
|
|
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);
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Sets the tree's indent width per hierarchy level and recompute widths.
|
|
//
|
|
// sets cxIndent
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void NEAR TV_SetIndent(PTREE pTree, UINT 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 = 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 NEAR TV_SetItemHeight(PTREE pTree)
|
|
{
|
|
// height MUST be even with TVS_HASLINES -- go ahead and make it always even
|
|
pTree->cyItem = (max(pTree->cyImage, pTree->cyText) + 1) & ~1;
|
|
pTree->cFullVisible = pTree->cyWnd / pTree->cyItem;
|
|
|
|
TV_CreateIndentBmps(pTree);
|
|
TV_CalcScrollBars(pTree);
|
|
}
|
|
|
|
// BUGBUG: does not deal with hfont == NULL
|
|
|
|
void NEAR 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;
|
|
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;
|
|
|
|
GetTextExtentPoint(hdc, &c, 1, &size);
|
|
pTree->cyText = 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);
|
|
|
|
if (pTree->cxIndent == 0) // first time init?
|
|
{
|
|
if (!pTree->cyItem) pTree->cyItem = pTree->cyText;
|
|
TV_SetIndent(pTree, 16 /*g_cxSmIcon*/ + MAGIC_INDENT);
|
|
}
|
|
|
|
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 NEAR PASCAL TV_CreateBoldFont(PTREE pTree)
|
|
{
|
|
LOGFONT lf;
|
|
|
|
if (pTree->hFontBold)
|
|
DeleteObject (pTree->hFontBold);
|
|
|
|
GetObject(pTree->hFont, sizeof (lf), &lf);
|
|
lf.lfWeight = FW_BOLD;
|
|
pTree->hFontBold = CreateFontIndirect(&lf);
|
|
}
|
|
|
|
|
|
HIMAGELIST NEAR TV_SetImageList(PTREE pTree, HIMAGELIST hImage, int iImageIndex)
|
|
{
|
|
int cx, cy;
|
|
HIMAGELIST hImageOld = NULL;
|
|
|
|
switch (iImageIndex) {
|
|
|
|
case TVSIL_STATE:
|
|
|
|
hImageOld = pTree->himlState;
|
|
pTree->himlState = hImage;
|
|
if (hImage) {
|
|
ImageList_GetIconSize(hImage, &pTree->cxState , &pTree->cyState);
|
|
} else {
|
|
pTree->cxState = 0;
|
|
}
|
|
break;
|
|
|
|
case TVSIL_NORMAL:
|
|
hImageOld = pTree->hImageList;
|
|
if (hImage && ImageList_GetIconSize(hImage, &cx, &cy))
|
|
{
|
|
pTree->cxImage = (cx + MAGIC_INDENT);
|
|
pTree->cyImage = cy;
|
|
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 = pTree->cyImage = 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;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Gets the item with the described relationship to the given item, NULL if
|
|
// no item can be found with that relationship.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
TREEITEM FAR * NEAR TV_GetNextItem(PTREE pTree, TREEITEM FAR * hItem, UINT 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_CHILD:
|
|
// Ian's wish for orthogonality
|
|
if (!hItem) // probably should include || (hItem == TVI_ROOT))
|
|
return pTree->hRoot->hKids;
|
|
break;
|
|
}
|
|
|
|
// all of these require a valid hItem
|
|
if (!hItem)
|
|
return NULL;
|
|
|
|
ValidateTreeItem(hItem, FALSE);
|
|
|
|
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 FAR * 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 NEAR 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;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// recomputes tree's fields that rely on the tree's client window size
|
|
//
|
|
// sets cxWnd, cyWnd, cFullVisible
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL NEAR TV_SizeWnd(PTREE pTree, UINT cxWnd, UINT cyWnd)
|
|
{
|
|
if (!cxWnd || !cyWnd)
|
|
{
|
|
RECT rc;
|
|
GetClientRect(pTree->ci.hwnd, &rc);
|
|
cxWnd = rc.right;
|
|
cyWnd = rc.bottom;
|
|
}
|
|
|
|
// if ((pTree->cxWnd == cxWnd) && (pTree->cyWnd == cyWnd))
|
|
// return FALSE;
|
|
|
|
pTree->cxWnd = cxWnd;
|
|
pTree->cyWnd = cyWnd;
|
|
pTree->cFullVisible = cyWnd / pTree->cyItem;
|
|
TV_CalcScrollBars(pTree);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void TV_HandleStateIconClick(PTREE pTree, HTREEITEM hItem)
|
|
{
|
|
TVITEM 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);
|
|
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// 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 NEAR TV_ButtonDown(PTREE pTree, UINT wMsg, UINT wFlags, int x, int y, UINT TVBD_flags)
|
|
{
|
|
UINT wHitCode;
|
|
TREEITEM FAR * hItem;
|
|
|
|
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);
|
|
|
|
if (wMsg == WM_LBUTTONDBLCLK)
|
|
{
|
|
//
|
|
// Cancel any name editing that might happen.
|
|
//
|
|
|
|
TV_CancelEditTimer(pTree);
|
|
|
|
if (wHitCode & (TVHT_ONITEM | TVHT_ONITEMBUTTON)) {
|
|
|
|
if (!SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, wFlags & MK_RBUTTON ? NM_RDBLCLK : NM_DBLCLK, NULL, pTree->ci.bUnicode))
|
|
TV_Expand(pTree, TVE_TOGGLE, hItem, TRUE);
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
if (!SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, wFlags & MK_RBUTTON ? NM_RDBLCLK : NM_DBLCLK, NULL, pTree->ci.bUnicode))
|
|
TV_Expand(pTree, TVE_TOGGLE, hItem, TRUE);
|
|
|
|
}
|
|
|
|
pTree->fScrollWait = FALSE;
|
|
|
|
} else { // WM_LBUTTONDOWN
|
|
|
|
if (wHitCode == TVHT_ONITEMBUTTON)
|
|
{
|
|
if (!SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, NM_CLICK, NULL, pTree->ci.bUnicode)) {
|
|
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)
|
|
{
|
|
BOOL fSameItem, bDragging;
|
|
TREEITEM FAR * hOldDrop;
|
|
|
|
Assert(hItem);
|
|
|
|
fSameItem = (hItem == pTree->hCaret);
|
|
|
|
// Show drop highlight to give some indication the item was clicked
|
|
hOldDrop = pTree->hDropTarget;
|
|
if (!fSameItem)
|
|
{
|
|
TV_SelectItem(pTree, TVGN_DROPHILITE, hItem, FALSE, FALSE, TVC_BYMOUSE);
|
|
}
|
|
|
|
if (TVBD_flags & TVBD_FROMWHEEL)
|
|
bDragging = FALSE;
|
|
else
|
|
bDragging = (pTree->ci.style & TVS_DISABLEDRAGDROP) ?
|
|
FALSE : CheckForDragBegin(pTree->ci.hwnd, x, y);
|
|
|
|
// Reset the drop highlight before continuing, but don't update
|
|
// right away, in case we are just about to change the selection
|
|
TV_SelectItem(pTree, TVGN_DROPHILITE, hOldDrop, FALSE, FALSE, TVC_BYMOUSE);
|
|
|
|
if (bDragging)
|
|
{
|
|
TV_SendBeginDrag(pTree, TVN_BEGINDRAG, hItem, x, y);
|
|
return;
|
|
}
|
|
|
|
if (!SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, NM_CLICK, NULL, pTree->ci.bUnicode)) {
|
|
|
|
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, TRUE, TRUE, TVC_BYMOUSE);
|
|
|
|
if (fSameItem && (wHitCode & TVHT_ONITEMLABEL) && pTree->fFocus)
|
|
{
|
|
//
|
|
// 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 (!pTree->fFocus)
|
|
SetFocus(pTree->ci.hwnd);
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Gets the item's text, data, and/or image.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
BOOL NEAR TV_OnGetItem(PTREE pTree, LPTV_ITEM ptvi)
|
|
{
|
|
// BUGBUG: validate
|
|
if (!ptvi || !ptvi->hItem)
|
|
return FALSE;
|
|
|
|
TV_GetItem(pTree, ptvi->hItem, ptvi->mask, ptvi);
|
|
|
|
return TRUE; // success
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
BOOL NEAR TV_OnGetItemA(PTREE pTree, LPTV_ITEMA ptvi) {
|
|
BOOL bRet;
|
|
LPSTR pszA = NULL;
|
|
LPWSTR pszW = NULL;
|
|
|
|
//HACK Alert! This code assumes that TV_ITEMA is exactly the same
|
|
// as TV_ITEMW except for the text pointer in the TV_ITEM
|
|
Assert(sizeof(TV_ITEMA) == sizeof(TV_ITEMW));
|
|
|
|
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, (LPTV_ITEMW)ptvi);
|
|
if (pszA) {
|
|
ConvertWToAN(pTree->ci.uiCodePage, pszA, ptvi->cchTextMax, (LPWSTR)(ptvi->pszText), -1);
|
|
LocalFree(pszW);
|
|
ptvi->pszText = pszA;
|
|
}
|
|
return bRet;
|
|
}
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Sets the item's text, data, and/or image.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
BOOL NEAR TV_SetItem(PTREE pTree, const TV_ITEM FAR *ptvi);
|
|
|
|
#ifdef UNICODE
|
|
BOOL NEAR TV_SetItemA(PTREE pTree, TV_ITEMA FAR *ptvi) {
|
|
LPSTR pszA = NULL;
|
|
BOOL lRet;
|
|
|
|
//HACK Alert! This code assumes that TV_ITEMA is exactly the same
|
|
// as TV_ITEMW except for the text pointer in the TV_ITEM
|
|
Assert(sizeof(TV_ITEMA) == sizeof(TV_ITEMW));
|
|
|
|
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, (const TV_ITEM FAR *)ptvi);
|
|
|
|
if (pszA) {
|
|
FreeProducedString(ptvi->pszText);
|
|
ptvi->pszText = pszA;
|
|
}
|
|
|
|
return lRet;
|
|
}
|
|
#endif
|
|
|
|
BOOL NEAR TV_SetItem(PTREE pTree, const TV_ITEM FAR *ptvi)
|
|
{
|
|
UINT uRDWFlags = RDW_INVALIDATE;
|
|
BOOL fEraseIfTransparent = FALSE;
|
|
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
|
|
#ifdef ACTIVE_ACCESSIBILITY
|
|
BOOL fName = FALSE;
|
|
BOOL fFocusSel = FALSE;
|
|
#endif
|
|
|
|
if (!ptvi || !ptvi->hItem)
|
|
return FALSE;
|
|
|
|
ValidateTreeItem(ptvi->hItem, FALSE);
|
|
|
|
// BUGBUG: send ITEMCHANING and ITEMCHANGED msgs
|
|
|
|
if (ptvi->mask & TVIF_TEXT)
|
|
{
|
|
uRDWFlags = RDW_INVALIDATE |RDW_ERASE;
|
|
bActualChange = TRUE;
|
|
if (ptvi->hItem->lpstr != LPSTR_TEXTCALLBACK)
|
|
{
|
|
Str_Set(&ptvi->hItem->lpstr, NULL);
|
|
}
|
|
|
|
if (!ptvi->pszText || (ptvi->pszText == LPSTR_TEXTCALLBACK))
|
|
{
|
|
ptvi->hItem->lpstr = LPSTR_TEXTCALLBACK;
|
|
}
|
|
else
|
|
{
|
|
if (ptvi->hItem->lpstr == LPSTR_TEXTCALLBACK)
|
|
ptvi->hItem->lpstr = NULL; // otherwise realloc's gonna bomb
|
|
if (!Str_Set(&ptvi->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"));
|
|
ptvi->hItem->lpstr = LPSTR_TEXTCALLBACK;
|
|
}
|
|
}
|
|
|
|
ptvi->hItem->iWidth = 0;
|
|
TV_ScrollBarsAfterSetWidth(pTree, ptvi->hItem);
|
|
|
|
#ifdef ACTIVE_ACCESSIBILITY
|
|
fName = TRUE;
|
|
#endif
|
|
}
|
|
|
|
if (ptvi->mask & TVIF_PARAM)
|
|
{
|
|
bActualChange = TRUE;
|
|
ptvi->hItem->lParam = ptvi->lParam;
|
|
}
|
|
|
|
if (ptvi->mask & TVIF_IMAGE)
|
|
{
|
|
if (ptvi->hItem->iImage != (WORD)I_IMAGECALLBACK) {
|
|
bActualChange = TRUE;
|
|
fEraseIfTransparent = TRUE;
|
|
if (pTree->hImageList && (ImageList_GetBkColor(pTree->hImageList) == (COLORREF)-1))
|
|
uRDWFlags |= RDW_ERASE;
|
|
|
|
}
|
|
ptvi->hItem->iImage = ptvi->iImage;
|
|
}
|
|
|
|
if (ptvi->mask & TVIF_SELECTEDIMAGE)
|
|
{
|
|
if (ptvi->hItem->iSelectedImage != (WORD)I_IMAGECALLBACK)
|
|
bActualChange = TRUE;
|
|
ptvi->hItem->iSelectedImage = ptvi->iSelectedImage;
|
|
}
|
|
|
|
if (ptvi->mask & TVIF_CHILDREN)
|
|
{
|
|
if (ptvi->hItem->fKids != KIDS_CALLBACK)
|
|
bActualChange = TRUE;
|
|
|
|
if (ptvi->cChildren == I_CHILDRENCALLBACK) {
|
|
ptvi->hItem->fKids = KIDS_CALLBACK;
|
|
} else {
|
|
if (ptvi->cChildren)
|
|
ptvi->hItem->fKids = KIDS_FORCE_YES;
|
|
else
|
|
ptvi->hItem->fKids = KIDS_FORCE_NO;
|
|
}
|
|
|
|
//
|
|
// If this item currently has no kid, reset the item.
|
|
//
|
|
if ((ptvi->cChildren == I_CHILDRENCALLBACK) && (ptvi->hItem->hKids == NULL))
|
|
{
|
|
ptvi->hItem->state &= ~TVIS_EXPANDEDONCE;
|
|
if (ptvi->hItem->hParent)
|
|
ptvi->hItem->state &= ~TVIS_EXPANDED;
|
|
}
|
|
}
|
|
|
|
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 = (ptvi->hItem->state ^ ptvi->state) & ptvi->stateMask;
|
|
|
|
if (change)
|
|
{
|
|
// BUGBUG: (TVIS_SELECTED | TVIS_DROPHILITED) changes
|
|
// should effect tree state
|
|
ptvi->hItem->state ^= change;
|
|
bActualChange = TRUE;
|
|
fEraseIfTransparent = TRUE;
|
|
|
|
if (ptvi->hItem->state & TVIS_BOLD) {
|
|
if (!pTree->hFontBold)
|
|
TV_CreateBoldFont(pTree);
|
|
}
|
|
|
|
if (change & TVIS_BOLD) {
|
|
// do this because changing the boldness
|
|
uRDWFlags |= RDW_ERASE;
|
|
ptvi->hItem->iWidth = 0;
|
|
TV_ScrollBarsAfterSetWidth(pTree, ptvi->hItem);
|
|
}
|
|
|
|
#ifdef ACTIVE_ACCESSIBILITY
|
|
fFocusSel = ((change & TVIS_SELECTED) != 0);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// 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 != ptvi->hItem))
|
|
{
|
|
if (fEraseIfTransparent) {
|
|
if (pTree->hImageList) {
|
|
if (ImageList_GetBkColor(pTree->hImageList) == CLR_NONE) {
|
|
uRDWFlags |= RDW_ERASE;
|
|
}
|
|
}
|
|
|
|
}
|
|
TV_InvalidateItem(pTree, ptvi->hItem, uRDWFlags);
|
|
|
|
// REVIEW: we might need to update the scroll bars if the
|
|
// text length changed!
|
|
}
|
|
|
|
#ifdef ACTIVE_ACCESSIBILITY
|
|
if (bActualChange)
|
|
{
|
|
if (fName)
|
|
MyNotifyWinEvent(EVENT_OBJECT_NAMECHANGE, pTree->ci.hwnd, OBJID_CLIENT,
|
|
(long)ptvi->hItem);
|
|
|
|
if (fFocusSel)
|
|
{
|
|
MyNotifyWinEvent(EVENT_OBJECT_FOCUS, pTree->ci.hwnd, OBJID_CLIENT,
|
|
(long)ptvi->hItem);
|
|
MyNotifyWinEvent(((ptvi->hItem->state & TVIS_SELECTED) ?
|
|
EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE),
|
|
pTree->ci.hwnd, OBJID_CLIENT, (long)ptvi->hItem);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Calls TV_CheckHit to get the hit test results and then package it in a
|
|
// structure back to the app.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
HTREEITEM NEAR TV_OnHitTest(PTREE pTree, LPTV_HITTESTINFO lptvh)
|
|
{
|
|
if (!lptvh)
|
|
return 0; //BUGBUG: 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;
|
|
}
|
|
|
|
void TV_HandleTTNShow(PTREE pTree)
|
|
{
|
|
TOOLINFO ToolInfo;
|
|
RECT rc;
|
|
|
|
ToolInfo.cbSize = sizeof(ToolInfo);
|
|
ToolInfo.hwnd = pTree->ci.hwnd;
|
|
ToolInfo.uId = (UINT)pTree->ci.hwnd;
|
|
|
|
SendMessage(pTree->hwndToolTips,TTM_GETTOOLINFO,0,(LPARAM)&ToolInfo);
|
|
TV_GetItemRect(pTree, (TREEITEM*)ToolInfo.lParam, &rc, TRUE);
|
|
ClientToScreen(pTree->ci.hwnd,(LPPOINT)&rc);
|
|
SetWindowPos(pTree->hwndToolTips, NULL, rc.left - g_cxBorder, rc.top - g_cyBorder,0,0,
|
|
SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
|
|
|
|
|
|
}
|
|
|
|
TREEITEM* TV_UpdateToolTipTarget(PTREE pTree )
|
|
{
|
|
RECT rc;
|
|
TOOLINFO ToolInfo;
|
|
TREEITEM *hItem;
|
|
UINT wHitCode;
|
|
|
|
GetCursorPos((LPPOINT)&rc);
|
|
ScreenToClient(pTree->ci.hwnd, (LPPOINT)&rc);
|
|
hItem = TV_CheckHit(pTree,rc.left,rc.top,&wHitCode);
|
|
|
|
if (!((wHitCode & TVHT_ONITEM) && TV_IsItemTruncated(pTree, hItem, &rc))) {
|
|
hItem = NULL;
|
|
}
|
|
|
|
ToolInfo.cbSize = sizeof(ToolInfo);
|
|
ToolInfo.hwnd = pTree->ci.hwnd;
|
|
ToolInfo.uId = (UINT)pTree->ci.hwnd;
|
|
SendMessage(pTree->hwndToolTips,TTM_GETTOOLINFO,0,(LPARAM)&ToolInfo);
|
|
// update the item we're showing the bubble for...
|
|
if (ToolInfo.lParam != (LPARAM)hItem) {
|
|
// the hide will keep us from flashing
|
|
ShowWindow(pTree->hwndToolTips, SW_HIDE);
|
|
UpdateWindow(pTree->hwndToolTips);
|
|
ToolInfo.lParam = (LPARAM)hItem;
|
|
SendMessage(pTree->hwndToolTips, TTM_SETTOOLINFO, 0, (LPARAM)&ToolInfo);
|
|
}
|
|
return hItem;
|
|
}
|
|
|
|
void TV_HandleNeedText(PTREE pTree, LPTOOLTIPTEXT lpttt)
|
|
{
|
|
TOOLINFO ToolInfo;
|
|
// COLORREF clrBk = CLR_DEFAULT;
|
|
// COLORREF clrText = CLR_DEFAULT;
|
|
TV_ITEM tvItem;
|
|
|
|
ToolInfo.cbSize = sizeof(ToolInfo);
|
|
ToolInfo.hwnd = pTree->ci.hwnd;
|
|
ToolInfo.uId = (UINT)pTree->ci.hwnd;
|
|
|
|
SendMessage(pTree->hwndToolTips,TTM_GETTOOLINFO,0,(LPARAM)&ToolInfo);
|
|
tvItem.hItem = (TREEITEM *)ToolInfo.lParam;
|
|
if (!tvItem.hItem) {
|
|
tvItem.hItem = TV_UpdateToolTipTarget(pTree);
|
|
if (!tvItem.hItem)
|
|
return;
|
|
}
|
|
|
|
tvItem.mask = TVIF_TEXT | TVIF_STATE;
|
|
tvItem.pszText = lpttt->szText;
|
|
tvItem.stateMask = TVIS_DROPHILITED | TVIS_SELECTED;
|
|
tvItem.cchTextMax = ARRAYSIZE(lpttt->szText);
|
|
TV_OnGetItem(pTree,&tvItem);
|
|
DebugMsg(DM_TRACE, TEXT("TV_HandleNeedText for %d returns %s"), tvItem.hItem, lpttt->szText);
|
|
|
|
#if 0
|
|
if (TV_ShouldItemDrawBlue(pTree, &tvItem, (pTree->ci.style & WS_DISABLED) ? TVDI_GRAYCTL : 0))
|
|
{
|
|
clrBk = g_clrHighlight;
|
|
clrText = g_clrHighlightText;
|
|
}
|
|
else if (!TV_ShouldItemDrawBlue(pTree, &tvItem, (pTree->ci.style & WS_DISABLED) ? TVDI_GRAYCTL : 0))
|
|
{
|
|
clrBk = g_clrWindow;
|
|
clrText = g_clrWindowText;
|
|
} else if (pTree->hHot == tvItem.hItem) {
|
|
clrText = GetSysColor(COLOR_HOTLIGHT);
|
|
}
|
|
|
|
if (clrBk != CLR_DEFAULT)
|
|
SendMessage(pTree->hwndToolTips, TTM_SETTIPBKCOLOR, (WPARAM)clrBk, 0);
|
|
|
|
if (clrText != CLR_DEFAULT)
|
|
SendMessage(pTree->hwndToolTips, TTM_SETTIPTEXTCOLOR, (WPARAM)clrText,0);
|
|
#endif
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// TV_Timer
|
|
//
|
|
// Checks to see if it is our name editing timer. If so it calls of to
|
|
// do name editing
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
LRESULT NEAR 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 NEAR 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 do not commit changes
|
|
// as if the validation fails and we attempt to display
|
|
// an error message will cause the system to hang!
|
|
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);
|
|
|
|
void TV_InitCheckBoxes(PTREE pTree)
|
|
{
|
|
HIMAGELIST himl;
|
|
TVITEM ti;
|
|
|
|
himl = CreateCheckBoxImagelist(pTree->hImageList, TRUE);
|
|
if (pTree->hImageList) {
|
|
ImageList_SetBkColor(himl, ImageList_GetBkColor(pTree->hImageList));
|
|
}
|
|
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 NEAR TV_OnStyleChanged(PTREE pTree, UINT gwl, LPSTYLESTRUCT pinfo)
|
|
{
|
|
// Style changed: redraw everything...
|
|
//
|
|
// try to do this smartly, avoiding unnecessary redraws
|
|
if (gwl == GWL_STYLE)
|
|
{
|
|
DWORD changeFlags;
|
|
#ifdef WINDOWS_ME
|
|
DWORD old;
|
|
|
|
old = pTree->ci.style;
|
|
pTree->ci.style &= ~TVS_RTLREADING;
|
|
#endif
|
|
|
|
TV_DismissEdit(pTree, FALSE); // Cancels edits
|
|
|
|
changeFlags = pTree->ci.style ^ pinfo->styleNew; // those that changed
|
|
pTree->ci.style = pinfo->styleNew; // change our version
|
|
|
|
if (changeFlags & (TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT))
|
|
TV_CreateIndentBmps(pTree);
|
|
|
|
if (changeFlags & TVS_CHECKBOXES) {
|
|
if (pTree->ci.style & TVS_CHECKBOXES) {
|
|
TV_InitCheckBoxes(pTree);
|
|
}
|
|
}
|
|
|
|
#ifdef WINDOWS_ME
|
|
pTree->ci.style |= (old & TVS_RTLREADING);
|
|
#endif
|
|
}
|
|
#ifdef WINDOWS_ME
|
|
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
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
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 (!(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 (IsWindowVisible(pTree->hwndToolTips)) {
|
|
// force it down and up.. if we got a mouse move, then it's over us
|
|
TV_UpdateToolTipTarget(pTree);
|
|
} else {
|
|
RelayToToolTips(pTree->hwndToolTips, pTree->ci.hwnd, WM_MOUSEMOVE, wParam, dwPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NEAR 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// TV_WndProc
|
|
//
|
|
// Take a guess.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
LRESULT CALLBACK TV_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
PTREE pTree = (PTREE)GetWindowInt(hwnd, 0);
|
|
|
|
#ifdef HOT_TRACKING
|
|
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);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_MOUSELEAVE:
|
|
pTree->fTrackSet = FALSE;
|
|
TV_InvalidateItem(pTree, pTree->hHot, RDW_INVALIDATE);
|
|
pTree->hHot = NULL;
|
|
break;
|
|
|
|
#ifdef UNICODE
|
|
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, (LPTV_ITEMA)lParam);
|
|
|
|
case TVM_SETITEMA:
|
|
if (!lParam)
|
|
return 0;
|
|
|
|
return (LRESULT)TV_SetItemA(pTree, (TV_ITEMA FAR *)lParam);
|
|
|
|
#endif
|
|
case TVM_INSERTITEM:
|
|
if (!lParam)
|
|
return 0;
|
|
|
|
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, FALSE);
|
|
return TV_DeleteItem(pTree, (TREEITEM FAR *)lParam, TVDI_NORMAL);
|
|
|
|
case TVM_GETNEXTITEM:
|
|
return (LRESULT)TV_GetNextItem(pTree, (TREEITEM FAR *)lParam, wParam);
|
|
|
|
case TVM_GETITEMRECT:
|
|
// lParam points to hItem to get rect from on input
|
|
if (!lParam)
|
|
return 0;
|
|
return (LRESULT)TV_GetItemRect(pTree, *(HTREEITEM FAR *)lParam, (LPRECT)lParam, wParam);
|
|
|
|
case TVM_GETITEM:
|
|
if (!lParam)
|
|
return 0;
|
|
|
|
return (LRESULT)TV_OnGetItem(pTree, (LPTV_ITEM)lParam);
|
|
|
|
case TVM_SETITEM:
|
|
if (!lParam)
|
|
return 0;
|
|
|
|
return (LRESULT)TV_SetItem(pTree, (const TV_ITEM FAR *)lParam);
|
|
|
|
case TVM_ENSUREVISIBLE:
|
|
if (!lParam)
|
|
return 0;
|
|
|
|
return TV_EnsureVisible(pTree, (TREEITEM FAR *)lParam);
|
|
|
|
case TVM_SETIMAGELIST:
|
|
return (LRESULT)(UINT)TV_SetImageList(pTree, (HIMAGELIST)lParam, (int)wParam);
|
|
|
|
case TVM_EXPAND:
|
|
// BUGBUG: Validate TREEITEM FAR*
|
|
if (!lParam)
|
|
return (LRESULT)NULL;
|
|
return TV_Expand(pTree, wParam, (TREEITEM FAR *)lParam, FALSE);
|
|
|
|
case TVM_HITTEST:
|
|
return (LRESULT)TV_OnHitTest(pTree, (LPTV_HITTESTINFO)lParam);
|
|
|
|
case TVM_GETCOUNT:
|
|
return MAKELRESULTFROMUINT(pTree->cItems);
|
|
|
|
case TVM_GETIMAGELIST:
|
|
if (!pTree)
|
|
return 0;
|
|
|
|
switch (wParam) {
|
|
case TVSIL_NORMAL:
|
|
return MAKELRESULTFROMUINT(pTree->hImageList);
|
|
case TVSIL_STATE:
|
|
return MAKELRESULTFROMUINT(pTree->himlState);
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
case TVM_GETISEARCHSTRINGA:
|
|
if (GetFocus() == pTree->ci.hwnd)
|
|
return (LRESULT)GetIncrementSearchStringA(pTree->ci.uiCodePage, (LPSTR)lParam);
|
|
else
|
|
return 0;
|
|
#endif
|
|
|
|
case TVM_GETISEARCHSTRING:
|
|
if (GetFocus() == pTree->ci.hwnd)
|
|
return (LRESULT)GetIncrementSearchString((LPTSTR)lParam);
|
|
else
|
|
return 0;
|
|
|
|
#ifdef UNICODE
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
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, (UINT)wParam);
|
|
pTree->fIndentSet = TRUE;
|
|
break;
|
|
|
|
case TVM_GETINDENT:
|
|
return MAKELRESULTFROMUINT(pTree->cxIndent);
|
|
|
|
case TVM_CREATEDRAGIMAGE:
|
|
if (!pTree->hImageList || !lParam)
|
|
return (LRESULT)NULL;
|
|
else
|
|
return MAKELRESULTFROMUINT(TV_CreateDragImage(pTree, (TREEITEM FAR *)lParam));
|
|
|
|
case TVM_GETEDITCONTROL:
|
|
return (LRESULT)(UINT)pTree->hwndEdit;
|
|
|
|
case TVM_SORTCHILDREN:
|
|
return TV_SortChildren(pTree, (TREEITEM FAR *)lParam, (BOOL)wParam);
|
|
|
|
case TVM_SORTCHILDRENCB:
|
|
return TV_SortChildrenCB(pTree, (TV_SORTCB FAR *)lParam, (BOOL)wParam);
|
|
|
|
case TVM_SELECTITEM:
|
|
return TV_SelectItem(pTree, wParam, (TREEITEM FAR *)lParam, TRUE, TRUE, TVC_UNKNOWN);
|
|
|
|
case TVM_ENDEDITLABELNOW:
|
|
return TV_DismissEdit(pTree, (BOOL)wParam);
|
|
|
|
case TVM_GETTOOLTIPS:
|
|
return (LRESULT)(UINT)pTree->hwndToolTips;
|
|
|
|
case TVM_SETTOOLTIPS:{
|
|
HWND hwndOld = pTree->hwndToolTips;
|
|
|
|
pTree->hwndToolTips = (HWND)wParam;
|
|
return (LRESULT)(UINT)hwndOld;
|
|
}
|
|
|
|
case WM_CHAR:
|
|
if (pTree->iPuntChar) {
|
|
pTree->iPuntChar--;
|
|
return TRUE;
|
|
} else {
|
|
return HANDLE_WM_CHAR(pTree, wParam, lParam, TV_OnChar);
|
|
}
|
|
|
|
case WM_CREATE:
|
|
return TV_OnCreate(hwnd, (LPCREATESTRUCT)lParam);
|
|
|
|
case WM_DESTROY:
|
|
TV_DestroyTree(pTree);
|
|
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_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(0, NULL);
|
|
goto CallDWP;
|
|
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
TV_ButtonDown(pTree, uMsg, wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
|
|
break;
|
|
|
|
case WM_KILLFOCUS:
|
|
// Reset wheel scroll amount
|
|
gcWheelDelta = 0;
|
|
|
|
if (!pTree)
|
|
break;
|
|
|
|
pTree->fFocus = FALSE;
|
|
if (pTree->hCaret)
|
|
{
|
|
TV_InvalidateItem(pTree, pTree->hCaret, RDW_INVALIDATE);
|
|
UpdateWindow(pTree->ci.hwnd);
|
|
}
|
|
SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, NM_KILLFOCUS, NULL, pTree->ci.bUnicode);
|
|
IncrementSearchString(0, NULL);
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
Assert(gcWheelDelta == 0);
|
|
|
|
if (!pTree)
|
|
break;
|
|
|
|
pTree->fFocus = TRUE;
|
|
if (pTree->hCaret)
|
|
{
|
|
TV_InvalidateItem(pTree, pTree->hCaret, RDW_INVALIDATE);
|
|
#ifdef ACTIVE_ACCESSIBILITY
|
|
MyNotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, (long)pTree->hCaret);
|
|
#endif
|
|
}
|
|
else
|
|
TV_SelectItem(pTree, TVGN_CARET, pTree->hTop, TRUE, TRUE, TVC_UNKNOWN);
|
|
|
|
SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, NM_SETFOCUS, NULL, pTree->ci.bUnicode);
|
|
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, wParam);
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
TV_OnMouseMove(pTree, 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:
|
|
if (((LPNMHDR)lParam)->hwndFrom == pTree->hwndToolTips) {
|
|
LPNMHDR lpnm = (LPNMHDR)lParam;
|
|
|
|
switch (lpnm->code) {
|
|
case TTN_NEEDTEXT:
|
|
TV_HandleNeedText(pTree, (LPTOOLTIPTEXT)lpnm);
|
|
break;
|
|
|
|
case TTN_SHOW:
|
|
TV_HandleTTNShow(pTree);
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFYFORMAT:
|
|
return CIHandleNotifyFormat(&pTree->ci, lParam);
|
|
|
|
case WM_MBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
goto CallDWP;
|
|
|
|
case WM_SYSKEYDOWN:
|
|
TV_KeyDown(pTree, wParam, lParam);
|
|
//fall through
|
|
|
|
default:
|
|
// Special handling of magellan mouse message
|
|
if (uMsg == g_msgMSWheel && pTree) {
|
|
BOOL fScroll;
|
|
BOOL fDataZoom;
|
|
DWORD dwStyle;
|
|
int cScrollLines;
|
|
int cPage;
|
|
int pos;
|
|
int cDetants;
|
|
|
|
#if defined(WINNT) && defined(WM_MOUSEWHEEL)
|
|
int iWheelDelta = (int)(short)HIWORD(wParam);
|
|
fScroll = !(wParam & (MK_SHIFT | MK_CONTROL));
|
|
fDataZoom = (wParam & MK_SHIFT);
|
|
#else
|
|
int iWheelDelta = (int)wParam;
|
|
fDataZoom = (GetKeyState(VK_SHIFT) < 0);
|
|
fScroll = !fDataZoom && GetKeyState(VK_CONTROL) >= 0;
|
|
#endif
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
CallDWP:
|
|
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;
|
|
}
|
|
}
|
|
|
|
return CallWindowProc(pTree->pfnEditWndProc, hwnd, msg, wParam, lParam);
|
|
}
|
|
|
|
|
|
void NEAR TV_SetEditSize(PTREE pTree)
|
|
{
|
|
RECT rcLabel;
|
|
|
|
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);
|
|
|
|
SetEditInPlaceSize(pTree->hwndEdit, &rcLabel, pTree->hFont, FALSE);
|
|
}
|
|
|
|
|
|
void NEAR TV_CancelEditTimer(PTREE pTree)
|
|
{
|
|
if (pTree->fNameEditPending)
|
|
{
|
|
KillTimer(pTree->ci.hwnd, IDT_NAMEEDIT);
|
|
pTree->fNameEditPending = FALSE;
|
|
}
|
|
}
|
|
|
|
// BUGBUG: very similar code in lvicon.c
|
|
|
|
|
|
HWND NEAR TV_EditLabel(PTREE pTree, HTREEITEM hItem, LPTSTR pszInitial)
|
|
{
|
|
TCHAR szLabel[MAX_PATH];
|
|
TV_DISPINFO nm;
|
|
|
|
if (!hItem || !(pTree->ci.style & TVS_EDITLABELS))
|
|
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;
|
|
TV_GetItem(pTree, hItem, TVIF_TEXT | TVIF_STATE, &nm.item);
|
|
|
|
pTree->hwndEdit = CreateEditInPlaceWindow(pTree->ci.hwnd,
|
|
pszInitial? pszInitial : nm.item.pszText, ARRAYSIZE(szLabel),
|
|
WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD | ES_LEFT | ES_AUTOHSCROLL,
|
|
(nm.item.state & TVIS_BOLD) ? pTree->hFontBold : pTree->hFont);
|
|
|
|
if (pTree->hwndEdit) {
|
|
//
|
|
// 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)SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, TVN_BEGINLABELEDIT, &nm.hdr, pTree->ci.bUnicode))
|
|
{
|
|
DestroyWindow(pTree->hwndEdit);
|
|
pTree->hwndEdit = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
TV_ScrollIntoView(pTree, hItem);
|
|
|
|
// Ok To continue - so Show the window and set focus to it.
|
|
SetFocus(pTree->hwndEdit);
|
|
ShowWindow(pTree->hwndEdit, SW_SHOW);
|
|
|
|
pTree->pfnEditWndProc = SubclassWindow(pTree->hwndEdit, TV_EditWndProc);
|
|
|
|
pTree->htiEdit = hItem;
|
|
|
|
TV_SetEditSize(pTree);
|
|
RescrollEditWindow(pTree->hwndEdit);
|
|
}
|
|
|
|
return pTree->hwndEdit;
|
|
}
|
|
|
|
|
|
// BUGBUG: very similar code in lvicon.c
|
|
|
|
BOOL NEAR 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;
|
|
}
|
|
|
|
// BUGBUG: this will fail if the program deleted the items out
|
|
// from underneath us (while we are waiting for the edit timer).
|
|
// make delete item invalidate our edit item
|
|
htiEdit = pTree->htiEdit;
|
|
|
|
ValidateTreeItem(htiEdit, FALSE);
|
|
|
|
if (htiEdit != NULL)
|
|
{
|
|
TV_DISPINFO nm;
|
|
TCHAR szLabel[MAX_PATH];
|
|
|
|
// 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)SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, TVN_ENDLABELEDIT, &nm.hdr, pTree->ci.bUnicode);
|
|
if (fOkToContinue && !fCancel)
|
|
{
|
|
//
|
|
// 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, &nm.item);
|
|
}
|
|
else
|
|
{
|
|
SendNotifyEx(pTree->ci.hwndParent, pTree->ci.hwnd, TVN_SETDISPINFO, &nm.hdr, pTree->ci.bUnicode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|