//this file is part of docking functionality for Notepad++
//Copyright (C)2006 Jens Lorenz <jens.plugin.npp@gmx.de>
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "dockingResource.h"
#include "math.h"
#include "Docking.h"
#include "DockingCont.h"
#include "DropData.h"
#include "SplitterContainer.h"
#include "WindowInterface.h"
#include "SysMsg.h"
#include "ToolTip.h"
#include <Commctrl.h>
#include <shlobj.h>
#include "common_func.h"

#ifndef WH_MOUSE_LL
#define WH_MOUSE_LL 14
#endif

static HWND		hWndServer		= NULL;
static HHOOK	hookMouse		= NULL;

static LRESULT CALLBACK hookProcMouse(UINT nCode, WPARAM wParam, LPARAM lParam)
{
    if(nCode < 0)
    {
		::CallNextHookEx(hookMouse, nCode, wParam, lParam);
        return 0;
    }

    switch (wParam)
    {
		case WM_MOUSEMOVE:
		case WM_NCMOUSEMOVE:
			::PostMessage(hWndServer, wParam, 0, 0);
			break;
		case WM_LBUTTONUP:
		case WM_NCLBUTTONUP:
			::PostMessage(hWndServer, wParam, 0, 0);
			break;
        default: 
			break;
	}

	return ::CallNextHookEx(hookMouse, nCode, wParam, lParam);
}


DockingCont::DockingCont(void)
{
	_isMouseOver		= FALSE;
	_isMouseClose		= FALSE;
	_isMouseDown		= FALSE;
	_isFloating			= false;
	_isTopCaption		= CAPTION_TOP;
	_dragFromTab		= FALSE;
	_hContTab			= NULL;
	_hDefaultTabProc	= NULL;
	_beginDrag			= FALSE;
	_prevItem			= 0;
	_hFont				= NULL;
	_bTabTTHover		= FALSE;
	_bCaptionTT			= FALSE;
	_bCapTTHover		= FALSE;
	_hoverMPos			= posClose;
	_bDrawOgLine		= TRUE;
	_vTbData.clear();
}

DockingCont::~DockingCont()
{
	::DeleteObject(_hFont);
}


void DockingCont::doDialog(bool willBeShown, bool isFloating)
{
	if (!isCreated())
	{
		create(IDD_CONTAINER_DLG);

		_isFloating  = isFloating;

		if (_isFloating == true)
		{
			::SetWindowLong(_hSelf, GWL_STYLE, POPUP_STYLES);
			::SetWindowLong(_hSelf, GWL_EXSTYLE, POPUP_EXSTYLES);
			::ShowWindow(_hCaption, SW_HIDE);
		}
		else
		{
			::SetWindowLong(_hSelf, GWL_STYLE, CHILD_STYLES);
			::SetWindowLong(_hSelf, GWL_EXSTYLE, CHILD_EXSTYLES);
			::ShowWindow(_hCaption, SW_SHOW);
		}

		_hFont = ::CreateFont(14, 0, 0, 0,
			FW_NORMAL, FALSE, FALSE, FALSE,
			ANSI_CHARSET, OUT_DEFAULT_PRECIS,
			CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
			DEFAULT_PITCH | FF_ROMAN,
			"MS Shell Dlg");
	}

	display(willBeShown);
}


tTbData* DockingCont::createToolbar(tTbData data, Window **ppWin)
{
	tTbData *pTbData = new tTbData;

	*pTbData = data;

	/* force window style of client window */
	::SetWindowLong(pTbData->hClient, GWL_STYLE, CHILD_STYLES);
	::SetWindowLong(pTbData->hClient, GWL_EXSTYLE, CHILD_EXSTYLES);

	/* restore position if plugin is in floating state */
	if ((_isFloating == true) && (::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0) == 0))
	{
		reSizeToWH(pTbData->rcFloat);
	}

	/* set attached child window */
    ::SetParent(pTbData->hClient, ::GetDlgItem(_hSelf, IDC_CLIENT_TAB));

	/* set names for captions and view toolbar */
	viewToolbar(pTbData);

	/* attach to list */
	_vTbData.push_back(pTbData);

	return pTbData;
}


void DockingCont::removeToolbar(tTbData TbData)
{
	INT			iItemCnt	= 0;

	/* remove from list */
	for (size_t iTb = 0; iTb < _vTbData.size(); iTb++)
	{
		if (_vTbData[iTb]->hClient == TbData.hClient)
		{
			/* remove tab */
			removeTab(_vTbData[iTb]);

			/* free resources */
			delete _vTbData[iTb];
			vector<tTbData*>::iterator itr = _vTbData.begin() + iTb;
			_vTbData.erase(itr);
		}
	}
}


tTbData* DockingCont::findToolbarByWnd(HWND hClient)
{
	tTbData*	pTbData		= NULL;

	/* find entry by handle */
	for (size_t iTb = 0; iTb < _vTbData.size(); iTb++)
	{
		if (hClient == _vTbData[iTb]->hClient)
		{
			pTbData = _vTbData[iTb];
		}
	}
	return pTbData;
}

tTbData* DockingCont::findToolbarByName(char* pszName)
{
	tTbData*	pTbData		= NULL;

	/* find entry by handle */
	for (size_t iTb = 0; iTb < _vTbData.size(); iTb++)
	{
		if (strcmp(pszName, _vTbData[iTb]->pszName) == 0)
		{
			pTbData = _vTbData[iTb];
		}
	}
	return pTbData;
}

void DockingCont::setActiveTb(tTbData* pTbData)
{
	INT iItem = SearchPosInTab(pTbData);
	setActiveTb(iItem);
}

void DockingCont::setActiveTb(INT iItem)
{
	//if ((iItem != -1) && (iItem < ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0)))
	if (iItem < ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0))
	{
		SelectTab(iItem);
	}
}

INT DockingCont::getActiveTb(void)
{
	return ::SendMessage(_hContTab, TCM_GETCURSEL, 0, 0);
}

tTbData* DockingCont::getDataOfActiveTb(void)
{
	tTbData*	pTbData	= NULL;
	INT			iItem	= getActiveTb();

	if (iItem != -1)
	{
		TCITEM	tcItem	= {0};

		tcItem.mask		= TCIF_PARAM;
		::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);
		pTbData = (tTbData*)tcItem.lParam;
	}

	return pTbData;
}

vector<tTbData*> DockingCont::getDataOfVisTb(void)
{
	vector<tTbData*>	vTbData;
	TCITEM				tcItem		= {0};
	INT					iItemCnt	= ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);

	tcItem.mask	= TCIF_PARAM;

	for(INT iItem = 0; iItem < iItemCnt; iItem++)
	{
		::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);
		vTbData.push_back((tTbData*)tcItem.lParam);
	}
	return vTbData;
}

bool DockingCont::isTbVis(tTbData* data)
{
	bool				bRet		= false;
	TCITEM				tcItem		= {0};
	INT					iItemCnt	= ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);

	tcItem.mask	= TCIF_PARAM;

	for(INT iItem = 0; iItem < iItemCnt; iItem++)
	{
		::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);
		if (((tTbData*)tcItem.lParam) == data)
		{
			bRet = true;
			break;
		}
	}
	return bRet;
}


/*********************************************************************************
 *    Process function of caption bar
 */
LRESULT DockingCont::runProcCaption(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
	static ToolTip	toolTip;

	switch (Message)
	{
		case WM_LBUTTONDOWN:
		{
			_isMouseDown = TRUE;

			if (isInRect(hwnd, LOWORD(lParam), HIWORD(lParam)) == posClose)
			{
				_isMouseClose	= TRUE;
				_isMouseOver	= TRUE;

				/* start hooking */
				hWndServer		= _hCaption;
				if (GetVersion() & 0x80000000)
				{
					hookMouse	= ::SetWindowsHookEx(WH_MOUSE, (HOOKPROC)hookProcMouse, _hInst, 0);
				}
				else
				{
					hookMouse	= ::SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)hookProcMouse, _hInst, 0);
				}

				if (!hookMouse)
				{
					DWORD dwError = ::GetLastError();
					TCHAR  str[128];
					::wsprintf(str, "GetLastError() returned %lu", dwError);
					::MessageBox(NULL, str, "SetWindowsHookEx(MOUSE) failed", MB_OK | MB_ICONERROR);
				}
				::RedrawWindow(hwnd, NULL, NULL, TRUE);
			}

			focusClient();
			return TRUE;
		}
		case WM_LBUTTONUP:
		{
			_isMouseDown = FALSE;
			if (_isMouseClose == TRUE)
			{
				/* end hooking */
				::UnhookWindowsHookEx(hookMouse);

				if (_isMouseOver == TRUE)
				{
					doClose();
				}
				_isMouseClose	= FALSE;
				_isMouseOver	= FALSE;
			}
			
			focusClient();
			return TRUE;
		}
		case WM_LBUTTONDBLCLK:
		{
			if (isInRect(hwnd, LOWORD(lParam), HIWORD(lParam)) == posCaption)
				::SendMessage(_hParent, DMM_FLOATALL, 0, (LPARAM)this);

			focusClient();
			return TRUE;
		}
		case WM_MOUSEMOVE:
		{
			POINT	pt			= {0};

			/* get correct cursor position */
			::GetCursorPos(&pt);
			::ScreenToClient(_hCaption, &pt);

			if (_isMouseDown == TRUE)
			{
				if (_isMouseClose == FALSE)
				{
                    /* keep sure that button is still down and within caption */
                    if ((wParam == MK_LBUTTON) && (isInRect(hwnd, pt.x, pt.y) == posCaption))
                    {
    					_dragFromTab = FALSE;
    					NotifyParent(DMM_MOVE);
    					_isMouseDown = FALSE;
                    }
                    else
                    {
                        _isMouseDown = FALSE;
                    }
				}
				else
				{
					BOOL    isMouseOver	= _isMouseOver;
					_isMouseOver = (isInRect(hwnd, pt.x, pt.y) == posClose ? TRUE : FALSE);

					/* if state is changed draw new */
					if (_isMouseOver != isMouseOver)
					{
						::SetFocus(NULL);
						::RedrawWindow(hwnd, NULL, NULL, TRUE);
					}
				}
			}
			else if (_bCapTTHover == FALSE)
			{
				_hoverMPos = isInRect(hwnd, LOWORD(lParam), HIWORD(lParam));

				if ((_bCaptionTT == TRUE) || (_hoverMPos == posClose))
				{
					TRACKMOUSEEVENT tme;
					tme.cbSize = sizeof(tme);
					tme.hwndTrack = hwnd;
					tme.dwFlags = TME_LEAVE | TME_HOVER;
					tme.dwHoverTime = 1000;
					_bCapTTHover = _TrackMouseEvent(&tme);
				}
			}
			else if ((_bCapTTHover == TRUE) &&
				(_hoverMPos != isInRect(hwnd, LOWORD(lParam), HIWORD(lParam))))
			{
				toolTip.destroy();
				_bCapTTHover = FALSE;
			}
			return TRUE;
		}
		case WM_MOUSEHOVER:
		{
			RECT	rc	= {0};
			POINT	pt	= {0};


			/* get mouse position */
			::GetCursorPos(&pt);

			toolTip.init(_hInst, hwnd);
			if (_hoverMPos == posCaption)
			{
				toolTip.Show(rc, _pszCaption, pt.x, pt.y + 20);
			}
			else
			{
				toolTip.Show(rc, "Close", pt.x, pt.y + 20);
			}
			return TRUE;
		}
		case WM_MOUSELEAVE:
		{
			toolTip.destroy();
			_bCapTTHover = FALSE;
			return TRUE;
		}
		case WM_SIZE:
		{
			::GetWindowRect(hwnd, &_rcCaption);
			ScreenToClient(hwnd, &_rcCaption);
			break;
		}
		case WM_SETTEXT:
		{
			::RedrawWindow(hwnd, NULL, NULL, TRUE);
			return TRUE;
		}
		default:
			break;
	}

	return ::CallWindowProc(_hDefaultCaptionProc, hwnd, Message, wParam, lParam);
}

void DockingCont::drawCaptionItem(DRAWITEMSTRUCT *pDrawItemStruct)
{
	HBRUSH		bgbrush		= NULL;
	HFONT		hOldFont	= NULL;
	RECT		rc			= pDrawItemStruct->rcItem;
	HDC			hDc			= pDrawItemStruct->hDC;
	HPEN		hPen		= ::CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_BTNSHADOW));
	BITMAP		bmp			= {0};
	HBITMAP		hBmpCur		= NULL;
	HBITMAP		hBmpOld 	= NULL;
	HBITMAP		hBmpNew		= NULL;
	UINT		length  	= strlen(_pszCaption);

	INT nSavedDC			= ::SaveDC(hDc);

	/* begin with paint */
	::SetBkMode(hDc, TRANSPARENT);

	if (_isActive == TRUE) {
		bgbrush = ::CreateSolidBrush(::GetSysColor(COLOR_ACTIVECAPTION));
		::SetTextColor(hDc, ::GetSysColor(COLOR_CAPTIONTEXT));
	} else {
		bgbrush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
	}

	/* set text and/or caption grid */
	if (_isTopCaption == TRUE)
	{
		if (_isActive == TRUE)
		{
			/* fill background */
			::FillRect(hDc, &rc, bgbrush);
			rc.right	-= 1;
			rc.bottom	-= 1;
		}
		else
		{
			/* fill background */
			rc.right	-= 1;
			rc.bottom	-= 1;
			::FillRect(hDc, &rc, bgbrush);

			/* draw grid lines */
			HPEN	hOldPen = (HPEN)::SelectObject(hDc, hPen);

			MoveToEx(hDc, rc.left , rc.top , NULL);
			LineTo  (hDc, rc.right, rc.top );
			LineTo  (hDc, rc.right, rc.bottom );
			LineTo  (hDc, rc.left , rc.bottom );
			LineTo  (hDc, rc.left , rc.top);
		}

		/* draw text */
		rc.left		+= 2;
		rc.top		+= 1;
		rc.right	-= 16;
		hOldFont = (HFONT)::SelectObject(hDc, _hFont);
		::DrawText(hDc, _pszCaption, length, &rc, DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);

		/* calculate text size and if its trankated... */
		SIZE	size	= {0};
		GetTextExtentPoint32(hDc, _pszCaption, length, &size);
		_bCaptionTT = (((rc.right - rc.left) < size.cx) ? TRUE : FALSE);

		::SelectObject(hDc, hOldFont);
	}
	else
	{
		/* create local font for vertical draw */
		HFONT	hFont;

		if (_isActive == TRUE)
		{
			/* fill background */
			::FillRect(hDc, &rc, bgbrush);
			rc.right	-= 1;
			rc.bottom	-= 1;
		}
		else
		{
			/* fill background */
			rc.right	-= 1;
			rc.bottom	-= 1;
			::FillRect(hDc, &rc, bgbrush);

			/* draw grid lines */
			HPEN	hOldPen = (HPEN)::SelectObject(hDc, hPen);

			MoveToEx(hDc, rc.left , rc.top , NULL);
			LineTo  (hDc, rc.right, rc.top );
			LineTo  (hDc, rc.right, rc.bottom );
			LineTo  (hDc, rc.left , rc.bottom );
			LineTo  (hDc, rc.left , rc.top);
		}

		/* draw text */
		rc.left		+= 1;
		rc.top		+= HIGH_CAPTION;
		/* to make ellipsis working */
		rc.right	= rc.bottom - rc.top;
		rc.bottom	+= 14;

		hFont = ::CreateFont(12, 0, 90 * 10, 0,
			 FW_NORMAL, FALSE, FALSE, FALSE,
			 ANSI_CHARSET, OUT_DEFAULT_PRECIS,
			 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
			 DEFAULT_PITCH | FF_ROMAN,
			 "MS Shell Dlg");

		hOldFont = (HFONT)::SelectObject(hDc, hFont);
		::DrawText(hDc, _pszCaption, length, &rc, DT_BOTTOM | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);

		/* calculate text size and if its trankated... */
		SIZE	size	= {0};
		GetTextExtentPoint32(hDc, _pszCaption, length, &size);
		_bCaptionTT = (((rc.bottom - rc.top) < size.cy) ? TRUE : FALSE);

		::SelectObject(hDc, hOldFont);
		::DeleteObject(hFont);
	}
	::DeleteObject(hPen);
	::DeleteObject(bgbrush);

	/* draw button */
	HDC			dcMem		= ::CreateCompatibleDC(NULL);

	/* select correct bitmap */
	if ((_isMouseOver == TRUE) && (_isMouseDown == TRUE))
		hBmpCur = ::LoadBitmap(_hInst, MAKEINTRESOURCE(IDB_CLOSE_DOWN));
	else
		hBmpCur = ::LoadBitmap(_hInst, MAKEINTRESOURCE(IDB_CLOSE_UP));

	/* blit bitmap into the destination */
	::GetObject(hBmpCur, sizeof(bmp), &bmp);
	hBmpOld = (HBITMAP)::SelectObject(dcMem, hBmpCur);
	hBmpNew = ::CreateCompatibleBitmap(dcMem, bmp.bmWidth, bmp.bmHeight);

	rc = pDrawItemStruct->rcItem;
	::SelectObject(hDc, hBmpNew);

	if (_isTopCaption == TRUE)
		::BitBlt(hDc, rc.right-bmp.bmWidth-CLOSEBTN_POS_LEFT, CLOSEBTN_POS_TOP, bmp.bmWidth, bmp.bmHeight, dcMem, 0, 0, SRCCOPY);
	else
		::BitBlt(hDc, CLOSEBTN_POS_LEFT, CLOSEBTN_POS_LEFT, bmp.bmWidth, bmp.bmHeight, dcMem, 0, 0, SRCCOPY);

	::SelectObject(dcMem, hBmpOld);
	::DeleteObject(hBmpCur);
	::DeleteObject(hBmpNew);
	::DeleteDC(dcMem);

	::RestoreDC(hDc, nSavedDC);
}

eMousePos DockingCont::isInRect(HWND hwnd, INT x, INT y)
{
	RECT		rc;
	eMousePos	ret	= posOutside;

	::GetWindowRect(hwnd, &rc);
	ScreenToClient(hwnd, &rc);

	if (_isTopCaption == TRUE)
	{
		if ((x > rc.left) && (x < rc.right-HIGH_CAPTION) && (y > rc.top) && (y < rc.bottom))
		{
			ret = posCaption;
		}
		else if ((x > rc.right-(12+CLOSEBTN_POS_LEFT)) && (x < (rc.right-CLOSEBTN_POS_LEFT)) && 
			(y > (rc.top+CLOSEBTN_POS_TOP)) && (y < (rc.bottom-CLOSEBTN_POS_TOP)))
		{
			ret = posClose;
		}
	}
	else
	{
		if ((x > rc.left) && (x < rc.right) && (y > rc.top+HIGH_CAPTION) && (y < rc.bottom))
		{
			ret = posCaption;
		}
		else if ((x > rc.left+CLOSEBTN_POS_LEFT) && (x < rc.right-CLOSEBTN_POS_LEFT) && 
			(y > (rc.top+CLOSEBTN_POS_TOP)) && (y < (rc.top+(12+CLOSEBTN_POS_LEFT))))
		{
			ret = posClose;
		}
	}

	return ret;
}


/*********************************************************************************
 *    Process function of tab
 */
LRESULT DockingCont::runProcTab(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
	static	ToolTip	toolTip;

	switch (Message)
	{
		case WM_LBUTTONDOWN:
		{
			_beginDrag	= TRUE;
			return TRUE;
		}
		case WM_LBUTTONUP:
		{
			INT				iItem	= 0;
			TCHITTESTINFO	info	= {0};

			/* get selected sub item */
			info.pt.x = LOWORD(lParam);
			info.pt.y = HIWORD(lParam);
			iItem = ::SendMessage(hwnd, TCM_HITTEST, 0, (LPARAM)&info);

			SelectTab(iItem);
			_beginDrag = FALSE;
			return TRUE;
		}
		case WM_LBUTTONDBLCLK:
		{
			NotifyParent((_isFloating == true)?DMM_DOCK:DMM_FLOAT);
			return TRUE;
		}
		case WM_MBUTTONUP:
		{
			INT				iItem	= 0;
			TCITEM			tcItem	= {0};
			TCHITTESTINFO	info	= {0};

			/* get selected sub item */
			info.pt.x = LOWORD(lParam);
			info.pt.y = HIWORD(lParam);
			iItem = ::SendMessage(hwnd, TCM_HITTEST, 0, (LPARAM)&info);

			SelectTab(iItem);

			/* get data and hide toolbar */
			tcItem.mask		= TCIF_PARAM;
			::SendMessage(hwnd, TCM_GETITEM, iItem, (LPARAM)&tcItem);

			/* notify child windows */
			if (NotifyParent(DMM_CLOSE) == 0)
			{
				hideToolbar((tTbData*)tcItem.lParam);
			}
			return TRUE;
		}
		case WM_MOUSEMOVE:
		{
			INT				iItem	= 0;
			TCHITTESTINFO	info	= {0};

			/* get selected sub item */
			info.pt.x = LOWORD(lParam);
			info.pt.y = HIWORD(lParam);
			iItem = ::SendMessage(hwnd, TCM_HITTEST, 0, (LPARAM)&info);

			if ((_beginDrag == TRUE) && (wParam == MK_LBUTTON))
			{
				
				SelectTab(iItem);

				/* send moving message to parent window */
				_dragFromTab = TRUE;
				NotifyParent(DMM_MOVE);
				_beginDrag = FALSE;
			}
            else
            {
				INT	iItemSel = ::SendMessage(hwnd, TCM_GETCURSEL, 0, 0);

				if ((_bTabTTHover == FALSE) && (iItem != iItemSel))
				{
					TRACKMOUSEEVENT tme;
					tme.cbSize = sizeof(tme);
					tme.hwndTrack = hwnd;
					tme.dwFlags = TME_LEAVE | TME_HOVER;
					tme.dwHoverTime = 1000;
					_bTabTTHover = _TrackMouseEvent(&tme);
				}
				else
				{
					if (iItem == iItemSel)
					{
						toolTip.destroy();
						_bTabTTHover = FALSE;
					}
					else if (iItem != _iLastHovered)
					{
						TCITEM	tcItem	= {0};
						RECT	rc		= {0};

						/* destroy old tooltip */
						toolTip.destroy();

						/* recalc mouse position */
						::ClientToScreen(hwnd, &info.pt);

						/* get text of toolbar */
						tcItem.mask		= TCIF_PARAM;
						::SendMessage(hwnd, TCM_GETITEM, iItem, (LPARAM)&tcItem);

						toolTip.init(_hInst, hwnd);
						toolTip.Show(rc, ((tTbData*)tcItem.lParam)->pszName, info.pt.x, info.pt.y + 20);
					}
				}

				/* save last hovered item */
				_iLastHovered = iItem;

				_beginDrag = FALSE;
			}
			return TRUE;
		}
		case WM_MOUSEHOVER:
		{
			INT				iItem	= 0;
			TCITEM			tcItem	= {0};
			RECT			rc		= {0};
			TCHITTESTINFO	info	= {0};

			/* get selected sub item */
			info.pt.x = LOWORD(lParam);
			info.pt.y = HIWORD(lParam);
			iItem = ::SendMessage(hwnd, TCM_HITTEST, 0, (LPARAM)&info);

			/* recalc mouse position */
			::ClientToScreen(hwnd, &info.pt);

			/* get text of toolbar */
			tcItem.mask		= TCIF_PARAM;
			::SendMessage(hwnd, TCM_GETITEM, iItem, (LPARAM)&tcItem);

			toolTip.init(_hInst, hwnd);
			toolTip.Show(rc, ((tTbData*)tcItem.lParam)->pszName, info.pt.x, info.pt.y + 20);
			return TRUE;
		}
		case WM_MOUSELEAVE:
		{
			toolTip.destroy();
			_bTabTTHover = FALSE;
			return TRUE;
		}
		case WM_NOTIFY:
		{
			LPNMHDR	lpnmhdr = (LPNMHDR)lParam;

			if ((lpnmhdr->hwndFrom == _hContTab) && (lpnmhdr->code == TCN_GETOBJECT))
			{
				INT				iItem	= 0;
				TCHITTESTINFO	info	= {0};

				/* get selected sub item */
				info.pt.x = LOWORD(lParam);
				info.pt.y = HIWORD(lParam);
				iItem = ::SendMessage(hwnd, TCM_HITTEST, 0, (LPARAM)&info);

				SelectTab(iItem);
			}
			break;
		}
		default:
			break;
	}

	return ::CallWindowProc(_hDefaultTabProc, hwnd, Message, wParam, lParam);
}

void DockingCont::drawTabItem(DRAWITEMSTRUCT *pDrawItemStruct)
{
	TCITEM	tcItem		= {0};
	RECT	rc			= pDrawItemStruct->rcItem;
	
	INT		nTab		= pDrawItemStruct->itemID;
	bool	isSelected	= (nTab == getActiveTb());

	/* get current selected item */
	tcItem.mask = TCIF_PARAM;
	::SendMessage(_hContTab, TCM_GETITEM, nTab, (LPARAM)&tcItem);

	char*	text	= ((tTbData*)tcItem.lParam)->pszName;
	INT		length	= strlen(((tTbData*)tcItem.lParam)->pszName);


	/* get drawing context */
	HDC hDc = pDrawItemStruct->hDC;

	INT nSavedDC = ::SaveDC(hDc);

	// For some bizarre reason the rcItem you get extends above the actual
	// drawing area. We have to workaround this "feature".
	rc.top += ::GetSystemMetrics(SM_CYEDGE);

	::SetBkMode(hDc, TRANSPARENT);
	HBRUSH hBrush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
	::FillRect(hDc, &rc, hBrush);
	::DeleteObject((HGDIOBJ)hBrush);

	/* draw orange bar */
	if ((_bDrawOgLine == TRUE) && (isSelected))
	{
		RECT barRect  = rc;
		barRect.top  += rc.bottom - 4;

		hBrush = ::CreateSolidBrush(RGB(250, 170, 60));
		::FillRect(hDc, &barRect, hBrush);
		::DeleteObject((HGDIOBJ)hBrush);

	}

	/* draw icon if enabled */
	if (((tTbData*)tcItem.lParam)->uMask & DWS_ICONTAB)
	{
		HIMAGELIST	hImageList	= (HIMAGELIST)::SendMessage(_hParent, DMM_GETIMAGELIST, 0, 0);
		INT			iPosImage	= ::SendMessage(_hParent, DMM_GETICONPOS, 0, (LPARAM)((tTbData*)tcItem.lParam)->hClient);

		if ((hImageList != NULL) && (iPosImage >= 0))
		{
			/* Get height of image so we */
			IMAGEINFO	info		= {0};
			RECT &		imageRect	= info.rcImage;
			
			ImageList_GetImageInfo(hImageList, iPosImage, &info);
			ImageList_Draw(hImageList, iPosImage, hDc, rc.left + 3, 2, ILD_NORMAL);

			if (isSelected == true)
			{
				rc.left += imageRect.right - imageRect.left + 5;
			}
		}
	}

	if (isSelected == true)
	{
		COLORREF _unselectedColor = RGB(0, 0, 0);
		::SetTextColor(hDc, _unselectedColor);

		/* draw text */
		rc.top -= ::GetSystemMetrics(SM_CYEDGE);
		::SelectObject(hDc, _hFont);
		::DrawText(hDc, text, length, &rc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX);
	}

	::RestoreDC(hDc, nSavedDC);
}


/*********************************************************************************
 *    Process function of dialog
 */
BOOL CALLBACK DockingCont::run_dlgProc(UINT Message, WPARAM wParam, LPARAM lParam)
{
	switch (Message) 
	{
		case WM_NCACTIVATE:
		{
			/* Note: lParam to identify the trigger window */
			if ((INT)lParam != -1)
			{
				::SendMessage(_hParent, WM_NCACTIVATE, wParam, 0);
			}
			break;
		}
		case WM_INITDIALOG:
		{
			_hContTab = ::GetDlgItem(_hSelf, IDC_TAB_CONT);
			_hCaption = ::GetDlgItem(_hSelf, IDC_BTN_CAPTION);

			/* intial subclassing of caption */
			::SetWindowLong(_hCaption, GWL_USERDATA, reinterpret_cast<LONG>(this));
			_hDefaultCaptionProc = reinterpret_cast<WNDPROC>(::SetWindowLong(_hCaption, GWL_WNDPROC, reinterpret_cast<LONG>(wndCaptionProc)));

			/* intial subclassing of tab */
			::SetWindowLong(_hContTab, GWL_USERDATA, reinterpret_cast<LONG>(this));
			_hDefaultTabProc = reinterpret_cast<WNDPROC>(::SetWindowLong(_hContTab, GWL_WNDPROC, reinterpret_cast<LONG>(wndTabProc)));

			/* set min tab width */
			::SendMessage(_hContTab, TCM_SETMINTABWIDTH, 0, (LPARAM)MIN_TABWIDTH);
			break;
		}
		case WM_NCCALCSIZE:
		case WM_SIZE:
		{
			onSize();
			break;
		}
		case WM_DRAWITEM :
		{
			/* draw tab or caption */
			if (((DRAWITEMSTRUCT *)lParam)->CtlID == IDC_TAB_CONT)
			{
				drawTabItem((DRAWITEMSTRUCT *)lParam);
				return TRUE;
			}
			else
			{
				drawCaptionItem((DRAWITEMSTRUCT *)lParam);
				return TRUE;
			}
			break;
		}
		case WM_NCLBUTTONDBLCLK :
		{
			RECT	rcWnd		= {0};
			RECT	rcClient	= {0};
			POINT	pt			= {HIWORD(lParam), LOWORD(lParam)};

			getWindowRect(rcWnd);
			getClientRect(rcClient);
			ClientToScreen(_hSelf, &rcClient);
			rcWnd.bottom = rcClient.top;

			/* if in caption */
			if ((rcWnd.top  < pt.x) && (rcWnd.bottom > pt.x) &&
				(rcWnd.left < pt.y) && (rcWnd.right  > pt.y))
			{
				NotifyParent(DMM_DOCKALL);
				return TRUE;
			}
			break;
		}
		case WM_SYSCOMMAND :
		{
			switch (wParam & 0xfff0)
			{
				case SC_MOVE:
					NotifyParent(DMM_MOVE);
					return TRUE;
				default: 
					break;
			}
			return FALSE;
		}
		case WM_COMMAND : 
		{
			switch (LOWORD(wParam))
			{   
				case IDCANCEL:
					doClose();
					return TRUE;
				default :
					break;
			}
			break;
		}
		default:
			break;
	}

	return FALSE;
}

void DockingCont::onSize(void)
{
	TCITEM	tcItem		= {0};
	RECT	rc			= {0};
	RECT	rcTemp		= {0};
	UINT	iItemCnt	= ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);
	UINT	iTabOff		= 0;

	getClientRect(rc);

	if (iItemCnt >= 1)
	{
		/* resize to docked window */
		if (_isFloating == false)
		{
			/* draw caption */
			if (_isTopCaption == TRUE)
			{
				::SetWindowPos(_hCaption, NULL, rc.left, rc.top, rc.right, HIGH_CAPTION, SWP_NOZORDER | SWP_NOACTIVATE);
				rc.top		+= HIGH_CAPTION;
				rc.bottom	-= HIGH_CAPTION;
			}
			else
			{
				::SetWindowPos(_hCaption, NULL, rc.left, rc.top, HIGH_CAPTION, rc.bottom, SWP_NOZORDER | SWP_NOACTIVATE);
				rc.left		+= HIGH_CAPTION;
				rc.right	-= HIGH_CAPTION;
			}

			if (iItemCnt >= 2)
			{
				/* resize tab and plugin control if tabs exceeds one */
				/* resize tab */
				rcTemp = rc;
				rcTemp.top		= (rcTemp.bottom + rcTemp.top) - (HIGH_TAB+CAPTION_GAP);
				rcTemp.bottom	= HIGH_TAB;
				iTabOff			= HIGH_TAB;

				::SetWindowPos(_hContTab, NULL,
								rcTemp.left, rcTemp.top, rcTemp.right, rcTemp.bottom, 
								SWP_NOZORDER | SWP_SHOWWINDOW |  SWP_NOACTIVATE);
			}

			/* resize client area for plugin */
			rcTemp = rc;
			if (_isTopCaption == TRUE)
			{
				rcTemp.top    += CAPTION_GAP;
				rcTemp.bottom -= (iTabOff + CAPTION_GAP);
			}
			else
			{
				rcTemp.left   += CAPTION_GAP;
				rcTemp.right  -= CAPTION_GAP;
				rcTemp.bottom -= iTabOff;
			}

			/* set position of client area */
			::SetWindowPos(::GetDlgItem(_hSelf, IDC_CLIENT_TAB), NULL,
							rcTemp.left, rcTemp.top, rcTemp.right, rcTemp.bottom, 
							SWP_NOZORDER | SWP_NOACTIVATE);
		}
		/* resize to float window */
		else
		{
			/* update floating size */
			if (_isFloating == true)
			{
				for (size_t iTb = 0; iTb < _vTbData.size(); iTb++)
				{
					getWindowRect(_vTbData[iTb]->rcFloat);
				}
			}			

			/* draw caption */
			if (iItemCnt >= 2)
			{
				/* resize tab if size of elements exceeds one */
				rcTemp = rc;
				rcTemp.top    = rcTemp.bottom - (HIGH_TAB+CAPTION_GAP);
				rcTemp.bottom = HIGH_TAB;

				::SetWindowPos(_hContTab, NULL,
								rcTemp.left, rcTemp.top, rcTemp.right, rcTemp.bottom, 
								SWP_NOZORDER | SWP_SHOWWINDOW);
			}

			/* resize client area for plugin */
			rcTemp = rc;
			rcTemp.bottom -= ((iItemCnt == 1)?0:HIGH_TAB);

			::SetWindowPos(::GetDlgItem(_hSelf, IDC_CLIENT_TAB), NULL,
							rcTemp.left, rcTemp.top, rcTemp.right, rcTemp.bottom, 
							SWP_NOZORDER | SWP_NOACTIVATE);
		}
		

		/* get active item data */
		UINT	iItemCnt = ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);

		/* resize visible plugin windows */
		for (UINT iItem = 0; iItem < iItemCnt; iItem++)
		{
			tcItem.mask		= TCIF_PARAM;
			::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);

			::SetWindowPos(((tTbData*)tcItem.lParam)->hClient, NULL,
							0, 0, rcTemp.right, rcTemp.bottom, 
							SWP_NOZORDER);
		}
	}
}

void DockingCont::doClose(void)
{
	INT	iItemOff	= 0;
	INT	iItemCnt	= ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);

	for (INT iItem = 0; iItem < iItemCnt; iItem++)
	{
		TCITEM		tcItem		= {0};

		/* get item data */
		SelectTab(iItemOff);
		tcItem.mask	= TCIF_PARAM;
		::SendMessage(_hContTab, TCM_GETITEM, iItemOff, (LPARAM)&tcItem);

		/* notify child windows */
		if (NotifyParent(DMM_CLOSE) == 0)
		{
			/* delete tab */
			hideToolbar((tTbData*)tcItem.lParam);
		}
		else
		{
			iItemOff++;
		}
	}

	if (iItemOff == 0)
	{
		/* hide dialog first */
		this->doDialog(false);
		::SendMessage(_hParent, WM_SIZE, 0, 0);
	}
}

void DockingCont::showToolbar(tTbData* pTbData, BOOL state)
{
	if (state == SW_SHOW)
	{
		viewToolbar(pTbData);
	}
	else
	{
		hideToolbar(pTbData);
	}
}

INT DockingCont::hideToolbar(tTbData *pTbData, BOOL hideClient)
{
	INT		iItem	= SearchPosInTab(pTbData);

	/* delete item */
	if (TRUE == ::SendMessage(_hContTab, TCM_DELETEITEM, iItem, 0))
	{
		UINT	iItemCnt = ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);

		if (iItemCnt != 0)
		{
			TCITEM		tcItem = {0};

			tcItem.mask	= TCIF_PARAM;

			if (iItem == iItemCnt)
			{
				iItem--;
			}

			/* activate new selected item and view plugin dialog */
			_prevItem = iItem;
			SelectTab(iItem);

			/* hide tabs if only one element */
			if (iItemCnt == 1)
			{
				::ShowWindow(_hContTab, SW_HIDE);
			}
		}
		else 
		{
			/* hide dialog */
			this->doDialog(false);

			/* send message to docking manager for resize */
			if (_isFloating == false)
			{
				::SendMessage(_hParent, WM_SIZE, 0, 0);
			}
		}

		/* keep sure, that client is hide!!! */
		if (hideClient == TRUE)
		{
			::ShowWindow(pTbData->hClient, SW_HIDE);
		}
	}
	onSize();

	return iItem;
}

void DockingCont::viewToolbar(tTbData *pTbData)
{
	TCITEM		tcItem		= {0};
	INT			iItemCnt	= ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);

	if (iItemCnt > 0)
	{
		UINT	iItem	= getActiveTb();

		tcItem.mask		= TCIF_PARAM;
		::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);
		
		/* hide active dialog */
		::ShowWindow(((tTbData*)tcItem.lParam)->hClient, SW_HIDE);
	}

	/* create new tab if it not exists */
	INT iTabPos = SearchPosInTab(pTbData);
	if (iTabPos == -1)
	{
		/* set only params and text even if icon available */
		tcItem.mask			= TCIF_PARAM;
		tcItem.lParam		= (LPARAM)pTbData;
		::SendMessage(_hContTab, TCM_INSERTITEM, iItemCnt, (LPARAM)&tcItem);
		SelectTab(iItemCnt);
	}
	/* if exists select it and update data */
	else
	{
		tcItem.mask			= TCIF_PARAM;
		tcItem.lParam		= (LPARAM)pTbData;
		::SendMessage(_hContTab, TCM_SETITEM, iTabPos, (LPARAM)&tcItem);
		SelectTab(iTabPos);
	}

	/* show dialog and notify parent to update dialog view */
	if (isVisible() == false)
	{
		this->doDialog();
		::SendMessage(_hParent, WM_SIZE, 0, 0);
	}

	/* set position of client */
	onSize();
}

INT DockingCont::SearchPosInTab(tTbData* pTbData)
{
	TCITEM	tcItem		= {0};
	INT		iItemCnt	= ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);
	INT		ret			= -1;

	tcItem.mask	= TCIF_PARAM;

	for (INT iItem = 0; iItem < iItemCnt; iItem++)
	{
		::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);

		if (((tTbData*)tcItem.lParam)->hClient == pTbData->hClient)
		{
			ret = iItem;
			break;
		}
	}
	return ret;
}

void DockingCont::SelectTab(INT iTab)
{
	if (iTab != -1)
	{
		LPSTR	pszMaxTxt	= NULL;
		TCITEM	tcItem		= {0};
		SIZE	size		= {0};
		INT		maxWidth	= 0;
		INT		iItemCnt	= ::SendMessage(_hContTab, TCM_GETITEMCOUNT, 0, 0);

		/* get data of new active dialog */
		tcItem.mask		= TCIF_PARAM;
		::SendMessage(_hContTab, TCM_GETITEM, iTab, (LPARAM)&tcItem);

		/* show active dialog */
		::ShowWindow(((tTbData*)tcItem.lParam)->hClient, SW_SHOW);
		::SetFocus(((tTbData*)tcItem.lParam)->hClient);

		if (iTab != _prevItem)
		{
			/* hide previous dialog */
			::SendMessage(_hContTab, TCM_GETITEM, _prevItem, (LPARAM)&tcItem);
			::ShowWindow(((tTbData*)tcItem.lParam)->hClient, SW_HIDE);
		}

		/* resize tab item */

		/* get at first largest item ... */
		HDC		hDc	= ::GetDC(_hContTab);
		SelectObject(hDc, _hFont);

		for (INT iItem = 0; iItem < iItemCnt; iItem++)
		{
			LPSTR	pszTabTxt	= NULL;

			::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);
			pszTabTxt = ((tTbData*)tcItem.lParam)->pszName;

			/* get current font width */
			GetTextExtentPoint32(hDc, pszTabTxt, strlen(pszTabTxt), &size);

			if (maxWidth < size.cx) 
			{
				maxWidth	= size.cx;
				pszMaxTxt	= pszTabTxt;
			}
		}
		::ReleaseDC(_hSelf, hDc);

		tcItem.mask	= TCIF_TEXT;
		for (INT iItem = 0; iItem < iItemCnt; iItem++)
		{
			if (iItem == iTab)
			{
				/* fake here an icon before text ... */
				char	szText[64];

				strcpy(szText, "    ");
				strcat(szText, pszMaxTxt);
				tcItem.pszText		= szText;
				tcItem.cchTextMax	= strlen(szText);
			}
			else
			{
				/* ... and resize old and new item */
				tcItem.pszText		= "";
				tcItem.cchTextMax	= strlen("");
			}
			::SendMessage(_hContTab, TCM_SETITEM, iItem, (LPARAM)&tcItem);
		}

		/* selects the pressed tab and store previous tab */
		::SendMessage(_hContTab, TCM_SETCURSEL, iTab, 0);
		_prevItem = iTab;

		/* update caption text */
		updateCaption();

		onSize();
	}
}

void DockingCont::updateCaption(void)
{
	TCITEM			tcItem	= {0};
	INT				iItem	= getActiveTb();

	if (iItem < 0)
		return;

	/* get data of new active dialog */
	tcItem.mask		= TCIF_PARAM;
	::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);

	/* update caption text */
	strcpy(_pszCaption, ((tTbData*)tcItem.lParam)->pszName);

	/* test if additional information are available */
	if ((((tTbData*)tcItem.lParam)->uMask & DWS_ADDINFO) && 
		(strlen(((tTbData*)tcItem.lParam)->pszAddInfo) != 0))
	{
		strcat(_pszCaption, " - ");
		strcat(_pszCaption, ((tTbData*)tcItem.lParam)->pszAddInfo);
	}

	if (_isFloating == true)
	{
		::SetWindowText(_hSelf, _pszCaption);
	}
	else
	{
		::SetWindowText(_hCaption, _pszCaption);
	}
}

void DockingCont::focusClient(void)
{
	TCITEM		tcItem	= {0};
	INT			iItem	= getActiveTb();	

	if (iItem != -1)
	{
		/* get data of new active dialog */
		tcItem.mask		= TCIF_PARAM;
		::SendMessage(_hContTab, TCM_GETITEM, iItem, (LPARAM)&tcItem);

		/* set focus */
		::SetFocus(((tTbData*)tcItem.lParam)->hClient);
	}
}

LPARAM DockingCont::NotifyParent(UINT message)
{
	return ::SendMessage(_hParent, message, 0, (LPARAM)this);
}

