// WaitingTreeCtrl.cpp : implementation file
//
/////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2000-2001 by Paolo Messina
// (http://www.geocities.com/ppescher - ppescher@yahoo.com)
//
// The contents of this file are subject to the Artistic License (the "License").
// You may not use this file except in compliance with the License. 
// You may obtain a copy of the License at:
// http://www.opensource.org/licenses/artistic-license.html
//
// If you find this code useful, credits would be nice!
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "WaitingTreeCtrl.h"

#pragma warning(push)
#pragma warning(disable: 4201)
#include <mmsystem.h>
#pragma warning(pop)

#pragma comment(lib, "winmm.lib")


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CWaitingTreeCtrl

CWaitingTreeCtrl::CWaitingTreeCtrl()
{
	m_bDrawSnapshot = FALSE;

	m_sWaitMsg = _T("Loading...");
	m_bShowWaitMsg = FALSE;
	m_hIconMsg = NULL;	// default: blank icon
	m_nTimerDelay = 0;	// default: no timer

	m_hRedrawEvent = NULL;
	m_hTimerEvent = NULL;
	m_hThread = NULL;
}

CWaitingTreeCtrl::~CWaitingTreeCtrl()
{
}


BEGIN_MESSAGE_MAP(CWaitingTreeCtrl, CTreeCtrl)
	//{{AFX_MSG_MAP(CWaitingTreeCtrl)
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemExpanding)
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDED, OnItemExpanded)
	ON_WM_ERASEBKGND()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CWaitingTreeCtrl message handlers

void CWaitingTreeCtrl::OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	if (pNMTreeView->action & TVE_EXPAND)
		PreExpandItem(pNMTreeView->itemNew.hItem);

	*pResult = 0;
}

void CWaitingTreeCtrl::OnItemExpanded(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	if (pNMTreeView->action & TVE_EXPAND)
		ExpandItem(pNMTreeView->itemNew.hItem);
	else if (pNMTreeView->action & TVE_COLLAPSE)
	{
		if (WantsRefresh(pNMTreeView->itemNew.hItem))
		{
			// delete child items
			DeleteChildren(pNMTreeView->itemNew.hItem);
		}
	}

	*pResult = 0;
}

void CWaitingTreeCtrl::PreAnimation(HTREEITEM hItemMsg)
{
	UNREFERENCED_PARAMETER(hItemMsg);
}

void CWaitingTreeCtrl::PostAnimation()
{
}

void CWaitingTreeCtrl::DoAnimation(BOOL bTimerEvent, int iMaxSteps, int iStep)
{
	UNREFERENCED_PARAMETER(bTimerEvent);
	UNREFERENCED_PARAMETER(iMaxSteps);
	UNREFERENCED_PARAMETER(iStep);
}

int CWaitingTreeCtrl::GetPopulationCount(int *piMaxSubItems)
{
	if (piMaxSubItems != NULL)
		*piMaxSubItems = m_iItemCount;
	return m_iItemIndex;
}

void CWaitingTreeCtrl::SetPopulationCount(int iMaxSubItems, int iFirstSubItem)
{
	m_iItemCount = iMaxSubItems;
	m_iItemIndex = iFirstSubItem;

	SetEvent(m_hRedrawEvent);
}

void CWaitingTreeCtrl::UpdatePopulation(int iSubItems)
{
	m_iItemIndex = iSubItems;

	SetEvent(m_hRedrawEvent);
}

void CWaitingTreeCtrl::IncreasePopulation(int iSubItemsToAdd)
{
	m_iItemIndex += iSubItemsToAdd;

	SetEvent(m_hRedrawEvent);
}

void CWaitingTreeCtrl::SetAnimationDelay(UINT nMilliseconds)
{
	// if greater than zero, periodic DoAnimation() will be called
	m_nTimerDelay = nMilliseconds;
}

DWORD WINAPI CWaitingTreeCtrl::AnimationThreadProc(LPVOID pThis)
{
	CWaitingTreeCtrl* me = (CWaitingTreeCtrl*)pThis;

	HANDLE events[2] = { me->m_hTimerEvent, me->m_hRedrawEvent };
	
	while (!me->m_bAbortAnimation)
	{
		DWORD wait = WaitForMultipleObjects(2, events, FALSE, INFINITE);
		
		if (me->m_bAbortAnimation || wait == WAIT_FAILED)
			break;

		if (wait == WAIT_OBJECT_0)	// timer event
			me->DoAnimation(TRUE, me->m_iItemCount, me->m_iItemIndex);
		else	// redraw event
			me->DoAnimation(FALSE, me->m_iItemCount, me->m_iItemIndex);
	}

	return 0;
}

void CWaitingTreeCtrl::StartAnimation()
{
	// user-defined setup
	PreAnimation(m_hItemMsg);

	// animation can go
	m_bAbortAnimation = FALSE;
	// automatic reset events, signaled
	m_hTimerEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
	m_hRedrawEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
	// start animation thread
	DWORD dwThreadID = 0;
	m_hThread = CreateThread(NULL, 0, AnimationThreadProc, this,
		THREAD_PRIORITY_HIGHEST, &dwThreadID);
	// setup timer, if specified
	if (m_nTimerDelay > 0)
		m_nTimerID = (UINT)timeSetEvent(m_nTimerDelay, 5, (LPTIMECALLBACK)m_hTimerEvent,
			0, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET);
}

void CWaitingTreeCtrl::StopAnimation()
{
	// stop and destroy timer
	timeKillEvent(m_nTimerID);
	// signal thread to terminate
	m_bAbortAnimation = TRUE;
	SetEvent(m_hRedrawEvent);	// make sure it can see the signal
	// wait thread termination
	WaitForSingleObject(m_hThread, INFINITE);
	// clean up
	CloseHandle(m_hTimerEvent);
	m_hTimerEvent = NULL;
	CloseHandle(m_hRedrawEvent);
	m_hRedrawEvent = NULL;
	CloseHandle(m_hThread);
	m_hThread = NULL;

	// user-defined cleanup
	PostAnimation();
}

void CWaitingTreeCtrl::PopulateRoot()
{
	PreExpandItem(TVI_ROOT);
	ExpandItem(TVI_ROOT);
	// force update, don't scroll
	SetRedraw(FALSE);
	SCROLLINFO si;
	GetScrollInfo(SB_HORZ, &si);
	EnsureVisible(GetChildItem(TVI_ROOT));
	SetScrollInfo(SB_HORZ, &si, FALSE);
	SetRedraw();
}

void CWaitingTreeCtrl::PreExpandItem(HTREEITEM hItem)
{
	if (!NeedsChildren(hItem))
	{
		if (WantsRefresh(hItem))
		{
			// delete child items before populating
			DeleteChildren(hItem);
		}
		else
		{
			// doesn't want new items
			m_hItemToPopulate = NULL;
			return;
		}
	}
	// if it wants new child items, go on
	m_hItemToPopulate = hItem;

	// fix redraw when expanded programatically
	UpdateWindow();
	// hide changes until it's expanded
	SetRedraw(FALSE);
	// add wait msg, to allow item expansion
	m_hItemMsg = InsertItem(m_sWaitMsg, m_hItemToPopulate);
	// zero progress
	m_iItemCount = 1;
	m_iItemIndex = 0;
}

void CWaitingTreeCtrl::ExpandItem(HTREEITEM hItem)
{
	if (m_hItemToPopulate == NULL)
		return;	// just expand, doesn't want new items

	ASSERT(hItem == m_hItemToPopulate);	// should never fail!!!

	if (m_bShowWaitMsg)
	{
		// display wait msg now, make sure it's visible
		SetRedraw();
		EnsureVisible(m_hItemMsg);
		UpdateWindow();
	}
	// setup animation thread, call PreAnimation
	StartAnimation();
	// draw icon
	if (m_bShowWaitMsg)
		DrawUserIcon();
	// delay redraw after populating
	SetRedraw(FALSE);
	// take a snapshot of the background
	TakeSnapshot();
	// del temporary item (wait msg still shown)
	DeleteItem(m_hItemMsg);
	// fill in with sub items
	BOOL bCheckChildren = PopulateItem(hItem);
	// clean up animation thread, call PostAnimation
	StopAnimation();
	// change parent to reflect current children number
	if (hItem != TVI_ROOT)
	{
		TVITEM item;
		item.hItem = hItem;
		item.mask = TVIF_HANDLE | TVIF_CHILDREN;
		item.cChildren = NeedsChildren(hItem) ? 0 : 1;
		if (bCheckChildren)
			SetItem(&item);
		else if (item.cChildren == 0)
			// restore item's plus button if no children inserted
			SetItemState(hItem, 0, TVIS_EXPANDED);
	}
	// clean up snapshot
	DestroySnapshot();
	// redraw now
	SetRedraw(TRUE);
	// scroll like in a standard expansion
	HTREEITEM hChild = GetChildItem(hItem);
	while (hChild != NULL && GetFirstVisibleItem() != hItem)
	{
//		EnsureVisible(hChild);
		hChild = GetNextSiblingItem(hChild);
	}
}

BOOL CWaitingTreeCtrl::WantsRefresh(HTREEITEM hItem)
{
	UNREFERENCED_PARAMETER(hItem);

	// default implementation, no refresh
	return FALSE;
}

BOOL CWaitingTreeCtrl::GetItemImageRect(HTREEITEM hItem, LPRECT pRect)
{
	if (GetImageList(TVSIL_NORMAL) == NULL)
		return FALSE;	// no images

	CRect rc;
	// get item rect
	if (!GetItemRect(hItem, &rc, TRUE))
		return FALSE;

	int cx = GetSystemMetrics(SM_CXSMICON);
	int cy = GetSystemMetrics(SM_CYSMICON);

	// move onto the icon space
	int margin = (rc.Height()-cy)/2;
	rc.OffsetRect(-cx-3 , margin);
	rc.right = rc.left + cx;	// make it square
	rc.bottom = rc.top + cy;	// make it square

	*pRect = rc;
	return TRUE;
}

void CWaitingTreeCtrl::DrawUserIcon()
{
	// draw user defined icon

	CRect rcIcon;
	if (!GetItemImageRect(m_hItemMsg, &rcIcon))
		return;	// no image

	// create background brush with current bg color (take rgb part only)
	HBRUSH hBrush = CreateSolidBrush(GetBkColor() & 0x00FFFFFF);

	CClientDC dc(this);

	if (m_hIconMsg != NULL)
		DrawIconEx(dc.GetSafeHdc(), rcIcon.left, rcIcon.top, m_hIconMsg,
			rcIcon.Width(), rcIcon.Height(), 0, hBrush, DI_NORMAL);
	else
		FillRect(dc.GetSafeHdc(), &rcIcon, hBrush);

	DeleteObject(hBrush);
}

void CWaitingTreeCtrl::SetWaitMessage(LPCTSTR pszText, HICON hIcon)
{
	m_sWaitMsg = pszText;
	m_hIconMsg = hIcon;
}

void CWaitingTreeCtrl::RefreshSubItems(HTREEITEM hParent)
{
	if (hParent != TVI_ROOT && !ItemHasChildren(hParent))
		return;

	SetRedraw(FALSE);
	DeleteChildren(hParent);
	if (hParent == TVI_ROOT)
		PopulateRoot();
	else
	{
		PreExpandItem(hParent);
		ExpandItem(hParent);
	}
	SetRedraw(TRUE);
}

inline BOOL CWaitingTreeCtrl::NeedsChildren(HTREEITEM hParent)
{
	return (GetChildItem(hParent) == NULL);
}

void CWaitingTreeCtrl::DeleteChildren(HTREEITEM hParent)
{
	HTREEITEM hChild = GetChildItem(hParent);
	HTREEITEM hNext;

	while (hChild != NULL)
	{
		hNext = GetNextSiblingItem(hChild);
		DeleteItem(hChild);
		hChild = hNext;
	}
}

BOOL CWaitingTreeCtrl::OnEraseBkgnd(CDC* pDC) 
{
	if (!m_bDrawSnapshot)
		return CTreeCtrl::OnEraseBkgnd(pDC);

	DrawSnapshot(pDC);
	SetEvent(m_hRedrawEvent);

	return TRUE;
}

void CWaitingTreeCtrl::TakeSnapshot()
{
	CClientDC dc(this);
	CRect rcClient;
	GetClientRect(&rcClient);
	int width = rcClient.Width(), height = rcClient.Height();

	// create the snapshot
	CDC dcSnapshot;
	dcSnapshot.CreateCompatibleDC(&dc);
	m_bmpSnapshot.CreateCompatibleBitmap(&dc, width, height);
	// copy the control's background
	CBitmap* pOldBmp = dcSnapshot.SelectObject(&m_bmpSnapshot);
	dcSnapshot.BitBlt(0, 0, width, height, &dc, 0, 0, SRCCOPY);
	dcSnapshot.SelectObject(pOldBmp);

	m_bDrawSnapshot = TRUE;
}

void CWaitingTreeCtrl::DrawSnapshot(CDC *pDC)
{
	BITMAP bm;
	m_bmpSnapshot.GetBitmap(&bm);

	// prepare the snapshot
	CDC dcSnapshot;
	dcSnapshot.CreateCompatibleDC(pDC);
	// copy to the control's background
	CBitmap* pOldBmp = dcSnapshot.SelectObject(&m_bmpSnapshot);
	pDC->BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &dcSnapshot, 0, 0, SRCCOPY);
	dcSnapshot.SelectObject(pOldBmp);
}

void CWaitingTreeCtrl::DestroySnapshot()
{
	m_bmpSnapshot.DeleteObject();

	m_bDrawSnapshot = FALSE;
}
