// ResizableDialog.cpp : implementation file


#include "stdafx.h"
#include "apl_gui.h"

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

/////////////////////////////////////////////////////////////////////////////
// CaplResizableDialog

inline void CaplResizableDialog::Construct()
{
	m_bInitDone = FALSE;

	m_bUseMinTrack = TRUE;
	m_bUseMaxTrack = FALSE;
	m_bUseMaxRect = FALSE;

	m_bShowGrip = TRUE;
	
	m_bEnableSaveRestore = FALSE;

	m_szGripSize.cx = GetSystemMetrics(SM_CXVSCROLL);
	m_szGripSize.cy = GetSystemMetrics(SM_CYHSCROLL);

	bResizebleStyle = false;
	m_showTooltipOnDisableButtons = false;
}

CaplResizableDialog::CaplResizableDialog()
{
	Construct();
}

CaplResizableDialog::CaplResizableDialog(UINT nIDTemplate, CWnd* pParentWnd)
	: CaplTranslateDialog(nIDTemplate, pParentWnd)
{
	Construct();
}

CaplResizableDialog::CaplResizableDialog(LPCTSTR lpszTemplateName, CWnd* pParentWnd)
	: CaplTranslateDialog(lpszTemplateName, pParentWnd)
{
	Construct();
}

CaplResizableDialog::~CaplResizableDialog()
{
	// for safety
	m_arrLayout.RemoveAll();	
}


BEGIN_MESSAGE_MAP(CaplResizableDialog, CaplTranslateDialog)
	//{{AFX_MSG_MAP(CaplResizableDialog)
	ON_WM_NCHITTEST()
	ON_WM_GETMINMAXINFO()
	ON_WM_SIZE()
	ON_WM_DESTROY()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CaplResizableDialog message handlers


BOOL CaplResizableDialog::OnInitDialog() 
{
	CaplTranslateDialog::OnInitDialog();

	UpdateGripPos();

	// gets the template size as the min track size
	CRect rc;
	GetWindowRect(&rc);
	m_ptMinTrackSize.x = rc.Width();
	m_ptMinTrackSize.y = rc.Height();

	m_bInitDone = TRUE;

	DWORD dwStyle(GetStyle());
	bResizebleStyle = (dwStyle&WS_THICKFRAME)!=0;

	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CaplResizableDialog::InitOkCancelButton(CaplButton &okBtn, CaplButton &cancelBtn)
{
	okBtn.SetIcon(GetAplStdIco16(APLSTDICO_OK));
	okBtn.SetFlat(FALSE);	

	cancelBtn.SetIcon(GetAplStdIco16(APLSTDICO_CANCEL));
	cancelBtn.SetFlat(FALSE);
}

void CaplResizableDialog::OnDestroy() 
{
	CaplTranslateDialog::OnDestroy();
	
	if (m_bEnableSaveRestore)
		SaveWindowRect();

	// remove old windows
	m_arrLayout.RemoveAll();
}

void CaplResizableDialog::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	if (m_bShowGrip && !IsZoomed() && bResizebleStyle)
	{
		// draw size-grip
		dc.DrawFrameControl(&m_rcGripRect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
	}
}

void CaplResizableDialog::OnSize(UINT nType, int cx, int cy) 
{
	CWnd::OnSize(nType, cx, cy);
	
	if (nType == SIZE_MAXHIDE || nType == SIZE_MAXSHOW)
		return;		// arrangement not needed

	if (m_bInitDone)
	{
		ArrangeLayout();
		UpdateUserData();
	}
}

LRESULT CaplResizableDialog::OnNcHitTest(CPoint point) 
{
	CPoint pt = point;
	ScreenToClient(&pt);

	// if in size grip and in client area
	if (m_bShowGrip && m_rcGripRect.PtInRect(pt) &&
		pt.x >= 0 && pt.y >= 0)
		return HTBOTTOMRIGHT;
	
	return CaplTranslateDialog::OnNcHitTest(point);
}

void CaplResizableDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
{
	if (!m_bInitDone)
		return;

	if (m_bUseMinTrack)
		lpMMI->ptMinTrackSize = m_ptMinTrackSize;

	if (m_bUseMaxTrack)
		lpMMI->ptMaxTrackSize = m_ptMaxTrackSize;

	if (m_bUseMaxRect)
	{
		lpMMI->ptMaxPosition = m_ptMaxPos;
		lpMMI->ptMaxSize = m_ptMaxSize;
	}
}

// layout functions

void CaplResizableDialog::AddAnchor(HWND wnd, CSize tl_type, CSize br_type, bool bRefreshAlways /*= false*/)
{
	ASSERT(wnd != NULL && ::IsWindow(wnd));
	ASSERT(::IsChild(*this, wnd));
	ASSERT(tl_type != NOANCHOR);

	// get control's window class
	
	CString st;
	GetClassName(wnd, st.GetBufferSetLength(MAX_PATH), MAX_PATH);
	st.ReleaseBuffer();
	st.MakeUpper();

	BOOL refresh = FALSE;

	// add the style 'clipsiblings' to a GroupBox
	// to avoid unnecessary repainting of controls inside
	//if (st == "BUTTON")
	//{
	//	refresh = TRUE;
		//DWORD style = GetWindowLong(wnd, GWL_STYLE);
	//	if (style & BS_GROUPBOX)
	//		SetWindowLong(wnd, GWL_STYLE, style | WS_CLIPSIBLINGS);
	//}

	// wnd classes that don't redraw client area correctly
	// when the hor scroll pos changes due to a resizing
	BOOL hscroll = FALSE;
	if (st == _T("LISTBOX"))
		hscroll = TRUE;

	// wnd classes that need refresh when resized
	if (st == _T("STATIC"))
	{
		DWORD style = GetWindowLong(wnd, GWL_STYLE);

		switch (style & SS_TYPEMASK)
		{
		case SS_LEFT:
		case SS_CENTER:
		case SS_RIGHT:
			// word-wrapped text needs refresh
			refresh = TRUE;
		}

		// centered images or text need refresh
		if (style & SS_CENTERIMAGE)
			refresh = TRUE;

		// simple text never needs refresh
		if ((style & SS_TYPEMASK) == SS_SIMPLE)
			refresh = FALSE;
	}

	// get dialog's and control's rect
	CRect wndrc, objrc;

	GetClientRect(&wndrc);
	::GetWindowRect(wnd, &objrc);
	ScreenToClient(&objrc);
	
	CSize tl_margin, br_margin;

	if (br_type == NOANCHOR)
		br_type = tl_type;
	
	// calculate margin for the top-left corner

	tl_margin.cx = objrc.left - wndrc.Width() * tl_type.cx / 100;
	tl_margin.cy = objrc.top - wndrc.Height() * tl_type.cy / 100;
	
	// calculate margin for the bottom-right corner

	br_margin.cx = objrc.right - wndrc.Width() * br_type.cx / 100;
	br_margin.cy = objrc.bottom - wndrc.Height() * br_type.cy / 100;

	// add to the list
	Layout obj(wnd, tl_type, tl_margin,	br_type, br_margin, hscroll, bRefreshAlways?true:refresh);
	m_arrLayout.Add(obj);
}

BOOL CaplResizableDialog::RemoveAnchor(HWND wnd)
{
	BOOL find = FALSE;
		
	for (int i = 0 ; i < m_arrLayout.GetSize(); ++i)
	{
		Layout& obj = m_arrLayout.GetAt(i);
		if (obj.hwnd == wnd)
		{
			find = TRUE;

			m_arrLayout.RemoveAt(i);

			break;
		}
	}

	return find;
}

void CaplResizableDialog::ArrangeLayout()
{
	// update size-grip
	InvalidateRect(&m_rcGripRect);
	UpdateGripPos();
	InvalidateRect(&m_rcGripRect);

	// init some vars
	CRect wndrc;
	GetClientRect(&wndrc);

	int i, count = m_arrLayout.GetSize();
	HDWP hdwp = BeginDeferWindowPos(count);

	for (i=0; i<count; ++i)
	{
		Layout& obj = m_arrLayout[i];

		CRect objrc, newrc;
		CWnd* wnd = CWnd::FromHandle(obj.hwnd); // temporary solution

		wnd->GetWindowRect(&objrc);
		ScreenToClient(&objrc);
		
		// calculate new top-left corner

		newrc.left = obj.tl_margin.cx + wndrc.Width() * obj.tl_type.cx / 100;
		newrc.top = obj.tl_margin.cy + wndrc.Height() * obj.tl_type.cy / 100;
		
		// calculate new bottom-right corner

		newrc.right = obj.br_margin.cx + wndrc.Width() * obj.br_type.cx / 100;
		newrc.bottom = obj.br_margin.cy + wndrc.Height() * obj.br_type.cy / 100;

		if (!newrc.EqualRect(&objrc))
		{
			if (obj.adj_hscroll)
			{
				// needs repainting, due to horiz scrolling
				int diff = newrc.Width() - objrc.Width();
				int max = wnd->GetScrollLimit(SB_HORZ);
			
				obj.need_refresh = FALSE;
				if (max > 0 && wnd->GetScrollPos(SB_HORZ) > max - diff)
				{
					obj.need_refresh = TRUE;
				}
			}

			// set flags 
			DWORD flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
			if (newrc.TopLeft() == objrc.TopLeft())
				flags |= SWP_NOMOVE;
			if (newrc.Size() == objrc.Size())
				flags |= SWP_NOSIZE;
			
			DeferWindowPos(hdwp, obj.hwnd, NULL, newrc.left, newrc.top,
				newrc.Width(), newrc.Height(), flags);
		}
	}
	// go re-arrange child windows
	EndDeferWindowPos(hdwp);

	// refresh those that need
	for (i=0; i<count; ++i)
	{
		Layout& obj = m_arrLayout[i];
		CWnd* wnd = CWnd::FromHandle(obj.hwnd); // temporary solution
	
		if (obj.need_refresh)
		{
			wnd->Invalidate();
			wnd->UpdateWindow();
		}
	}
}

void CaplResizableDialog::UpdateGripPos()
{
	// size-grip goes bottom right in the client area

	GetClientRect(&m_rcGripRect);

	m_rcGripRect.left = m_rcGripRect.right - m_szGripSize.cx;
	m_rcGripRect.top = m_rcGripRect.bottom - m_szGripSize.cy;
}

// protected members

void CaplResizableDialog::ShowSizeGrip(BOOL bShow)
{
	if (m_bShowGrip != bShow)
	{
		m_bShowGrip = bShow;
		InvalidateRect(&m_rcGripRect);
	}
}

void CaplResizableDialog::SetMaximizedRect(const CRect& rc)
{
	m_bUseMaxRect = TRUE;

	m_ptMaxPos = rc.TopLeft();
	m_ptMaxSize.x = rc.Width();
	m_ptMaxSize.y = rc.Height();
}

void CaplResizableDialog::ResetMaximizedRect()
{
	m_bUseMaxRect = FALSE;
}

void CaplResizableDialog::SetMinTrackSize(const CSize& size)
{
	m_bUseMinTrack = TRUE;

	m_ptMinTrackSize.x = size.cx;
	m_ptMinTrackSize.y = size.cy;
}

void CaplResizableDialog::ResetMinTrackSize()
{
	m_bUseMinTrack = FALSE;
}

void CaplResizableDialog::SetMaxTrackSize(const CSize& size)
{
	m_bUseMaxTrack = TRUE;

	m_ptMaxTrackSize.x = size.cx;
	m_ptMaxTrackSize.y = size.cy;
}

void CaplResizableDialog::ResetMaxTrackSize()
{
	m_bUseMaxTrack = FALSE;
}

// NOTE: this must be called after all the other settings
//       to have the dialog and its controls displayed properly
void CaplResizableDialog::EnableSaveRestore(LPCTSTR pszSection, LPCTSTR pszEntry)
{
	m_sSection = pszSection;
	m_sEntry = pszEntry;

	m_bEnableSaveRestore = TRUE;

	LoadWindowRect();
}


// used to save/restore window's size and position
// either in the registry or a private .INI file
// depending on your application settings

#define PROFILE_FMT 	_T("%d,%d,%d,%d,%d,%d")

void CaplResizableDialog::SaveWindowRect()
{
	CString data;
	WINDOWPLACEMENT wp;

	ZeroMemory(&wp, sizeof(WINDOWPLACEMENT));
	wp.length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(&wp);
	
	RECT& rc = wp.rcNormalPosition;	// alias

	data.Format(PROFILE_FMT, rc.left, rc.top,
		rc.right, rc.bottom, wp.showCmd, wp.flags);

	CWinApp *app=AfxGetApp(); // При вызове из ActiveX возвращает 0
	if(0!=app) app->WriteProfileString(m_sSection, m_sEntry, data);
}

void CaplResizableDialog::LoadWindowRect()
{
	CString data;
	WINDOWPLACEMENT wp;

	CWinApp *app=AfxGetApp(); // При вызове из ActiveX возвращает 0
	if(0!=app) data = app->GetProfileString(m_sSection, m_sEntry);
	
	if (data.IsEmpty())	// never saved before
		return;
	
	ZeroMemory(&wp, sizeof(WINDOWPLACEMENT));
	wp.length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(&wp);

	RECT& rc = wp.rcNormalPosition;	// alias

	if (_stscanf(data, PROFILE_FMT, &rc.left, &rc.top,
		&rc.right, &rc.bottom, &wp.showCmd, &wp.flags) == 6)
	{
		SetWindowPlacement(&wp);
	}

	CenterWindow();
}


bool CaplResizableDialog::ModifyAnchor(HWND wnd)
{
	ASSERT(wnd != NULL && ::IsWindow(wnd));
	ASSERT(::IsChild(*this, wnd));

	bool find = false;
	Layout obj;
	int i;
	for(i=0;i<m_arrLayout.GetSize();i++)
	{
		obj = m_arrLayout.GetAt(i);
		if(obj.hwnd==wnd) {find = true;break;}
	}
	if(!find) return false;

	// get dialog's and control's rect
	CRect wndrc, objrc;

	GetClientRect(&wndrc);
	::GetWindowRect(wnd, &objrc);
	ScreenToClient(&objrc);
	
	CSize tl_margin, br_margin;

	// calculate margin for the top-left corner

	obj.tl_margin.cx = objrc.left - wndrc.Width() * obj.tl_type.cx / 100;
	obj.tl_margin.cy = objrc.top - wndrc.Height() * obj.tl_type.cy / 100;
	
	// calculate margin for the bottom-right corner

	obj.br_margin.cx = objrc.right - wndrc.Width() * obj.br_type.cx / 100;
	obj.br_margin.cy = objrc.bottom - wndrc.Height() * obj.br_type.cy / 100;

	// add to the list
	m_arrLayout.SetAt(i,obj);
	return true;
}

BOOL CaplResizableDialog::PreTranslateMessage(MSG* pMsg)
{
	if(m_showTooltipOnDisableButtons && pMsg && pMsg->message == WM_MOUSEMOVE)
	{
		// запомним значения
		LPARAM param = pMsg->lParam;
		HWND hWnd = pMsg->hwnd;

		// активируем тултип у кнопки, которая неактивна
		::EnumChildWindows(GetSafeHwnd(), GetBtnByPoint, (LPARAM)pMsg);

		// вернем прежние значения
		pMsg->lParam = param;
		pMsg->hwnd = hWnd;
	}

	return __super::PreTranslateMessage(pMsg);	
}

BOOL CaplResizableDialog::GetBtnByPoint(HWND hWnd, LPARAM param)
{
	if(0 == hWnd)
		return TRUE;

	MSG *msg = reinterpret_cast<MSG*>(param);
	if(0 == msg)
		return TRUE;

	// контролл
	CWnd *wnd = CWnd::FromHandle(hWnd);
	if(0 == wnd)
		return TRUE;			

	// нас интересует только CaplButton
	CaplButton *btn = dynamic_cast<CaplButton*>(wnd);
	if(0 == btn)
		return TRUE;

	// если кнопка активна, то таких извратов не нужно
	if(btn->IsWindowEnabled() == TRUE)
		return TRUE;

	// родитель нам тоже понадобится
	CWnd *parentWnd = btn->GetParent();
	if(0 == parentWnd)
		return TRUE;

	// точка, где была мышка
	POINT pt;
	::GetCursorPos(&pt);	
	parentWnd->ScreenToClient(&pt);

	// область контролла
	CRect rect;
	btn->GetWindowRect(&rect);
	parentWnd->ScreenToClient(&rect);  
	
	if(rect.PtInRect(pt) )
	{	
		// активируем тултип кнопки
		parentWnd->ClientToScreen(&pt);
		btn->ScreenToClient(&pt);		
		
		msg->lParam = MAKELPARAM(pt.x, pt.y);
		msg->hwnd = hWnd;

		CToolTipCtrl *toolTip = btn->GetToolTip();
		if(toolTip != 0 && toolTip->GetSafeHwnd() != 0)
			toolTip->RelayEvent(msg);									

		return FALSE;
	}
	
	return TRUE;
}