You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2037 lines
45 KiB
C++

// XTPTreeBase.cpp : implementation file
//
// This file is a part of the XTREME CONTROLS MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "Common/XTPColorManager.h"
#include "Common/XTPDrawHelpers.h"
#include "Common/XTPSystemHelpers.h"
#include "XTPTreeBase.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#ifndef SCROLL_TIMER_PERIOD
#define SCROLL_TIMER_PERIOD 75
#endif
#ifndef HOVER_TIMER_PERIOD
#define HOVER_TIMER_PERIOD 85
#endif
#ifndef COLOR_HOTLIGHT
#define COLOR_HOTLIGHT 26
#endif // COLOR_HOTLIGHT
/////////////////////////////////////////////////////////////////////////////
// CXTPTreeBase
CXTPTreeBase::CXTPTreeBase()
: m_hSelect(NULL)
, m_bMultiSelect(false)
, m_bBandLabel(true)
, m_bActionDone(false)
, m_bOkToEdit(true)
, m_htiEdit(NULL)
, m_htiLast(NULL)
{
m_pTreeCtrl = NULL;
}
CXTPTreeBase::~CXTPTreeBase()
{
}
/////////////////////////////////////////////////////////////////////////////
// CXTPTreeBase message handlers
void CXTPTreeBase::OnLButtonDown(UINT nFlags, CPoint point)
{
// If multiselect control, process possible left
// click drag selection.
if (m_bMultiSelect)
{
UINT nHitFlags = 0;
HTREEITEM hItemHit = m_pTreeCtrl->HitTest(point, &nHitFlags);
HTREEITEM hItemSel = m_pTreeCtrl->GetSelectedItem();
// if expanding/contracting call base class.
if ((nHitFlags & (TVHT_ONITEMBUTTON | TVHT_ONITEMSTATEICON)) != 0)
{
m_pTreeCtrl->Default();
return;
}
if (HasEditLabels())
{
if (!(nFlags & (MK_CONTROL | MK_SHIFT)) && (nHitFlags & TVHT_ONITEMLABEL))
{
if (m_bOkToEdit && hItemHit == hItemSel)
{
SelectAll(FALSE);
SelectItem(hItemHit);
m_pTreeCtrl->Default();
return;
}
}
if ((nHitFlags & TVHT_ONITEM) == 0)
{
m_bOkToEdit = false;
}
else if (GetFocusedItem() == hItemSel)
{
m_bOkToEdit = true;
}
}
OnButtonDown(TRUE, nFlags, point);
}
else
{
m_pTreeCtrl->Default();
}
}
void CXTPTreeBase::OnRButtonDown(UINT /*nFlags*/, CPoint point)
{
if (m_pTreeCtrl->GetStyle() & TVS_SINGLEEXPAND)
{
m_pTreeCtrl->Default();
return;
}
// hittest to get the tree item under the cursor
// and select it.
UINT uFlags = 0;
HTREEITEM hItem = m_pTreeCtrl->HitTest(point, &uFlags);
if (hItem != NULL && (uFlags & TVHT_ONITEM) != 0)
{
// if the item is not selected, clear previous
// selections and select the item under cursor.
if (!IsSelected(hItem))
{
SelectAll(FALSE);
SelectItem(hItem);
}
}
else
{
// clear previous selections.
SelectAll(FALSE);
}
// call Default() so correct notification messages are
// sent such as TVN_BEGINRDRAG.
m_pTreeCtrl->Default();
// get the owner of the tree control.
//HWND hWnd = m_pTreeCtrl->GetOwner()->m_hWnd;
//if (::IsWindow(hWnd))
//{
// construct a NMHDR struct...
//NMHDR mHDR;
//mHDR.hwndFrom = m_pTreeCtrl->m_hWnd;
//mHDR.code = NM_RCLICK;
//mHDR.idFrom = m_pTreeCtrl->GetDlgCtrlID();
// and send a WM_NOTIFY message to our owner.
//SendNotify(&mHDR);
//}
}
void CXTPTreeBase::OnSetFocus(CWnd* /*pOldWnd*/)
{
if (m_bMultiSelect)
{
//'emulated' selected items will remain greyed
// if application gets covered
HTREEITEM hItem = GetFirstSelectedItem();
while (hItem)
{
RECT rect;
m_pTreeCtrl->GetItemRect(hItem, &rect, TRUE);
m_pTreeCtrl->InvalidateRect(&rect);
hItem = GetNextSelectedItem(hItem);
}
}
m_pTreeCtrl->Default();
}
void CXTPTreeBase::OnKillFocus(CWnd* /*pNewWnd*/)
{
m_pTreeCtrl->Default();
if (m_bMultiSelect)
{
//'emulated' selected items may not get
// refreshed to grey
HTREEITEM hItem = GetFirstSelectedItem();
while (hItem)
{
RECT rect;
m_pTreeCtrl->GetItemRect(hItem, &rect, TRUE);
m_pTreeCtrl->InvalidateRect(&rect);
hItem = GetNextSelectedItem(hItem);
}
}
}
AFX_INLINE BOOL IsArrowKey(UINT nChar)
{
return (nChar == VK_UP) || (nChar == VK_DOWN) || (nChar == VK_PRIOR) || (nChar == VK_NEXT) || (nChar == VK_HOME) || (nChar == VK_END) ||
(nChar == VK_LEFT) || (nChar == VK_RIGHT) || (nChar == VK_BACK) || (nChar == VK_ADD) || (nChar == VK_MULTIPLY) || (nChar == VK_SUBTRACT);
}
void CXTPTreeBase::OnKeyDown(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/)
{
if (!m_bMultiSelect)
{
m_pTreeCtrl->Default();
return;
}
BOOL bCtrl = (::GetKeyState(VK_CONTROL) < 0);
BOOL bShift = (::GetKeyState(VK_SHIFT) < 0);
if (!bCtrl && !bShift && IsArrowKey(nChar))
{
UINT uCount = GetSelectedCount();
if ((uCount > 1) || (uCount == 1 && !IsSelected(GetFocusedItem())))
SelectAll(FALSE);
}
HTREEITEM hSel = NULL;
switch (nChar)
{
case VK_UP:
case VK_DOWN:
case VK_PRIOR:
case VK_NEXT:
hSel = m_pTreeCtrl->GetSelectedItem();
if (!m_hSelect)
{
m_hSelect = hSel;
}
if (!bCtrl && !bShift)
{
m_hSelect = NULL; //reset
}
break;
}
BOOL bDir = (nChar == VK_UP) || (nChar == VK_PRIOR);
m_pTreeCtrl->Default();
if (!hSel || (!bCtrl && !bShift))
{
return;
}
HTREEITEM hNext = (nChar == VK_NEXT) || (nChar == VK_PRIOR) ? GetFocusedItem() :
bDir ? m_pTreeCtrl->GetPrevVisibleItem(hSel) : m_pTreeCtrl->GetNextVisibleItem(hSel);
if (!hNext)
{
hNext = hSel;
}
if (bShift)
{
SelectItems(m_hSelect, hNext, TRUE);
}
else if (bCtrl)
{
SetItemState(hNext, TVIS_FOCUSED, TVIS_FOCUSED);
}
}
BOOL CXTPTreeBase::OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
{
TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
*pResult = 0;
// set m_htiEdit equal to pTVDispInfo->item.hItem.
m_htiEdit = pTVDispInfo->item.hItem;
return FALSE; //pass to parent
}
BOOL CXTPTreeBase::OnEndLabelEdit(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
*pResult = 0;
// set m_htiEdit equal to NULL.
m_htiEdit = NULL;
return FALSE; //pass to parent
}
BOOL CXTPTreeBase::OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult)
{
if (!m_bMultiSelect)
return FALSE;
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
*pResult = 0;
if ((pNMTreeView->action == TVE_COLLAPSE) || (pNMTreeView->action == TVE_COLLAPSERESET))
{
// clear selection of children, it would be confusing otherwise
// - the notifications can be over-ridden to stop the de-selection
// if required.
// Unfortunately, the parent window can't over-ride this functionality
// because MFC gives this class first crack. So if changes are required
// a derived class will have to be used..
ASSERT(pNMTreeView->itemNew.hItem);
// if a descendent item has focus the parent will get selected as a
// consequence of collapsing the tree, so preserve
// (if the parent was already selected it will gain focus, but
// that's acceptable)
BOOL bWasSel = IsSelected(pNMTreeView->itemNew.hItem);
BOOL bWasFocus = SelectChildren(pNMTreeView->itemNew.hItem, FALSE, TRUE);
if (bWasFocus && !bWasSel)
{
FocusItem(pNMTreeView->itemNew.hItem); // give parent focus.
}
}
return FALSE; //pass to parent
}
HTREEITEM CXTPTreeBase::GetNextItem(HTREEITEM hItem) const
{
HTREEITEM hti = NULL;
if (m_pTreeCtrl->ItemHasChildren(hItem))
{
hti = m_pTreeCtrl->GetChildItem(hItem);
}
if (hti == NULL)
{
while ((hti = m_pTreeCtrl->GetNextSiblingItem(hItem)) == NULL)
{
if ((hItem = m_pTreeCtrl->GetParentItem(hItem)) == NULL)
return NULL;
}
}
return hti;
}
HTREEITEM CXTPTreeBase::GetPrevItem(HTREEITEM hItem) const
{
HTREEITEM hti = NULL;
hti = m_pTreeCtrl->GetPrevSiblingItem(hItem);
if (hti == NULL)
{
hti = m_pTreeCtrl->GetParentItem(hItem);
}
else
{
hti = GetLastItem(hti);
}
return hti;
}
HTREEITEM CXTPTreeBase::GetLastItem(HTREEITEM hItem) const
{
// Temporary used variable
HTREEITEM htiNext;
// Get the last item at the top level
if (hItem == NULL)
{
hItem = m_pTreeCtrl->GetRootItem();
htiNext = m_pTreeCtrl->GetNextSiblingItem(hItem);
while (htiNext != NULL)
{
hItem = htiNext;
htiNext = m_pTreeCtrl->GetNextSiblingItem(htiNext);
}
}
while (m_pTreeCtrl->ItemHasChildren(hItem) != NULL)
{
// Find the last child of hItem
htiNext = m_pTreeCtrl->GetChildItem(hItem);
while (htiNext != NULL)
{
hItem = htiNext;
htiNext = m_pTreeCtrl->GetNextSiblingItem(htiNext);
}
}
return hItem;
}
HTREEITEM CXTPTreeBase::FindItemInBranch(LPCTSTR lpszSearch, BOOL bCaseSensitive /*= FALSE*/,
BOOL bWholeWord /*= FALSE*/, HTREEITEM htiItem /*= NULL*/)
{
HTREEITEM htiFound = NULL;
if (!m_pTreeCtrl->ItemHasChildren(htiItem))
return NULL;
CString strSearch = lpszSearch;
int iLen = strSearch.GetLength();
if (iLen == 0)
{
return NULL;
}
if (!bCaseSensitive)
{
strSearch.MakeLower();
}
HTREEITEM htiChild = m_pTreeCtrl->GetChildItem(htiItem);
while (htiChild != NULL)
{
if (m_pTreeCtrl->ItemHasChildren(htiChild))
{
htiFound = FindItemInBranch(lpszSearch, bCaseSensitive, bWholeWord, htiChild);
if (htiFound != NULL)
return htiFound;
}
CString strItemText = m_pTreeCtrl->GetItemText(htiChild);
if (!bCaseSensitive)
{
strItemText.MakeLower();
}
int iIndex;
while ((iIndex = strItemText.Find(strSearch)) != -1)
{
// Search string found
if (bWholeWord)
{
// Check preceding char
if (iIndex != 0)
{
if (_istalpha(strItemText[iIndex-1]) || strItemText[iIndex-1] == '_')
{
// Not whole word
strItemText = strItemText.Right(strItemText.GetLength() -
iIndex - iLen);
continue;
}
}
// Check succeeding char
if (strItemText.GetLength() > iIndex + iLen &&
(_istalpha(strItemText[iIndex + iLen]) ||
(strItemText[iIndex + iLen] == '_')))
{
// Not whole word
strItemText = strItemText.Right(strItemText.GetLength() -
iIndex - strSearch.GetLength());
continue;
}
}
if (IsFindValid(htiChild))
{
return htiChild;
}
else
{
break;
}
}
htiChild = m_pTreeCtrl->GetNextSiblingItem(htiChild);
}
return NULL;
}
HTREEITEM CXTPTreeBase::FindItem(LPCTSTR lpszSearch, BOOL bCaseSensitive /*= FALSE*/,
BOOL bDownDir /*= TRUE*/, BOOL bWholeWord /*= FALSE*/, HTREEITEM hItem /*= NULL*/)
{
CString str = lpszSearch;
int lenSearchStr = str.GetLength();
if (lenSearchStr == 0)
{
return NULL;
}
// For the first pass, set the current item equal to the selection
HTREEITEM htiSel = hItem ? hItem : m_pTreeCtrl->GetSelectedItem();
// If NULL, use root item.
if (htiSel == NULL)
htiSel = m_pTreeCtrl->GetRootItem();
HTREEITEM htiCur = htiSel;
CString strSearch = str;
// make sure it ends if we started with no selection
if ((htiCur == NULL) && (htiSel != NULL))
{
if (bDownDir)
{
htiCur = m_pTreeCtrl->GetRootItem();
}
else
{
htiCur = GetLastItem(NULL);
}
}
if (!bCaseSensitive)
{
strSearch.MakeLower();
}
// For the first pass only, we check to see if it
// is the item we're looking for.
BOOL bFirstPass = TRUE;
BOOL bFoundRoot = FALSE;
while (htiCur && (htiCur != htiSel || bFirstPass))
{
bFirstPass = FALSE;
CString strItemText = m_pTreeCtrl->GetItemText(htiCur);
if (!bCaseSensitive)
{
strItemText.MakeLower();
}
int iIndex;
while ((iIndex = strItemText.Find(strSearch)) != -1)
{
// Search string found
if (bWholeWord)
{
// Check preceding char
if (iIndex != 0)
{
if (_istalpha(strItemText[iIndex-1]) || strItemText[iIndex-1] == '_')
{
// Not whole word
strItemText = strItemText.Right(strItemText.GetLength() -
iIndex - lenSearchStr);
continue;
}
}
// Check succeeding char
if (strItemText.GetLength() > iIndex + lenSearchStr &&
(_istalpha(strItemText[iIndex + lenSearchStr]) ||
(strItemText[iIndex + lenSearchStr] == '_')))
{
// Not whole word
strItemText = strItemText.Right(strItemText.GetLength() -
iIndex - strSearch.GetLength());
continue;
}
}
if (IsFindValid(htiCur))
{
return htiCur;
}
else
{
break;
}
}
htiCur = bDownDir ? GetNextItem(htiCur) : GetPrevItem(htiCur);
if (htiCur == NULL)
{
if (bFoundRoot)
return NULL;
if (bDownDir)
{
htiCur = m_pTreeCtrl->GetRootItem();
}
else
{
htiCur = GetLastItem(NULL);
}
bFoundRoot = TRUE;
}
}
return NULL;
}
BOOL CXTPTreeBase::IsFindValid(HTREEITEM)
{
return TRUE;
}
void CXTPTreeBase::SetItemFont(HTREEITEM hItem, LOGFONT& logfont)
{
CLRFONT cf;
if (!m_mapColorFont.Lookup(hItem, cf))
{
cf.color = COLORREF_NULL;
}
cf.logfont = logfont;
m_mapColorFont[hItem] = cf;
}
BOOL CXTPTreeBase::GetItemFont(HTREEITEM hItem, LOGFONT* plogfont)
{
CLRFONT cf;
if (!m_mapColorFont.Lookup(hItem, cf))
{
return FALSE;
}
if (cf.logfont.lfFaceName[0] == _T('\0'))
{
return FALSE;
}
*plogfont = cf.logfont;
return TRUE;
}
void CXTPTreeBase::SetItemBold(HTREEITEM hItem, BOOL bBold)
{
SetItemState(hItem, bBold ? TVIS_BOLD : 0, TVIS_BOLD);
m_pTreeCtrl->InvalidateRect(NULL);
}
BOOL CXTPTreeBase::GetItemBold(HTREEITEM hItem)
{
return GetItemState(hItem, TVIS_BOLD) & TVIS_BOLD;
}
void CXTPTreeBase::SetItemColor(HTREEITEM hItem, COLORREF color)
{
CLRFONT cf;
m_mapColorFont.Lookup(hItem, cf);
cf.color = color;
m_mapColorFont[hItem] = cf;
m_pTreeCtrl->InvalidateRect(NULL);
}
COLORREF CXTPTreeBase::GetItemColor(HTREEITEM hItem)
{
CLRFONT cf;
if (m_mapColorFont.Lookup(hItem, cf))
return cf.color;
return COLORREF_NULL;
}
void CXTPTreeBase::SetItemBackColor(HTREEITEM hItem, COLORREF color)
{
CLRFONT cf;
m_mapColorFont.Lookup(hItem, cf);
cf.colorBack = color;
m_mapColorFont[hItem] = cf;
m_pTreeCtrl->InvalidateRect(NULL);
}
COLORREF CXTPTreeBase::GetItemBackColor(HTREEITEM hItem)
{
CLRFONT cf;
if (m_mapColorFont.Lookup(hItem, cf))
return cf.colorBack;
return COLORREF_NULL;
}
COLORREF CXTPTreeBase::GetTreeBackColor() const
{
// if the tree is disabled return disabled back color.
if (!m_pTreeCtrl->IsWindowEnabled())
return GetXtremeColor(COLOR_3DFACE);
#if _MSC_VER >= 1200 // MFC 6.0
if (XTPSystemVersion()->GetComCtlVersion() >= MAKELONG(71, 4))
{
// is there a user defined color available ?
COLORREF crBack = m_pTreeCtrl->GetBkColor();
if (crBack != COLORREF_NULL)
return crBack;
}
#endif
// return the system window color.
return GetXtremeColor(COLOR_WINDOW);
}
COLORREF CXTPTreeBase::GetTreeTextColor() const
{
// if the tree is disabled return disabled text color.
if (!m_pTreeCtrl->IsWindowEnabled())
return GetXtremeColor(COLOR_GRAYTEXT);
#if _MSC_VER >= 1200 // MFC 6.0
if (XTPSystemVersion()->GetComCtlVersion() >= MAKELONG(71, 4))
{
// is ther a user defined color available ?
COLORREF crText = m_pTreeCtrl->GetTextColor();
if (crText != COLORREF_NULL)
return crText;
}
#endif
// return the system window text color.
return GetXtremeColor(COLOR_WINDOWTEXT);
}
COLORREF CXTPTreeBase::GetItemBackColor(UINT uState, bool bTreeHasFocus, DWORD dwStyle, COLORREF crBack) const
{
// if the tree item is selected or drop highlighted.
if (uState & TVIS_SELECTED || uState & TVIS_DROPHILITED)
{
// if the tree has focus and draw the text background hilite color.
if (bTreeHasFocus || uState & TVIS_DROPHILITED)
{
if (m_pTreeCtrl->IsWindowEnabled())
return GetXtremeColor(COLOR_HIGHLIGHT);
else
return GetXtremeColor(COLOR_GRAYTEXT);
}
// if the tree does not have focus and the TVS_SHOWSELALWAYS flag is
// set draw the text background hilite color.
else if (dwStyle & TVS_SHOWSELALWAYS)
{
if (m_pTreeCtrl->IsWindowEnabled())
return GetXtremeColor(COLOR_3DFACE);
else
return GetXtremeColor(COLOR_GRAYTEXT);
}
}
return crBack;
}
COLORREF CXTPTreeBase::GetItemTextColor(UINT uState, bool bTreeHasFocus, DWORD dwStyle, COLORREF crText) const
{
// if the tree item is selected or drop highlighted.
if (uState & TVIS_SELECTED || uState & TVIS_DROPHILITED)
{
// if the tree has focus and draw the text hilite color.
if (bTreeHasFocus || uState & TVIS_DROPHILITED)
{
if (m_pTreeCtrl->IsWindowEnabled())
return GetXtremeColor(COLOR_HIGHLIGHTTEXT);
else
return GetXtremeColor(COLOR_3DFACE);
}
// if the tree does not have focus and the TVS_SHOWSELALWAYS flag is
// set draw the text hilite color.
else if (dwStyle & TVS_SHOWSELALWAYS)
{
if (m_pTreeCtrl->IsWindowEnabled())
return GetXtremeColor(COLOR_WINDOWTEXT);
else
return GetXtremeColor(COLOR_3DFACE);
}
}
return crText;
}
void CXTPTreeBase::DoPaint(CDC& dc, BOOL bInternal)
{
// Get the client rect.
CRect rcClient;
m_pTreeCtrl->GetClientRect(&rcClient);
const COLORREF crTreeBack = GetTreeBackColor();
const COLORREF crTreeText = GetTreeTextColor();
// Paint to a memory device context to help
// eliminate screen flicker.
CXTPBufferDC memDC(dc, rcClient);
memDC.FillSolidRect(rcClient, crTreeBack);
// Now let the window do its default painting...
m_pTreeCtrl->DefWindowProc(WM_PAINT, (WPARAM)memDC.m_hDC, 0);
if (!bInternal)
return;
int nSavedDC = memDC.SaveDC();
// check to see if a tree label is getting edited.
BOOL bEditing = (HasEditLabels() && m_pTreeCtrl->GetEditControl());
// if not editing, make sure the edit item is set to NULL.
if (!bEditing)
m_htiEdit = NULL;
// check to see if the tree is enabled.
BOOL bIsEnabled = m_pTreeCtrl->IsWindowEnabled();
// check to see if the tree has focus.
bool bTreeHasFocus = (CWnd::GetFocus() == m_pTreeCtrl);
// get the style for the tree.
DWORD dwStyle = m_pTreeCtrl->GetStyle();
// get the visible count.
HTREEITEM hItem = m_pTreeCtrl->GetFirstVisibleItem();
int iVisibleCount = m_pTreeCtrl->GetVisibleCount()+1;
LOGFONT treefont;
if (!m_pTreeCtrl->GetFont()->GetLogFont(&treefont))
{
::GetObject(::GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &treefont);
}
while (hItem && iVisibleCount--)
{
// get the tree item rect.
CRect rcItem;
m_pTreeCtrl->GetItemRect(hItem, &rcItem, TRUE);
// refresh the background.
CRect rcFill = rcItem;
rcFill.right = rcClient.right;
memDC.FillSolidRect(&rcFill, crTreeBack);
// get the state of the tree item.
UINT uState = GetItemState(hItem, TVIS_SELECTED | TVIS_FOCUSED | TVIS_DROPHILITED);
if (m_pTreeCtrl->GetDropHilightItem() && uState & TVIS_SELECTED && hItem != m_pTreeCtrl->GetDropHilightItem())
uState ^= TVIS_SELECTED;
BOOL bSelected = ((uState & (TVIS_SELECTED | TVIS_DROPHILITED)) != 0);
bool bUnderline = m_htiLast == hItem;
// define the background and text colors.
COLORREF crItemBack = GetItemBackColor(
uState, bTreeHasFocus, dwStyle, crTreeBack);
COLORREF crItemText = GetItemTextColor(
uState, bTreeHasFocus, dwStyle, crTreeText);
// if the item is getting edited paint white.
if (m_htiEdit == hItem)
{
crItemBack = crTreeBack;
crItemText = crTreeBack;
}
// create the LOGFONT used by the tree item.
LOGFONT logfont;
logfont = treefont;
CLRFONT cf;
if (m_mapColorFont.Lookup(hItem, cf))
{
if (!bSelected && bIsEnabled)
{
if (cf.color != COLORREF_NULL)
crItemText = cf.color;
if (cf.colorBack != COLORREF_NULL)
crItemBack = cf.colorBack;
}
if (cf.logfont.lfFaceName[0] != _T('\0'))
{
logfont = cf.logfont;
}
}
if (GetItemBold(hItem))
{
logfont.lfWeight = FW_BOLD;
}
if (dwStyle & TVS_TRACKSELECT)
{
logfont.lfUnderline = bUnderline ? (BYTE)1 : (BYTE)0;
if (logfont.lfUnderline && !bSelected)
{
if (XTPSystemVersion()->IsWin95() || XTPSystemVersion()->IsWinNT4())
crItemText = GetXtremeColor(COLOR_HIGHLIGHT);
else
crItemText = GetXtremeColor(COLOR_HOTLIGHT);
}
}
// create a CFont object from the LOGFONT structure and
// select it into the current device context.
CFont fontDC;
fontDC.CreateFontIndirect(&logfont);
CFont* pOldFont = memDC.SelectObject(&fontDC);
// get the text for the tree item and determine its size.
CString strItem = m_pTreeCtrl->GetItemText(hItem);
CSize sizeText = memDC.GetTextExtent(strItem);
// if the text is wider than the tree item label, adjust accordingly.
if (rcItem.Width() < sizeText.cx)
{
rcItem.right = rcItem.left + sizeText.cx + 2;
}
// set the font foreground and background colors.
memDC.SetBkColor(crItemBack);
memDC.SetTextColor(crItemText);
// draw the label background if selected.
if (bSelected)
{
memDC.FillSolidRect(&rcItem, crItemBack);
}
CRect rcText(rcItem);
rcText.OffsetRect(3, 0);
// draw the text and restore the device context.
memDC.DrawText(strItem, &rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_NOPREFIX);
// draw the focus rect.
if ((uState & TVIS_FOCUSED))
{
if (!bEditing && bIsEnabled && CWnd::GetFocus() == m_pTreeCtrl)
{
memDC.SetTextColor(crTreeText);
memDC.DrawFocusRect(&rcItem);
}
}
memDC.SelectObject(pOldFont);
fontDC.DeleteObject();
// move to the next visible item.
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
memDC.RestoreDC(nSavedDC);
}
HTREEITEM CXTPTreeBase::GetPrevSelectedItem(HTREEITEM hItem) const
{
for (hItem = m_pTreeCtrl->GetPrevVisibleItem(hItem);
hItem != NULL;
hItem = m_pTreeCtrl->GetPrevVisibleItem(hItem))
{
if (GetItemState(hItem, TVIS_SELECTED) & TVIS_SELECTED)
{
return hItem;
}
}
return NULL;
}
UINT CXTPTreeBase::GetSelectedCount() const
{
UINT nCount = 0;
HTREEITEM hItem = GetFirstSelectedItem();
while (hItem)
{
nCount++;
hItem = GetNextSelectedItem(hItem);
}
return nCount;
}
BOOL CXTPTreeBase::EnableMultiSelect(BOOL bMultiSelect)
{
BOOL bReturn = m_bMultiSelect;
m_bMultiSelect = bMultiSelect;
if (!m_bMultiSelect)
{
HTREEITEM hItem = m_pTreeCtrl->GetSelectedItem();
if (hItem && !IsSelected(hItem))
{
hItem = NULL;
}
SelectAllIgnore(FALSE, hItem);
if (hItem)
{
SelectItem(hItem);
}
}
return bReturn;
}
BOOL CXTPTreeBase::SetItemState(HTREEITEM hItem, UINT nState, UINT nStateMask)
{
ASSERT(hItem);
if (!hItem)
return FALSE;
if (!m_bMultiSelect)
{
return m_pTreeCtrl->SetItemState(hItem, nState, nStateMask);
}
HTREEITEM hFocus = m_pTreeCtrl->GetSelectedItem(); // current focus
BOOL bWasFocus = (hFocus == hItem);
BOOL bFocusWasSel = hFocus && IsSelected(hFocus); // selection state of current focus
BOOL bWasSel = IsSelected(hItem); // select state of acting item
UINT nS = (nState & ~TVIS_FOCUSED);
UINT nSM = (nStateMask & ~TVIS_FOCUSED);
BOOL bVista = XTPSystemVersion()->IsWinVistaOrGreater();
if (nStateMask & TVIS_FOCUSED)
{
//wanted to affect focus
if (nState & TVIS_FOCUSED)
{
if (bVista)
{
//wanted to set focus
if (!bWasFocus && hFocus)
{
//because SelectItem would de-select the current 'real' selection
// (the one with focus), need to make the tree ctrl think there is
// no 'real' selection but still keep the the old item selected
//it has to be done before the SelectItem call because
// otherwise the TVN_SELCHANGING/ED notification handlers
// wouldn't be able to get the proper list of selected items
m_pTreeCtrl->SelectItem(NULL); //will cause notify, but can be taken as focus change
m_pTreeCtrl->SetItemState(hFocus, TVIS_SELECTED, TVIS_SELECTED);
}
if (!m_pTreeCtrl->SelectItem(hItem)) //set focus (will consequently select, if not already focused)
{
return FALSE;
}
if (!bWasFocus && hFocus)
{
m_pTreeCtrl->SetItemState(hFocus, bFocusWasSel ? TVIS_SELECTED : 0, TVIS_SELECTED);
}
}
else
{
//wanted to set focus
if (!bWasFocus && bFocusWasSel)
{
//because SelectItem would de-select the current 'real' selection
// (the one with focus), need to make the tree ctrl think there is
// no 'real' selection but still keep the the old item selected
//it has to be done before the SelectItem call because
// otherwise the TVN_SELCHANGING/ED notification handlers
// wouldn't be able to get the proper list of selected items
m_pTreeCtrl->SelectItem(NULL); //will cause notify, but can be taken as focus change
m_pTreeCtrl->SetItemState(hFocus, TVIS_SELECTED, TVIS_SELECTED);
m_pTreeCtrl->UpdateWindow();
}
if (!m_pTreeCtrl->SelectItem(hItem)) //set focus (will consequently select, if not already focused)
{
return FALSE;
}
}
if (nStateMask & TVIS_SELECTED)
{
//wanted to affect select state
if (nState & TVIS_SELECTED)
{
//wanted to select, already done if wasn't focused
if (!bVista && (!bWasFocus || bFocusWasSel))
{
nS &= ~TVIS_SELECTED;
nSM &= ~TVIS_SELECTED;
}
}
//else wanted to clear, base call will do that
}
else
{
//didn't want to affect select state
if (!bWasSel)
{
//it wasn't previously selected, let base clear (correct)
nS &= ~TVIS_SELECTED;
nSM |= TVIS_SELECTED;
}
//else was already selected, no harm done
}
}
else
{
//wanted to clear focus
if (bWasFocus)
{
//it had the focus
m_pTreeCtrl->SelectItem(NULL); //clear focus
if (!(nStateMask & TVIS_SELECTED))
{
//didn't want to affect select state
if (bWasSel)
{
//it was selected, so restore
ASSERT(!(nS & TVIS_SELECTED));
ASSERT(!(nSM & TVIS_SELECTED));
//set state here, to avoid double-notify
m_pTreeCtrl->SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
//let base do other states
}
}
else if (nState & TVIS_SELECTED)
{
//wanted to select (but clear focus)
if (bWasSel)
{
//if was selected, restore
m_pTreeCtrl->SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
}
//don't want to notify, default did it
nS &= ~TVIS_SELECTED;
nSM &= ~TVIS_SELECTED;
}
}
}
}
if (!nSM)
{
return TRUE; //no other states to alter
}
if (nSM & TVIS_SELECTED)
{
//still need to alter selection state
NMTREEVIEW nmtv;
ZeroMemory(&nmtv, sizeof(NMTREEVIEW));
nmtv.hdr.hwndFrom = m_pTreeCtrl->m_hWnd;
nmtv.hdr.idFrom = ::GetDlgCtrlID(m_pTreeCtrl->m_hWnd);
nmtv.hdr.code = TVN_SELCHANGING;
nmtv.itemOld.mask = nmtv.itemNew.mask = 0;
nmtv.itemOld.hItem = nmtv.itemNew.hItem = NULL;
TVITEM& item = (nS & TVIS_SELECTED) ? nmtv.itemNew : nmtv.itemOld;
item.mask = TVIF_HANDLE | TVIF_PARAM;
item.hItem = hItem;
item.lParam = m_pTreeCtrl->GetItemData(hItem);
item.state = nS;
item.stateMask = nSM;
if (SendNotify(&nmtv.hdr))
{
return FALSE; //sel-changing stopped
}
VERIFY(m_pTreeCtrl->SetItemState(hItem, nS, nSM));
nmtv.hdr.code = TVN_SELCHANGED;
SendNotify(&nmtv.hdr);
nS &= ~TVIS_SELECTED;
nSM &= ~TVIS_SELECTED;
}
if (!nSM)
{
return TRUE;
}
return m_pTreeCtrl->SetItemState(hItem, nS, nSM);
}
UINT CXTPTreeBase::GetItemState(HTREEITEM hItem, UINT nStateMask) const
{
UINT n = m_pTreeCtrl->GetItemState(hItem, nStateMask & ~TVIS_FOCUSED);
if (nStateMask & TVIS_FOCUSED)
{
if (m_pTreeCtrl->GetSelectedItem() == hItem)
{
n |= TVIS_FOCUSED;
}
}
return n;
}
BOOL CXTPTreeBase::SelectItem(HTREEITEM hItem)
{
if (m_bMultiSelect)
{
return SetItemState(hItem,
TVIS_SELECTED | TVIS_FOCUSED, TVIS_SELECTED | TVIS_FOCUSED);
}
else
{
return m_pTreeCtrl->SelectItem(hItem);
}
}
BOOL CXTPTreeBase::FocusItem(HTREEITEM hItem)
{
ASSERT(m_bMultiSelect);
BOOL bRet = FALSE;
if (hItem)
{
bRet = SetItemState(hItem, TVIS_FOCUSED, TVIS_FOCUSED);
}
else
{
hItem = m_pTreeCtrl->GetSelectedItem();
if (hItem)
{
bRet = SetItemState(hItem, 0, TVIS_FOCUSED);
}
}
return bRet;
}
LRESULT CXTPTreeBase::SendNotify(LPNMHDR pNMHDR)
{
CWnd* pWndOwner = m_pTreeCtrl->GetOwner();
if (pWndOwner->GetSafeHwnd())
{
return ::SendMessage(pWndOwner->m_hWnd, WM_NOTIFY,
(WPARAM)pNMHDR->idFrom, (LPARAM)pNMHDR);
}
return NULL;
}
HTREEITEM CXTPTreeBase::GetFirstSelectedItem() const
{
HTREEITEM hItem = m_pTreeCtrl->GetRootItem();
while (hItem)
{
if (IsSelected(hItem))
{
break;
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
return hItem;
}
HTREEITEM CXTPTreeBase::GetNextSelectedItem(HTREEITEM hItem) const
{
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
while (hItem)
{
if (IsSelected(hItem))
{
break;
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
return hItem;
}
void CXTPTreeBase::SelectAll(BOOL bSelect /*= TRUE*/, HTREEITEM htItem/*= NULL*/)
{
bSelect = !!bSelect; //ensure 0 or 1
UINT nState = bSelect ? TVIS_SELECTED : 0;
if (htItem == NULL)
htItem = m_pTreeCtrl->GetRootItem();
while (htItem)
{
if (IsSelected(htItem) != bSelect)
{
SetItemState(htItem, nState, TVIS_SELECTED);
}
htItem = m_pTreeCtrl->GetNextVisibleItem(htItem);
}
}
void CXTPTreeBase::SelectAllIgnore(BOOL bSelect, HTREEITEM hIgnore)
{
//special case to avoid multiple notifications for
// the same item
bSelect = !!bSelect; //ensure 0 or 1
UINT nState = bSelect ? TVIS_SELECTED : 0;
HTREEITEM hItem = m_pTreeCtrl->GetRootItem();
while (hItem)
{
if (hItem != hIgnore)
{
if (IsSelected(hItem) != bSelect)
{
SetItemState(hItem, nState, TVIS_SELECTED);
}
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
}
void CXTPTreeBase::SelectItems(HTREEITEM hFirst, HTREEITEM hLast, BOOL bOnly /*= TRUE*/)
{
//locate (and select) either first or last
// (so order is arbitrary)
HTREEITEM hItem = m_pTreeCtrl->GetRootItem();
while (hItem)
{
if ((hItem == hFirst) || (hItem == hLast))
{
if (hFirst != hLast)
{
if (!IsSelected(hItem))
{
SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
break;
}
if (bOnly && IsSelected(hItem))
{
SetItemState(hItem, 0, TVIS_SELECTED);
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
//select rest of range
while (hItem)
{
if (!IsSelected(hItem))
{
SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
}
if ((hItem == hFirst) || (hItem == hLast))
{
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
break;
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
if (!bOnly)
{
return;
}
while (hItem)
{
if (IsSelected(hItem))
{
SetItemState(hItem, 0, TVIS_SELECTED);
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
}
BOOL CXTPTreeBase::OnButtonDown(BOOL bLeft, UINT nFlags, CPoint point)
{
UINT nHF = 0;
HTREEITEM hItem = NULL;
if (!m_bMultiSelect)
return TRUE;
BOOL bBase = FALSE;
hItem = m_pTreeCtrl->HitTest(point, &nHF);
if (hItem)
{
//base always handles expanding items
//(doesn't really mean much to right button, but pass anyway)
bBase = (nHF & (TVHT_ONITEMBUTTON));
if (!bBase && bLeft && (m_pTreeCtrl->GetStyle() & TVS_CHECKBOXES))
{
//when the tree has check-boxes, the default handler makes
// a quick selection of the clicked item, then re-selects
// the previously selected item - to cause a sel-changed
// notification. Fortunately it doesn't affect the multi-
// selection, so can pass on.
bBase = (nHF & TVHT_ONITEMSTATEICON);
#ifdef _MST_MULTI_CHECK
//Use the above define if you want all selected items to
// be checked the same when any one of them is checked
// - Interestingly this doesn't happen in the listview control
// (LVS_EX_CHECKBOXES)
if (bBase)
{
//the default selection notification would mess
// the multi-selection up, so generate the notification
// manually
// (anyway, this is smoother than the selection flicker
// the default gives)
NMTREEVIEW nmtv;
ZeroMemory(&nmtv, sizeof(NMTREEVIEW));
#ifdef TVN_CHKCHANGE
nmtv.hdr.code = TVN_CHKCHANGE;
#else
nmtv.hdr.code = TVN_SELCHANGED;
#endif
nmtv.hdr.hwndFrom = m_hWnd;
nmtv.hdr.idFrom = ::GetDlgCtrlID(m_hWnd);
nmtv.itemOld.hItem = NULL;
nmtv.itemNew.mask = TVIF_HANDLE | TVIF_PARAM;
BOOL bChk = !GetCheck(hItem);
if (IsSelected(hItem))
{
HTREEITEM h = GetFirstSelectedItem();
while (h)
{
if (!GetCheck(h) == bChk) //! to ensure 0 or 1
{
SetCheck(h, bChk);
#ifdef TVN_CHKCHANGE
//only send multiple check-change
// notifications (not sel-changed)
if (h != hItem) //clicked item will be done last
{
nmtv.itemNew.hItem = h;
nmtv.itemNew.lParam = m_pTreeCtrl->GetItemData(h);
SendNotify(&nmtv.hdr);
}
#endif
}
h = GetNextSelectedItem(h);
}
}
else
{
SetCheck(hItem, bChk);
}
//notify clicked item
nmtv.itemNew.hItem = hItem;
nmtv.itemNew.lParam = m_pTreeCtrl->GetItemData(hItem);
SendNotify(&nmtv.hdr);
return TRUE;
}
#endif
}
}
if (bBase)
return TRUE;
if (!hItem || (nHF & (TVHT_ONITEMRIGHT | TVHT_NOWHERE | TVHT_ONITEMINDENT)))
{
//clicked in space, do rubber-banding
DoBanding(nFlags, point);
return TRUE;
}
ASSERT(nHF & (TVHT_ONITEM | TVHT_ONITEMSTATEICON)); //nothing else left
DoPreSelection(hItem, bLeft, nFlags);
DoAction(hItem, bLeft, nFlags, point);
return m_bActionDone; // as set in doaction
}
void CXTPTreeBase::DoPreSelection(HTREEITEM hItem, BOOL bLeft, UINT nFlags)
{
if (bLeft)
{
//if shift key down, select immediately
if ((nFlags & MK_SHIFT))
{
if (!m_hSelect)
{
m_hSelect = m_pTreeCtrl->GetSelectedItem(); //focus
}
SetItemState(hItem, TVIS_FOCUSED, TVIS_FOCUSED); //focus changes to last clicked
SelectItems(m_hSelect, hItem, !(nFlags & MK_CONTROL));
}
else
{
if (!(nFlags & MK_CONTROL))
{
//if ctrl was down, then the selection is delayed until
// mouse up, otherwise select the one item
if (!IsSelected(hItem))
{
SelectAllIgnore(FALSE, hItem);
}
SetItemState(hItem, TVIS_SELECTED | TVIS_FOCUSED, TVIS_SELECTED | TVIS_FOCUSED);
}
m_hSelect = NULL; //reset when a non-shift operation occurs
}
return;
}
//right mouse
if (nFlags & (MK_CONTROL | MK_SHIFT))
{
if (!(nFlags & MK_SHIFT))
{
m_hSelect = hItem;
}
return; //do nothing if shift or ctrl
}
if (!IsSelected(hItem))
{
SelectAllIgnore(FALSE, hItem);
}
SetItemState(hItem, TVIS_SELECTED | TVIS_FOCUSED, TVIS_SELECTED | TVIS_FOCUSED);
}
void CXTPTreeBase::DoAction(HTREEITEM hItem, BOOL bLeft, UINT nFlags, CPoint point)
{
m_bActionDone = false;
::SetCapture(m_pTreeCtrl->m_hWnd);
ASSERT(::GetCapture() == m_pTreeCtrl->m_hWnd);
MSG msg;
UINT nDone = 0;
CPoint pt;
CSize sizeDrag(::GetSystemMetrics(SM_CXDRAG), ::GetSystemMetrics(SM_CYDRAG));
while (!nDone && ::GetMessage(&msg, NULL, 0, 0))
{
if (::GetCapture() != m_pTreeCtrl->m_hWnd)
{
break;
}
switch (msg.message)
{
case WM_MOUSEMOVE:
pt.x = LOWORD(msg.lParam);
pt.y = HIWORD(msg.lParam);
if ((abs(pt.x - point.x) > sizeDrag.cx) ||
((abs(pt.y - point.y) > sizeDrag.cy)))
{
nDone = 2;
}
//because we exit loop, button up will still be dispatched
// which means WM_CONTEXTMENU will be sent after TVN_BEGINRDRAG
// - this is the same behavior as original tree
break;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
nDone = 1;
break;
default:
::DispatchMessage(&msg);
break;
}
}
::ReleaseCapture();
ASSERT(::GetCapture() != m_pTreeCtrl->m_hWnd);
//construct tree notification info
NMTREEVIEW nmtv;
ZeroMemory(&nmtv, sizeof(NMTREEVIEW));
nmtv.hdr.hwndFrom = m_pTreeCtrl->m_hWnd;
nmtv.hdr.idFrom = ::GetDlgCtrlID(m_pTreeCtrl->m_hWnd);
nmtv.itemNew.mask = TVIF_HANDLE | TVIF_PARAM;
nmtv.itemNew.hItem = hItem;
nmtv.itemNew.lParam = m_pTreeCtrl->GetItemData(hItem);
DWORD dwStyle = m_pTreeCtrl->GetStyle();
if (nDone == 1)
{
//click
if (!(nFlags & MK_SHIFT) && bLeft)
{
if ((nFlags & MK_CONTROL))
{
UINT nState = (GetItemState(hItem, TVIS_SELECTED) & TVIS_SELECTED) ? 0 : TVIS_SELECTED;
SetItemState(hItem, TVIS_FOCUSED | nState, TVIS_FOCUSED | TVIS_SELECTED);
}
else
{
SelectAllIgnore(FALSE, hItem);
SetItemState(hItem, TVIS_FOCUSED | TVIS_SELECTED, TVIS_FOCUSED | TVIS_SELECTED);
}
}
if (::GetFocus() != m_pTreeCtrl->m_hWnd)
{
::SetFocus(m_pTreeCtrl->m_hWnd);
}
nmtv.hdr.code = bLeft ? NM_CLICK : NM_RCLICK;
SendNotify(&nmtv.hdr);
m_bActionDone = true;
}
else if (nDone == 2)
{
//drag
SetItemState(hItem, TVIS_FOCUSED | TVIS_SELECTED, TVIS_FOCUSED | TVIS_SELECTED);
if (!(dwStyle & TVS_DISABLEDRAGDROP))
{
nmtv.hdr.code = bLeft ? TVN_BEGINDRAG : TVN_BEGINRDRAG;
nmtv.ptDrag = point;
SendNotify(&nmtv.hdr);
}
m_bActionDone = true;
}
}
void CXTPTreeBase::DoBanding(UINT nFlags, CPoint point)
{
if (::GetFocus() != m_pTreeCtrl->m_hWnd)
{
::SetFocus(m_pTreeCtrl->m_hWnd);
}
::SetCapture(m_pTreeCtrl->m_hWnd);
CTypedPtrList<CPtrList, HTREEITEM> list;
if (nFlags & (MK_SHIFT | MK_CONTROL))
{
GetSelectedList(list);
}
CClientDC dc(m_pTreeCtrl);
CRect rectCli;
m_pTreeCtrl->GetClientRect(&rectCli);
MSG msg;
BOOL bDone = FALSE;
CPoint pt;
CSize sizeDrag(::GetSystemMetrics(SM_CXDRAG), ::GetSystemMetrics(SM_CYDRAG));
BOOL bDrag = FALSE;
CSize sizeEdge(1, 1);
UINT_PTR nTimer = m_pTreeCtrl->SetTimer(1, SCROLL_TIMER_PERIOD, NULL); //for scroll
CPoint ptScr(m_pTreeCtrl->GetScrollPos(SB_HORZ), m_pTreeCtrl->GetScrollPos(SB_VERT));
CRect rect(0, 0, 0, 0);
UINT h = 0;
HTREEITEM hItem = m_pTreeCtrl->GetRootItem();
if (hItem)
{
m_pTreeCtrl->GetItemRect(hItem, &rect, FALSE);
ptScr.y *= (h = rect.Height()); //this assumes equal height items
}
point += ptScr;
while (!bDone && ::GetMessage(&msg, NULL, 0, 0))
{
if (::GetCapture() != m_pTreeCtrl->m_hWnd)
{
break;
}
switch (msg.message)
{
case WM_TIMER:
{
if (msg.wParam == SCROLL_TIMER_PERIOD)
{
pt = msg.pt;
m_pTreeCtrl->ScreenToClient(&pt);
if (rectCli.PtInRect(pt))
{
::DispatchMessage(&msg);
break;
}
msg.lParam = MAKELPARAM(pt.x, pt.y);
}
else
{
break;
}
}
//fall through to mouse move
case WM_MOUSEMOVE:
pt.x = LOWORD(msg.lParam);
pt.y = HIWORD(msg.lParam);
if (!bDrag)
{
if ((abs(pt.x - point.x) <= sizeDrag.cx) &&
((abs(pt.y - point.y) <= sizeDrag.cy)))
{
break;
}
bDrag = TRUE;
if (!(nFlags & (MK_CONTROL | MK_SHIFT)))
{
SelectAll(FALSE);
}
m_pTreeCtrl->UpdateWindow();
rect.SetRect(point, point);
dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge);
}
dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge); //delete
if (pt.y < rectCli.top)
{
::SendMessage(m_pTreeCtrl->m_hWnd, WM_VSCROLL, SB_LINEUP, 0);
}
else if (pt.y >= rectCli.bottom)
{
::SendMessage(m_pTreeCtrl->m_hWnd, WM_VSCROLL, SB_LINEDOWN, 0);
}
if (pt.x < rectCli.left)
{
::SendMessage(m_pTreeCtrl->m_hWnd, WM_HSCROLL, SB_LINELEFT, 0);
}
else if (pt.x >= rectCli.right)
{
::SendMessage(m_pTreeCtrl->m_hWnd, WM_HSCROLL, SB_LINERIGHT, 0);
}
ptScr = point;
ptScr.x -= m_pTreeCtrl->GetScrollPos(SB_HORZ);
ptScr.y -= m_pTreeCtrl->GetScrollPos(SB_VERT) * h;
rect.SetRect(ptScr, pt);
rect.NormalizeRect();
UpdateSelectionForRect(rect, nFlags, list);
dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge); //draw
break;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
bDone = TRUE;
break;
case WM_KEYDOWN:
if (LOWORD(msg.wParam) == VK_ESCAPE)
{
SelectAll(FALSE);
bDone = TRUE;
break;
}
//dispatch
default:
::DispatchMessage(&msg);
break;
}
}
m_pTreeCtrl->KillTimer(nTimer);
::ReleaseCapture();
if (bDrag)
{
dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge);
}
else
{
if (!(nFlags & (MK_CONTROL | MK_SHIFT)))
{
SelectAll(FALSE);
}
}
}
void CXTPTreeBase::UpdateSelectionForRect(LPCRECT pRect, UINT nFlags, CTypedPtrList<CPtrList, HTREEITEM>& list)
{
CRect rect;
BOOL bSel;
POSITION pos;
HTREEITEM hItem = m_pTreeCtrl->GetRootItem();
while (hItem)
{
bSel = IsSelected(hItem);
m_pTreeCtrl->GetItemRect(hItem, &rect, m_bBandLabel);
if (rect.IntersectRect(rect, pRect))
{
//item in rect
pos = list.Find(hItem);
if (!bSel && !pos)
{
SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
}
else if ((nFlags & MK_CONTROL) && pos)
{
SetItemState(hItem, 0, TVIS_SELECTED);
}
else if ((nFlags & MK_SHIFT) && pos)
{
list.RemoveAt(pos); //if shift and in rect, don't lock anymore
}
}
else
{
//item not in rect
pos = list.Find(hItem);
if (bSel && !pos)
{
SetItemState(hItem, 0, TVIS_SELECTED);
}
else if (pos)
{
SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED);
}
}
hItem = m_pTreeCtrl->GetNextVisibleItem(hItem);
}
m_pTreeCtrl->UpdateWindow();
}
BOOL CXTPTreeBase::SelectChildren(HTREEITEM hParent, BOOL bSelect /*= TRUE*/, BOOL bRecurse /*= TRUE*/)
{
UINT nS = bSelect ? TVIS_SELECTED : 0;
BOOL bFocusWasInHere = FALSE;
HTREEITEM hItem = GetNextItem(hParent, TVGN_CHILD);
while (hItem)
{
UINT nState = GetItemState(hItem, TVIS_SELECTED | TVIS_EXPANDED | TVIS_FOCUSED);
if ((nState & TVIS_SELECTED) != nS)
{
SetItemState(hItem, nS, TVIS_SELECTED);
}
bFocusWasInHere |= (nState & TVIS_FOCUSED);
if (bRecurse && (nState & TVIS_EXPANDED))
{
bFocusWasInHere |= SelectChildren(hItem, bSelect, bRecurse);
}
hItem = m_pTreeCtrl->GetNextSiblingItem(hItem);
}
return bFocusWasInHere;
}
void CXTPTreeBase::GetSelectedList(CTypedPtrList<CPtrList, HTREEITEM>& list) const
{
list.RemoveAll();
HTREEITEM hItem = GetFirstSelectedItem();
while (hItem)
{
list.AddTail(hItem);
hItem = GetNextSelectedItem(hItem);
}
}
BOOL CXTPTreeBase::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
NMHDR* pNMHDR = (NMHDR*)lParam;
switch (pNMHDR->code)
{
case TTN_SHOW:
{
// get a pointer to the tooltip control.
HWND hWnd = TreeView_GetToolTips(m_pTreeCtrl->m_hWnd);
if (hWnd != NULL)
{
// make sure the tooltip is at the top of the "Z" order, otherwise
// it will appear behind popup windows....
::SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
}
break;
}
return m_pTreeCtrl->CTreeCtrl::OnNotify(wParam, lParam, pResult);
}
BOOL CXTPTreeBase::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
// ensure that keystrokes are handled by the edit control.
if (HasEditLabels() && m_pTreeCtrl->GetEditControl())
{
::TranslateMessage(pMsg);
::DispatchMessage(pMsg);
return TRUE;
}
// toggle expand / contract when return key is hit.
if (pMsg->wParam == VK_RETURN)
{
HTREEITEM htItem = m_pTreeCtrl->GetSelectedItem();
if (htItem != NULL)
{
m_pTreeCtrl->Expand(htItem, TVE_TOGGLE);
return TRUE;
}
}
}
return m_pTreeCtrl->CTreeCtrl::PreTranslateMessage(pMsg);
}
BOOL CXTPTreeBase::OnDeleteItem(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// Remove the tree item from the map.
m_mapColorFont.RemoveKey(pNMTreeView->itemOld.hItem);
*pResult = 0;
return FALSE;
}
bool CXTPTreeBase::HasEditLabels() const
{
return ((m_pTreeCtrl->GetStyle() & TVS_EDITLABELS) == TVS_EDITLABELS);
}
void CXTPTreeBase::OnSize(UINT /*nType*/, int /*cx*/, int /*cy*/)
{
m_pTreeCtrl->Default();
// MFCBUG: During label editing if the tree is resized while
// there is an edit box open the edit box is not moved to the
// new location of the tree item, but is left in it previous
// location.
if (HasEditLabels() && m_htiEdit)
{
CEdit* pEdit = m_pTreeCtrl->GetEditControl();
if (pEdit && ::IsWindow(pEdit->m_hWnd))
{
CRect rcEdit;
pEdit->GetWindowRect(&rcEdit);
CRect rcItem;
m_pTreeCtrl->GetItemRect(m_htiEdit, &rcItem, TRUE);
rcItem.top -= 1;
rcItem.left -= 3;
rcItem.right = rcItem.left + rcEdit.Width();
rcItem.bottom = rcItem.top + rcEdit.Height();
pEdit->MoveWindow(&rcItem);
}
}
}
void CXTPTreeBase::OnMouseMove(UINT /*nFlags*/, CPoint point)
{
if (m_pTreeCtrl->GetStyle() & TVS_TRACKSELECT)
{
TV_HITTESTINFO hit;
::ZeroMemory(&hit, sizeof(hit));
hit.pt = point;
m_pTreeCtrl->HitTest(&hit);
HTREEITEM hItem = hit.hItem && hit.flags & TVHT_ONITEM ? hit.hItem : NULL;
if (m_htiLast != hItem)
{
m_htiLast = hItem;
if (m_htiLast)
{
UINT_PTR uID = m_pTreeCtrl->SetTimer(HOVER_TIMER_PERIOD, 55, NULL);
ASSERT(uID == HOVER_TIMER_PERIOD);
}
m_pTreeCtrl->Invalidate(FALSE);
}
}
m_pTreeCtrl->Default();
}
void CXTPTreeBase::OnTimer(UINT_PTR nIDEvent)
{
if (m_pTreeCtrl->GetStyle() & TVS_TRACKSELECT)
{
if (nIDEvent == HOVER_TIMER_PERIOD)
{
if (m_htiLast != NULL)
{
CPoint pt(::GetMessagePos());
CRect rc;
m_pTreeCtrl->GetWindowRect(&rc);
if (rc.PtInRect(pt) == FALSE)
{
m_pTreeCtrl->KillTimer(HOVER_TIMER_PERIOD);
m_htiLast = NULL;
m_pTreeCtrl->Invalidate(FALSE);
}
}
else
{
m_pTreeCtrl->KillTimer(HOVER_TIMER_PERIOD);
}
return;
}
}
m_pTreeCtrl->Default();
}
void CXTPTreeBase::OnNcMouseMove(UINT /*nHitTest*/, CPoint /*point*/)
{
if (m_pTreeCtrl->GetStyle() & TVS_TRACKSELECT)
{
if (m_htiLast)
{
m_htiLast = NULL;
m_pTreeCtrl->Invalidate(FALSE);
}
}
m_pTreeCtrl->Default();
}
bool CXTPTreeBase::Init()
{
if (!::IsWindow(m_pTreeCtrl->GetSafeHwnd()))
return false;
return true;
}