// CollapsedBox.cpp : implementation file
//
#include "stdafx.h"

#include <set>
#include <map>
#include <list>
#include <vector>
#include <algorithm>

#include "aplguiex.h"

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


#define WM_TEST_CBX_ATTACHED			WM_USER + 237
#define WM_COMMAND_HIDE_CTRL			WM_USER + 238
//*******************************************************************************

// кнопка [+]/[-]
class CPlusMinsButton : public CStatic
{
public:
	CPlusMinsButton(){m_bPlus = false;}
	void SetState(bool bPlus){m_bPlus = bPlus;}
	void Change(){m_bPlus = !m_bPlus;}
	bool IsPlus(){return m_bPlus;}
protected:
	bool m_bPlus;
	void PaintPM(CDC &dc);
	afx_msg void OnPaint();
	DECLARE_MESSAGE_MAP()
};

// реализация 
class CCollapsedBox_Impl
{
public:
	struct SSubCtrl
	{
		bool bWisible;
		bool bEnable;
		HWND wnd;
		SSubCtrl(HWND w, bool b = true){wnd = w; bWisible = b;}
	};
	typedef std::list<SSubCtrl>		listHWND;
	typedef listHWND::iterator		listHWNDIter;
	struct SData
	{
		listHWND *arr;
		std::set<HWND> childs;
		HWND frm;
		HWND dlg;
		RECT reFrm;
		CCollapsedBox_Impl *impl;
	};

	int m_minH;
	int m_dh;
	bool m_bAttached;
	listHWND m_listSC;
	CCollapsedBox *m_cbox;
	CCollapsedBoxList_Impl *m_listImpl;
	CPlusMinsButton m_pmBtn;
	CButton m_checkBox;

	int m_nBottomYChange;

	int m_nStartText;
	bool m_bStateChanging;
	bool m_bCommandHideMode;
	bool m_bCommandDisabMode;
	bool m_bPMVisible;
	bool m_bCBVisible;

	SSubCtrl *FindByIDC(int idc);
	SSubCtrl *FindByHWND(HWND w);

	static BOOL CALLBACK FnCtrl(HWND w, LPARAM par);
	CCollapsedBox_Impl(CCollapsedBox *box);
	~CCollapsedBox_Impl();
	bool IsExpanded() {return !m_pmBtn.IsPlus();}
	void ShowCtrls(bool bShow);
	void ClearCtrls();
	void FillArr();
	void HResize(bool bExpanding);
	bool SendExpandingMsg(bool bExpanding, int& dh, int code);
	void SendExpMsg(bool bExpanding, int dh, int code, bool bListMsg = true);
	int  SetState(bool bExpand, bool bSendMsg);

	void Move(int dx, int dy);

	bool ShowWnd(HWND w, bool bShow);
	bool EnabWnd(HWND w, bool bEn);

	bool IsCtrlVisible(int nIDC);

	void SetEnableCtrls(bool bEn, bool bSendMsg);

	static LRESULT CALLBACK FnCtrlWinProc(HWND w, UINT m, WPARAM wp, LPARAM lp);

	struct S1
	{
		CCollapsedBox_Impl *impl;
		WNDPROC oldProc;
	};
	typedef std::map<HWND, S1> mapHCB;
	typedef mapHCB::iterator mapHCBIter;

	static mapHCB sm_mapHCB;

	static CCollapsedBox_Impl *GetCBoxByCtrl(HWND ctrl);

	static int sm_nPMSpaceCount;
	static int sm_nCHSpaceCount;
	static int sm_nDXPMCB;
};

int CCollapsedBox_Impl::sm_nPMSpaceCount = 5;
int CCollapsedBox_Impl::sm_nCHSpaceCount = 5;
int CCollapsedBox_Impl::sm_nDXPMCB = 15;
CCollapsedBox_Impl::mapHCB CCollapsedBox_Impl::sm_mapHCB;

class CCollapsedBoxList_Impl
{
public:
	typedef std::vector<CCollapsedBox *>		vectorCBX;
	typedef vectorCBX::iterator	vectorCBXIter;
	typedef std::vector<int> vectorInt;

	CCollapsedBoxList *m_list;
	vectorCBX m_vectorCBX;
	vectorCBX m_vectorCreated;
	CWnd *m_dlg;
	bool m_bSendMsg;

	CCollapsedBoxList_Impl(CCollapsedBoxList *cbl);
	~CCollapsedBoxList_Impl();

	void AddItem(CCollapsedBox *pCBX, bool bCreated);
	int  SetStateAll(bool bExpand, bool bSendMsg);

	void SendMsg(bool bExpanding, int dh);
	CCollapsedBox *FindById(int nIDC, int *n = 0);

	void MoveNexts(int n, int dx, int dy);
	void Moves(vectorInt &vect);

	int GetBottomYChange(int i);
};

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK CCollapsedBox_Impl::FnCtrlWinProc(HWND w, UINT m, WPARAM wp, LPARAM lp)
{
	mapHCBIter iter = sm_mapHCB.find(w);
	if(iter == sm_mapHCB.end()) return 0;

	if (!w || !::IsWindow(w))
	{
		sm_mapHCB.erase(iter);
		return 0;
	}

	if(m == WM_COMMAND_HIDE_CTRL)
	{
		CCollapsedBox_Impl *pCBXI = iter->second.impl;
		pCBXI->m_bCommandHideMode = true;
		::ShowWindow(w, SW_HIDE);
		return 0;
	}

	if(m == WM_SHOWWINDOW) 
	{
		CCollapsedBox_Impl *pCBXI = iter->second.impl;
		bool bShow = (wp != 0);
		if(!pCBXI->ShowWnd(w, bShow)) 
		{
			// прячем окно, потому что рамка свернута
			::PostMessage(w, WM_COMMAND_HIDE_CTRL, 0, 0);
			return 0;
		}
		else
			return ::CallWindowProc(iter->second.oldProc, w, m, wp, lp);
	}

	if(m == WM_ENABLE)
	{
		CCollapsedBox_Impl *pCBXI = iter->second.impl;
		bool bEnab = (wp != 0);
		if(!pCBXI->EnabWnd(w, bEnab)) 
		{
			// прячем окно, потому что рамка свернута
		//	::PostMessage(w, WM_COMMAND_HIDE_CTRL, 0, 0);
			pCBXI->m_bCommandDisabMode = true;
			::EnableWindow(w, FALSE);
			return 0;
		}
		else
			return ::CallWindowProc(iter->second.oldProc, w, m, wp, lp);
	}

	if(m == WM_DESTROY)
	{
		::SetWindowLong(w, GWL_WNDPROC, (LONG)iter->second.oldProc);
		mapHCBIter iter_loc = sm_mapHCB.find(w);
		if (iter_loc != sm_mapHCB.end() && w && ::IsWindow(w))
		{
			CCollapsedBox_Impl* pCBXI = iter_loc->second.impl;
			listHWNDIter iter_list;
			for (iter_list = pCBXI->m_listSC.begin(); iter_list != pCBXI->m_listSC.end(); ++iter_list)
			{
				if (w == iter_list->wnd)
				{
					pCBXI->m_listSC.erase(iter_list);
					break;
				}
			}
		}
		sm_mapHCB.erase(iter);
		return ::SendMessage(w, m, wp, lp);
	}
	return ::CallWindowProc(iter->second.oldProc, w, m, wp, lp);
}

CCollapsedBox_Impl *CCollapsedBox_Impl::GetCBoxByCtrl(HWND ctrl)
{
	if(!ctrl) return NULL;
	mapHCBIter iter = sm_mapHCB.find(ctrl);
	return ((iter != sm_mapHCB.end()) ? iter->second.impl : NULL);
}

CCollapsedBox *CCollapsedBox::GetCBoxByCtrl(HWND ctrl)
{
	CCollapsedBox_Impl *impl = CCollapsedBox_Impl::GetCBoxByCtrl(ctrl);
	return (impl ? impl->m_cbox : NULL);
}


BEGIN_MESSAGE_MAP(CPlusMinsButton, CStatic)
	ON_WM_PAINT()
END_MESSAGE_MAP()
void CPlusMinsButton::PaintPM(CDC &dc)
{
	dc.FillSolidRect(0, 0, 13, 13, 0xffffff);	dc.Rectangle(0, 0, 13, 13);
	int t = 4;
	if(m_bPlus){t = 3; dc.MoveTo(6, t); dc.LineTo(6, 13-t);}
	dc.MoveTo(t, 6); dc.LineTo(13-t, 6);
}
void CPlusMinsButton::OnPaint(){	CPaintDC dc(this);	PaintPM(dc);}
//////////////////////////////////////////////////////////////////////////

CCollapsedBox_Impl::CCollapsedBox_Impl(CCollapsedBox *box)
{
	m_cbox = box;
	m_nStartText = sm_nPMSpaceCount;
	m_minH = 17;
	m_dh = m_minH;
	m_bAttached = false;
	m_listImpl = NULL;
	m_bStateChanging = false;
	m_bCommandHideMode = false;
	m_bCommandDisabMode = false;
	m_bPMVisible = true;
	m_bCBVisible = false;
	m_nBottomYChange = 0;
}

CCollapsedBox_Impl::~CCollapsedBox_Impl()
{
	ClearCtrls();
}

BOOL CCollapsedBox_Impl::FnCtrl(HWND w, LPARAM par)
{
	if(!w) return TRUE;
	SData *data = (SData *)par;
	data->childs.insert(w);
	if(w == data->frm) return TRUE;
	HWND dlg = ::GetParent(w);
	if(dlg != data->dlg) return TRUE;
	RECT re1;
	::GetWindowRect(w, &re1);
	S1 s1;
	if(CaplCtrlHelper::Rect1InRect2(&re1, &data->reFrm))
	{
		SSubCtrl sc(w, (::GetWindowLong(w, GWL_STYLE) & WS_VISIBLE) != 0);
		sc.bEnable = CaplCtrlHelper::IsEnable(w);
		data->arr->push_back(sc);
		s1.impl = data->impl;
		s1.oldProc = (WNDPROC)::GetWindowLong(w, GWL_WNDPROC);
		if(s1.oldProc == FnCtrlWinProc) return NULL;
		sm_mapHCB.insert(std::make_pair(w, s1));
		::SetWindowLong(w, GWL_WNDPROC, (LONG)FnCtrlWinProc);
	}
	return TRUE;
}

bool CCollapsedBox_Impl::IsCtrlVisible(int nIDC)
{
	SSubCtrl *ss = FindByIDC(nIDC);
	return (ss ? ss->bWisible : false);
}

CCollapsedBox_Impl::SSubCtrl *CCollapsedBox_Impl::FindByHWND(HWND w)
{
	if(!::IsWindow(w)) return NULL;
	mapHCBIter iter_sm = sm_mapHCB.find(w);
	if (iter_sm == sm_mapHCB.end()) return NULL;
	listHWNDIter iter;
	for (iter = m_listSC.begin(); iter != m_listSC.end(); ++iter)
		if(w == iter->wnd) return &(*iter);
	return NULL;
}

CCollapsedBox_Impl::SSubCtrl *CCollapsedBox_Impl::FindByIDC(int idc)
{
	if(idc == 0) return NULL;
	listHWNDIter iter;
	int idc1;
	for (iter = m_listSC.begin(); iter != m_listSC.end(); ++iter)
	{
		idc1 = ::GetDlgCtrlID(iter->wnd);
		if(idc == idc1) return &(*iter);
	}
	return NULL;
}

bool CCollapsedBox_Impl::ShowWnd(HWND w, bool bShow)
{
	if(m_bCommandHideMode)
	{
		m_bCommandHideMode = false;
		return true;
	}
	if(m_bStateChanging) return true;

	SSubCtrl *ss = FindByHWND(w);
	if(!ss) return true;
	ss->bWisible = bShow;

	return (IsExpanded() || !bShow || !m_bPMVisible);
}

bool CCollapsedBox_Impl::EnabWnd(HWND w, bool bEn)
{
	if(m_bCommandDisabMode)
	{
		m_bCommandDisabMode = false;
		return true;
	}
	if(m_bStateChanging) return true;

	SSubCtrl *ss = FindByHWND(w);
	if(!ss) return true;
	ss->bEnable = bEn;

	return (m_cbox->IsChecked() || !bEn || !m_bCBVisible);
}

void CCollapsedBox_Impl::FillArr()
{
	CWnd *dlg = m_cbox->GetParent();
	if(!dlg) return;
	if(!::IsWindow(dlg->m_hWnd)) return;
	SData data;
	data.frm = m_cbox->m_hWnd;
	if(!data.frm) return;
	m_listSC.clear();
	data.arr = &m_listSC;
	data.impl = this;
	data.dlg = dlg->m_hWnd;

	::GetWindowRect(data.frm, &data.reFrm);
	::EnumChildWindows(dlg->m_hWnd, FnCtrl, (LPARAM)&data);
	CWnd* pWndChild = dlg->GetWindow(GW_CHILD);
	while (pWndChild)
	{
		std::set<HWND>::iterator it = data.childs.find(pWndChild->m_hWnd);
		if (it == data.childs.end())
			FnCtrl(pWndChild->m_hWnd, (LPARAM)&data);
		pWndChild = pWndChild->GetNextWindow();
	}
}

void CCollapsedBox_Impl::ShowCtrls(bool bShow)
{
	int sh = (bShow ? SW_SHOW : SW_HIDE);
	listHWNDIter iter;
	m_bStateChanging = true;
	for (iter = m_listSC.begin(); iter != m_listSC.end(); ++iter)
	{
		if(bShow){if(!iter->bWisible) continue;}
		::ShowWindow(iter->wnd, sh);
	}
	m_bStateChanging = false;
}

void CCollapsedBox_Impl::ClearCtrls()
{
	listHWNDIter iter_list;
	for (iter_list = m_listSC.begin(); iter_list != m_listSC.end(); ++iter_list)
	{
		mapHCBIter iter = sm_mapHCB.find(iter_list->wnd);
		if (iter != sm_mapHCB.end())
		{
			::SetWindowLong(iter_list->wnd, GWL_WNDPROC, (LONG)iter->second.oldProc);
			sm_mapHCB.erase(iter);
		}
	}
	m_listSC.clear();
}

void CCollapsedBox_Impl::HResize(bool bExpanding)
{
	int dh = (bExpanding ? m_dh : m_minH);
	CRect re;
	m_cbox->GetWindowRect(re);
	m_cbox->SetWindowPos(0,0,0, re.Width(), dh, SWP_NOZORDER|SWP_NOMOVE);
}

void CCollapsedBox_Impl::SetEnableCtrls(bool bEn, bool bSendMsg)
{
	listHWNDIter iter;
	m_bStateChanging = true;
	int t;
	for (iter = m_listSC.begin(); iter != m_listSC.end(); ++iter)
	{
		if(bEn){if(!iter->bEnable) continue;}
		t = CaplCtrlHelper::CtrlType(iter->wnd);
		CaplCtrlHelper::SetEnabWnd(iter->wnd, bEn, t);
	}
	m_bStateChanging = false;
	if(bSendMsg)
		SendExpMsg(bEn, 0, APL_COLLBOX_CHECKED_NMESSAGE, false);
}

int CCollapsedBox_Impl::SetState(bool bExpand, bool bSendMsg)
{
	if(!::IsWindow(m_cbox->m_hWnd)) return 0;
	if(!m_bPMVisible) return 0;
	if(IsExpanded() == bExpand) return 0;
	int dh = m_dh;
	if(!bExpand)
	{
		CRect reFrm;
		m_cbox->GetWindowRect(reFrm);
		dh = reFrm.Height();
	}
	if(bSendMsg)
	{
		int loc_dh = dh - m_minH;
		if(SendExpandingMsg(bExpand, loc_dh, APL_COLLBOX_EXPANDING_NMESSAGE)==false)
			return 0;
		dh = loc_dh + m_minH;
	}
	m_dh = dh;
	ShowCtrls(bExpand);
	HResize(bExpand);
	m_pmBtn.Change();
	if(bSendMsg)
		SendExpMsg(bExpand, m_dh - m_minH, APL_COLLBOX_EXPANDED_NMESSAGE, true);
	int ret = m_dh - m_minH;
	return (bExpand ? ret : -ret);
}

bool CCollapsedBox_Impl::SendExpandingMsg(bool bExpanding, int& dh, int code)
{
	NMCOLLAPSEDBOX nm;
	memset(&nm, 0, sizeof(NMCOLLAPSEDBOX));
	nm.hdr.idFrom	= m_cbox->GetDlgCtrlID();
	nm.hdr.code		= code;
	nm.hdr.hwndFrom = m_cbox->m_hWnd;
	nm.dh = dh;
	nm.bExpanded = bExpanding;
	nm.pCBX = m_cbox;
	if(!bExpanding) nm.dh = -nm.dh;
	CWnd *dlg = m_cbox->GetParent();
	if(!dlg) return false;
	if(!dlg->m_hWnd) return false;

	if(dlg->SendMessage(WM_NOTIFY, (WPARAM)nm.hdr.idFrom, (LPARAM)&nm)!=0)
		return false;

	dh = nm.dh;
	if(!bExpanding) dh = -dh;
	return true;
}

void CCollapsedBox_Impl::SendExpMsg(bool bExpanding, int dh, int code, bool bListMsg)
{
	NMCOLLAPSEDBOX nm;
	memset(&nm, 0, sizeof(NMCOLLAPSEDBOX));
	nm.hdr.idFrom	= m_cbox->GetDlgCtrlID();
	nm.hdr.code		= code;
	nm.hdr.hwndFrom = m_cbox->m_hWnd;
	nm.dh = dh;
	nm.bExpanded = bExpanding;
	nm.pCBX = m_cbox;
	if(!bExpanding) nm.dh = -nm.dh;
	CWnd *dlg = m_cbox->GetParent();
	if(!dlg) return;
	if(!dlg->m_hWnd) return;

	dlg->SendMessage(WM_NOTIFY, (WPARAM)nm.hdr.idFrom, (LPARAM)&nm);

	if(m_listImpl && bListMsg)
	{
		int nn = -1;
		m_listImpl->FindById(m_cbox->GetDlgCtrlID(), &nn);
		if(nn >= 0)
			m_listImpl->MoveNexts(nn, 0, nm.dh);
		m_listImpl->SendMsg(bExpanding, nm.dh);
	}
}

void CCollapsedBox_Impl::Move(int dx, int dy)
{
	if((dy == 0) && (dx == 0)) return;
	CaplCtrlHelper::MoveWnd(m_cbox->m_hWnd, dx, dy);;
	m_cbox->Invalidate(TRUE);
	listHWNDIter iter;
	HWND w;
	for (iter = m_listSC.begin(); iter != m_listSC.end(); ++iter)
	{
		w = iter->wnd;
		CaplCtrlHelper::MoveWnd(w, dx, dy);
		::InvalidateRect(w, NULL, TRUE);
	}
}
/////////////////////////////////////////////////////////////////////////////
// CCollapsedBox

CCollapsedBox::CCollapsedBox()
{
	m_impl = new CCollapsedBox_Impl(this);
}

CCollapsedBox::~CCollapsedBox()
{
	bool bAttached = m_impl->m_bAttached;
	delete m_impl;
	m_impl = NULL;
	if(bAttached) 
		Detach();
}

IMPLEMENT_DYNAMIC(CCollapsedBox, CButton)

BEGIN_MESSAGE_MAP(CCollapsedBox, CButton)
	//{{AFX_MSG_MAP(CCollapsedBox)
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_BN_CLICKED(10, OnStaticPM)
	ON_BN_CLICKED(11, OnCheckBoxCh)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCollapsedBox message handlers

void CCollapsedBox::OnStaticPM(){Change();}

void CCollapsedBox::OnCheckBoxCh()
{
	SetCheckEnable(!IsChecked());
}

BOOL CCollapsedBox::PreCreateWindow(CREATESTRUCT& cs) 
{
	return CButton::PreCreateWindow(cs);
}

int CCollapsedBox::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CButton::OnCreate(lpCreateStruct) == -1)return -1;
	return 0;
}

void CCollapsedBox::OnDestroy()
{
	m_impl->ClearCtrls();
	CButton::OnDestroy();
}

void CCollapsedBox::SetOptions(bool bPlusMinus, bool bCheckBox)
{
	if(!this) return;
	if(!::IsWindow(m_hWnd)) return;
	bool bCh1 = (bPlusMinus != m_impl->m_bPMVisible);
	bool bCh2 = (bCheckBox != m_impl->m_bCBVisible);
	if(!bCh1 && !bCh2) return;
	
	if(bCh1) // поменялся режим коллапсинга
	{
		m_impl->m_pmBtn.ShowWindow(bPlusMinus ? SW_SHOW : SW_HIDE);
		if(!bPlusMinus)	Expand();
		m_impl->m_bPMVisible = bPlusMinus;
		CaplCtrlHelper::MoveWnd(m_impl->m_checkBox, 
			(bPlusMinus ? CCollapsedBox_Impl::sm_nDXPMCB : -CCollapsedBox_Impl::sm_nDXPMCB), 0);
		if(!bCh2 && bCheckBox) m_impl->m_checkBox.Invalidate();
	}

	if(bCh2) // поменялся режим енабла
	{
		m_impl->m_checkBox.ShowWindow(bCheckBox ? SW_SHOW : SW_HIDE);
		if(!bCheckBox) SetCheckEnable(true);
		m_impl->m_bCBVisible = bCheckBox;
	}

	int nSpaces =	(bPlusMinus ? CCollapsedBox_Impl::sm_nPMSpaceCount : 0) +
					(bCheckBox  ? CCollapsedBox_Impl::sm_nCHSpaceCount : 0);

	if(nSpaces != m_impl->m_nStartText)
	{
		// поменялось количество пробелов - переустанавливаем текст
		CString s1;
		GetWindowText(s1);
		m_impl->m_nStartText = nSpaces;
		SetWindowText(s1);
	}
}

bool CCollapsedBox::IsChecked()
{
	if(!this) return true;
	if(!::IsWindow(m_impl->m_checkBox.m_hWnd)) return true;
	return (m_impl->m_checkBox.GetCheck() != 0);
}

void CCollapsedBox::SetCheckEnable(bool bEnable, bool bSendMsg)
{
	if(!this) return;
	if(!::IsWindow(m_hWnd)) return;
	if(!m_impl->m_bCBVisible) return;
	if(IsChecked() == bEnable) return;
	m_impl->m_checkBox.SetCheck(bEnable ? 1 : 0);
	m_impl->SetEnableCtrls(bEnable, bSendMsg);
}

bool CCollapsedBox::IsCtrlEnable(int nIDCtrl)
{
	CCollapsedBox_Impl::SSubCtrl *ss = m_impl->FindByIDC(nIDCtrl);
	return (ss ? ss->bEnable : false);
}

bool CCollapsedBox::IsCtrlEnable(HWND w)
{
	CCollapsedBox_Impl::SSubCtrl *ss = m_impl->FindByHWND(w);
	return (ss ? ss->bEnable : false);
}

void CCollapsedBox::PreSubclassWindow() 
{
	m_impl->FillArr();

	static TCHAR bufSpaces[30];
	
	CRect re1(9, 0, 22, 13);
	m_impl->m_pmBtn.Create(_T(""), WS_CHILD|WS_VISIBLE|SS_NOTIFY, re1, this, 10);

	re1.OffsetRect(CCollapsedBox_Impl::sm_nDXPMCB, 0);
	m_impl->m_checkBox.Create(_T(""), WS_CHILD|BS_CHECKBOX, re1, this, 11);
	m_impl->m_checkBox.SetCheck(1);

	CString sText;
	int i;
	GetWindowText(sText);
	for (i=0; i<CCollapsedBox_Impl::sm_nPMSpaceCount; ++i) bufSpaces[i] = _T(' ');
	bufSpaces[i] = 0;
	SetWindowText(bufSpaces + sText);
//	SetWindowText("          " + sText);

	CButton::PreSubclassWindow();
}

LRESULT CCollapsedBox::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
	static TCHAR buf[2048];
	LPCTSTR strc;
	LPTSTR  str;
	LRESULT ret;
	int i;
	switch(message)
	{
	case WM_TEST_CBX_ATTACHED: return (LRESULT)777;
	case WM_GETTEXTLENGTH: 
		return CButton::WindowProc(message, wParam, lParam) - m_impl->m_nStartText;
	case WM_GETTEXT: 
		if(m_impl->m_nStartText == 0) 
			return CButton::WindowProc(message, wParam, lParam);
		else
		{
			ret = CButton::WindowProc(message, (WPARAM)(wParam + m_impl->m_nStartText), (LPARAM)buf);
			str = (LPTSTR)lParam;
			strc = buf + m_impl->m_nStartText;
			for (i=0; strc[i]; ++i) str[i] = strc[i];
			str[i] = strc[i];
			return ret - m_impl->m_nStartText;
		}

	case WM_SETTEXT: 
		if(m_impl->m_nStartText == 0) 
			return CButton::WindowProc(message, wParam, lParam);
		else
		{
			strc = (LPCTSTR)lParam;
			for(i=0; i<m_impl->m_nStartText; ++i) buf[i] = _T(' ');
			str = buf + m_impl->m_nStartText;
			i=0;
			if(strc) for (i=0; strc[i]; ++i) str[i] = strc[i];
			str[i] = 0;
			str = buf;
			return CButton::WindowProc(message, wParam, (LPARAM)str);
		}
	}
	return CButton::WindowProc(message, wParam, lParam);
}

int CCollapsedBox::GetDH()
{
	return (m_impl->m_dh - m_impl->m_minH);
}

void CCollapsedBox::EnumCtrls()
{
	CCollapsedBox_Impl::listHWNDIter iter;
	bool bVis, bEn, ret;
	for (iter = m_impl->m_listSC.begin(); iter != m_impl->m_listSC.end(); ++iter)
	{
		bVis = iter->bWisible;
		bEn  = iter->bEnable;
		ret  = EnumProc(iter->wnd, bVis, bEn);
	//	if(bVis != iter->bVisible)

		if(!ret) break;
	}
}

BOOL CCollapsedBox::Attach(HWND hWndNew)
{
	if(m_impl->m_bAttached || ::IsWindow(m_hWnd)) return TRUE; 
	if(!CButton::Attach(hWndNew)) return FALSE;
	m_impl->m_bAttached = true;
	if((int)::SendMessage(hWndNew, WM_TEST_CBX_ATTACHED, 0, 0) == 777)
		return TRUE;
	CWnd *dlg = GetParent();
	CButton::Detach();
	CDataExchange dx(dlg, FALSE);
	DDX_Control(&dx, ::GetDlgCtrlID(hWndNew), *this);
	return TRUE;
}

bool CCollapsedBox::IsExpanded(){return m_impl->IsExpanded();}

int CCollapsedBox::Collapse(bool bSendMsg)
{
	if(this == NULL) return 0;
	return m_impl->SetState(false, bSendMsg);
}

int CCollapsedBox::Expand(bool bSendMsg)
{
	if(this == NULL) return 0;
	return m_impl->SetState(true, bSendMsg);
}

int CCollapsedBox::Change(bool bSendMsg)
{
	if(this == NULL) return 0;
	return (IsExpanded() ? Collapse(bSendMsg) : Expand(bSendMsg));
}

void CCollapsedBox::Move(int dx, int dy)
{
	if(this == NULL) return;
	m_impl->Move(dx, dy);
}

bool CCollapsedBox::IsCtrlVisible(int nIDCtrl)
{
	return m_impl->IsCtrlVisible(nIDCtrl);
}

bool CCollapsedBox::IsCtrlVisible(HWND ctrl)
{
	if(!::IsWindow(ctrl)) return false;
	int idc = ::GetDlgCtrlID(ctrl);
	return m_impl->IsCtrlVisible(idc);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// CCollapsedBoxList

CCollapsedBoxList_Impl::CCollapsedBoxList_Impl(CCollapsedBoxList *cbl)
{
	m_list = cbl;
	m_dlg = NULL;
	m_bSendMsg = true;
}

CCollapsedBoxList_Impl::~CCollapsedBoxList_Impl()
{
	vectorCBXIter iter;
	CCollapsedBox *pCBX;
	for (iter = m_vectorCreated.begin(); iter != m_vectorCreated.end(); ++iter)
	{
		pCBX = *iter;
		delete pCBX;
	}
	m_vectorCreated.clear();
}

void CCollapsedBoxList_Impl::AddItem(CCollapsedBox *pCBX, bool bCreated)
{
	m_vectorCBX.push_back(pCBX);
	if(bCreated) m_vectorCreated.push_back(pCBX);
	pCBX->m_impl->m_listImpl = this;
}

int CCollapsedBoxList_Impl::SetStateAll(bool bExpand, bool bSendMsg)
{
	vectorCBXIter iter;
	CCollapsedBox *pCBX;
	m_bSendMsg = false;
	int dh = 0, t;
	vectorInt vi; vi.push_back(0);
	for (iter = m_vectorCBX.begin(); iter != m_vectorCBX.end(); ++iter)
	{
		pCBX = *iter;
		t = (bExpand ? pCBX->Expand() : pCBX->Collapse());
		vi.push_back(t);
		dh += t;
	}
	m_bSendMsg = true;
	Moves(vi);
	if(bSendMsg)
		SendMsg(bExpand, dh);
	return dh;
}

void CCollapsedBoxList_Impl::SendMsg(bool bExpanding, int dh)
{
	if(dh == 0) return;
	if(!m_bSendMsg) return;
	if(!m_dlg) return;
	if(!::IsWindow(m_dlg->m_hWnd)) return;
	m_dlg->SendMessage(WM_CBXLIST_H_CHANGE, (WPARAM)m_list, (LPARAM)dh);
}

CCollapsedBox *CCollapsedBoxList_Impl::FindById(int nIDC, int *n)
{
	int r; if(!n) n = &r; *n = -1;
	if(nIDC == 0) return NULL;
	*n = 0;
	vectorCBXIter iter;
	CCollapsedBox *pCBX;
	for (iter = m_vectorCBX.begin(); iter != m_vectorCBX.end(); ++iter)
	{
		pCBX = *iter;
		if(pCBX->GetDlgCtrlID() == nIDC) return pCBX;
		++(*n);
	}
	*n = -1;
	return NULL;
}

void CCollapsedBoxList_Impl::MoveNexts(int n, int dx, int dy)
{
	if(!m_bSendMsg) return;
	if((dx == 0) && (dy == 0)) return;
	int i, start = n+1;
	if(start < 0) start = 0;
	if(start >= (int)m_vectorCBX.size()) return;
	CCollapsedBox *pCBX;
	for (i=start; i< (int)m_vectorCBX.size(); ++i)
	{
		pCBX = m_vectorCBX[i];
		pCBX->Move(dx, dy);
	}
}

void CCollapsedBoxList_Impl::Moves(vectorInt &vect)
{
	if(!m_bSendMsg) return;
	int i, dy = 0;
	CCollapsedBox *pCBX;
	for (i=0; i<(int)m_vectorCBX.size(); ++i)
	{
		if(i <= (int)vect.size()) dy += vect[i]; 
		pCBX = m_vectorCBX[i];
		pCBX->Move(0, dy);
	}
}

int CCollapsedBoxList_Impl::GetBottomYChange(int i)
{
	if((i < 0) || (i >= (int)m_vectorCBX.size())) 
		return ((m_vectorCBX.size() > 0) ? m_vectorCBX.back()->m_impl->m_nBottomYChange : 0);
	CCollapsedBox *pCBX = m_vectorCBX[i];
	return pCBX->m_impl->m_nBottomYChange;
}
//////////////////////////////////////////////////////////////////////////
CCollapsedBoxList::CCollapsedBoxList()
{
	m_impl = new CCollapsedBoxList_Impl(this);
}

CCollapsedBoxList::~CCollapsedBoxList()
{
	delete m_impl;
	m_impl = NULL;
}

void CCollapsedBoxList::SetDlg(CWnd *dlg)
{
	if(!dlg) return;
	m_impl->m_dlg = dlg;
}

void CCollapsedBoxList::Add(CCollapsedBox &cb)
{
	if(!::IsWindow(cb.m_hWnd)) return;
	CWnd *dlg = cb.GetParent();
	if((m_impl->m_dlg && (m_impl->m_dlg != dlg)) || !dlg) return;
	m_impl->m_dlg = dlg;
	m_impl->AddItem(&cb, false);
}

CCollapsedBox *CCollapsedBoxList::Add(int nIDCtrl)
{
	if(!m_impl->m_dlg) return NULL;
	if(!::IsWindow(m_impl->m_dlg->m_hWnd)) return NULL;

	CWnd *w = m_impl->m_dlg->GetDlgItem(nIDCtrl);
	
	if(!w) return NULL;
	if(!::IsWindow(w->m_hWnd)) return NULL;
	
	// может, сделаю проверку на рамку, если настроение будет

	bool bTrueWnd = (w->IsKindOf(RUNTIME_CLASS(CCollapsedBox)) == TRUE);
	CCollapsedBox *pCBX = (CCollapsedBox *)w;
		
	if(!bTrueWnd)
	{
		pCBX = new CCollapsedBox;
		if(!pCBX) return NULL;
		pCBX->Attach(w->m_hWnd);
	}

	m_impl->AddItem(pCBX, !bTrueWnd);

	return pCBX;
}

int CCollapsedBoxList::CollapseAll(bool bSendMsg)
{
	return m_impl->SetStateAll(false, bSendMsg);
}

int CCollapsedBoxList::ExpandAll(bool bSendMsg)
{
	return m_impl->SetStateAll(true, bSendMsg);
}

CCollapsedBox *CCollapsedBoxList::GetBox(int n)
{
	if((n<0) || (n>=(int)m_impl->m_vectorCBX.size())) return NULL;
	return m_impl->m_vectorCBX[n];
}

CCollapsedBox *CCollapsedBoxList::GetBoxByIDC(int nIDCtrl)
{
	return m_impl->FindById(nIDCtrl);
}

void CCollapsedBoxList::BeginMultiExpand()
{

}

void CCollapsedBoxList::AddExpand(int n, bool bExpand)
{
	CCollapsedBox *pCBX = GetBox(n);
	if(!pCBX) return;
	if(bExpand) pCBX->Expand();
	else pCBX->Collapse();
}

void CCollapsedBoxList::AddExpandByIDC(int nIDCtrl, bool bExpand)
{
	CCollapsedBox *pCBX = GetBoxByIDC(nIDCtrl);
	if(!pCBX) return;
	if(bExpand) pCBX->Expand();
	else pCBX->Collapse();
}

int  CCollapsedBoxList::EndMultiExpand()
{
	return 0;
}

void CCollapsedBoxList::Move(int dx, int dy)
{
	m_impl->MoveNexts(-1, dx, dy);
}

int CCollapsedBoxList::GetSize()
{
	return (int)m_impl->m_vectorCBX.size();
}

int CCollapsedBoxList::BottomYChange(int i)
{
	return m_impl->GetBottomYChange(i);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// CaplCtrlHelper

bool CaplCtrlHelper::SCB::TestRect(LPCRECT re)
{
	return (bTest ? CaplCtrlHelper::Rect1InRect2(re, re1) : true);
}

bool CaplCtrlHelper::SCB::TestRect(HWND wnd)
{
	if(!bTest) return true;
	CRect re;
	::GetWindowRect(wnd, re);
	return CaplCtrlHelper::Rect1InRect2(re, re1);
}

bool CaplCtrlHelper::Rect1InRect2(LPCRECT re1, LPCRECT re2)
{
	return ((re1->left  >= re2->left) &&(re1->top    >= re2->top)
		  &&(re1->right <= re2->right)&&(re1->bottom <= re2->bottom));
}

BOOL CALLBACK CaplCtrlHelper::FnEnabWnd(HWND wnd, LPARAM par)
{
	if(!wnd) return TRUE;
	SWisEn *data = (SWisEn *)par;
	if(data->bParentTest)
	{
		HWND dlg11 = ::GetParent(wnd);
		if(data->dlg != dlg11) return TRUE;
	}
	if(!data->TestRect(wnd)) return TRUE;
	CaplCtrlHelper::EnableWnd(wnd, data->en != FALSE);
	return TRUE;
}

BOOL CALLBACK CaplCtrlHelper::FnHideShow(HWND wnd, LPARAM par)
{
	if(!wnd) return TRUE;
	SWisEn *data = (SWisEn *)par;
	HWND dlg11 = ::GetParent(wnd);
	if(data->dlg != dlg11) return TRUE;
	if(!data->TestRect(wnd)) return TRUE;
	::ShowWindow(wnd, data->en);
	return TRUE;
}

BOOL CALLBACK CaplCtrlHelper::FnMove(HWND wnd, LPARAM par)
{
	if(!wnd) return TRUE;
	SMove *mv = (SMove *)par;
	HWND dlg11 = ::GetParent(wnd);
	if(mv->dlg != dlg11) return TRUE;
	CRect re;
	::GetWindowRect(wnd, re);
	if(!mv->TestRect(re)) return TRUE;
	CCollapsedBox *cb = CCollapsedBox::GetCBoxByCtrl(wnd);
	if(cb) //if(!cb->IsExpanded())
	{
		CRect re2;
		cb->GetWindowRect(re2);
		if(!mv->TestRect(re2)) return TRUE;
	}
	re.OffsetRect(mv->pt1.x, mv->pt1.y);
	::MoveWindow(wnd, re.left, re.top, re.Width(), re.Height(), TRUE);
	return TRUE;
}


void CaplCtrlHelper::MoveWnd(HWND hwnd, int dx, int dy)
{
	if(!hwnd) return;
	CRect re, pre;
	HWND pwnd = ::GetParent(hwnd);
	::GetWindowRect(hwnd, re);
	if(pwnd)
	{
		::GetClientRect(pwnd, pre);

		::ClientToScreen(pwnd, (LPPOINT)(LPRECT)pre);
		::ClientToScreen(pwnd, ((LPPOINT)(LPRECT)pre)+1);
//		if (::GetWindowLong(pwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
//			CRect::SwapLeftRight(pre);

		re.OffsetRect(-pre.TopLeft());
	}
	re.OffsetRect(dx, dy);
	::MoveWindow(hwnd, re.left, re.top, re.Width(), re.Height(), TRUE);
}

void CaplCtrlHelper::MoveWnd(CWnd &wnd, int dx, int dy)
{
	MoveWnd(wnd.m_hWnd, dx, dy);
}

void CaplCtrlHelper::MoveWnd(CWnd *dlg, int idc, int dx, int dy)
{
	CWnd *wnd = dlg->GetDlgItem(idc);
	if(wnd) MoveWnd(*wnd, dx, dy);
}

void CaplCtrlHelper::MoveWnds(CWnd *dlg, int idcFrm, int dx, int dy)
{
	if(!dlg || (idcFrm == 0)) return;
	if(!dlg->m_hWnd) return;
	CWnd *w = dlg->GetDlgItem(idcFrm);
	if(!w) return;
	CRect pre;
	dlg->GetClientRect(pre);
	dlg->ClientToScreen(pre);
	SMove mv;
	mv.dlg = dlg->m_hWnd;
	mv.pt1.x = dx - pre.left;
	mv.pt1.y = dy - pre.top;
	w->GetWindowRect(mv.re1);
	mv.bTest = true;
	::EnumChildWindows(dlg->m_hWnd, FnMove, (LPARAM)&mv);
}

void CaplCtrlHelper::MoveWnds(CWnd *dlg, const int *idcs, int cou, int dx, int dy)
{
	if(!dlg || !idcs || (cou < 1)) return;
	CWnd *w;
	for (int i=0; i<cou; i++)
	{
		w = dlg->GetDlgItem(idcs[i]);
		if(w) MoveWnd(w->m_hWnd, dx, dy);
	}
}

void CaplCtrlHelper::ResizeWnd(CWnd *dlg, int idc, int dx, int dy)
{
	CWnd *wnd = dlg->GetDlgItem(idc);
	if(wnd) ResizeWnd(*wnd, dx, dy);
}

void CaplCtrlHelper::ResizeWnd(CWnd &wnd, int dw, int dh)
{
	CRect re;
	wnd.GetWindowRect(re);
	wnd.SetWindowPos(0,0,0, re.Width()+dw, re.Height()+dh, SWP_NOZORDER|SWP_NOMOVE);
}


void CaplCtrlHelper::ShowEnab(CWnd *dlg, int idcFrm, int bShowEn, WNDENUMPROC fn)
{
	if(!dlg || (idcFrm == 0)) return;
	if(!dlg->m_hWnd) return;
	CWnd *w = dlg->GetDlgItem(idcFrm);
	if(!w) return;
	SWisEn mv;
	mv.dlg = dlg->m_hWnd;
	w->GetWindowRect(mv.re1);
	mv.bTest = true;
	mv.en = bShowEn;
	::EnumChildWindows(dlg->m_hWnd, fn, (LPARAM)&mv);
}

void CaplCtrlHelper::ShowEnab(CWnd *dlg, const int *idcs, int cou, int bShowEn, WNDENUMPROC fn)
{
	if(!dlg || !idcs || (cou < 1)) return;
	HWND w;
	SWisEn par;
	par.en = bShowEn;
	par.dlg = dlg->m_hWnd;
	for (int i=0; i<cou; i++)
	{
		dlg->GetDlgItem(idcs[i], &w);
		fn(w, (LPARAM)&par);
	}
}

void CaplCtrlHelper::AllEnab(CWnd *dlg, bool bEnable)
{
	if(!dlg) return; if(!dlg->m_hWnd) return;
	SWisEn p;
	p.dlg = dlg->m_hWnd;
	p.en = (bEnable ? TRUE : FALSE);
	::EnumChildWindows(dlg->m_hWnd, FnEnabWnd, (LPARAM)&p);
}

void CaplCtrlHelper::EnableWnd(HWND wnd, bool bEn)
{
	if(!wnd) return;
	int t = CaplCtrlHelper::CtrlType(wnd);
	if(t < 1) return;
	CCollapsedBox_Impl *cbx = CCollapsedBox_Impl::GetCBoxByCtrl(wnd);
	if(cbx)
	{
		CCollapsedBox_Impl::SSubCtrl *ss = cbx->FindByHWND(wnd);
		if(ss) ss->bEnable = bEn;
		if(!cbx->m_checkBox.GetCheck()) return;
	}
	SetEnabWnd(wnd, bEn, t);
}

void CaplCtrlHelper::SetEnabWnd(HWND wnd, bool bEn, int tp)
{
	if(tp == 1) SendMessage(wnd, EM_SETREADONLY, (bEn ? FALSE : TRUE), 0);
	else if(tp == 2) EnableWindow(wnd, (bEn ? TRUE : FALSE));
}

void CaplCtrlHelper::EnableWnd(CWnd *dlg, int idc, bool bEn)
{
	if(!dlg) return;
	HWND w = NULL;
	dlg->GetDlgItem(idc, &w);
	EnableWnd(w, bEn);
}

HTREEITEM CaplCtrlHelper::GetNextTreeItem(CTreeCtrl &tree, HTREEITEM item, bool bWithChild)
{
	HTREEITEM hRes; 
    if (bWithChild && tree.ItemHasChildren(item))
		return tree.GetChildItem(item); 
    while ((hRes = tree.GetNextSiblingItem(item)) == NULL) 
    { 
        if ((item = tree.GetParentItem(item)) == NULL) 
			return NULL; 
    }
    return hRes; 
}

bool CaplCtrlHelper::IsEnable(HWND wnd)
{
	if(!wnd) return false;
	int t = CtrlType(wnd);
	if(t == 1) return ((GetWindowLong(wnd, GWL_STYLE) & ES_READONLY) != ES_READONLY);
	if(t == 2) return (IsWindowEnabled(wnd) != FALSE);
	return true;
}

// 0 - static, 1 - ro, 2 - enab
int CaplCtrlHelper::CtrlType(HWND wnd)
{
	if(!wnd) return -1;
	static TCHAR str[20];

	GetClassName(wnd, str, 8);
	CString cs = str;
	cs.MakeUpper();
	if(cs == "EDIT") return 1;
	if(cs == "STATIC")return 0;
	if(cs == "BUTTON")
	{
		LONG st = GetWindowLong(wnd, GWL_STYLE);
		if((st & BS_GROUPBOX) != BS_GROUPBOX) 	return 2;
		else return 0;
	}
	return 2;
}

void CCollapsedBox::SetMinH(int iMinH /* = 17 */)
{
	if (m_impl)
		m_impl->m_minH = iMinH;
}