1324 lines
26 KiB
C++
1324 lines
26 KiB
C++
//-----------------------------------------------------------------------------
|
|
// File: flextree.cpp
|
|
//
|
|
// Desc: Implements a tree class, similar to a Windows tree control,
|
|
// based on CFlexWnd. It is used by the page to display the action
|
|
// list when the user wishes to assign an action to a control.
|
|
//
|
|
// Copyright (C) 1999-2000 Microsoft Corporation. All Rights Reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "common.hpp"
|
|
|
|
|
|
CFlexTree::CFlexTree() :
|
|
m_pRoot(NULL), m_bDirty(FALSE), m_bNeedPaintBkgnd(FALSE),
|
|
m_rgbBkColor(RGB(255,255,255)),
|
|
m_pCurSel(NULL), m_pLastAdded(NULL), m_bOwnerDraw(FALSE),
|
|
m_bVertSB(FALSE), m_bHorzSB(FALSE),
|
|
m_nVertSBWidth(11), m_nHorzSBHeight(11)
|
|
{
|
|
RECT z = {0,0,0,0};
|
|
m_defmargin = z;
|
|
m_clDefNormal.dwMask = CLMF_ALL;
|
|
m_clDefSelected.dwMask = CLMF_ALL;
|
|
m_ptScrollOrigin.x = m_ptScrollOrigin.y = 0;
|
|
|
|
// allocate root
|
|
m_pRoot = new CFTItem;
|
|
if (m_pRoot != NULL)
|
|
{
|
|
m_pRoot->SetRoot(this);
|
|
assert(m_pRoot->IsRoot());
|
|
}
|
|
}
|
|
|
|
CFlexTree::~CFlexTree()
|
|
{
|
|
if (m_pRoot)
|
|
delete m_pRoot;
|
|
m_pRoot = NULL;
|
|
}
|
|
|
|
CFTItem::CFTItem()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
void CFTItem::Init()
|
|
{
|
|
m_pUserData = NULL;
|
|
m_bExpanded = FALSE;
|
|
m_pTree = NULL;
|
|
m_pParent = NULL;
|
|
m_pPrev = NULL;
|
|
m_pNext = NULL;
|
|
m_pFirst = NULL;
|
|
m_pLast = NULL;
|
|
m_ptszCaption = NULL;
|
|
m_nIndent = m_nWidth = m_nHeight = m_nBranchHeight = 0;
|
|
m_nChildIndent = 11;
|
|
RECT z = {0,0,0,0};
|
|
m_margin = z;
|
|
memset(&m_UserGUID, 0, sizeof(GUID));
|
|
SetCaption(TEXT(""));
|
|
}
|
|
|
|
CFTItem::~CFTItem()
|
|
{
|
|
// detach from parent (unless root)
|
|
if (!IsRoot())
|
|
Detach();
|
|
|
|
// remove all children
|
|
FreeChildren();
|
|
|
|
// free stuff
|
|
SetCaption(NULL);
|
|
}
|
|
|
|
void CFTItem::SetRoot(CFlexTree *pTree)
|
|
{
|
|
if (pTree == NULL)
|
|
{
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
SetTree(pTree);
|
|
|
|
m_nIndent = 0;
|
|
m_nHeight = 0;
|
|
m_nWidth = 0;
|
|
m_nChildIndent = 0;
|
|
|
|
m_bExpanded = TRUE;
|
|
POINT origin = {0, 0};
|
|
m_origin = origin;
|
|
}
|
|
|
|
BOOL CFTItem::IsRoot() const
|
|
{
|
|
return m_pTree != NULL && m_pParent == NULL;
|
|
}
|
|
|
|
CFTItem *CFlexTree::GetFirstItem() const
|
|
{
|
|
if (m_pRoot == NULL)
|
|
return NULL;
|
|
|
|
return m_pRoot->GetFirstChild();
|
|
}
|
|
|
|
CFTItem *CFlexTree::GetLastItem() const
|
|
{
|
|
if (m_pRoot == NULL)
|
|
return NULL;
|
|
|
|
return m_pRoot->GetLastChild();
|
|
}
|
|
|
|
BOOL CFTItem::IsOnTree() const
|
|
{
|
|
return m_pTree != NULL;
|
|
}
|
|
|
|
BOOL CFTItem::IsAttached() const
|
|
{
|
|
if (IsRoot())
|
|
{
|
|
assert(0);
|
|
return TRUE;
|
|
}
|
|
|
|
return m_pParent != NULL;
|
|
}
|
|
|
|
BOOL CFTItem::IsAlone() const
|
|
{
|
|
return
|
|
m_pTree == NULL &&
|
|
m_pParent == NULL &&
|
|
m_pPrev == NULL &&
|
|
m_pNext == NULL &&
|
|
m_pFirst == NULL &&
|
|
m_pLast == NULL;
|
|
}
|
|
|
|
void CFTItem::FreeChildren()
|
|
{
|
|
while (m_pFirst != NULL)
|
|
{
|
|
CFTItem *pChild = m_pFirst;
|
|
delete pChild;
|
|
}
|
|
}
|
|
|
|
#define FORALLCHILDREN(pChild) \
|
|
for (CFTItem *pChild = m_pFirst; pChild != NULL; pChild = pChild->m_pNext)
|
|
|
|
void CFTItem::SetTree(CFlexTree *pTree)
|
|
{
|
|
// don't do anything if we've already got this tree
|
|
if (m_pTree == pTree)
|
|
return;
|
|
|
|
// if we are currently on a tree, tell it to lose any potential dangling pointers to us
|
|
if (m_pTree)
|
|
m_pTree->LosePointer(this);
|
|
|
|
// actually set this tree
|
|
m_pTree = pTree;
|
|
|
|
// set all children to this tree
|
|
FORALLCHILDREN(pChild)
|
|
pChild->SetTree(pTree);
|
|
}
|
|
|
|
void CFTItem::Detach()
|
|
{
|
|
// don't allow root detachment
|
|
if (IsRoot())
|
|
{
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
// if we're already detached, do nothing
|
|
if (!IsAttached())
|
|
return;
|
|
|
|
// unlink from parent
|
|
if (m_pParent->m_pFirst == this)
|
|
m_pParent->m_pFirst = m_pNext;
|
|
if (m_pParent->m_pLast == this)
|
|
m_pParent->m_pLast = m_pPrev;
|
|
m_pParent = NULL;
|
|
|
|
// unlink from siblings
|
|
if (m_pPrev)
|
|
m_pPrev->m_pNext = m_pNext;
|
|
if (m_pNext)
|
|
m_pNext->m_pPrev = m_pPrev;
|
|
m_pPrev = m_pNext = NULL;
|
|
|
|
// save tree because we're about to lose it
|
|
CFlexTree *pTree = m_pTree;
|
|
|
|
// tell ourself and all children that we are no longer on a tree
|
|
SetTree(NULL);
|
|
|
|
// the tree needs to be recalced
|
|
if (pTree)
|
|
SetTreeDirty(pTree);
|
|
}
|
|
|
|
BOOL CFTItem::Attach(CFTItem *to, ATTACHREL rel)
|
|
{
|
|
// can't attach root to anything, can't attach to nothing, and can't attach if already attached
|
|
if (IsRoot() || to == NULL || IsAttached())
|
|
{
|
|
assert(0);
|
|
return FALSE;
|
|
}
|
|
|
|
// first make sure we're not attaching to root in an impossible way
|
|
if (to->IsRoot())
|
|
switch (rel)
|
|
{
|
|
// only the following are valid for attaching to root
|
|
case ATTACH_FIRSTCHILD:
|
|
case ATTACH_LASTCHILD:
|
|
break;
|
|
|
|
// all others are invalid
|
|
default:
|
|
assert(0);
|
|
return FALSE;
|
|
}
|
|
|
|
// now convert attaching as first/last sibling to equiv first/last child of parent
|
|
switch (rel)
|
|
{
|
|
case ATTACH_FIRSTSIBLING:
|
|
return Attach(to->m_pParent, ATTACH_FIRSTCHILD);
|
|
|
|
case ATTACH_LASTSIBLING:
|
|
return Attach(to->m_pParent, ATTACH_LASTCHILD);
|
|
}
|
|
|
|
// send to the more specific attach function
|
|
switch (rel)
|
|
{
|
|
case ATTACH_FIRSTCHILD:
|
|
return Attach(to, NULL, to->m_pFirst);
|
|
|
|
case ATTACH_LASTCHILD:
|
|
return Attach(to, to->m_pLast, NULL);
|
|
|
|
case ATTACH_BEFORE:
|
|
return Attach(to->m_pParent, to->m_pPrev, to);
|
|
|
|
case ATTACH_AFTER:
|
|
return Attach(to->m_pParent, to, to->m_pNext);
|
|
|
|
default:
|
|
assert(0); // unhandled rel
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL CFTItem::Attach(CFTItem *pParent, CFTItem *pPrev, CFTItem *pNext)
|
|
{
|
|
// can't attach root to anything, can't attach to no parent, and can't attach if already attached
|
|
if (IsRoot() || pParent == NULL || IsAttached())
|
|
{
|
|
assert(0);
|
|
return FALSE;
|
|
}
|
|
|
|
// prev/next, if provided, must be children of parent
|
|
if ((pPrev && pPrev->m_pParent != pParent) ||
|
|
(pNext && pNext->m_pParent != pParent))
|
|
{
|
|
assert(0);
|
|
return FALSE;
|
|
}
|
|
|
|
// pPrev and pNext must be consecutive
|
|
if ((pPrev && pPrev->m_pNext != pNext) ||
|
|
(pNext && pNext->m_pPrev != pPrev))
|
|
{
|
|
assert(0);
|
|
return FALSE;
|
|
}
|
|
|
|
// insert
|
|
if (pPrev)
|
|
pPrev->m_pNext = this;
|
|
else
|
|
pParent->m_pFirst = this;
|
|
|
|
if (pNext)
|
|
pNext->m_pPrev = this;
|
|
else
|
|
pParent->m_pLast = this;
|
|
|
|
// attach
|
|
m_pParent = pParent;
|
|
m_pPrev = pPrev;
|
|
m_pNext = pNext;
|
|
|
|
// set the tree
|
|
SetTree(pParent->m_pTree);
|
|
|
|
// tree needs to be recalced
|
|
SetTreeDirty();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFlexTree::SetDirty()
|
|
{
|
|
m_bDirty = TRUE;
|
|
Invalidate();
|
|
}
|
|
|
|
void CFTItem::SetTreeDirty(CFlexTree *pTree)
|
|
{
|
|
if (pTree == NULL)
|
|
pTree = m_pTree;
|
|
|
|
if (pTree)
|
|
pTree->SetDirty();
|
|
}
|
|
|
|
void CFTItem::SetWidth(int i)
|
|
{
|
|
if (m_nWidth == i)
|
|
return;
|
|
m_nWidth = i;
|
|
SetTreeDirty();
|
|
}
|
|
|
|
void CFTItem::SetHeight(int i)
|
|
{
|
|
if (m_nHeight == i)
|
|
return;
|
|
m_nHeight = i;
|
|
SetTreeDirty();
|
|
}
|
|
|
|
void CFTItem::SetIndent(int i)
|
|
{
|
|
if (m_nIndent == i)
|
|
return;
|
|
m_nIndent = i;
|
|
SetTreeDirty();
|
|
}
|
|
|
|
void CFTItem::SetChildIndent(int i)
|
|
{
|
|
if (m_nChildIndent == i)
|
|
return;
|
|
m_nChildIndent = i;
|
|
SetTreeDirty();
|
|
}
|
|
|
|
void CFlexTree::OnPaint(HDC hDC)
|
|
{
|
|
HDC hBDC = NULL, hODC = NULL;
|
|
CBitmap *pbm = NULL;
|
|
|
|
m_bNeedPaintBkgnd = TRUE;
|
|
|
|
if (!InRenderMode())
|
|
{
|
|
hODC = hDC;
|
|
pbm = CBitmap::Create(GetClientSize(), m_rgbBkColor, hDC);
|
|
if (pbm != NULL)
|
|
{
|
|
hBDC = pbm->BeginPaintInto();
|
|
if (hBDC != NULL)
|
|
{
|
|
hDC = hBDC;
|
|
m_bNeedPaintBkgnd = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
InternalPaint(hDC);
|
|
|
|
if (!InRenderMode())
|
|
{
|
|
if (pbm != NULL)
|
|
{
|
|
if (hBDC != NULL)
|
|
{
|
|
pbm->EndPaintInto(hBDC);
|
|
pbm->Draw(hODC);
|
|
}
|
|
delete pbm;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFlexTree::InternalPaint(HDC hDC)
|
|
{
|
|
// get client rect
|
|
RECT rect;
|
|
GetClientRect(&rect);
|
|
|
|
// get view rect (ideal coordinates we're viewing)
|
|
RECT view = rect;
|
|
OffsetRect(&view, m_ptScrollOrigin.x, m_ptScrollOrigin.y);
|
|
|
|
// paint background if necessary
|
|
if (m_bNeedPaintBkgnd)
|
|
{
|
|
HBRUSH hBrush = CreateSolidBrush(m_rgbBkColor);
|
|
if (hBrush != NULL)
|
|
{
|
|
HGDIOBJ hOldBrush = SelectObject(hDC, hBrush);
|
|
HGDIOBJ hOldPen = SelectObject(hDC, GetStockObject(NULL_PEN));
|
|
RECT t = rect;
|
|
InflateRect(&t, 1, 1);
|
|
Rectangle(hDC, t.left, t.top, t.right, t.bottom);
|
|
SelectObject(hDC, hOldPen);
|
|
SelectObject(hDC, hOldBrush);
|
|
DeleteObject((HGDIOBJ)hBrush);
|
|
}
|
|
}
|
|
|
|
// recalculate if necessary
|
|
Calc();
|
|
|
|
// start with the first visible item
|
|
CFTItem *pItem = GetFirstVisibleItem();
|
|
|
|
// draw until we go out of view
|
|
for (; pItem != NULL; pItem = pItem->GetNextOut())
|
|
{
|
|
RECT irect;
|
|
pItem->GetItemRect(irect);
|
|
if (irect.top >= view.bottom)
|
|
break;
|
|
|
|
OffsetRect(&irect, -m_ptScrollOrigin.x, -m_ptScrollOrigin.y);
|
|
|
|
POINT oldorg;
|
|
OffsetViewportOrgEx(hDC, irect.left, irect.top, &oldorg);
|
|
|
|
// TODO: clip
|
|
|
|
if (!pItem->FireOwnerDraw(hDC))
|
|
pItem->OnPaint(hDC);
|
|
|
|
SetViewportOrgEx(hDC, oldorg.x, oldorg.y, NULL);
|
|
}
|
|
|
|
// Fill in the small square at bottom right corner if we have both scroll bars.
|
|
if (m_bVertSB && m_bHorzSB)
|
|
{
|
|
HBRUSH hBrush = CreateSolidBrush(m_clDefNormal.rgbLineColor);
|
|
if (hBrush != NULL)
|
|
{
|
|
HGDIOBJ hOldBrush = SelectObject(hDC, hBrush);
|
|
|
|
HGDIOBJ hPen = CreatePen(PS_SOLID, 0, m_clDefNormal.rgbLineColor);
|
|
if (hPen != NULL)
|
|
{
|
|
HGDIOBJ hOldPen = SelectObject(hDC, hPen);
|
|
|
|
RECT rc = rect;
|
|
SIZE size;
|
|
size = m_VertSB.GetClientSize();
|
|
rc.left = rc.right - size.cx;
|
|
size = m_HorzSB.GetClientSize();
|
|
rc.top = rc.bottom - size.cy;
|
|
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
|
|
|
|
SelectObject(hDC, hOldPen);
|
|
DeleteObject((HGDIOBJ) hPen);
|
|
}
|
|
|
|
SelectObject(hDC, hOldBrush);
|
|
DeleteObject((HGDIOBJ)hBrush);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFlexTree::Calc()
|
|
{
|
|
if (!m_bDirty)
|
|
return;
|
|
m_bDirty = FALSE;
|
|
|
|
CalcItems();
|
|
|
|
BOOL bH = FALSE, bV = FALSE;
|
|
if (m_pRoot != NULL)
|
|
{
|
|
SIZE view = GetClientSize();
|
|
SIZE all = {m_nTotalWidth, m_pRoot->m_nBranchHeight};
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
// Added GetFirstVisibleItem() check since we don't want scroll bar if nothing is to be
|
|
// displayed.
|
|
if (!bV && all.cy > view.cy && GetFirstVisibleItem())
|
|
{
|
|
bV = TRUE;
|
|
view.cx -= m_nVertSBWidth;
|
|
}
|
|
|
|
if (!bH && all.cx > view.cx)
|
|
{
|
|
bH = TRUE;
|
|
view.cy -= m_nHorzSBHeight;
|
|
}
|
|
}
|
|
|
|
if (bH)
|
|
{
|
|
m_HorzSB.SetValues(0, all.cx, view.cx, m_ptScrollOrigin.x);
|
|
MoveWindow(m_HorzSB.m_hWnd, 0, view.cy, view.cx, m_nHorzSBHeight, TRUE);
|
|
}
|
|
|
|
if (bV)
|
|
{
|
|
m_VertSB.SetValues(0, all.cy, view.cy, m_ptScrollOrigin.y);
|
|
MoveWindow(m_VertSB.m_hWnd, view.cx, 0, m_nVertSBWidth, view.cy, TRUE);
|
|
}
|
|
}
|
|
|
|
if (bH && !m_bHorzSB || !bH && m_bHorzSB)
|
|
{
|
|
ShowWindow(m_HorzSB.m_hWnd, bH ? SW_SHOW : SW_HIDE);
|
|
if (!bH)
|
|
{
|
|
m_ptScrollOrigin.x = 0;
|
|
Invalidate();
|
|
}
|
|
}
|
|
m_bHorzSB = bH;
|
|
|
|
if (bV && !m_bVertSB || !bV && m_bVertSB)
|
|
{
|
|
ShowWindow(m_VertSB.m_hWnd, bV ? SW_SHOW : SW_HIDE);
|
|
if (!bV)
|
|
{
|
|
m_ptScrollOrigin.y = 0;
|
|
Invalidate();
|
|
}
|
|
}
|
|
m_bVertSB = bV;
|
|
}
|
|
|
|
void CFlexTree::CalcItems()
|
|
{
|
|
// can't do anything without root
|
|
if (m_pRoot == NULL)
|
|
return;
|
|
|
|
// calculate the entire tree in out/down order starting with first child of root...
|
|
POINT origin = {0, 0};
|
|
CFTItem *pItem = m_pRoot->m_pFirst;
|
|
m_nTotalWidth = 0;
|
|
while (pItem != NULL)
|
|
{
|
|
// let this item know its out
|
|
|
|
// get parent origin
|
|
CFTItem *pParent = pItem->m_pParent;
|
|
assert(pParent != NULL);
|
|
|
|
// calc origin...
|
|
|
|
// if we're the first child
|
|
if (pItem->m_pPrev == NULL)
|
|
{
|
|
// base origin on the parent
|
|
if (pParent)
|
|
{
|
|
origin.x = pParent->m_origin.x - pParent->m_nIndent + pParent->m_nChildIndent + pItem->m_nIndent;
|
|
origin.y = pParent->m_origin.y + pParent->m_nHeight;
|
|
}
|
|
}
|
|
else // otherwise
|
|
{
|
|
// base origin on the previous sibling
|
|
CFTItem *pPrev = pItem->m_pPrev;
|
|
assert(pPrev != NULL);
|
|
if (pPrev)
|
|
{
|
|
origin.x = pPrev->m_origin.x - pPrev->m_nIndent + pItem->m_nIndent;
|
|
origin.y = pPrev->m_origin.y + pPrev->m_nBranchHeight;
|
|
}
|
|
}
|
|
|
|
// set origin
|
|
pItem->m_origin = origin;
|
|
|
|
// see which direction we'll be going next
|
|
CFTItem *pNext = pItem->GetNextOut();
|
|
enum {RIGHT, DOWN, BACK, NOWHERE, INVALID} dir = INVALID;
|
|
if (pNext == NULL)
|
|
dir = NOWHERE;
|
|
else if (pNext == pItem->m_pNext)
|
|
dir = DOWN;
|
|
else if (pNext == pItem->m_pFirst)
|
|
dir = RIGHT;
|
|
else
|
|
dir = BACK;
|
|
|
|
// if we're going down, back, or nowhere, we can complete this item's branchheight
|
|
switch (dir)
|
|
{
|
|
case DOWN:
|
|
case BACK:
|
|
case NOWHERE:
|
|
pItem->m_nBranchHeight = pItem->m_nHeight;
|
|
break;
|
|
}
|
|
|
|
// calc for skipped items when going back
|
|
switch (dir)
|
|
{
|
|
case BACK:
|
|
case NOWHERE:
|
|
{
|
|
CFTItem *pStop = NULL;
|
|
if (dir == BACK)
|
|
pStop = pNext->m_pParent;
|
|
CFTItem *pWalk = pItem->m_pParent;
|
|
while (1)
|
|
{
|
|
if (pWalk == pStop)
|
|
break;
|
|
|
|
pWalk->m_nBranchHeight = pItem->m_origin.y +
|
|
pItem->m_nBranchHeight - pWalk->m_origin.y;
|
|
|
|
pWalk = pWalk->m_pParent;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case INVALID:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
RECT rect;
|
|
pItem->GetItemRect(rect);
|
|
if (rect.right > m_nTotalWidth)
|
|
m_nTotalWidth = rect.right;
|
|
|
|
// now go to next item
|
|
pItem = pNext;
|
|
}
|
|
}
|
|
|
|
CFTItem *CFTItem::GetNextOut() const
|
|
{
|
|
return GetNext(TRUE);
|
|
}
|
|
|
|
CFTItem *CFTItem::GetNext(BOOL bOutOnly) const
|
|
{
|
|
// if we have a child and we're expanded (or we're not looking for out only), return the first child (going 'right')
|
|
if ((m_bExpanded || !bOutOnly) && m_pFirst != NULL)
|
|
return m_pFirst;
|
|
|
|
// if we have a next sibling, return it (going 'down')
|
|
if (m_pNext != NULL)
|
|
return m_pNext;
|
|
|
|
// climb up parents until we get to one with another next sibling
|
|
for (CFTItem *pItem = m_pParent; pItem != NULL; pItem = pItem->m_pParent)
|
|
if (pItem->m_pNext != NULL)
|
|
return pItem->m_pNext;
|
|
|
|
// if we didn't find one, we're done
|
|
return NULL;
|
|
}
|
|
|
|
void CFTItem::GetItemRect(RECT &rect) const
|
|
{
|
|
rect.left = m_origin.x;
|
|
rect.top = m_origin.y;
|
|
rect.right = rect.left + m_nWidth;
|
|
rect.bottom = rect.top + m_nHeight;
|
|
}
|
|
|
|
void CFTItem::GetBranchRect(RECT &rect) const
|
|
{
|
|
rect.left = m_origin.x;
|
|
rect.top = m_origin.y;
|
|
rect.right = rect.left + m_nWidth;
|
|
rect.bottom = rect.top + m_nBranchHeight;
|
|
}
|
|
|
|
void CFTItem::SetCaption(LPCTSTR tszCaption)
|
|
{
|
|
if (m_ptszCaption != NULL)
|
|
free(m_ptszCaption);
|
|
|
|
if (tszCaption == NULL)
|
|
m_ptszCaption = NULL;
|
|
else
|
|
m_ptszCaption = _tcsdup(tszCaption);
|
|
|
|
RecalcText();
|
|
}
|
|
|
|
LPCTSTR CFTItem::GetCaption() const
|
|
{
|
|
return m_ptszCaption;
|
|
}
|
|
|
|
void CFTItem::SetMargin(const RECT &rect)
|
|
{
|
|
m_margin = rect;
|
|
RecalcText();
|
|
}
|
|
|
|
void CFTItem::RecalcText()
|
|
{
|
|
// calculate size from text dimensions and margin
|
|
SIZE size = {0, 0};
|
|
if (HasCaption())
|
|
{
|
|
RECT trect = {0, 0, 1, 1};
|
|
HDC hDC = CreateCompatibleDC(NULL);
|
|
if (hDC != NULL)
|
|
{
|
|
HGDIOBJ hOld = NULL;
|
|
HFONT hFont = IsSelected() ? m_clSelected.hFont : m_clNormal.hFont;
|
|
if (hFont)
|
|
hOld = SelectObject(hDC, hFont);
|
|
DrawText(hDC, m_ptszCaption, -1, &trect, DT_CALCRECT | DT_NOPREFIX);
|
|
if (hFont)
|
|
SelectObject(hDC, hOld);
|
|
DeleteDC(hDC);
|
|
SIZE tsize = {trect.right - trect.left, trect.bottom - trect.top};
|
|
size = tsize;
|
|
}
|
|
}
|
|
SetWidth(m_margin.left + m_margin.right + size.cx);
|
|
SetHeight(m_margin.top + m_margin.bottom + size.cy);
|
|
|
|
// redraw
|
|
Invalidate();
|
|
}
|
|
|
|
void CFTItem::Invalidate()
|
|
{
|
|
if (m_pTree)
|
|
m_pTree->Invalidate();
|
|
}
|
|
|
|
typedef CArray<LPDIACTIONW, LPDIACTIONW &> RGLPDIACW;
|
|
|
|
void CFTItem::OnPaint(HDC hDC)
|
|
{
|
|
CAPTIONLOOK &cl = (IsSelected() && !GetTree()->GetReadOnly()) ? m_clSelected : m_clNormal; // Always use normal color if read-only since we gray out everything.
|
|
::SetBkMode(hDC, cl.nBkMode);
|
|
|
|
LPDIACTIONW lpac = NULL;
|
|
if (m_pUserData)
|
|
lpac = ((RGLPDIACW *)m_pUserData)->GetAt(0); // Get the DIACTION this item holds.
|
|
if (GetTree()->GetReadOnly() || (lpac && (lpac->dwFlags & DIA_APPFIXED))) // If read-only or the action has DIA_APPFIXED flag, use gray color for texts.
|
|
::SetTextColor(hDC, RGB(GetRValue(cl.rgbTextColor) >> 1, GetGValue(cl.rgbTextColor) >> 1, GetBValue(cl.rgbTextColor) >> 1));
|
|
else
|
|
::SetTextColor(hDC, cl.rgbTextColor);
|
|
::SetBkColor(hDC, cl.rgbBkColor);
|
|
HGDIOBJ hOld = NULL;
|
|
if (cl.hFont)
|
|
hOld = SelectObject(hDC, cl.hFont);
|
|
RECT trect = {m_margin.left, m_margin.top, m_margin.left + 1, m_margin.top + 1};
|
|
DrawText(hDC, m_ptszCaption, -1, &trect, DT_NOPREFIX | DT_NOCLIP);
|
|
if (cl.hFont)
|
|
SelectObject(hDC, hOld);
|
|
}
|
|
|
|
BOOL CFlexTree::Create(HWND hParent, const RECT &rect, BOOL bVisible, BOOL bOwnerDraw)
|
|
{
|
|
m_bOwnerDraw = bOwnerDraw;
|
|
|
|
if (CFlexWnd::Create(hParent, rect, bVisible) == NULL)
|
|
return FALSE;
|
|
|
|
FLEXSCROLLBARCREATESTRUCT cs;
|
|
cs.dwSize = sizeof(FLEXSCROLLBARCREATESTRUCT);
|
|
cs.min = 0;
|
|
cs.max = 10;
|
|
cs.page = 3;
|
|
cs.pos = 5;
|
|
cs.hWndParent = m_hWnd;
|
|
cs.hWndNotify = NULL;
|
|
cs.rect.left = 0;
|
|
cs.rect.top = 0;
|
|
cs.rect.right = 5;
|
|
cs.rect.bottom = 5;
|
|
cs.bVisible = FALSE;
|
|
cs.dwFlags = FSBF_VERT;
|
|
m_VertSB.Create(&cs);
|
|
cs.dwFlags = FSBF_HORZ;
|
|
m_HorzSB.Create(&cs);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFlexTree::SetDefCaptionLook(const CAPTIONLOOK &cl, BOOL bSel)
|
|
{
|
|
CAPTIONLOOK &set = bSel ? m_clDefSelected : m_clDefNormal;
|
|
|
|
if (cl.dwMask & CLMF_TEXTCOLOR)
|
|
set.rgbTextColor = cl.rgbTextColor;
|
|
if (cl.dwMask & CLMF_BKCOLOR)
|
|
set.rgbBkColor = cl.rgbBkColor;
|
|
if (cl.dwMask & CLMF_LINECOLOR)
|
|
set.rgbLineColor = cl.rgbLineColor;
|
|
if (cl.dwMask & CLMF_BKMODE)
|
|
set.nBkMode = cl.nBkMode;
|
|
if (cl.dwMask & CLMF_BKEXTENDS)
|
|
set.bBkExtends = cl.bBkExtends;
|
|
if (cl.dwMask & CLMF_FONT)
|
|
set.hFont = cl.hFont;
|
|
}
|
|
|
|
void CFlexTree::GetDefCaptionLook(CAPTIONLOOK &cl, BOOL bSel) const
|
|
{
|
|
const CAPTIONLOOK &from = bSel ? m_clDefSelected : m_clDefNormal;
|
|
|
|
cl = from;
|
|
cl.dwMask = CLMF_ALL;
|
|
}
|
|
|
|
void CFlexTree::SetBkColor(COLORREF rgb)
|
|
{
|
|
m_rgbBkColor = rgb;
|
|
Invalidate();
|
|
}
|
|
|
|
COLORREF CFlexTree::GetBkColor() const
|
|
{
|
|
return m_rgbBkColor;
|
|
}
|
|
|
|
void CFlexTree::SetCurSel(CFTItem *pItem)
|
|
{
|
|
if (pItem == m_pCurSel)
|
|
return;
|
|
|
|
CFTItem *pOld = m_pCurSel;
|
|
m_pCurSel = pItem;
|
|
|
|
if (pOld)
|
|
pOld->SelChangedInternal();
|
|
if (m_pCurSel)
|
|
m_pCurSel->SelChangedInternal();
|
|
|
|
FireSelChanged(m_pCurSel, pOld);
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
CFTItem *CFlexTree::GetCurSel() const
|
|
{
|
|
return m_pCurSel;
|
|
}
|
|
|
|
CFTItem *CFlexTree::FindItem(const GUID &guid, void *pUserData) const
|
|
{
|
|
// go until we get to the item with specified guid and userdata
|
|
for (CFTItem *pItem = GetFirstItem(); pItem != NULL; pItem = pItem->GetNext())
|
|
if (pItem->IsUserGUID(guid) && pItem->GetUserData() == pUserData)
|
|
return pItem;
|
|
|
|
// unless there isn't one
|
|
return NULL;
|
|
}
|
|
|
|
CFTItem *CFlexTree::FindItemEx(const GUID &guid, DWORD dwUser, void *pUser) const
|
|
{
|
|
// go until we get to the item with specified guid and found item returns true
|
|
for (CFTItem *pItem = GetFirstItem(); pItem != NULL; pItem = pItem->GetNext())
|
|
if (pItem->IsUserGUID(guid) && pItem->FoundItem(dwUser, pUser))
|
|
return pItem;
|
|
|
|
// unless there isn't one
|
|
return NULL;
|
|
}
|
|
|
|
void CFTItem::SetCaptionLook(const CAPTIONLOOK &cl, BOOL bSel)
|
|
{
|
|
CAPTIONLOOK &set = bSel ? m_clSelected : m_clNormal;
|
|
|
|
if (cl.dwMask & CLMF_TEXTCOLOR)
|
|
set.rgbTextColor = cl.rgbTextColor;
|
|
if (cl.dwMask & CLMF_BKCOLOR)
|
|
set.rgbBkColor = cl.rgbBkColor;
|
|
if (cl.dwMask & CLMF_LINECOLOR)
|
|
set.rgbLineColor = cl.rgbLineColor;
|
|
if (cl.dwMask & CLMF_BKMODE)
|
|
set.nBkMode = cl.nBkMode;
|
|
if (cl.dwMask & CLMF_BKEXTENDS)
|
|
set.bBkExtends = cl.bBkExtends;
|
|
if (cl.dwMask & CLMF_FONT)
|
|
set.hFont = cl.hFont;
|
|
|
|
if (IsSelected() == bSel)
|
|
RecalcText();
|
|
}
|
|
|
|
void CFTItem::GetCaptionLook(CAPTIONLOOK &cl, BOOL bSel) const
|
|
{
|
|
const CAPTIONLOOK &from = bSel ? m_clSelected : m_clNormal;
|
|
|
|
cl = from;
|
|
cl.dwMask = CLMF_ALL;
|
|
}
|
|
|
|
void CFTItem::GetMargin(RECT &rect) const
|
|
{
|
|
rect = m_margin;
|
|
}
|
|
|
|
void CFTItem::SelChangedInternal()
|
|
{
|
|
if (m_clNormal.hFont != m_clSelected.hFont)
|
|
RecalcText();
|
|
}
|
|
|
|
CFTItem *CFlexTree::DefAddItem(LPCTSTR tszCaption, CFTItem *to, ATTACHREL rel)
|
|
{
|
|
if (m_pRoot == NULL)
|
|
return NULL;
|
|
|
|
if (!to)
|
|
return DefAddItem(tszCaption, rel);
|
|
|
|
if (!IsMine(to))
|
|
{
|
|
assert(0); // can't add relative to item that doesn't belong to this tree
|
|
return NULL;
|
|
}
|
|
|
|
CFTItem *p = new CFTItem;
|
|
if (!p)
|
|
return NULL;
|
|
|
|
p->SetCaptionLook(m_clDefNormal);
|
|
p->SetCaptionLook(m_clDefSelected, TRUE);
|
|
p->SetChildIndent(m_nDefChildIndent);
|
|
p->SetMargin(m_defmargin);
|
|
p->SetCaption(tszCaption);
|
|
|
|
p->Attach(to, rel);
|
|
|
|
return m_pLastAdded = p;
|
|
}
|
|
|
|
CFTItem *CFlexTree::DefAddItem(LPCTSTR tszCaption, ATTACHREL rel)
|
|
{
|
|
if (m_pRoot == NULL)
|
|
return NULL;
|
|
|
|
assert(this != NULL);
|
|
if (this == NULL) // prevent infinite recursion possibility
|
|
return NULL;
|
|
|
|
if (!m_pLastAdded)
|
|
return DefAddItem(tszCaption, m_pRoot, ATTACH_LASTCHILD);
|
|
else
|
|
return DefAddItem(tszCaption, m_pLastAdded, rel);
|
|
}
|
|
|
|
void CFlexTree::SetDefMargin(const RECT &rect)
|
|
{
|
|
m_defmargin = rect;
|
|
}
|
|
|
|
void CFlexTree::GetDefMargin(RECT &rect) const
|
|
{
|
|
rect = m_defmargin;
|
|
}
|
|
|
|
BOOL CFlexTree::IsMine(CFTItem *pItem)
|
|
{
|
|
if (pItem == NULL)
|
|
return FALSE;
|
|
|
|
return pItem->m_pTree == this;
|
|
}
|
|
|
|
BOOL CFTItem::IsSelected() const
|
|
{
|
|
if (!m_pTree)
|
|
return FALSE;
|
|
|
|
return m_pTree->m_pCurSel == this;
|
|
}
|
|
|
|
CFTItem *CFlexTree::GetFirstVisibleItem() const
|
|
{
|
|
// get view rect (ideal coordinates we're viewing)
|
|
RECT view;
|
|
GetClientRect(&view);
|
|
OffsetRect(&view, m_ptScrollOrigin.x, m_ptScrollOrigin.y);
|
|
|
|
// start at first child of root
|
|
CFTItem *pItem = m_pRoot->GetFirstChild();
|
|
if (pItem == NULL)
|
|
return NULL;
|
|
|
|
// find first item in view
|
|
RECT branch, irect;
|
|
while (1)
|
|
{
|
|
// find first branch in view
|
|
while (1)
|
|
{
|
|
pItem->GetBranchRect(branch);
|
|
if (branch.bottom > view.top)
|
|
break;
|
|
|
|
pItem = pItem->GetNextSibling();
|
|
if (pItem == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
// now actually go through items
|
|
pItem->GetItemRect(irect);
|
|
if (irect.bottom > view.top)
|
|
break;
|
|
|
|
pItem = pItem->GetNextOut();
|
|
if (pItem == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
// we got it, so return it
|
|
return pItem;
|
|
}
|
|
|
|
CFTItem *CFlexTree::GetItemFromPoint(POINT point) const
|
|
{
|
|
if (m_hWnd == NULL)
|
|
return NULL;
|
|
|
|
RECT rect;
|
|
GetClientRect(&rect);
|
|
if (!PtInRect(&rect, point))
|
|
return NULL;
|
|
|
|
for (CFTItem *pItem = GetFirstVisibleItem(); pItem != NULL; pItem = pItem->GetNextOut())
|
|
{
|
|
RECT irect;
|
|
pItem->GetItemRect(irect);
|
|
OffsetRect(&irect, -m_ptScrollOrigin.x, -m_ptScrollOrigin.y);
|
|
|
|
if (irect.top >= rect.bottom)
|
|
return NULL;
|
|
|
|
if (PtInRect(&irect, point))
|
|
return pItem;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CFlexTree::OnMouseOver(POINT point, WPARAM fwKeys)
|
|
{
|
|
// Send mouse over notification to page to update info box.
|
|
HWND hParent = ::GetParent(m_hWnd);
|
|
if (hParent)
|
|
SendMessage(hParent, WM_FLEXTREENOTIFY, FTN_MOUSEOVER, NULL);
|
|
|
|
CFTItem *pItem = GetItemFromPoint(point);
|
|
if (!pItem)
|
|
return;
|
|
POINT rel = {point.x - pItem->m_origin.x, point.y - pItem->m_origin.y};
|
|
pItem->OnMouseOver(point, fwKeys);
|
|
}
|
|
|
|
void CFlexTree::OnClick(POINT point, WPARAM fwKeys, BOOL bLeft)
|
|
{
|
|
// If the tree is read-only, ignore all clicks.
|
|
if (GetReadOnly())
|
|
return;
|
|
|
|
CFTItem *pItem = GetItemFromPoint(point);
|
|
if (!pItem)
|
|
{
|
|
FireClick(NULL, point, fwKeys, bLeft);
|
|
return;
|
|
}
|
|
POINT rel = {point.x - pItem->m_origin.x, point.y - pItem->m_origin.y};
|
|
pItem->OnClick(point, fwKeys, bLeft);
|
|
}
|
|
|
|
void CFTItem::OnClick(POINT point, WPARAM fwKeys, BOOL bLeft)
|
|
{
|
|
FireClick(point, fwKeys, bLeft);
|
|
}
|
|
|
|
void CFlexTree::OnWheel(POINT point, WPARAM wParam)
|
|
{
|
|
if (!m_bVertSB) return;
|
|
|
|
int nPage = MulDiv(m_VertSB.GetPage(), 9, 10) >> 1; // Half a page at a time
|
|
|
|
if ((int)wParam >= 0)
|
|
m_VertSB.AdjustPos(-nPage);
|
|
else
|
|
m_VertSB.AdjustPos(nPage);
|
|
|
|
m_ptScrollOrigin.y = m_VertSB.GetPos();
|
|
if (m_ptScrollOrigin.y < 0)
|
|
m_ptScrollOrigin.y = 0;
|
|
Invalidate();
|
|
}
|
|
|
|
void CFlexTree::FireClick(CFTItem *pItem, POINT point, WPARAM fwKeys, BOOL bLeft)
|
|
{
|
|
FLEXTREENOTIFY n;
|
|
n.pTree = this;
|
|
n.pItem = pItem;
|
|
n.pOldItem = NULL;
|
|
n.hDC = NULL;
|
|
n.point = point;
|
|
n.fwKeys = fwKeys;
|
|
n.bLeft = bLeft;
|
|
|
|
HWND hParent = ::GetParent(m_hWnd);
|
|
if (hParent)
|
|
SendMessage(hParent, WM_FLEXTREENOTIFY, FTN_CLICK, (LRESULT)(LPVOID)&n);
|
|
}
|
|
|
|
BOOL CFlexTree::FireOwnerDraw(CFTItem *pItem, HDC hDC)
|
|
{
|
|
if (!m_bOwnerDraw)
|
|
return FALSE;
|
|
|
|
FLEXTREENOTIFY n;
|
|
n.pTree = this;
|
|
n.pItem = pItem;
|
|
n.pOldItem = NULL;
|
|
n.hDC = hDC;
|
|
|
|
HWND hParent = ::GetParent(m_hWnd);
|
|
if (hParent)
|
|
return (BOOL)SendMessage(hParent, WM_FLEXTREENOTIFY, FTN_OWNERDRAW, (LRESULT)(LPVOID)&n);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
void CFlexTree::FireSelChanged(CFTItem *pItem, CFTItem *pOld)
|
|
{
|
|
assert(pItem == m_pCurSel);
|
|
|
|
FLEXTREENOTIFY n;
|
|
n.pTree = this;
|
|
n.pItem = pItem;
|
|
n.pOldItem = pOld;
|
|
n.hDC = NULL;
|
|
|
|
HWND hParent = ::GetParent(m_hWnd);
|
|
if (hParent)
|
|
SendMessage(hParent, WM_FLEXTREENOTIFY, FTN_SELCHANGED, (LRESULT)(LPVOID)&n);
|
|
}
|
|
|
|
void CFTItem::FireClick(POINT point, WPARAM fwKeys, BOOL bLeft)
|
|
{
|
|
if (m_pTree)
|
|
m_pTree->FireClick(this, point, fwKeys, bLeft);
|
|
}
|
|
|
|
BOOL CFTItem::FireOwnerDraw(HDC hDC)
|
|
{
|
|
if (m_pTree)
|
|
return m_pTree->FireOwnerDraw(this, hDC);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
void CFTItem::Expand(BOOL bAll)
|
|
{
|
|
InternalExpand(TRUE, bAll);
|
|
}
|
|
|
|
void CFTItem::Collapse(BOOL bAll)
|
|
{
|
|
InternalExpand(FALSE, bAll);
|
|
}
|
|
|
|
void CFTItem::InternalExpand(BOOL bExpand, BOOL bAll)
|
|
{
|
|
if (!HasChildren())
|
|
return;
|
|
|
|
BOOL bE = m_bExpanded;
|
|
if (!IsRoot())
|
|
m_bExpanded = bExpand;
|
|
|
|
if (bAll)
|
|
FORALLCHILDREN(pChild)
|
|
pChild->InternalExpand(bExpand, TRUE);
|
|
|
|
if (bE != m_bExpanded)
|
|
SetTreeDirty();
|
|
}
|
|
|
|
BOOL CFTItem::IsOut() const
|
|
{
|
|
CFTItem *pParent = GetParent();
|
|
for (; pParent != NULL; pParent = pParent->GetParent())
|
|
if (!pParent->IsExpanded())
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
void CFlexTree::FreeAll()
|
|
{
|
|
if (m_pRoot)
|
|
m_pRoot->FreeChildren();
|
|
}
|
|
|
|
void CFTItem::EnsureVisible()
|
|
{
|
|
// TBD
|
|
}
|
|
|
|
void CFlexTree::LosePointer(CFTItem *pItem)
|
|
{
|
|
if (m_pCurSel == pItem)
|
|
SetCurSel(NULL);
|
|
if (m_pLastAdded == pItem)
|
|
m_pLastAdded = NULL;
|
|
}
|
|
|
|
void CFlexTree::SetRootChildIndent(int i)
|
|
{
|
|
if (!m_pRoot)
|
|
return;
|
|
m_pRoot->m_nChildIndent = i;
|
|
SetDirty();
|
|
}
|
|
|
|
int CFlexTree::GetRootChildIndent() const
|
|
{
|
|
if (!m_pRoot)
|
|
return 0;
|
|
return m_pRoot->m_nChildIndent;
|
|
}
|
|
|
|
void CFlexTree::SetDefChildIndent(int i)
|
|
{
|
|
m_nDefChildIndent = i;
|
|
}
|
|
|
|
int CFlexTree::GetDefChildIndent() const
|
|
{
|
|
return m_nDefChildIndent;
|
|
}
|
|
|
|
void CFTItem::PaintInto(HDC hDC)
|
|
{
|
|
if (hDC != NULL)
|
|
OnPaint(hDC);
|
|
}
|
|
|
|
LRESULT CFlexTree::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case WM_FLEXVSCROLL:
|
|
case WM_FLEXHSCROLL:
|
|
{
|
|
int code = (int)wParam;
|
|
CFlexScrollBar *pSB = (CFlexScrollBar *)lParam;
|
|
if (!pSB)
|
|
return 0;
|
|
|
|
int nLine = 5;
|
|
int nPage = MulDiv(pSB->GetPage(), 9, 10);
|
|
|
|
switch (code)
|
|
{
|
|
case SB_LINEUP: pSB->AdjustPos(-nLine); break;
|
|
case SB_LINEDOWN: pSB->AdjustPos(nLine); break;
|
|
case SB_PAGEUP: pSB->AdjustPos(-nPage); break;
|
|
case SB_PAGEDOWN: pSB->AdjustPos(nPage); break;
|
|
case SB_THUMBTRACK: pSB->SetPos(pSB->GetThumbPos()); break;
|
|
}
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_FLEXHSCROLL:
|
|
m_ptScrollOrigin.x = pSB->GetPos();
|
|
break;
|
|
|
|
case WM_FLEXVSCROLL:
|
|
m_ptScrollOrigin.y = pSB->GetPos();
|
|
break;
|
|
}
|
|
|
|
Invalidate();
|
|
return 0;
|
|
}
|
|
|
|
default:
|
|
return CFlexWnd::WndProc(hWnd, msg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
void CFlexTree::SetScrollBarColors(COLORREF bk, COLORREF fill, COLORREF line)
|
|
{
|
|
m_VertSB.SetColors(bk, fill, line);
|
|
m_HorzSB.SetColors(bk, fill, line);
|
|
}
|