text:
// ==========================================================================
// 							   Class Implementation :
//								COXTabViewContainer
// ==========================================================================

// Version: 9.3

// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement").  Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office.  For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.

// //////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "OXTabView.h"

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

COXTabViewContainer* PASCAL GetParentTabViewContainer(CWnd* pWnd,
													  BOOL bOnlyActive/*=TRUE*/)
{
#if defined (_WINDLL)
#if defined (_AFXDLL)
	AFX_MANAGE_STATE(AfxGetAppModuleState());
#else
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif
#endif

	ASSERT(pWnd!=NULL);
	HWND hWndParent=::GetParent(pWnd->GetSafeHwnd());
	if(hWndParent==NULL)
		return NULL;

	COXTabViewContainer* pContainer=
		(COXTabViewContainer*)CWnd::FromHandlePermanent(hWndParent);
	if(pContainer!=NULL)
	{
		ASSERT(::IsWindow(pContainer->m_hWnd));
		if(::IsWindow(pContainer->m_hWnd))
		{
			if(::GetWindowLongPtr(pContainer->m_hWnd,GWL_USERDATA)==
				ID_TABVIEWCONTAINER_SIGN)
			{
				if(!bOnlyActive || pContainer->IsActivePage(pWnd))
				{
					return pContainer;
				}
			}
		}
	}
	return NULL;
}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// COXTabViewContainer
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNCREATE(COXTabViewContainer,CWnd)

COXTabViewContainer::COXTabViewContainer()
{
	EmptyRects();
	m_nLastTabBtnAreaWidth=-1;
	m_nTabBtnAreaOrigin=0;

	m_arrUniqueIDs.Add(1);
	m_nActivePageIndex=-1;

	m_nPressedScrlBtn=NONE;
	m_bIsScrlBtnPressed=FALSE;
	m_nScrollPageTimer=-1;

	m_bIsSplitterPressed=FALSE;

	m_nLastTabBtnAreaWidth=ID_INITABBTNAREAWIDTH;

	if((HFONT)m_fontTabBtnText==NULL)
		m_fontTabBtnText.CreatePointFont(80,_T("MS Sans Serif"));
	if((HFONT)m_fontActiveTabBtnText==NULL && (HFONT)m_fontTabBtnText!=NULL)
	{
		LOGFONT lf;
		if(m_fontTabBtnText.GetLogFont(&lf)!=0)
		{
			lf.lfWeight=FW_BOLD;
			m_fontActiveTabBtnText.CreateFontIndirect(&lf);
		}
	}

	ASSERT((HFONT)m_fontTabBtnText!=NULL && (HFONT)m_fontActiveTabBtnText!=NULL);
}

COXTabViewContainer::~COXTabViewContainer()
{
}

BEGIN_MESSAGE_MAP(COXTabViewContainer, CWnd)
	//{{AFX_MSG_MAP(COXTabViewContainer)
	ON_WM_SIZE()
	ON_WM_PAINT()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_TIMER()
	ON_WM_SETCURSOR()
	ON_WM_ERASEBKGND()
	ON_WM_CANCELMODE()
	ON_WM_DESTROY()
	ON_WM_HSCROLL()
	ON_WM_VSCROLL()
	ON_WM_SETTINGCHANGE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

BOOL COXTabViewContainer::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName,
								 DWORD dwStyle, const RECT& rect,
								 CWnd* pParentWnd, UINT nID,
								 CCreateContext* pContext/*=NULL*/)
{
	UNREFERENCED_PARAMETER(lpszClassName);
	UNREFERENCED_PARAMETER(lpszWindowName);
	UNREFERENCED_PARAMETER(pContext);
	return Create(pParentWnd,rect,dwStyle,nID);
}

BOOL COXTabViewContainer::Create(CWnd* pParentWnd, CRect rect/*=CRect(0,0,0,0)*/,
								 DWORD dwStyle/*=WS_CHILD|WS_VISIBLE*/,
								 UINT nID/*=AFX_IDW_PANE_FIRST*/)
{
	ASSERT(pParentWnd != NULL);
	ASSERT(dwStyle & WS_CHILD);
	ASSERT(nID != 0);

	// the Windows scroll bar styles bits turn on the smart scrollbars
	DWORD dwCreateStyle=dwStyle&~(WS_HSCROLL|WS_VSCROLL);
	dwCreateStyle&=~WS_BORDER;

	dwCreateStyle|=WS_CHILD;

	// define our own window class
	WNDCLASS wndClass;
	wndClass.style=CS_DBLCLKS;
    wndClass.lpfnWndProc=AfxWndProc;
    wndClass.cbClsExtra=0;
    wndClass.cbWndExtra=0;
    wndClass.hInstance=AfxGetInstanceHandle();
    wndClass.hIcon=0;
    wndClass.hCursor=::LoadCursor(NULL,IDC_ARROW);
    wndClass.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1);
    wndClass.lpszMenuName=NULL;
	wndClass.lpszClassName=_T("TabViewContainer");

	if(!AfxRegisterClass(&wndClass))
		return FALSE;

	if (!CreateEx(WS_EX_CLIENTEDGE,wndClass.lpszClassName,NULL,
		dwCreateStyle,rect.left,rect.top,rect.Width(),rect.Height(),
		pParentWnd->m_hWnd,(HMENU)(INT_PTR)nID,NULL))
	{
		return FALSE;       // create invisible
	}

	// remove WS_EX_CLIENTEDGE style from parent window
	pParentWnd->ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_DRAWFRAME);

	// sign
	::SetWindowLongPtr(GetSafeHwnd(),GWL_USERDATA,ID_TABVIEWCONTAINER_SIGN);

	SetScrollStyle(0,TRUE);

	CalcLayout();

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// COXTabViewContainer command routing

BOOL COXTabViewContainer::OnCommand(WPARAM wParam, LPARAM lParam)
{
	if (CWnd::OnCommand(wParam,lParam))
		return TRUE;

	// route commands from the container to the parent frame window
	if(GetParentFrame()!=NULL)
		return (BOOL)GetParentFrame()->SendMessage(WM_COMMAND,wParam,lParam);
	else
		return FALSE;
}

BOOL COXTabViewContainer::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
	if (CWnd::OnNotify(wParam,lParam,pResult))
		return TRUE;

	// route commands from the container to the parent frame window

	if(GetParentFrame()!=NULL)
	{
		*pResult=GetParentFrame()->SendMessage(WM_NOTIFY,wParam,lParam);
		return TRUE;
	}
	else
	{
		*pResult=0;
		return FALSE;
	}
}

//////////////////////////////////////////////////////////////////

void COXTabViewContainer::OnDestroy()
{
	if(m_nScrollPageTimer!=-1)
	{
		KillTimer(m_nScrollPageTimer);
		m_nScrollPageTimer=-1;
	}

	CWnd::OnDestroy();
}

void COXTabViewContainer::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);

	// TODO: Add your message handler code here
	if(nType!=SIZE_MINIMIZED && cx>0 && cy>0)
	{
		CalcLayout();

		UpdateScrollSizes();

		CWnd* pWnd=GetActivePage();
		if(pWnd!=NULL)
		{
			ASSERT(::IsWindow(pWnd->m_hWnd));
			if(::IsWindow(pWnd->m_hWnd))
				pWnd->MoveWindow(m_rectPage);
		}

		RedrawContainer();
	}

}

void COXTabViewContainer::OnPaint()
{
	CPaintDC dc(this); // device context for painting

	// TODO: Add your message handler code here

	CWnd* pWnd=GetActivePage();
	if(pWnd!=NULL)
	{
		CRect rect;
		pWnd->GetWindowRect(rect);
		ScreenToClient(rect);
		if(m_rectPage!=rect && !rect.IsRectEmpty())
		{
			CRgn rgnInTheory;
			CRgn rgnInReality;
			if(rgnInTheory.CreateRectRgnIndirect(m_rectPage) &&
				rgnInReality.CreateRectRgnIndirect(rect))
			{
				if(rgnInTheory.CombineRgn(&rgnInTheory,&rgnInReality,
					RGN_DIFF)!=ERROR)
				{
					CBrush* pBrush=NULL;
					CBrush brush;
					HBRUSH hBrush=(HBRUSH)(INT_PTR)::GetClassLongPtr(pWnd->m_hWnd,
						GCL_HBRBACKGROUND);
					if(hBrush==NULL)
					{
						if(brush.CreateSolidBrush(::GetSysColor(COLOR_WINDOW)))
							pBrush=&brush;
						else
							pBrush=dc.GetCurrentBrush();
					}
					else
					{
						pBrush=CBrush::FromHandle(hBrush);
					}

					if(pBrush!=NULL)
						dc.FillRgn(&rgnInTheory,pBrush);
				}
			}
		}
	}

	DrawScrollButtons(&dc);
	DrawSplitter(&dc);
	DrawSizeBar(&dc);
	DrawTabBtnArea(&dc);

	// Do not call CWnd::OnPaint() for painting messages
}

void COXTabViewContainer::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default

	if(m_nPressedScrlBtn!=NONE)
	{
		int hitTest=HitTest(point);
		// send corresponding messages
		if(((int)m_nPressedScrlBtn==hitTest && !m_bIsScrlBtnPressed) ||
			((int)m_nPressedScrlBtn!=hitTest && m_bIsScrlBtnPressed))
		{
			m_bIsScrlBtnPressed=!m_bIsScrlBtnPressed;
			RedrawScrollButton(m_nPressedScrlBtn);
		}
	}
	else if(m_bIsSplitterPressed)
	{
		CPoint ptMoved=point;
		if(ptMoved.x>m_rectPage.right-ID_MINSCROLLBARWIDTH+ID_SPLITTERWIDTH/2)
			ptMoved.x=m_rectPage.right-ID_MINSCROLLBARWIDTH+ID_SPLITTERWIDTH/2;
		if(ptMoved.x<m_rectScrollToEndBtn.right+ID_SPLITTERWIDTH/2)
			ptMoved.x=m_rectScrollToEndBtn.right+ID_SPLITTERWIDTH/2;

		int nOldSplitterLeft=m_rectSplitter.left;
		m_rectSplitter.left=ptMoved.x-ID_SPLITTERWIDTH/2;
		if(nOldSplitterLeft!=m_rectSplitter.left)
		{
			m_rectSplitter.right=m_rectSplitter.left+ID_SPLITTERWIDTH;
			m_rectScrollBarHorz.left=m_rectSplitter.right;

			m_nLastTabBtnAreaWidth=m_rectSplitter.left-m_rectScrollToEndBtn.right;

			m_rectTabBtnArea.right+=m_rectSplitter.left-nOldSplitterLeft;
			if(m_rectTabBtnArea.right<m_rectTabBtnArea.left)
				m_rectTabBtnArea.right=m_rectTabBtnArea.left;

			if(::IsWindow(m_scrlBarHorz.GetSafeHwnd()))
				m_scrlBarHorz.MoveWindow(m_rectScrollBarHorz);
			RedrawSplitter();

			if(nOldSplitterLeft<m_rectSplitter.left)
			{
				CRect rect=m_rectSplitter;
				rect.left=nOldSplitterLeft;
				rect.right=m_rectSplitter.left;
				RedrawWindow(rect);

				if(m_nTabBtnAreaOrigin<0)
				{
					ASSERT(GetPageCount()==m_arrTabBtnRects.GetSize());
					rect=m_arrTabBtnRects[GetPageCount()-1];
					rect+=m_rectTabBtnArea.TopLeft();
					if(rect.right+m_nTabBtnAreaOrigin<=m_rectTabBtnArea.right)
					{
						m_nTabBtnAreaOrigin+=
							m_rectSplitter.left-nOldSplitterLeft;
						m_nTabBtnAreaOrigin=
							m_nTabBtnAreaOrigin>0 ? 0 : m_nTabBtnAreaOrigin;
						RedrawTabBtnArea();
					}
				}

			}
		}
	}

	CWnd::OnMouseMove(nFlags, point);
}

void COXTabViewContainer::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default

	CWnd::OnLButtonDown(nFlags, point);

	int hitTest=HitTest(point);
	if(hitTest>=0 && hitTest<GetPageCount() && hitTest!=GetActivePageIndex())
		SetActivePageIndex(hitTest);
	else
		StartTracking(point);
}

void COXTabViewContainer::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default

	StopTracking(point);

	CWnd::OnLButtonUp(nFlags,point);
}

// v9.3 - update 03 - 64-bit - using OXTPARAM here - see UTB64Bit.h
void COXTabViewContainer::OnTimer(OXTPARAM nIDEvent)
{
	// TODO: Add your message handler code here and/or call default

	if((int)nIDEvent==m_nScrollPageTimer)
	{
		if(m_nPressedScrlBtn!=NONE && m_bIsScrlBtnPressed)
			ScrollPage(m_nPressedScrlBtn);
	}

	CWnd::OnTimer(nIDEvent);
}

BOOL COXTabViewContainer::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	// TODO: Add your message handler code here and/or call default

	CPoint point;
	::GetCursorPos(&point);
	ScreenToClient(&point);
	int hitTest=HitTest(point);
	if(m_bIsSplitterPressed || hitTest==SPLITTER)
	{
		HCURSOR hCursor=AfxGetApp()->
			LoadCursor(MAKEINTRESOURCE(AFX_IDC_HSPLITBAR));
		if(hCursor==NULL)
			hCursor=::LoadCursor(NULL,IDC_SIZEWE);
		SetCursor(hCursor);
		return TRUE;
	}

	return CWnd::OnSetCursor(pWnd, nHitTest, message);
}

BOOL COXTabViewContainer::OnEraseBkgnd(CDC* pDC)
{
	// TODO: Add your message handler code here and/or call default

	if(GetPageCount()==0)
	{
		CRect rect;
		pDC->GetClipBox(rect);
		pDC->FillSolidRect(rect,::GetSysColor(COLOR_BTNFACE));
	}

	return TRUE;
}

void COXTabViewContainer::OnCancelMode()
{
	CWnd::OnCancelMode();

	// TODO: Add your message handler code here

	CPoint point;
	::GetCursorPos(&point);
	ScreenToClient(&point);
	StopTracking(point);
}

void COXTabViewContainer::OnHScroll(UINT nSBCode, UINT nPos,
									CScrollBar* pScrollBar)
{
	// TODO: Add your message handler code here and/or call default

	ASSERT(pScrollBar==GetHorzScrollBar());

	CWnd* pWnd=GetActivePage();
	ASSERT(pWnd!=NULL);
	if(pWnd!=NULL)
	{
		ASSERT(::IsWindow(pWnd->m_hWnd));
		pWnd->SendMessage(WM_HSCROLL,MAKEWPARAM(nSBCode,nPos),
			(LPARAM)pScrollBar->m_hWnd);
	}
}

void COXTabViewContainer::OnVScroll(UINT nSBCode, UINT nPos,
									CScrollBar* pScrollBar)
{
	// TODO: Add your message handler code here and/or call default

	ASSERT(pScrollBar==GetVertScrollBar());

	CWnd* pWnd=GetActivePage();
	ASSERT(pWnd!=NULL);
	if(pWnd!=NULL)
	{
		ASSERT(::IsWindow(pWnd->m_hWnd));
		pWnd->SendMessage(WM_VSCROLL,MAKEWPARAM(nSBCode,nPos),
			(LPARAM)pScrollBar->m_hWnd);
	}
}

void COXTabViewContainer::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
	UNREFERENCED_PARAMETER(uFlags);
	UNREFERENCED_PARAMETER(lpszSection);

	CalcLayout();

	UpdateScrollSizes();

	CWnd* pWnd=GetActivePage();
	if(pWnd!=NULL)
	{
		ASSERT(::IsWindow(pWnd->m_hWnd));
		if(::IsWindow(pWnd->m_hWnd))
			pWnd->MoveWindow(m_rectPage);
	}

	RedrawContainer();

	CWnd::OnSettingChange(uFlags, lpszSection);
}

//////////////////////////////////////////////////////////////////

BOOL COXTabViewContainer::SaveState(LPCTSTR lpszProfileName)
{
	CWinApp* pApp=AfxGetApp();
	if(pApp==NULL)
		return FALSE;

	BOOL bResult=TRUE;
	bResult&=pApp->WriteProfileInt(lpszProfileName,
		_T("TabButtonsAreaWidth"),m_nLastTabBtnAreaWidth);

	return bResult;
}

BOOL COXTabViewContainer::LoadState(LPCTSTR lpszProfileName)
{
	CWinApp* pApp=AfxGetApp();
	if(pApp==NULL)
		return FALSE;

	m_nLastTabBtnAreaWidth=pApp->GetProfileInt(lpszProfileName,
		_T("TabButtonsAreaWidth"),m_nLastTabBtnAreaWidth);

	CalcLayout();
	UpdateScrollSizes();
	RedrawContainer();

	return TRUE;
}

BOOL COXTabViewContainer::InsertPage(int nIndex,
									 CRuntimeClass* pClass,
									 CCreateContext* pContext,
									 LPCTSTR lpszTitle/*=NULL*/)
{
	ASSERT_VALID(this);
	ASSERT(nIndex>=0 && nIndex<=GetPageCount());
	ASSERT(pClass!=NULL);
	ASSERT(pClass->IsDerivedFrom(RUNTIME_CLASS(CWnd)));
	ASSERT(AfxIsValidAddress(pClass,sizeof(CRuntimeClass),FALSE));

	if(!(nIndex>=0 && nIndex<=GetPageCount()) || pClass==NULL)
		return FALSE;

	CCreateContext context;
	if(pContext==NULL)
	{
		// if no context specified, generate one from the currently active
		// view if possible
		CView* pOldView=(CView*)GetActivePage();
		if(pOldView!=NULL && pOldView->IsKindOf(RUNTIME_CLASS(CView)))
		{
			// set info about last pane
			ASSERT(context.m_pCurrentFrame==NULL);
			context.m_pLastView=pOldView;
			context.m_pCurrentDoc=pOldView->GetDocument();
			if(context.m_pCurrentDoc!=NULL)
			{
				context.m_pNewDocTemplate=context.m_pCurrentDoc->
					GetDocTemplate();
			}
		}
		pContext=&context;
	}

	CWnd* pWnd;
	TRY
	{
		pWnd=(CWnd*)pClass->CreateObject();
		if(pWnd==NULL)
			AfxThrowMemoryException();
	}
	CATCH_ALL(e)
	{
		TRACE(_T("COXTabViewContainer::InsertPage: Out of memory inserting new page\n"));
		// Note: DELETE_EXCEPTION(e) not required
		return FALSE;
	}
	END_CATCH_ALL

	ASSERT_KINDOF(CWnd,pWnd);
	ASSERT(pWnd->m_hWnd==NULL);       // not yet created

	DWORD dwStyle=AFX_WS_DEFAULT_VIEW;
#if _MFC_VER < 0x0700
	if(afxData.bWin4)
#endif
		dwStyle&=~WS_BORDER;

	DWORD dwID=GetUniqueId();

	// Create with the right size
	if(!pWnd->Create(NULL,NULL,dwStyle,m_rectPage,this,dwID,pContext))
	{
		TRACE(_T("COXTabViewContainer::InsertPage: couldn't create new page\n"));
		// pWnd will be cleaned up by PostNcDestroy
		return FALSE;
	}

	if(InsertPage(nIndex,pWnd,lpszTitle))
	{
		CWnd* pWnd=GetPage(nIndex);
		ASSERT(pWnd!=NULL);
		ASSERT(::IsWindow(pWnd->m_hWnd));
		if(pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		{
			// send initial notification message
			pWnd->SendMessage(WM_INITIALUPDATE);
		}
		return TRUE;
	}

	return FALSE;
}

BOOL COXTabViewContainer::InsertPage(int nIndex, CWnd* pWnd,
									 LPCTSTR lpszTitle/*=NULL*/)
{
	ASSERT_VALID(this);
	ASSERT(nIndex>=0 && nIndex<=GetPageCount());
	ASSERT(pWnd!=NULL);
	ASSERT(::IsWindow(pWnd->m_hWnd));

	if(!(nIndex>=0 && nIndex<=GetPageCount())
		|| pWnd==NULL || !::IsWindow(pWnd->m_hWnd))
	{
		return FALSE;
	}

	// set this container as parent window of the specified page
	pWnd->SetParent(this);

	PAGEINFO pi;
	pi.pWnd=pWnd;
	if(lpszTitle==NULL)
	{
		pWnd->GetWindowText(pi.sTitle);
		if(pi.sTitle.IsEmpty())
		{
			pi.sTitle.Format(_T("%d"),nIndex);
		}
	}
	else
	{
		pi.sTitle=lpszTitle;
	}

	m_arrPages.InsertAt(nIndex,pi);

	CalcTabBtnRects();

	SetActivePageIndex(nIndex);

	RedrawContainer();

	return TRUE;
}

BOOL COXTabViewContainer::DeletePage(CWnd* pWnd,
									 BOOL bDestroy/*=TRUE*/)
{
	ASSERT(pWnd!=NULL);
	ASSERT(IsPage(pWnd));
	int nIndex=-1;
	if(FindPage(pWnd,nIndex))
	{
		return DeletePage(nIndex,bDestroy);
	}
	return FALSE;
}

BOOL COXTabViewContainer::DeletePage(int nIndex,
									 BOOL bDestroy/*=TRUE*/)
{
	ASSERT_VALID(this);
	ASSERT(nIndex>=0 && nIndex<GetPageCount());

	if(nIndex>=0 && nIndex<GetPageCount())
	{
		// if active page is being deleted - activate next
		int nActivePage=GetActivePageIndex();
		if(nActivePage==nIndex)
		{
			if(nActivePage==GetPageCount()-1)
				SetActivePageIndex(0);
			else
				SetActivePageIndex(GetPageCount()>1 ? nActivePage+1 : 0);
		}
		CWnd* pWnd=GetPage(nIndex);
		ASSERT(pWnd!=NULL);
		m_arrUniqueIDs.Add(pWnd->GetDlgCtrlID());
		if(bDestroy && ::IsWindow(pWnd->m_hWnd))
			VERIFY(pWnd->DestroyWindow());

		m_arrPages.RemoveAt(nIndex);

		nActivePage=GetActivePageIndex();
		if(nActivePage>=nIndex)
			m_nActivePageIndex--;

		CalcTabBtnRects();

		RedrawContainer();

		return TRUE;
	}

	return FALSE;
}

BOOL COXTabViewContainer::SetPageTitle(int nIndex, LPCTSTR lpszTitle)
{
	ASSERT(nIndex>=0 && nIndex<GetPageCount());

	if(nIndex>=0 && nIndex<GetPageCount())
	{
		PAGEINFO& pi=m_arrPages[nIndex];
		if(pi.sTitle.Compare(lpszTitle)!=0)
		{
			pi.sTitle=lpszTitle;
			CalcTabBtnRects();
			CRect rect=m_arrTabBtnRects[nIndex];
			if(rect.left<m_rectTabBtnArea.right &&
				rect.right>m_rectTabBtnArea.right)
			{
				RedrawTabBtnArea();
			}
		}
		return TRUE;
	}

	return FALSE;
}

BOOL COXTabViewContainer::SetActivePageIndex(int nIndex)
{
	if(nIndex==m_nActivePageIndex)
	{
		return TRUE;
	}

	CWnd* pWndOld=GetActivePage();
	if(pWndOld!=NULL)
	{
		ASSERT(::IsWindow(pWndOld->m_hWnd));
		if(::IsWindow(pWndOld->m_hWnd))
		{
			UpdateScrollInfo();

			pWndOld->ShowWindow(SW_HIDE);

			PAGEINFO pi=m_arrPages[GetActivePageIndex()];
			m_nActivePageIndex=-1;
			UINT nBar=(pi.bHasScrollHorz&pi.bHasScrollVert ? SB_BOTH :
				(pi.bHasScrollHorz ? SB_HORZ :
				(pi.bHasScrollVert ? SB_VERT : 0)));
			if(nBar!=0)
			{
				pWndOld->ShowScrollBar(nBar,TRUE);
			}
		}
	}

	m_nActivePageIndex=nIndex;
	if(m_nActivePageIndex>=0 && m_nActivePageIndex<GetPageCount())
	{
		EnsureTabBtnVisible(m_nActivePageIndex);

		CWnd* pWnd=GetPage(m_nActivePageIndex);
		if(pWnd!=NULL)
		{
			ASSERT(::IsWindow(pWnd->m_hWnd));
			if(::IsWindow(pWnd->m_hWnd))
			{
				IniScrollInfo();

				pWnd->ShowWindow(SW_SHOW);
				CRect rect;
				pWnd->GetWindowRect(rect);
				if(rect.Width()!=m_rectPage.Width() ||
					rect.Height()!=m_rectPage.Height())
				{
					pWnd->MoveWindow(m_rectPage);
				}
				else
				{
					pWnd->SendMessage(WM_SIZE,SIZE_RESTORED,
						MAKELPARAM(rect.Width(),rect.Height()));
				}

				// set the focus to the page
				CFrameWnd* pFrameWnd=(CFrameWnd*)GetParent();
				ASSERT(pFrameWnd!=NULL);
				if(pFrameWnd->IsKindOf(RUNTIME_CLASS(CFrameWnd)))
				{
					if(pWnd->IsKindOf(RUNTIME_CLASS(CView)))
					{
						pFrameWnd->SetActiveView((CView*)pWnd);
					}
					else
					{
						if(pWndOld!=NULL &&
							pWndOld->IsKindOf(RUNTIME_CLASS(CView)))
						{
							pFrameWnd->SetActiveView(NULL);
						}
						pWnd->SetFocus();
					}
				}
				else
				{
					pWnd->SetFocus();
				}

				RedrawTabBtnArea();

			}
		}
	}
	else
		return FALSE;

	return TRUE;
}

void COXTabViewContainer::SetScrollStyle(DWORD dwScrollStyle,
										 BOOL bForceToRebuild/*=FALSE*/)
{
	ASSERT(::IsWindow(GetSafeHwnd()));

	if(!bForceToRebuild &&
		m_dwScrollStyle==(dwScrollStyle & (WS_VSCROLL | WS_HSCROLL)))
	{
		return;
	}

	m_dwScrollStyle=(dwScrollStyle & (WS_VSCROLL | WS_HSCROLL));

	if(::IsWindow(m_scrlBarHorz.GetSafeHwnd()))
	{
		m_scrlBarHorz.DestroyWindow();
	}
	if(::IsWindow(m_scrlBarVert.GetSafeHwnd()))
	{
		m_scrlBarVert.DestroyWindow();
	}

	VERIFY(m_scrlBarHorz.Create(SBS_HORZ|WS_CHILD|WS_VISIBLE|
		((dwScrollStyle & WS_HSCROLL) ? 0 : WS_DISABLED),
		m_rectScrollBarHorz,this,AFX_IDW_HSCROLL_FIRST));
	VERIFY(m_scrlBarVert.Create(SBS_VERT|WS_CHILD|WS_VISIBLE|
		((dwScrollStyle & WS_VSCROLL) ? 0 : WS_DISABLED),
		m_rectScrollBarVert,this,AFX_IDW_VSCROLL_FIRST));
}

void COXTabViewContainer::CalcLayout()
{
	ASSERT(::IsWindow(GetSafeHwnd()));

	EmptyRects();

	CRect rect;
	GetClientRect(rect);

	if(rect.IsRectEmpty())
		return;

	CSize szScrollBtn=CSize(::GetSystemMetrics(SM_CXHSCROLL),
		::GetSystemMetrics(SM_CYHSCROLL));

	// scroll buttons
	//
	m_rectScrollToStartBtn=CRect(rect.left,rect.bottom-szScrollBtn.cy,
		rect.left+szScrollBtn.cx,rect.bottom);

	m_rectScrollBackwardBtn=m_rectScrollToStartBtn;
	m_rectScrollBackwardBtn.OffsetRect(m_rectScrollToStartBtn.Width(),0);

	m_rectScrollForwardBtn=m_rectScrollBackwardBtn;
	m_rectScrollForwardBtn.OffsetRect(m_rectScrollBackwardBtn.Width(),0);

	m_rectScrollToEndBtn=m_rectScrollForwardBtn;
	m_rectScrollToEndBtn.OffsetRect(m_rectScrollForwardBtn.Width(),0);
	//
	/////////////////////////////////////////////

	// page rect
	m_rectPage=rect;
	m_rectPage.right-=szScrollBtn.cx;
	m_rectPage.bottom-=szScrollBtn.cy;

	int nTabBtnAreaWidth=m_nLastTabBtnAreaWidth;
	if(m_rectPage.right<m_rectScrollToEndBtn.right+
		nTabBtnAreaWidth+ID_MINSCROLLBARWIDTH)
	{
		nTabBtnAreaWidth=m_rectPage.right-
			m_rectScrollToEndBtn.right-ID_MINSCROLLBARWIDTH;
	}

	// tab buttons area
	m_rectTabBtnArea=m_rectScrollToStartBtn;
	m_rectTabBtnArea.left=m_rectScrollToEndBtn.right;
	m_rectTabBtnArea.right=m_rectTabBtnArea.left+nTabBtnAreaWidth;
	if(m_rectTabBtnArea.right<m_rectTabBtnArea.left)
		m_rectTabBtnArea.right=m_rectTabBtnArea.left;

	// splitter rect
	m_rectSplitter=m_rectScrollToStartBtn;
	m_rectSplitter.left=m_rectTabBtnArea.right;
	m_rectSplitter.right=m_rectSplitter.left+ID_SPLITTERWIDTH;

	// horz scroll bar
	m_rectScrollBarHorz=m_rectScrollToStartBtn;
	m_rectScrollBarHorz.left=m_rectSplitter.right;
	m_rectScrollBarHorz.right=m_rectPage.right;

	// vert scroll bar
	m_rectScrollBarVert=m_rectPage;
	m_rectScrollBarVert.left=m_rectPage.right;
	m_rectScrollBarVert.right=rect.right;

	// size bar
	m_rectSizeBar=rect;
	m_rectSizeBar.top=m_rectScrollBarVert.bottom;
	m_rectSizeBar.left=m_rectScrollBarHorz.right;

}

void COXTabViewContainer::CalcTabBtnRects()
{
	m_arrTabBtnRects.RemoveAll();

	CString sTitle;
	int nLeftSide=0;
	CRect rect;

	CClientDC dc(this);
	ASSERT((HFONT)m_fontActiveTabBtnText!=NULL);
	if((HFONT)m_fontActiveTabBtnText!=NULL)
		dc.SelectObject(&m_fontActiveTabBtnText);

	for(int nIndex=0; nIndex<GetPageCount(); nIndex++)
	{
		rect=m_rectTabBtnArea;
		rect-=rect.TopLeft();
		rect.right=0xffff;

		sTitle=GetPageTitle(nIndex);

		dc.DrawText(sTitle,rect,DT_CALCRECT|DT_LEFT|DT_SINGLELINE|DT_VCENTER);
		rect.OffsetRect(nLeftSide,0);
		rect.right+=2*ID_TABBTNOVERLAPSIZE+2*ID_TABBTNGAPSIZE;

		m_arrTabBtnRects.Add(rect);

		nLeftSide=rect.right-ID_TABBTNOVERLAPSIZE;
	}
}

void COXTabViewContainer::EmptyRects()
{
	m_rectScrollForwardBtn.SetRectEmpty();
	m_rectScrollBackwardBtn.SetRectEmpty();
	m_rectScrollToStartBtn.SetRectEmpty();
	m_rectScrollToEndBtn.SetRectEmpty();

	m_rectTabBtnArea.SetRectEmpty();

	m_rectScrollBarHorz.SetRectEmpty();
	m_rectScrollBarVert.SetRectEmpty();

	m_rectSplitter.SetRectEmpty();

	m_rectSizeBar.SetRectEmpty();

	m_rectPage.SetRectEmpty();
}

void COXTabViewContainer::UpdateScrollSizes()
{
	if(::IsWindow(m_scrlBarHorz.GetSafeHwnd()))
	{
		m_scrlBarHorz.MoveWindow(m_rectScrollBarHorz);
	}
	if(::IsWindow(m_scrlBarVert.GetSafeHwnd()))
	{
		m_scrlBarVert.MoveWindow(m_rectScrollBarVert);
	}
}

void COXTabViewContainer::IniScrollInfo()
{
	CWnd* pWnd=GetActivePage();
	if(pWnd==NULL)
	{
		return;
	}

	PAGEINFO& pi=m_arrPages[GetActivePageIndex()];

	pi.bHasScrollHorz=((pWnd->GetStyle()&WS_HSCROLL)==WS_HSCROLL);
	pi.bHasScrollVert=((pWnd->GetStyle()&WS_VSCROLL)==WS_VSCROLL);

	if(pi.bHasScrollHorz)
	{
		pi.GetScrollInfo(pWnd,TRUE);
	}
	if(pi.bHasScrollVert)
	{
		pi.GetScrollInfo(pWnd,FALSE);
	}

	DWORD dwStyle=(pi.bHasScrollHorz ? WS_HSCROLL : 0) |
		(pi.bHasScrollVert ? WS_VSCROLL : 0);
	SetScrollStyle(dwStyle,TRUE);
	pWnd->ShowScrollBar(SB_BOTH,FALSE);
	pWnd->RedrawWindow();

	if(pi.bHasScrollHorz ||
		(pi.scrlInfoHorz.nMax>0 && pi.scrlInfoHorz.nPage>0 &&
		pi.scrlInfoHorz.nPos>0 && pi.scrlInfoHorz.nPos<=pi.scrlInfoHorz.nMax))
	{
		ASSERT(::IsWindow(m_scrlBarHorz.GetSafeHwnd()));
		m_scrlBarHorz.SetScrollInfo(&pi.scrlInfoHorz);
	}
	if(pi.bHasScrollVert ||
		(pi.scrlInfoVert.nMax>0 && pi.scrlInfoVert.nPage>0 &&
		pi.scrlInfoVert.nPos>0 && pi.scrlInfoVert.nPos<=pi.scrlInfoVert.nMax))
	{
		ASSERT(::IsWindow(m_scrlBarVert.GetSafeHwnd()));
		m_scrlBarVert.SetScrollInfo(&pi.scrlInfoVert);
	}

}

static BOOL g_bUpdatingScrollInfo=FALSE;
static BOOL g_bUpdatingScrollState=FALSE;

void COXTabViewContainer::UpdateScrollInfo()
{
	if(g_bUpdatingScrollInfo || g_bUpdatingScrollState)
	{
		return;
	}

	g_bUpdatingScrollInfo=TRUE;

	CWnd* pWnd=GetActivePage();
	if(pWnd==NULL || !pWnd->IsWindowVisible())
	{
		g_bUpdatingScrollInfo=FALSE;
		return;
	}

	PAGEINFO& pi=m_arrPages[GetActivePageIndex()];

	if(pi.bHasScrollHorz)
	{
		pWnd->ModifyStyle(WS_VISIBLE,NULL);
		pWnd->ShowScrollBar(SB_HORZ,TRUE);
		pWnd->ModifyStyle(NULL,WS_HSCROLL,SWP_DRAWFRAME);

		pi.GetScrollInfo(pWnd,TRUE);

		ASSERT(::IsWindow(m_scrlBarHorz.GetSafeHwnd()));
		m_scrlBarHorz.SetScrollInfo(&pi.scrlInfoHorz);

		pWnd->ShowScrollBar(SB_HORZ,FALSE);
		pWnd->ModifyStyle(WS_HSCROLL,NULL,SWP_DRAWFRAME);
		pWnd->ModifyStyle(NULL,WS_VISIBLE);
	}
	else
	{
		pi.scrlInfoHorz.cbSize=sizeof(SCROLLINFO);
		pWnd->GetScrollInfo(SB_HORZ,&pi.scrlInfoHorz);
	}

	if(pi.bHasScrollVert)
	{
		pWnd->ModifyStyle(WS_VISIBLE,NULL);
		pWnd->ShowScrollBar(SB_VERT,TRUE);
		pWnd->ModifyStyle(NULL,WS_VSCROLL,SWP_DRAWFRAME);

		pi.GetScrollInfo(pWnd,FALSE);

		ASSERT(::IsWindow(m_scrlBarVert.GetSafeHwnd()));
		m_scrlBarVert.SetScrollInfo(&pi.scrlInfoVert);

		pWnd->ShowScrollBar(SB_VERT,FALSE);
		pWnd->ModifyStyle(WS_VSCROLL,NULL,SWP_DRAWFRAME);
		pWnd->ModifyStyle(NULL,WS_VISIBLE);
	}
	else
	{
		pi.scrlInfoVert.cbSize=sizeof(SCROLLINFO);
		pWnd->GetScrollInfo(SB_VERT,&pi.scrlInfoVert);
	}

	g_bUpdatingScrollInfo=FALSE;
}

void COXTabViewContainer::UpdateScrollState()
{
	if(g_bUpdatingScrollState || g_bUpdatingScrollInfo)
	{
		return;
	}

	g_bUpdatingScrollState=TRUE;

	CWnd* pWnd=GetActivePage();
	if(pWnd==NULL || !pWnd->IsWindowVisible())
	{
		g_bUpdatingScrollState=FALSE;
		return;
	}

	PAGEINFO& pi=m_arrPages[GetActivePageIndex()];

	BOOL bHasScrollHorz=((pWnd->GetStyle() & WS_HSCROLL)==WS_HSCROLL);
	BOOL bHasScrollVert=((pWnd->GetStyle() & WS_VSCROLL)==WS_VSCROLL);

	if(bHasScrollHorz || bHasScrollVert)
	{
		if(bHasScrollHorz)
		{
			pi.bHasScrollHorz=bHasScrollHorz;
			pWnd->ModifyStyle(WS_HSCROLL,NULL,SWP_DRAWFRAME);
			pWnd->ShowScrollBar(SB_HORZ,FALSE);
		}
		if(bHasScrollVert)
		{
			pi.bHasScrollVert=bHasScrollVert;
			pWnd->ModifyStyle(WS_VSCROLL,NULL,SWP_DRAWFRAME);
			pWnd->ShowScrollBar(SB_VERT,FALSE);
		}
	}

	pWnd->ModifyStyle(WS_VISIBLE,NULL);
	pWnd->ShowScrollBar(SB_HORZ,TRUE);
	pWnd->ModifyStyle(NULL,WS_HSCROLL,SWP_DRAWFRAME);
	pWnd->ShowScrollBar(SB_VERT,TRUE);
	pWnd->ModifyStyle(NULL,WS_VSCROLL,SWP_DRAWFRAME);

	pi.GetScrollInfo(pWnd,TRUE);
	if(pi.bHasScrollHorz)
	{
		if(pi.scrlInfoHorz.nMax==0 || pi.scrlInfoHorz.nPage==0 ||
			pi.scrlInfoHorz.nMax<(int)pi.scrlInfoHorz.nPage)
		{
			pi.bHasScrollHorz=FALSE;
		}
	}
	else
	{
		if(pi.scrlInfoHorz.nMax>0 && pi.scrlInfoHorz.nPage>0 &&
			pi.scrlInfoHorz.nMax>=(int)pi.scrlInfoHorz.nPage)
		{
			pi.bHasScrollHorz=TRUE;
		}
	}

	pi.GetScrollInfo(pWnd,FALSE);
	if(pi.bHasScrollVert)
	{
		if(pi.scrlInfoVert.nMax==0 || pi.scrlInfoVert.nPage==0 ||
			pi.scrlInfoVert.nMax<(int)pi.scrlInfoVert.nPage)
		{
			pi.bHasScrollVert=FALSE;
		}
	}
	else
	{
		if(pi.scrlInfoVert.nMax>0 && pi.scrlInfoVert.nPage>0 &&
			pi.scrlInfoVert.nMax>=(int)pi.scrlInfoVert.nPage)
		{
			pi.bHasScrollVert=TRUE;
		}
	}

	pWnd->ShowScrollBar(SB_HORZ,FALSE);
	pWnd->ModifyStyle(WS_HSCROLL,NULL,SWP_DRAWFRAME);
	pWnd->ShowScrollBar(SB_VERT,FALSE);
	pWnd->ModifyStyle(WS_VSCROLL,NULL,SWP_DRAWFRAME);
	pWnd->ModifyStyle(NULL,WS_VISIBLE);

	DWORD dwStyle=GetScrollStyle();
	dwStyle=(pi.bHasScrollHorz ? WS_HSCROLL : 0) |
		(pi.bHasScrollVert ? WS_VSCROLL : 0);
	SetScrollStyle(dwStyle,FALSE);

	g_bUpdatingScrollState=FALSE;
}

void COXTabViewContainer::DrawScrollButtons(CDC* pDC)
{
	ASSERT_VALID(pDC);

	// scroll to start button
	DrawButton(pDC,m_rectScrollToStartBtn,SCRLSTARTBTN);
	// scroll backward button
	DrawButton(pDC,m_rectScrollBackwardBtn,SCRLBACKWARDBTN);
	// scroll forward button
	DrawButton(pDC,m_rectScrollForwardBtn,SCRLFORWARDBTN);
	// scroll to end button
	DrawButton(pDC,m_rectScrollToEndBtn,SCRLENDBTN);
}

void COXTabViewContainer::DrawTabBtnArea(CDC* pDC)
{
	ASSERT(m_arrTabBtnRects.GetSize()==GetPageCount());

	pDC->IntersectClipRect(m_rectTabBtnArea);

	pDC->FillSolidRect(m_rectTabBtnArea,::GetSysColor(COLOR_BTNFACE));

	CPen pen(PS_SOLID,1,::GetSysColor(COLOR_WINDOWTEXT));
	CPen* pOldPen=pDC->SelectObject(&pen);
	pDC->MoveTo(m_rectTabBtnArea.left,m_rectTabBtnArea.top);
	pDC->LineTo(m_rectTabBtnArea.right,m_rectTabBtnArea.top);

	CBrush brush;
	brush.CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
	CBrush* pOldBrush=pDC->SelectObject(&brush);

	CFont* pOldFont=NULL;
	ASSERT((HFONT)m_fontTabBtnText!=NULL);
	if((HFONT)m_fontTabBtnText!=NULL)
		pOldFont=pDC->SelectObject(&m_fontTabBtnText);

	for(int nIndex=0; nIndex<GetPageCount(); nIndex++)
	{
		if(nIndex!=GetActivePageIndex())
			DrawTabButton(pDC,nIndex);
	}

	if(GetActivePageIndex()!=-1)
	{
		CBrush brushActive;
		brushActive.CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
		pDC->SelectObject(&brushActive);

		ASSERT((HFONT)m_fontActiveTabBtnText!=NULL);
		if((HFONT)m_fontActiveTabBtnText!=NULL)
			pDC->SelectObject(&m_fontActiveTabBtnText);

		DrawTabButton(pDC,GetActivePageIndex());
	}

	if(pOldFont!=NULL)
		pDC->SelectObject(pOldFont);

	if(pOldBrush!=NULL)
		pDC->SelectObject(pOldBrush);

	if(pOldPen!=NULL)
		pDC->SelectObject(pOldPen);
}

void COXTabViewContainer::DrawSplitter(CDC* pDC)
{
	ASSERT_VALID(pDC);

	CRect rect=m_rectSplitter;

	pDC->Draw3dRect(rect,::GetSysColor(COLOR_BTNFACE),
		::GetSysColor(COLOR_WINDOWFRAME));
	rect.DeflateRect(1,1);
	pDC->Draw3dRect(rect,::GetSysColor(COLOR_BTNHILIGHT),
		::GetSysColor(COLOR_BTNSHADOW));
	rect.DeflateRect(1,1);

	// fill the middle
	COLORREF clr=::GetSysColor(COLOR_BTNFACE);
#ifdef _MAC
	// just use white for interior if less than 16 colors
	if(pDC->GetDeviceCaps(NUMCOLORS)<16)
		clr=RGB(0xFF,0xFF,0xFF);
#endif
	pDC->FillSolidRect(rect,clr);
}

void COXTabViewContainer::DrawSizeBar(CDC* pDC)
{
	ASSERT_VALID(pDC);

	CWnd* pParentWnd=GetParent();
	ASSERT(pParentWnd!=NULL);
	ASSERT(::IsWindow(pParentWnd->m_hWnd));
	if(pParentWnd->IsKindOf(RUNTIME_CLASS(CMDIChildWnd)) &&
		(pParentWnd->GetStyle()&WS_THICKFRAME)==WS_THICKFRAME &&
		(pParentWnd->GetStyle()&WS_MAXIMIZE)!=WS_MAXIMIZE)
		pDC->DrawFrameControl(m_rectSizeBar,DFC_SCROLL,DFCS_SCROLLSIZEGRIP);
	else
		pDC->FillSolidRect(m_rectSizeBar,::GetSysColor(COLOR_BTNFACE));
}

void COXTabViewContainer::DrawButton(CDC* pDC, CRect rect,
									 HITTEST nButtonType) const
{
	ASSERT_VALID(pDC);

	pDC->FillSolidRect(rect,::GetSysColor(COLOR_BTNFACE));

	COLORREF clrTopLeft;
	COLORREF clrBottomRight;
	if(m_nPressedScrlBtn==nButtonType)
	{
		CPoint point;
		::GetCursorPos(&point);
		ScreenToClient(&point);
		if(rect.PtInRect(point))
		{
			clrBottomRight=::GetSysColor(COLOR_BTNFACE);
			clrTopLeft=::GetSysColor(COLOR_BTNFACE);
			rect.OffsetRect(1,1);
		}
		else
		{
			clrTopLeft=::GetSysColor(COLOR_BTNHILIGHT);
			clrBottomRight=::GetSysColor(COLOR_BTNSHADOW);
		}
	}
	else
	{
		clrTopLeft=::GetSysColor(COLOR_BTNHILIGHT);
		clrBottomRight=::GetSysColor(COLOR_BTNSHADOW);
	}

	pDC->Draw3dRect(rect,clrTopLeft,clrBottomRight);

	// draw arrows
	//
	rect.DeflateRect(2,2);
	CRect rectCopy=rect;

	POINT arrPoints[3];
	CRgn rgn;
	CBrush brush;
	brush.CreateSolidBrush(::GetSysColor(COLOR_WINDOWTEXT));

	switch(nButtonType)
	{
	// start
	case SCRLSTARTBTN:
		rectCopy.DeflateRect(rectCopy.Width()/4,rectCopy.Height()/6);
		rectCopy.left+=rectCopy.Width()/4-2;
		if(rectCopy.Height()%2==0)
			rectCopy.bottom+=1;
		rectCopy.right=rectCopy.left+2;
		pDC->FillSolidRect(rectCopy,::GetSysColor(COLOR_WINDOWTEXT));
	// backward
	case SCRLBACKWARDBTN:
		rectCopy=rect;
		rectCopy.DeflateRect(rectCopy.Width()/4,rectCopy.Height()/6);
		if(nButtonType==SCRLSTARTBTN)
			rectCopy.left+=rectCopy.Width()/4;
		else
			rectCopy.right-=rectCopy.Width()/4;
		if(rectCopy.Height()%2==0)
			rectCopy.bottom++;
		arrPoints[0].x=rectCopy.right;
		arrPoints[0].y=rectCopy.top-1;
		arrPoints[1].x=rectCopy.right;
		arrPoints[1].y=rectCopy.bottom;
		arrPoints[2].x=rectCopy.left;
		arrPoints[2].y=rectCopy.top+rectCopy.Height()/2;
		rgn.CreatePolygonRgn(arrPoints,3,WINDING);
		pDC->FillRgn(&rgn,&brush);
		break;
	// end
	case SCRLENDBTN:
		rectCopy.DeflateRect(rectCopy.Width()/4,rectCopy.Height()/6);
		rectCopy.right-=rectCopy.Width()/4-2;
		if(rectCopy.Height()%2==0)
			rectCopy.bottom+=1;
		rectCopy.left=rectCopy.right-2;
		pDC->FillSolidRect(rectCopy,::GetSysColor(COLOR_WINDOWTEXT));
	// forward
	case SCRLFORWARDBTN:
		rectCopy=rect;
		rectCopy.DeflateRect(rectCopy.Width()/4,rectCopy.Height()/6);
		if(nButtonType==SCRLENDBTN)
			rectCopy.right-=rectCopy.Width()/4;
		else
			rectCopy.left+=rectCopy.Width()/4;
		if(rectCopy.Height()%2==0)
			rectCopy.bottom++;
		arrPoints[0].x=rectCopy.right;
		arrPoints[0].y=rectCopy.top+rectCopy.Height()/2;
		arrPoints[1].x=rectCopy.left;
		arrPoints[1].y=rectCopy.top-1;
		arrPoints[2].x=rectCopy.left;
		arrPoints[2].y=rectCopy.bottom;
		rgn.CreatePolygonRgn(arrPoints,3,WINDING);
		pDC->FillRgn(&rgn,&brush);
		break;
	default:
		ASSERT(FALSE);
	}
}

void COXTabViewContainer::DrawTabButton(CDC* pDC, int nIndex) const
{
	ASSERT(nIndex>=0 && nIndex<GetPageCount());

	CRect rect=m_arrTabBtnRects[nIndex];
	rect+=m_rectTabBtnArea.TopLeft();
	rect.OffsetRect(m_nTabBtnAreaOrigin,0);
	if(rect.right>m_rectTabBtnArea.left && rect.left<m_rectTabBtnArea.right)
	{
		rect.bottom=m_rectTabBtnArea.bottom;

		POINT arrPoints[4];
		arrPoints[0].x=rect.left;
		arrPoints[0].y=rect.top;
		arrPoints[1].x=rect.left+ID_TABBTNOVERLAPSIZE;
		arrPoints[1].y=rect.bottom;
		arrPoints[2].x=rect.right-ID_TABBTNOVERLAPSIZE;
		arrPoints[2].y=rect.bottom;
		arrPoints[3].x=rect.right;
		arrPoints[3].y=rect.top;

		CPen penTop(PS_SOLID,1,::GetSysColor(COLOR_BTNHILIGHT));
		CPen penBottom(PS_SOLID,1,::GetSysColor(COLOR_BTNSHADOW));
		CPen* pOldPen=NULL;
		if(nIndex==GetActivePageIndex())
		{
			pDC->Polygon(arrPoints,4);

			pOldPen=pDC->SelectObject(&penTop);
			arrPoints[0].x++;
			pDC->MoveTo(arrPoints[0]);
			pDC->LineTo(arrPoints[3]);

			pDC->SelectObject(&penBottom);
			arrPoints[1].y--;
			arrPoints[2].y--;
			pDC->MoveTo(arrPoints[1]);
			pDC->LineTo(arrPoints[2]);
		}
		else
		{
			pDC->Polygon(arrPoints,4);

			pOldPen=pDC->SelectObject(&penBottom);
			arrPoints[1].y--;
			arrPoints[2].y--;
			pDC->MoveTo(arrPoints[1]);
			pDC->LineTo(arrPoints[2]);
			arrPoints[2].x--;
			arrPoints[3].x--;
			pDC->MoveTo(arrPoints[2]);
			pDC->LineTo(arrPoints[3]);

			pDC->SelectObject(&penTop);
			arrPoints[0].x+=2;
			arrPoints[0].y++;
			arrPoints[1].x++;
			pDC->MoveTo(arrPoints[0]);
			pDC->LineTo(arrPoints[1]);
		}
		if(pOldPen!=NULL)
			pDC->SelectObject(pOldPen);

		CString sTitle=GetPageTitle(nIndex);
		if(!sTitle.IsEmpty())
		{
			COLORREF oldColor=pDC->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
			int nOldBkMode=pDC->SetBkMode(TRANSPARENT);
			pDC->DrawText(sTitle,rect,DT_CENTER|DT_SINGLELINE|DT_VCENTER);
			pDC->SetBkMode(nOldBkMode);
			pDC->SetTextColor(oldColor);
		}
	}
}

int COXTabViewContainer::HitTest(const CPoint& point) const
{
	if(m_rectSplitter.PtInRect(point))
		return SPLITTER;
	else if(m_rectScrollBarHorz.PtInRect(point))
		return SCROLLBARHORZ;
	else if(m_rectScrollBarVert.PtInRect(point))
		return SCROLLBARVERT;
	else if(m_rectScrollToStartBtn.PtInRect(point))
		return SCRLSTARTBTN;
	else if(m_rectScrollBackwardBtn.PtInRect(point))
		return SCRLBACKWARDBTN;
	else if(m_rectScrollForwardBtn.PtInRect(point))
		return SCRLFORWARDBTN;
	else if(m_rectScrollToEndBtn.PtInRect(point))
		return SCRLENDBTN;
	else if(m_rectTabBtnArea.PtInRect(point))
	{
		// test here for tab buttons
		for(int nIndex=0; nIndex<GetPageCount(); nIndex++)
		{
			CRect rect=m_arrTabBtnRects[nIndex];
			rect+=m_rectTabBtnArea.TopLeft();
			rect.OffsetRect(m_nTabBtnAreaOrigin,0);
			rect.bottom=m_rectTabBtnArea.bottom;
			if(rect.PtInRect(point))
				return nIndex;
		}

		return TABBTNAREA;
	}
	else if(m_rectSizeBar.PtInRect(point))
		return SIZEBAR;
	else if(m_rectPage.PtInRect(point))
		return PAGE;

	return NONE;
}

void COXTabViewContainer::RedrawScrollButton(HITTEST hitTest)
{
	switch(hitTest)
	{
	case SCRLSTARTBTN:
		RedrawWindow(m_rectScrollToStartBtn);
		break;
	case SCRLBACKWARDBTN:
		RedrawWindow(m_rectScrollBackwardBtn);
		break;
	case SCRLFORWARDBTN:
		RedrawWindow(m_rectScrollForwardBtn);
		break;
	case SCRLENDBTN:
		RedrawWindow(m_rectScrollToEndBtn);
		break;
	}
}

void COXTabViewContainer::ScrollPage(HITTEST nScrlBtn)
{
	BOOL m_bHasScrolled=FALSE;

	switch(nScrlBtn)
	{
	case SCRLSTARTBTN:
		if(CanScrollToStart())
		{
			m_nTabBtnAreaOrigin=0;
			m_bHasScrolled=TRUE;
		}
		break;
	case SCRLBACKWARDBTN:
		if(CanScrollBackward())
		{
			m_nTabBtnAreaOrigin+=ID_SCROLLTABBTNAREASTEP;
			m_nTabBtnAreaOrigin=
				m_nTabBtnAreaOrigin>0 ? 0 : m_nTabBtnAreaOrigin;
			m_bHasScrolled=TRUE;
		}
		break;
	case SCRLFORWARDBTN:
		if(CanScrollForward())
		{
			m_nTabBtnAreaOrigin-=ID_SCROLLTABBTNAREASTEP;
			CRect rect=m_arrTabBtnRects[GetPageCount()-1];
			rect+=m_rectTabBtnArea.TopLeft();
			if(rect.right+m_nTabBtnAreaOrigin<m_rectTabBtnArea.right)
				m_nTabBtnAreaOrigin=m_rectTabBtnArea.right-rect.right;
			m_bHasScrolled=TRUE;
		}
		break;
	case SCRLENDBTN:
		if(CanScrollToEnd())
		{
			CRect rect=m_arrTabBtnRects[GetPageCount()-1];
			rect+=m_rectTabBtnArea.TopLeft();
			m_nTabBtnAreaOrigin=m_rectTabBtnArea.right-rect.right;
			m_bHasScrolled=TRUE;
		}
		break;
	default:
		ASSERT(FALSE);
	}

	if(m_bHasScrolled)
		RedrawTabBtnArea();
}

void COXTabViewContainer::StartTracking(const CPoint& point)
{
	ASSERT(m_nPressedScrlBtn==NONE);
	m_nPressedScrlBtn=NONE;

	int hitTest=HitTest(point);
	if(hitTest==SCRLSTARTBTN || hitTest==SCRLBACKWARDBTN ||
		hitTest==SCRLFORWARDBTN || hitTest==SCRLENDBTN)
	{
		SetCapture();
		m_nPressedScrlBtn=(HITTEST)hitTest;
		m_bIsScrlBtnPressed=TRUE;
		RedrawScrollButton(m_nPressedScrlBtn);
		ScrollPage(m_nPressedScrlBtn);
		m_nScrollPageTimer=SetTimer(IDT_SCROLLPAGE_TIMER,
			ID_SCROLLPAGE_DELAY,NULL);
	}
	else if(hitTest==SPLITTER)
	{
		SetCapture();
		m_bIsSplitterPressed=TRUE;
	}
}

void COXTabViewContainer::StopTracking(const CPoint& point)
{
	if(::GetCapture()==GetSafeHwnd())
	{
		::ReleaseCapture();
	}

	if(m_nPressedScrlBtn!=NONE)
	{
		int hitTest=HitTest(point);
		if((int)m_nPressedScrlBtn==hitTest && m_nScrollPageTimer!=-1)
		{
			KillTimer(m_nScrollPageTimer);
			m_nScrollPageTimer=-1;
		}

		HITTEST nOldPressedScrlBtn=m_nPressedScrlBtn;
		m_nPressedScrlBtn=NONE;
		m_bIsScrlBtnPressed=FALSE;
		RedrawScrollButton(nOldPressedScrlBtn);
	}
	else if(m_bIsSplitterPressed)
	{
		m_bIsSplitterPressed=FALSE;
	}
}

DWORD COXTabViewContainer::GetUniqueId()
{
	int nCount=PtrToInt(m_arrUniqueIDs.GetSize());
	ASSERT(nCount>0);
	DWORD dwUniqueID=m_arrUniqueIDs[nCount-1];
	if(nCount==1)
	{
		m_arrUniqueIDs.SetAt(nCount-1,dwUniqueID+1);
	}
	return dwUniqueID;
}

void COXTabViewContainer::EnsureTabBtnVisible(int nIndex,
											  BOOL bPartialOk/*=FALSE*/)
{
	ASSERT(nIndex>=0 && nIndex<GetPageCount());

	if(m_rectTabBtnArea.Width()>0 && nIndex>=0 && nIndex<GetPageCount())
	{
		CRect rect=m_arrTabBtnRects[nIndex];
		rect+=m_rectTabBtnArea.TopLeft();
		rect.OffsetRect(m_nTabBtnAreaOrigin,0);
		if(rect.left>m_rectTabBtnArea.right ||
			rect.right<m_rectTabBtnArea.left ||
			(rect.left<m_rectTabBtnArea.left &&
			rect.right<m_rectTabBtnArea.right && !bPartialOk) ||
			(rect.left>m_rectTabBtnArea.left &&
			rect.right>m_rectTabBtnArea.right && !bPartialOk))
		{
			if(rect.left<m_rectTabBtnArea.left)
			{
				m_nTabBtnAreaOrigin+=m_rectTabBtnArea.left-rect.left;
				if(nIndex>0)
					m_nTabBtnAreaOrigin+=m_rectTabBtnArea.Width()/2<20 ?
						m_rectTabBtnArea.Width()/2 : 20;
			}
			else
			{
				m_nTabBtnAreaOrigin+=m_rectTabBtnArea.right-rect.right;
				if(nIndex<GetPageCount()-1)
					m_nTabBtnAreaOrigin-=m_rectTabBtnArea.Width()/2<20 ?
						m_rectTabBtnArea.Width()/2 : 20;
			}
			RedrawTabBtnArea();
		}
	}
}


text:
<agent xmlns="http://jadex.sourceforge.net/jadex" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jadex.sourceforge.net/jadex http://jadex.sourceforge.net/jadex-0.96.xsd" name="holiday" package="holiday.agent.xml">
<imports>
	<import>jadex.runtime.*</import>
	<import>goalplanner.*</import>
	<import>summary.*</import>
	<import>holiday.*</import>
</imports>
<goals>
	<achievegoal name="HolidayGoal">
		<parameter name="human_readable_name" class="String">
			<value>"holiday"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="AccommodationGoal">
		<parameter name="human_readable_name" class="String">
			<value>"accommodation"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="PayBackpackerGoal">
		<parameter name="human_readable_name" class="String">
			<value>"pay_backpacker"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="BookHotelGoal">
		<parameter name="human_readable_name" class="String">
			<value>"book_hotel"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="TransportGoal">
	<parameter name="human_readable_name" class="String">
			<value>"transport"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="BookAirportTransportGoal">
	<parameter name="human_readable_name" class="String">
			<value>"book_airport_transport"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="BookFlightGoal">
	<parameter name="human_readable_name" class="String">
			<value>"book_flight"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="PayTrainGoal">
	<parameter name="human_readable_name" class="String">
			<value>"pay_train"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="LuggageGoal">
	<parameter name="human_readable_name" class="String">
			<value>"luggage"</value>
		</parameter>
	</achievegoal>
	<achievegoal name="mastergoal"/>
	<metagoal name="metagoal">
		<parameterset name="applicables" class="ICandidateInfo"/>
		<parameterset name="result" class="ICandidateInfo" direction="out"/>
		<trigger>
			<goal ref="HolidayGoal"/>
			<goal ref="AccommodationGoal"/>
			<goal ref="PayBackpackerGoal"/>
			<goal ref="BookHotelGoal"/>
			<goal ref="TransportGoal"/>
			<goal ref="BookAirportTransportGoal"/>
			<goal ref="BookFlightGoal"/>
			<goal ref="PayTrainGoal"/>
			<goal ref="LuggageGoal"/>
		</trigger>
	</metagoal>
</goals>
<plans>
	<plan name="masterplan">
		<parameterset name="preferences" class="String">
			<value><![CDATA["and(dislike+(E2), dislike+(E4)) (0) >> like+(E2) (25) >> like+(E3) (50)"]]></value>
		</parameterset>
		<body class="MasterPlan"/>
		<trigger>
			<goal ref="mastergoal"/>
		</trigger>
	</plan>
	<plan name="pingplan">
		<body class="PingPlanOne"/>
		<trigger>
			<messageevent ref="any_message"/>
		</trigger>
	</plan>
	<plan name="metaplan">
		<parameterset name="applicables" class="ICandidateInfo">
			<goalmapping ref="metagoal.applicables"/>
		</parameterset>
		<parameterset name="result" class="ICandidateInfo" direction="out">
			<goalmapping ref="metagoal.result"/>
		</parameterset>
		<body class="MetaPlan"/>
		<trigger>
			<goal ref="metagoal"/>
		</trigger>
	</plan>
	<plan name="HolidayPlan">
		<parameterset name="subgoals" class="String">
			<value>"AccommodationGoal"</value>
			<value>"TransportGoal"</value>
			<value>"LuggageGoal"</value>
		</parameterset>
		<body class="HolidayPlan"/>
		<trigger>
			<goal ref="HolidayGoal"/>
		</trigger>
	</plan>
	<plan name="BackpackerPlan">
		<parameterset name="properties" class="Property">
			<value>new Property("type", "backpacker")</value>
			<value>new Property("quality", "basic")</value>
		</parameterset>
		<parameterset name="subgoals" class="String">
			<value>"PayBackpackerGoal"</value>
		</parameterset>
		<body class="BackpackerPlan"/>
		<trigger>
			<goal ref="AccommodationGoal"/>
		</trigger>
	</plan>
	<plan name="BackpackerDebitPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 100, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "debit")</value>
		</parameterset>
		<body class="BackpackerDebitPlan"/>
		<trigger>
			<goal ref="PayBackpackerGoal"/>
		</trigger>
	</plan>
	<plan name="BackpackerCreditPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 100, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "credit")</value>
		</parameterset>
		<body class="BackpackerCrebitPlan"/>
		<trigger>
			<goal ref="PayBackpackerGoal"/>
		</trigger>
	</plan>
	<plan name="HotelPlan">
		<parameterset name="properties" class="Property">
			<value>new Property("type", "hotel")</value>
		</parameterset>
		<parameterset name="subgoals" class="String">
			<value>"BookHotelGoal"</value>
		</parameterset>
		<body class="HotelPlan"/>
		<trigger>
			<goal ref="AccommodationGoal"/>
		</trigger>
	</plan>
	<plan name="BookThreeStarHotelPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 200, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "credit")</value>
			<value>new Property("quality", "3*")</value>
		</parameterset>
		<body class="BookThreeStarHotelPlan"/>
		<trigger>
			<goal ref="BookHotelGoal"/>
		</trigger>
	</plan>
	<plan name="BookFiveStarHotelPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 400, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "credit")</value>
			<value>new Property("quality", "5*")</value>
		</parameterset>
		<body class="BookFiveStarHotelPlan"/>
		<trigger>
			<goal ref="BookHotelGoal"/>
		</trigger>
	</plan>
	<plan name="FlightPlan">
		<parameterset name="properties" class="Property">
			<value>new Property("primary_transport", "plane")</value>
		</parameterset>
		<parameterset name="subgoals" class="String">
			<value>"BookAirportTransportGoal"</value>
			<value>"BookFlightGoal"</value>
		</parameterset>
		<body class="FlightPlan"/>
		<trigger>
			<goal ref="TransportGoal"/>
		</trigger>
	</plan>
	<plan name="TrainPlan">
		<parameterset name="properties" class="Property">
			<value>new Property("primary_transport", "train")</value>
		</parameterset>
		<parameterset name="subgoals" class="String">
			<value>"PayTrainGoal"</value>
		</parameterset>
		<body class="TrainPlan"/>
		<trigger>
			<goal ref="TransportGoal"/>
		</trigger>
	</plan>
	<plan name="ToAirportByTaxiPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 50, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("secondary_transport", "taxi")</value>
			<value>new Property("payment", new String[] {"debit", "credit"})</value>
		</parameterset>
		<body class="ToAirportByTaxiPlan"/>
		<trigger>
			<goal ref="BookAirportTransportGoal"/>
		</trigger>
	</plan>
	<plan name="ToAirportByPublicTransportPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 20, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("secondary_transport", "public_transport")</value>
			<value>new Property("payment", new String[] {"debit", "credit"})</value>
		</parameterset>
		<body class="ToAirportByPublicTransportPlan"/>
		<trigger>
			<goal ref="BookAirportTransportGoal"/>
		</trigger>
	</plan>
	<plan name="FlyWithJetstarPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 90, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "credit")</value>
			<value>new Property("airline", "jetstar")</value>
		</parameterset>
		<body class="FlyWithJetstarPlan"/>
		<trigger>
			<goal ref="BookFlightGoal"/>
		</trigger>
	</plan>
	<plan name="FlyWithQantasPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 150, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "credit")</value>
			<value>new Property("airline", "qantas")</value>
		</parameterset>
		<body class="FlyWithQantasPlan"/>
		<trigger>
			<goal ref="BookFlightGoal"/>
		</trigger>
	</plan>
	<plan name="PayTrainDebitPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 115, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "debit")</value>
		</parameterset>
		<body class="PayTrainDebitPlan"/>
		<trigger>
			<goal ref="PayTrainGoal"/>
		</trigger>
	</plan>
	<plan name="PayTrainCreditPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 115, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("payment", "credit")</value>
		</parameterset>
		<body class="PayTrainCreditPlan"/>
		<trigger>
			<goal ref="PayTrainGoal"/>
		</trigger>
	</plan>
	<plan name="Purchase30LiterBackpackPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 80, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("type", "backpack")</value>
			<value>new Property("size", "30liter")</value>
		</parameterset>
		<body class="Purchase30LiterBackpackPlan"/>
		<trigger>
			<goal ref="LuggageGoal"/>
		</trigger>
	</plan>
	<plan name="Purchase40LiterBackpackPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 150, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("type", "backpack")</value>
			<value>new Property("size", "40liter")</value>
		</parameterset>
		<body class="Purchase40LiterBackpackPlan"/>
		<trigger>
			<goal ref="LuggageGoal"/>
		</trigger>
	</plan>
	<plan name="Purchase50LiterBackpackPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 180, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("type", "backpack")</value>
			<value>new Property("size", "50liter")</value>
		</parameterset>
		<body class="Purchase50LiterBackpackPlan"/>
		<trigger>
			<goal ref="LuggageGoal"/>
		</trigger>
	</plan>
	<plan name="PurchaseTravelBagPlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 150, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("type", "bag")</value>
			<value>new Property("size", "40liter")</value>
		</parameterset>
		<body class="PurchaseTravelBagPlan"/>
		<trigger>
			<goal ref="LuggageGoal"/>
		</trigger>
	</plan>
	<plan name="PurchaseSuitcasePlan">
		<parameterset name="resources" class="Triple">
			<value>new Triple("money", 300, 0)</value>
		</parameterset>
		<parameterset name="properties" class="Property">
			<value>new Property("type", "suitcase")</value>
			<value>new Property("size", "50liter")</value>
		</parameterset>
		<body class="PurchaseSuitcasePlan"/>
		<trigger>
			<goal ref="LuggageGoal"/>
		</trigger>
	</plan>
</plans>
<events>
	<messageevent name="any_message" type="fipa" direction="send_receive"/>
</events>
<configurations>
	<configuration name="default">
	</configuration>
</configurations>
</agent>

redpajama_set_name: RedPajamaGithub


text:
package org.yeastrc.xlink.www.qc_data.scan_ms1_all_scan_intensity_heatmap.main;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.media.jai.InterpolationBilinear;
import javax.media.jai.JAI;
import java.awt.image.renderable.ParameterBlock;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.slf4j.LoggerFactory;  import org.slf4j.Logger;
import org.yeastrc.heatmap.impl.MultiColorMapper;
import org.yeastrc.spectral_storage.shared_server_client_importer.accum_scan_rt_mz_binned.dto.MS1_IntensitiesBinnedSummedMapRoot;
import org.yeastrc.spectral_storage.shared_server_client_importer.accum_scan_rt_mz_binned.dto.MS1_IntensitiesBinnedSummed_Summary_DataRoot;
import org.yeastrc.xlink.www.exceptions.ProxlWebappInternalErrorException;
import org.yeastrc.xlink.www.spectral_storage_service_interface.Call_Get_ScanPeakIntensityBinnedOn_RT_MZ_Webservice;
import org.yeastrc.xlink.dao.ScanFileDAO;

/**
 * Create a PNG image for the MS1 Intensity
 *
 * !!!!!!!!!!!   VERY IMPORTANT  !!!!!!!!!!!!!!!!!!!!
 * Increment VERSION_FOR_CACHING whenever change the resulting image since Caching the resulting image
 *
 */
public class MS1_All_IntensityHeatmapImage {

	private static final Logger log = LoggerFactory.getLogger( MS1_All_IntensityHeatmapImage.class);

	/**
	 *  !!!!!!!!!!!   VERY IMPORTANT  !!!!!!!!!!!!!!!!!!!!
	 *
	 *  Increment this value whenever change the resulting image since Caching the resulting image
	 */
	static final int VERSION_FOR_CACHING = 4;

	/**
	 *  Must be in ascending order since searching using Arrays.binarySearch
	 */
	static final int[] ALLOWED_REQUESTED_IMAGE_WIDTHS = { 1500 };

	private static final Color[] PLOTTED_PIXEL_COLORS =
		{ Color.white, new Color(82,46,155), new Color(255,246,11), Color.red };

	private static final String GREATER_THAN_OR_EQUAL_UNICODE_CHARACTER = "\u2265"; // (>= as single character)
	private static final String LESS_THAN_OR_EQUAL_UNICODE_CHARACTER = "\u2264"; // (<= as single character)

	private static final String X_AXIS_LABEL = "Retention Time (minutes)";

	private static final  String Y_AXIS_LABEL = "M/Z";

	//  Max pixel intensity per 8 bit channel for 24 bit color
	private static final int PIXEL_INTENSITY_PER_CHANNEL = (int) ( Math.pow( 2, 8 ) );

	/**
	 * Since displayed pixel is zero to ( # possible pixel value - 1 )
	 */
	private final static int PIXEL_INTENSITY_PER_CHANNEL_MINUS_ONE = PIXEL_INTENSITY_PER_CHANNEL - 1;

	private static final int TICK_MARK_LENGTH = 4;
	private static final int TICK_MARK_HORIZONTAL_AXIS_VERTICAL_OFFSET = 1;

	private static final int IMAGE_MARGIN_LEFT = 60;
	private static final int IMAGE_MARGIN_RIGHT = 130;
	private static final int IMAGE_MARGIN_TOP = 20;
	private static final int IMAGE_MARGIN_BOTTOM = 30;

	private static final int TEXT_HEIGHT = 10; // based on current font size.  Provides offset from top left to baseline

	private static final int HORIZONTAL_AXIS_LABEL_Y_OFFSET_FROM_IMAGE_BOTTOM = IMAGE_MARGIN_BOTTOM - 10; //  Measured up from bottom edge of image

	private static final int VERTICAL_AXIS_LABEL_X_OFFSET_FROM_IMAGE = 20;

	private static final int LEGEND_LABEL_VERTICAL_OFFSET_FROM_TOP_MARGIN = 10; // Label above Legend
	//  Top of Legend position
	private static final int LEGEND_VERTICAL_OFFSET_FROM_TOP_MARGIN = LEGEND_LABEL_VERTICAL_OFFSET_FROM_TOP_MARGIN + 8;

	private static final int LEGEND_OFFSET_FROM_IMAGE = 10;
	private static final int LEGEND_WIDTH = 10;
	private static final int LEGEND_LABELS_SIGNIFICANT_DIGITS = 4;

	/**
	 * private constructor
	 */
	private MS1_All_IntensityHeatmapImage(){}
	public static MS1_All_IntensityHeatmapImage getInstance( ) throws Exception {
		MS1_All_IntensityHeatmapImage instance = new MS1_All_IntensityHeatmapImage();
		return instance;
	}

	/**
	 * Returned object
	 *
	 */
	public static class MS1_All_IntensityHeatmapImageResult {

		byte[] imageAsBytes;

		public byte[] getImageAsBytes() {
			return imageAsBytes;
		}
		public void setImageAsBytes(byte[] imageAsBytes) {
			this.imageAsBytes = imageAsBytes;
		}
	}

	/**
	 * @param requestedImageWidth
	 * @return
	 */
	public boolean isRequestedImageWidthAllowed( int requestedImageWidth ) {
		if ( Arrays.binarySearch( ALLOWED_REQUESTED_IMAGE_WIDTHS, requestedImageWidth ) >= 0 ) {
			return true;
		}
		return false;
	}

	/**
	 * @param scanFileId
	 * @param ImageSize
	 * @return
	 * @throws Exception
	 */
	public MS1_All_IntensityHeatmapImageResult getHeatmap( int scanFileId, Integer requestedImageWidth, String requestQueryString ) throws Exception {

		if ( requestedImageWidth != null ) {
			if ( ! isRequestedImageWidthAllowed( requestedImageWidth ) ) {
				// It is expected that calling programs will call isRequestedImageWidthAllowed(...) first
				//  to validate that the value is allowed
				String msg = "requestedImageWidth is not an allowed value: " + requestedImageWidth
						+ ", allowed values: " + ALLOWED_REQUESTED_IMAGE_WIDTHS;
				log.error( msg );
				throw new IllegalArgumentException( msg );
			}
		}

		byte[] imageAsBytes =
				MS1_All_IntensityHeatmapImageCachedResultImageManager.getSingletonInstance()
				.retrieveImageDataFromCache( scanFileId, requestedImageWidth, requestQueryString );

		if ( imageAsBytes == null ) {

			MS1_IntensitiesBinnedSummedMapRoot ms1_IntensitiesBinnedSummedMapRoot =
				getMS1_IntensitiesBinnedSummedMapRoot( scanFileId );

			if ( ms1_IntensitiesBinnedSummedMapRoot == null ) {
				return new MS1_All_IntensityHeatmapImageResult();
			}

			BufferedImage bufferedImage =
					getImage( requestedImageWidth, ms1_IntensitiesBinnedSummedMapRoot, scanFileId /* for logging */ );

			ByteArrayOutputStream baos = new ByteArrayOutputStream(); // ( imageWidth * imageHeight * 4 );

			ImageIO.write( bufferedImage, "PNG", baos );

			baos.close();

			imageAsBytes = baos.toByteArray();
		}

		MS1_All_IntensityHeatmapImageCachedResultImageManager.getSingletonInstance()
		.saveImageDataToCache( scanFileId, requestedImageWidth, imageAsBytes, requestQueryString );

		MS1_All_IntensityHeatmapImageResult resultObj = new MS1_All_IntensityHeatmapImageResult();

		resultObj.setImageAsBytes( imageAsBytes );
		return resultObj;
	}

	/**
	 * @param requestedImageWidth
	 * @param ms1_IntensitiesBinnedSummedMapRoot
	 * @return
	 * @throws ProxlWebappInternalErrorException
	 * @throws Exception
	 */
	public BufferedImage getImage(
			Integer requestedImageWidth,
			MS1_IntensitiesBinnedSummedMapRoot ms1_IntensitiesBinnedSummedMapRoot,
			Integer scanFileId /* for logging */ )
			throws ProxlWebappInternalErrorException, Exception {

		CreateFullSizeImageForMS1IntensityDataResult createFullSizeImageForMS1IntensityDataResult =
				createFullSizeImageForMS1IntensityData(
						ms1_IntensitiesBinnedSummedMapRoot, PLOTTED_PIXEL_COLORS, scanFileId /* for logging */ );

		//  Add tick marks, legends, labels, etc
		BufferedImage bufferedImage =
				createImageWithAdditions( requestedImageWidth, ms1_IntensitiesBinnedSummedMapRoot, createFullSizeImageForMS1IntensityDataResult, PLOTTED_PIXEL_COLORS );

		return bufferedImage;
	}

	/**
	 * @param newImageWidth
	 * @param bufferedImage
	 * @return
	 */
	public BufferedImage createResizedImageForWidth( final int newImageWidth, final BufferedImage bufferedImage ) throws Exception {

		//  Resize the image to the image width

		int currentWidth = bufferedImage.getWidth();

		if ( currentWidth == newImageWidth ) {
			return bufferedImage;
		}

		boolean increasingSize = false;
		if ( newImageWidth > currentWidth ) {
			increasingSize = true;
		}

		BufferedImage bufferedImageResized = bufferedImage;

		//  If reducing size of image, incrementally scale image by cutting size in half
		//  until at desired image size

		while ( true ) {

			float xScale = 0.5f;
			int bufferedImageWidth = bufferedImageResized.getWidth();
			if ( increasingSize || bufferedImageWidth * xScale < newImageWidth ) {
				xScale = (float) newImageWidth / bufferedImageWidth;
			}

			float yScale = xScale;

			ParameterBlock pb = new ParameterBlock();
			pb.addSource( bufferedImageResized ); // The source image
			pb.add(xScale);         // The xScale
			pb.add(yScale);         // The yScale
			pb.add(0.0F);           // The x translation
			pb.add(0.0F);           // The y translation
			pb.add(new InterpolationBilinear()); // The interpolation

			bufferedImageResized = JAI.create("scale", pb, null).getAsBufferedImage();
			int bufferedImageWidthAfterShrunk = bufferedImageResized.getWidth();
			if ( increasingSize || bufferedImageWidthAfterShrunk <= newImageWidth ) {
				break;
			}
		}

		// For debugging, write out the image at this point
//		OutputStream osWithAdditions = new FileOutputStream( "ms1_intensitiesResizedCoreChart.png" );
//		ImageIO.write( bufferedImage, "PNG", osWithAdditions );
//		osWithAdditions.close();

		int bufferedImageResizedWidth = bufferedImageResized.getWidth();
		int bufferedImageResizedHeight = bufferedImageResized.getHeight();

		WritableRaster bufferedImageResizedRaster = bufferedImageResized.getRaster();
		int[] pixel = new int[ 3 ];

		//  Remove all black rows from bottom of result image
		int rowCountToRemove = 0;

		for ( int y_Index = bufferedImageResizedHeight - 1; y_Index > 0; y_Index-- ) {
			boolean foundAllBlack = true;
			for ( int x_Index = 0; x_Index < bufferedImageResizedWidth; x_Index++ ) {
				pixel = bufferedImageResizedRaster.getPixel( x_Index, y_Index, pixel );
				for ( int pixelIndex = 0; pixelIndex < pixel.length; pixelIndex++ ) {
					if ( pixel[ pixelIndex ] != 0 ) {
						foundAllBlack = false;
						break;
					}
				}
				if ( ! foundAllBlack ) {
					break;
				}
			}
			if ( ! foundAllBlack ) {
				break;
			}
			rowCountToRemove++;
		}

		if ( rowCountToRemove > 0 ) {
			log.warn( "Removing " + rowCountToRemove + " rows from bottom of rescaled image since all black" );
			int subImageHeight = bufferedImageResizedHeight - rowCountToRemove;
			bufferedImageResized = bufferedImageResized.getSubimage( 0, 0, bufferedImageResizedWidth, subImageHeight );
		}

		return bufferedImageResized;
	}

	/**
	 * @param ms1_IntensitiesBinnedSummedMapRoot
	 * @param pixelPlottedColors
	 * @return
	 * @throws ProxlWebappInternalErrorException
	 */
	public CreateFullSizeImageForMS1IntensityDataResult createFullSizeImageForMS1IntensityData(
			MS1_IntensitiesBinnedSummedMapRoot ms1_IntensitiesBinnedSummedMapRoot,
			Color[] pixelPlottedColors,
			Integer scanFileId /* for logging */ )
			throws ProxlWebappInternalErrorException {

		MS1_IntensitiesBinnedSummed_Summary_DataRoot summaryData =
				ms1_IntensitiesBinnedSummedMapRoot.getSummaryData();
		Map<Long, Map<Long, Double>> ms1_IntensitiesBinnedSummedMap =
				ms1_IntensitiesBinnedSummedMapRoot.getMs1_IntensitiesBinnedSummedMap();

		long binnedSummedIntensityCount = summaryData.getBinnedSummedIntensityCount();

		double intensityBinnedMinActual = summaryData.getIntensityBinnedMin();
		double intensityBinnedMaxActual = summaryData.getIntensityBinnedMax();

		if ( intensityBinnedMinActual < 1 ) {
			//  intensityBinnedMinActual cannot be zero since taking the log of zero is problematic
			log.warn( "intensityBinnedMinActual < 1"
					+ " so setting intensityBinnedMinActual = 1. "
					+ "intensityBinnedMinActual was: " + intensityBinnedMinActual
					+ ", scanFileId: " + scanFileId );
			intensityBinnedMinActual = 1;
		}

		double intensityBinnedMinUseFromActualMinMax = getIntensityUseFromIntensity( intensityBinnedMinActual );
		double intensityBinnedMaxUseFromActualMinMax = getIntensityUseFromIntensity( intensityBinnedMaxActual );

		if ( log.isDebugEnabled() ) {
			log.debug( "summaryData.getRtBinMaxInSeconds(): " + summaryData.getRtBinMaxInSeconds() );
			log.debug( "summaryData.getRtMaxPossibleValueInSeconds(): " + summaryData.getRtMaxPossibleValueInSeconds() );

			log.debug( "summaryData.getMzBinMaxInMZ(): " + summaryData.getMzBinMaxInMZ() );
			log.debug( "summaryData.getMzMaxPossibleValueInMZ(): " + summaryData.getMzMaxPossibleValueInMZ() );

			log.debug( "intensityBinnedMinActual: " + intensityBinnedMinActual );
			log.debug( "intensityBinnedMaxActual: " + intensityBinnedMaxActual );

			log.debug( "intensityBinnedMinUseFromActualMinMax - Before Interquartile computation: " + intensityBinnedMinUseFromActualMinMax );
			log.debug( "intensityBinnedMaxUseFromActualMinMax - Before Interquartile computation: " + intensityBinnedMaxUseFromActualMinMax );

			log.debug( "intensityBinned Min Max being used for processing (Currently natural log of min and max):" );
		}

		/////////////////////////////

		if ( binnedSummedIntensityCount > Integer.MAX_VALUE ) {
			String msg = "binnedSummedIntensityCount  > Integer.MAX_VALUE so unable to process the data since put it into an array.  "
					+ "binnedSummedIntensityCount: " + binnedSummedIntensityCount;
			log.error( msg );
			throw new ProxlWebappInternalErrorException(msg);
		}

		int binnedSummedIntensityCountInt = (int) binnedSummedIntensityCount;

		//  Put the intensity value into the array same as will use for plotting,
		//       currently taking the log in getIntensityUseFromIntensity(...)

		double[] intensityUseArrayForSort = new double[ binnedSummedIntensityCountInt ];

		{
			int intensityArrayForSortIndex = 0;
			for ( Map.Entry<Long, Map<Long, Double>> rtEntry : ms1_IntensitiesBinnedSummedMap.entrySet() ) {
				for ( Map.Entry<Long, Double> mzEntry : rtEntry.getValue().entrySet() ) {
					double intensityActual = mzEntry.getValue();
//					if ( intensityActual > 0 ) {
						//  Only process records for intensityActual > 0
						double intensityUse = getIntensityUseFromIntensity( intensityActual );
						intensityUseArrayForSort[ intensityArrayForSortIndex ] = intensityUse;
//					}
					intensityArrayForSortIndex++;
				}
			}
		}
		//   Available in Java 8
		//	Arrays.parallelSort( intensityArrayForSort );
		Arrays.sort( intensityUseArrayForSort );

		// Get a DescriptiveStatistics instance
		DescriptiveStatistics stats = new DescriptiveStatistics();

		// Add the data from the array
		for( int i = 0; i < intensityUseArrayForSort.length; i++) {
			stats.addValue( intensityUseArrayForSort[i] );
		}

		final int FIRST_QUARTER_PERCENTILE = 25;
		final int THIRD_QUARTER_PERCENTILE = 75;
		final double QUARTILE_MULTIPLIER = 2;

		// Compute some statistics
		double firstquarter = stats.getPercentile( FIRST_QUARTER_PERCENTILE );
		double thirdquarter = stats.getPercentile( THIRD_QUARTER_PERCENTILE );

		//  Compute the min and max based on percentiles using the intensity values from getIntensityUseFromIntensity(...)
		//        getIntensityUseFromIntensity(...) is currently taking the log

		double iqr =  thirdquarter - firstquarter; // Interquartile Range
		double intensityMinBasedOnPercentilesUse = firstquarter - QUARTILE_MULTIPLIER * iqr;
		double intensityMaxBasedOnPercentilesUse = thirdquarter + QUARTILE_MULTIPLIER * iqr;

		if ( intensityMinBasedOnPercentilesUse < intensityBinnedMinUseFromActualMinMax ) {
			if ( log.isDebugEnabled() ) {
				log.debug( "intensityMinBasedOnPercentilesUse < intensityBinnedMinUseFromActualMinMax "
						+ "so setting intensityMinBasedOnPercentilesActual = intensityBinnedMinUseFromActualMinMax.  "
						+ "intensityMinBasedOnPercentilesUse: " + intensityMinBasedOnPercentilesUse
						+ ", intensityBinnedMinActual: " + intensityBinnedMinUseFromActualMinMax );
			}
			intensityMinBasedOnPercentilesUse = intensityBinnedMinUseFromActualMinMax;
		}
		if ( intensityMaxBasedOnPercentilesUse > intensityBinnedMaxUseFromActualMinMax ) {
			if ( log.isDebugEnabled() ) {
				log.debug( "intensityMaxBasedOnPercentilesUse > intensityBinnedMaxUseFromActualMinMax "
						+ "so setting intensityMaxBasedOnPercentilesUse = intensityBinnedMaxUseFromActualMinMax.  "
						+ "intensityMinBasedOnPercentilesUse: " + intensityMinBasedOnPercentilesUse
						+ ", intensityBinnedMaxUseFromActualMinMax: " + intensityBinnedMaxUseFromActualMinMax );
			}
			intensityMaxBasedOnPercentilesUse = intensityBinnedMaxUseFromActualMinMax;
		}

		if ( intensityMinBasedOnPercentilesUse > intensityMaxBasedOnPercentilesUse ) {
			String msg = "ERROR: Computing Min and Max, Min > Max, "
					+ " intensityMinBasedOnPercentilesUse > intensityMaxBasedOnPercentilesUse"
					+ ", intensityMinBasedOnPercentilesUse: " + intensityMinBasedOnPercentilesUse
					+ ", intensityMaxBasedOnPercentilesUse: " + intensityMaxBasedOnPercentilesUse;
			log.error( msg );
			throw new ProxlWebappInternalErrorException( msg );
		}

		if ( log.isDebugEnabled() ) {
			log.debug( "intensityMinBasedOnPercentilesUse: " + intensityMinBasedOnPercentilesUse
					+ ", intensityMaxBasedOnPercentilesUse: " + intensityMaxBasedOnPercentilesUse
					);
		}

		MultiColorMapper colorMapper = new MultiColorMapper( intensityMinBasedOnPercentilesUse, intensityMaxBasedOnPercentilesUse, pixelPlottedColors );

		BufferedImage bufferedImage = createFullSizeImageOnlyDataActualImage(
				ms1_IntensitiesBinnedSummedMapRoot,
				intensityMinBasedOnPercentilesUse,
				colorMapper );

		CreateFullSizeImageForMS1IntensityDataResult result = new CreateFullSizeImageForMS1IntensityDataResult();

		result.bufferedImage = bufferedImage;

		result.intensityMax_ForPlotting = intensityMaxBasedOnPercentilesUse;
		result.intensityMin_ForPlotting = intensityMinBasedOnPercentilesUse;

		result.intensityMax_Actual = getRawIntensityFromIntensityUse( result.intensityMax_ForPlotting );
		result.intensityMin_Actual = getRawIntensityFromIntensityUse( result.intensityMin_ForPlotting );

		return result;
	}

	/**
	 * Result from method createFullSizeImageForMS1IntensityData
	 *
	 */
	public static class CreateFullSizeImageForMS1IntensityDataResult {
		BufferedImage bufferedImage;
		double intensityMax_Actual;
		double intensityMin_Actual;

		double intensityMax_ForPlotting;
		double intensityMin_ForPlotting;

		public BufferedImage getBufferedImage() {
			return bufferedImage;
		}
		public void setBufferedImage(BufferedImage bufferedImage) {
			this.bufferedImage = bufferedImage;
		}
		public double getIntensityMax_Actual() {
			return intensityMax_Actual;
		}
		public void setIntensityMax_Actual(double intensityMax_Actual) {
			this.intensityMax_Actual = intensityMax_Actual;
		}
		public double getIntensityMin_Actual() {
			return intensityMin_Actual;
		}
		public void setIntensityMin_Actual(double intensityMin_Actual) {
			this.intensityMin_Actual = intensityMin_Actual;
		}
		public double getIntensityMax_ForPlotting() {
			return intensityMax_ForPlotting;
		}
		public void setIntensityMax_ForPlotting(double intensityMax_ForPlotting) {
			this.intensityMax_ForPlotting = intensityMax_ForPlotting;
		}
		public double getIntensityMin_ForPlotting() {
			return intensityMin_ForPlotting;
		}
		public void setIntensityMin_ForPlotting(double intensityMin_ForPlotting) {
			this.intensityMin_ForPlotting = intensityMin_ForPlotting;
		}
	}

	/**
	 * @param ms1_IntensitiesBinnedSummedMapRoot
	 * @param intensityMinUse
	 * @param colorMapper
	 * @return
	 * @throws ProxlWebappInternalErrorException
	 */
	public BufferedImage createFullSizeImageOnlyDataActualImage(
			MS1_IntensitiesBinnedSummedMapRoot ms1_IntensitiesBinnedSummedMapRoot,
			double intensityMinUse,
			MultiColorMapper colorMapper) throws ProxlWebappInternalErrorException {

		Map<Long, Map<Long, Double>> ms1_IntensitiesBinnedSummedMap = ms1_IntensitiesBinnedSummedMapRoot.getMs1_IntensitiesBinnedSummedMap();
		MS1_IntensitiesBinnedSummed_Summary_DataRoot summaryData = ms1_IntensitiesBinnedSummedMapRoot.getSummaryData();

		int imageWidth = (int) ( summaryData.getRtMaxPossibleValueInSeconds() - summaryData.getRtBinMinInSeconds() );
		int imageHeight = (int) ( summaryData.getMzMaxPossibleValueInMZ() - summaryData.getMzBinMinInMZ() );

		BufferedImage bufferedImage = createBufferedImageForWidthHeight( imageWidth, imageHeight );

		WritableRaster raster = bufferedImage.getRaster();
		int[] nextPixel = new int[ 3 ];

		nextPixel[0] = PIXEL_INTENSITY_PER_CHANNEL_MINUS_ONE;
		nextPixel[1] = PIXEL_INTENSITY_PER_CHANNEL_MINUS_ONE;
		nextPixel[2] = PIXEL_INTENSITY_PER_CHANNEL_MINUS_ONE;

		//  Init image to white
		for ( int x = 0; x < imageWidth; x++ ) {
			for ( int y = 0; y < imageHeight; y++ ) {
				raster.setPixel(x, y, nextPixel );
			}
		}

//		int intensityTop10percentCounter = 0;
//		int intensityTop20percentCounter = 0;
//
//		int intensityRedBetween_100_150 = 0;

		for ( Map.Entry<Long, Map<Long, Double>> rtEntry : ms1_IntensitiesBinnedSummedMap.entrySet() ) {
			long retentionTimeBin = rtEntry.getKey();

			int xAxis = (int) ( retentionTimeBin - summaryData.getRtBinMinInSeconds() );

			for ( Map.Entry<Long, Double> mzEntry : rtEntry.getValue().entrySet() ) {
				long mzBin = mzEntry.getKey();

				//  flipping y axis so min mzBin is at the bottom of the image
				int yAxis = imageHeight - ( (int) ( mzBin - summaryData.getMzBinMinInMZ() ) ) - 1;

				double intensityActual = mzEntry.getValue();

				double intensityUse = intensityActual;

				//  Get converted intensity used for graphing.  May be no change or otherwise.   getIntensityUseFromIntensity(...) at top of this file
				intensityUse = getIntensityUseFromIntensity( intensityActual );

				double intensityUse_MinusMinBasedOnPercentiles = intensityUse - intensityMinUse;

				//  Update counters
//				if ( intensityUse_MinusMinBasedOnPercentiles > intensity_Max_Minus_Min_BasedOnPercentilesUse * 0.9 ) {
//					intensityTop10percentCounter++;
//				}
//				if ( intensityUse_MinusMinBasedOnPercentiles > intensity_Max_Minus_Min_BasedOnPercentilesUse * 0.8 ) {
//					intensityTop20percentCounter++;
//				}

				Color color = colorMapper.getColor( intensityUse_MinusMinBasedOnPercentiles );

				//  The values for the next pixel
				int pixelIntensity_Red = color.getRed();
				int pixelIntensity_Green = color.getGreen();
				int pixelIntensity_Blue = color.getBlue();

				nextPixel[0] = pixelIntensity_Red;
				nextPixel[1] = pixelIntensity_Green;
				nextPixel[2] = pixelIntensity_Blue;

				try {
					raster.setPixel( xAxis, yAxis, nextPixel );
				} catch ( Exception e ) {
					String msg = "Failed to add pixel at xAxis: " + xAxis
							+ ", yAxis: " + yAxis
							+ ", nextPixel: " + nextPixel
							+ ", imageWidth: " + imageWidth
							+ ", imageHeight: " + imageHeight;
					log.error( msg );
					throw new ProxlWebappInternalErrorException( msg );
				}
			}
		}
		return bufferedImage;
	}

	/**
	 *
	 * Add tick marks, legends, labels, etc
	 *
	 * @param bufferedImage
	 * @return
	 * @throws Exception
	 */
	public BufferedImage createImageWithAdditions(
			Integer requestedImageWidth,
			MS1_IntensitiesBinnedSummedMapRoot ms1_IntensitiesBinnedSummedMapRoot,
			CreateFullSizeImageForMS1IntensityDataResult createFullSizeImageForMS1IntensityDataResult,
			Color[] colors ) throws Exception {

		BufferedImage bufferedImage = createFullSizeImageForMS1IntensityDataResult.bufferedImage;

		MS1_IntensitiesBinnedSummed_Summary_DataRoot summaryData =
				ms1_IntensitiesBinnedSummedMapRoot.getSummaryData();

		int bufferedImageWidth = bufferedImage.getWidth();
		int bufferedImageHeight = bufferedImage.getHeight();

		if ( requestedImageWidth != null ) {

			//  adjust width and height of data image based on requestedImageWidth

			int newImageWidth = requestedImageWidth - IMAGE_MARGIN_LEFT - IMAGE_MARGIN_RIGHT;

			bufferedImage = createResizedImageForWidth( newImageWidth, bufferedImage );

			bufferedImageWidth = newImageWidth;
			bufferedImageHeight = (int) ( bufferedImage.getHeight() * ( (float) newImageWidth / bufferedImage.getWidth() ) );
		}

		//  Create new larger BufferedImage with space for scale bars and legend
		BufferedImage bufferedImageWithAdditions = createBufferedImageForWidthHeight(
				bufferedImageWidth + IMAGE_MARGIN_LEFT + IMAGE_MARGIN_RIGHT,
				bufferedImageHeight + IMAGE_MARGIN_TOP + IMAGE_MARGIN_BOTTOM );

		int bufferedImageWidthWithAdditions = bufferedImageWithAdditions.getWidth();
		int bufferedImageHeightWithAdditions = bufferedImageWithAdditions.getHeight();

		WritableRaster rasterWithAdditions = bufferedImageWithAdditions.getRaster();

		int[] nextPixelWithAdditions = new int[3];

		nextPixelWithAdditions[0] = PIXEL_INTENSITY_PER_CHANNEL_MINUS_ONE;
		nextPixelWithAdditions[1] = PIXEL_INTENSITY_PER_CHANNEL_MINUS_ONE;
		nextPixelWithAdditions[2] = PIXEL_INTENSITY_PER_CHANNEL_MINUS_ONE;

		//  Init image to white
		for ( int x = 0; x < bufferedImageWidthWithAdditions; x++ ) {
			for ( int y = 0; y < bufferedImageHeightWithAdditions; y++ ) {
				rasterWithAdditions.setPixel(x, y, nextPixelWithAdditions );
			}
		}

		Graphics2D graphicsObj = null;

		try {
			graphicsObj = bufferedImageWithAdditions.createGraphics();

			 RenderingHints renderingHints = new RenderingHints(
			            RenderingHints.KEY_TEXT_ANTIALIASING,
			            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			 graphicsObj.setRenderingHints( renderingHints );

			//  Will auto scale bufferedImage if needed
			boolean isDrawn = graphicsObj.drawImage( bufferedImage, IMAGE_MARGIN_LEFT, IMAGE_MARGIN_TOP, bufferedImageWidth, bufferedImageHeight, null /* Callback */ );
			if ( ! isDrawn ) {
				throw new ProxlWebappInternalErrorException( "graphicsObj.drawImage(...) returned false" );
			}

			//  Draw black box around main chart data
			graphicsObj.setColor( Color.BLACK );
			// left
			graphicsObj.drawLine( IMAGE_MARGIN_LEFT - 1, IMAGE_MARGIN_TOP - 1, IMAGE_MARGIN_LEFT - 1, IMAGE_MARGIN_TOP + bufferedImageHeight );
			// right
			graphicsObj.drawLine( IMAGE_MARGIN_LEFT + bufferedImageWidth, IMAGE_MARGIN_TOP - 1, IMAGE_MARGIN_LEFT + bufferedImageWidth, IMAGE_MARGIN_TOP + bufferedImageHeight );
			// top
			graphicsObj.drawLine( IMAGE_MARGIN_LEFT - 1, IMAGE_MARGIN_TOP - 1, IMAGE_MARGIN_LEFT + bufferedImageWidth, IMAGE_MARGIN_TOP - 1 );
			// bottom
			graphicsObj.drawLine( IMAGE_MARGIN_LEFT - 1, IMAGE_MARGIN_TOP + bufferedImageHeight, IMAGE_MARGIN_LEFT + bufferedImageWidth, IMAGE_MARGIN_TOP + bufferedImageHeight );

			Font font = graphicsObj.getFont(); // get current font

			String fontName = font.getName();
			int fontSize = font.getSize();

			if ( log.isDebugEnabled() ) {
				log.debug( "fontName: " + fontName + ", fontSize: " + fontSize );
			}

			Font fontSize12 = font.deriveFont( 12 );
			//  Change to current font, size 12
			graphicsObj.setFont( fontSize12 );

			// get metrics from the graphics
			FontMetrics fontMetrics = graphicsObj.getFontMetrics( graphicsObj.getFont() ); // from current font
			// get the height of a line of text in this
			// font and render context
//			int fontHeight = fontMetrics.getHeight();

			NumberFormat numberFormat = NumberFormat.getInstance();

			//  X axis label

			int x_axis_Label_ApproxWidth = getApproxStringWidth( X_AXIS_LABEL, fontMetrics );

			graphicsObj.drawString( X_AXIS_LABEL,
					IMAGE_MARGIN_LEFT + ( bufferedImageWidth / 2 ) - ( x_axis_Label_ApproxWidth / 2 ),
					IMAGE_MARGIN_TOP + bufferedImageHeight + HORIZONTAL_AXIS_LABEL_Y_OFFSET_FROM_IMAGE_BOTTOM );

			//  Draw X Axis Tick Marks and Labels

			//  Convert labels to minutes

			long retentionTimeBinMinMinutes = summaryData.getRtBinMinInSeconds() / 60;
			long retentionTimeBinMaxMinutes = summaryData.getRtBinMaxInSeconds() / 60;

			//  Draw left tick mark and label
			drawHorizontalAxisTickMarkAndLabel(
					numberFormat.format( retentionTimeBinMinMinutes ),
					IMAGE_MARGIN_LEFT,
					false, //  right align text
					IMAGE_MARGIN_TOP + bufferedImageHeight,
					graphicsObj,
					fontMetrics );

			//  Draw right tick mark and label
			drawHorizontalAxisTickMarkAndLabel(
					numberFormat.format( retentionTimeBinMaxMinutes ),
					IMAGE_MARGIN_LEFT + bufferedImageWidth,
					true, //  right align text
					IMAGE_MARGIN_TOP + bufferedImageHeight,
					graphicsObj,
					fontMetrics );

			//  Draw Y Axis Label text on Y Axis, rotated
			AffineTransform originalAffineTransform = graphicsObj.getTransform();
			graphicsObj.rotate( - Math.PI / 2 );

			//  Since Rotated, all X and Y are reversed

			//  Y axis label

			int y_axis_Label_ApproxWidth = getApproxStringWidth( Y_AXIS_LABEL, fontMetrics );

			graphicsObj.drawString(
					Y_AXIS_LABEL,
					- ( IMAGE_MARGIN_TOP + ( bufferedImageHeight / 2 ) + ( y_axis_Label_ApproxWidth / 2 ) ),
					IMAGE_MARGIN_LEFT - VERTICAL_AXIS_LABEL_X_OFFSET_FROM_IMAGE );

			//  Finish Rotation

			//  set back to previous/normal transform
			graphicsObj.setTransform( originalAffineTransform );

			//  Draw Y Axis Tick Marks and Labels
			graphicsObj.setColor( Color.BLACK );

			//  Draw top tick mark and label
			drawVerticalAxisTickMarkAndLabel(
					numberFormat.format( summaryData.getMzBinMaxInMZ() ),
					IMAGE_MARGIN_TOP + TEXT_HEIGHT,  // tickMark_Y_TextBaseline
					graphicsObj,
					fontMetrics );

			//  Draw bottom tick mark and label
			drawVerticalAxisTickMarkAndLabel(
					numberFormat.format( summaryData.getMzBinMinInMZ() ),
					IMAGE_MARGIN_TOP + bufferedImageHeight - 2, // tickMark_Y_TextBaseline
					graphicsObj,
					fontMetrics );

			//  Text above color legend on right

			graphicsObj.drawString(
					"Total Ion Current",
					IMAGE_MARGIN_LEFT + bufferedImageWidth + LEGEND_OFFSET_FROM_IMAGE,
					IMAGE_MARGIN_TOP + LEGEND_LABEL_VERTICAL_OFFSET_FROM_TOP_MARGIN );

			//  Draw color legend on right
			drawLegend(
					IMAGE_MARGIN_LEFT + bufferedImageWidth + LEGEND_OFFSET_FROM_IMAGE,
					IMAGE_MARGIN_TOP + LEGEND_VERTICAL_OFFSET_FROM_TOP_MARGIN,
					IMAGE_MARGIN_LEFT + bufferedImageWidth + LEGEND_OFFSET_FROM_IMAGE + LEGEND_WIDTH,
					IMAGE_MARGIN_TOP + bufferedImageHeight,
					createFullSizeImageForMS1IntensityDataResult,
					colors,
					bufferedImageWithAdditions,
					graphicsObj );

		} finally {
			if ( graphicsObj != null ) {
				graphicsObj.dispose();
			}
		}

		if ( log.isDebugEnabled() ) {

			int bufferedImageWithAdditionsWidth = bufferedImageWithAdditions.getWidth();
			int bufferedImageWithAdditionsHeight = bufferedImageWithAdditions.getHeight();

			log.debug(
					"bufferedImageWidth: " + bufferedImageWidth
					+ ", bufferedImageHeight: " + bufferedImageHeight
					+ ", bufferedImageWithAdditionsWidth: " + bufferedImageWithAdditionsWidth
					+ ", bufferedImageWithAdditionsHeight: " + bufferedImageWithAdditionsHeight );
		}

		return bufferedImageWithAdditions;
	}

	/**
	 * @param tickMarkLabel
	 * @param tickMark_X
	 * @param tickMark_top_Y
	 * @param graphicsObj
	 * @param fontMetrics
	 */
	public void drawHorizontalAxisTickMarkAndLabel(
			String tickMarkLabel,
			int tickMark_X,
			boolean rightAlignTheText, //  right align text
			int tickMark_top_Y,
			Graphics2D graphicsObj,
			FontMetrics fontMetrics) {

		int fontAscent = fontMetrics.getAscent();

		//  Skip drawing tick mark
//		graphicsObj.drawLine( tickMark_center_X, tickMark_top_Y, tickMark_center_X, tickMark_top_Y  + TICK_MARK_LENGTH);

		int tickMarkLabelApproxWidth = getApproxStringWidth( tickMarkLabel, fontMetrics );

		int tickMarkLabel_X = tickMark_X;
		if ( rightAlignTheText ) {
			tickMarkLabel_X -= tickMarkLabelApproxWidth;
		}
		int tickMarkLabel_Y = tickMark_top_Y + fontAscent + TICK_MARK_LENGTH + TICK_MARK_HORIZONTAL_AXIS_VERTICAL_OFFSET;

		graphicsObj.drawString( tickMarkLabel, tickMarkLabel_X, tickMarkLabel_Y );
	}

	/**
	 * @param tickMarkLabel
	 * @param tickMark_Y
	 * @param graphicsObj
	 * @param fontMetrics
	 */
	public void drawVerticalAxisTickMarkAndLabel(
			String tickMarkLabel,
			int tickMark_Y_TextBaseline,
			Graphics2D graphicsObj,
			FontMetrics fontMetrics) {

		//  Skip drawing tick mark
//		graphicsObj.drawLine( IMAGE_MARGIN_LEFT - 1 - TICK_MARK_LENGTH, tickMark_Y, IMAGE_MARGIN_LEFT - 1, tickMark_Y );

		int tickMarkLabelApproxWidth = getApproxStringWidth( tickMarkLabel, fontMetrics );

		int tickMarkLabel_X = IMAGE_MARGIN_LEFT - 1 - TICK_MARK_LENGTH - tickMarkLabelApproxWidth;
		int tickMarkLabel_Y = tickMark_Y_TextBaseline;

		graphicsObj.drawString( tickMarkLabel, tickMarkLabel_X, tickMarkLabel_Y );
	}

	/**
	 * @param stringForWidth
	 * @param metrics
	 * @return
	 */
	public int getApproxStringWidth( String stringForWidth, FontMetrics metrics) {
		// get the advance of the text in this font and render context.
		//  Advance width is the distance from the origin of the text to the position of a subsequently rendered string.
		int advance = metrics.stringWidth( stringForWidth );
		return advance;
	}

	/**
	 * Draw Vertical Legend
	 *
	 * @param start_X
	 * @param start_Y
	 * @param end_X
	 * @param end_Y
	 * @param createFullSizeImageForMS1IntensityDataResult
	 * @param colors
	 * @param bufferedImage
	 */
	public void drawLegend(
			int start_X, int start_Y, int end_X, int end_Y,
			CreateFullSizeImageForMS1IntensityDataResult createFullSizeImageForMS1IntensityDataResult,
			Color[] colors,
			BufferedImage bufferedImage,
			Graphics2D graphicsObj ) {

		int x_Count = end_X - start_X + 1;
		int y_Count = end_Y - start_Y + 1;

		MultiColorMapper mapper = new MultiColorMapper( 1.0, y_Count, colors );

        WritableRaster raster = bufferedImage.getRaster();

        for ( int y_index = 1; y_index <= y_Count; y_index++ ) {
        	Color color = mapper.getColor( y_index + 1.0 );
        	int[] nextPixel = { color.getRed(), color.getGreen(), color.getBlue() };

        	for ( int x_index = 0; x_index < x_Count; x_index++ ) {
        		raster.setPixel( x_index + start_X,  y_Count - y_index + start_Y, nextPixel );
        	}
        }

		//  Draw black box around legend
		graphicsObj.setColor( Color.BLACK );
		// left
		graphicsObj.drawLine( start_X - 1, start_Y - 1, start_X - 1, end_Y + 1 );
		// right
		graphicsObj.drawLine( end_X + 1, start_Y - 1, end_X + 1, end_Y + 1 );
		// top
		graphicsObj.drawLine( start_X - 1, start_Y - 1, end_X + 1, start_Y - 1 );
		// bottom
		graphicsObj.drawLine( start_X - 1, end_Y + 1, end_X + 1, end_Y + 1 );

//		Font font = graphicsObj.getFont(); // get current font
//
//		String fontName = font.getName();
//		int fontSize = font.getSize();
//
//		if ( log.isDebugEnabled() ) {
//			log.debug( "fontName: " + fontName + ", fontSize: " + fontSize );
//		}
//
//		Font fontSize12 = font.deriveFont( 12 );
//		//  Change to current font, size 12
//		graphicsObj.setFont( fontSize12 );

		//  Draw Y Axis Tick Marks
		graphicsObj.setColor( Color.BLACK );

		BigDecimal tickMarkValueToSignificantDigits = null;
		String tickMarkLabel = null;
		MathContext mcForPrecision = new MathContext( LEGEND_LABELS_SIGNIFICANT_DIGITS ); // number of signicant digits

		tickMarkValueToSignificantDigits = new BigDecimal( createFullSizeImageForMS1IntensityDataResult.intensityMax_Actual, mcForPrecision );
		tickMarkLabel = GREATER_THAN_OR_EQUAL_UNICODE_CHARACTER + " " + tickMarkValueToSignificantDigits.toString();

		drawLegendTickMarkAndLabel(
				tickMarkLabel,
				end_X + 1,
				start_Y,
				start_Y + TEXT_HEIGHT,
				graphicsObj );

		tickMarkValueToSignificantDigits = new BigDecimal( createFullSizeImageForMS1IntensityDataResult.intensityMin_Actual, mcForPrecision );
		tickMarkLabel = LESS_THAN_OR_EQUAL_UNICODE_CHARACTER + " " + tickMarkValueToSignificantDigits.toString();

		drawLegendTickMarkAndLabel(
				tickMarkLabel,
				end_X + 1,
				end_Y,
				end_Y,
				graphicsObj );

		graphicsObj.drawString( "Log Scale", end_X + TICK_MARK_LENGTH + 1, start_Y + ( ( end_Y - start_Y ) / 2 ) );
	}

	/**
	 * @param tickMarkLabelValue
	 * @param tickMark_Start_X
	 * @param tickMark_Y
	 * @param graphicsObj
	 */
	public void drawLegendTickMarkAndLabel(
			String tickMarkLabelValue,
			int tickMark_Start_X,
			int tickMark_Y,
			int tickMark_Y_TextBaseline,
			Graphics2D graphicsObj ) {

		//  Draw top tick mark and label
//		graphicsObj.drawLine( tickMark_Start_X, tickMark_Y, tickMark_Start_X + TICK_MARK_LENGTH, tickMark_Y );

		int tickMarkLabel_X = tickMark_Start_X + TICK_MARK_LENGTH + 2;
		int tickMarkLabel_Y = tickMark_Y_TextBaseline;

		graphicsObj.drawString( tickMarkLabelValue, tickMarkLabel_X, tickMarkLabel_Y );
	}

	/**
	 * @param imageWidth
	 * @param imageHeight
	 * @return
	 */
	public BufferedImage createBufferedImageForWidthHeight(int imageWidth, int imageHeight) {

		//  Create 24 bit color scale image
		BufferedImage bufferedImage =  new BufferedImage( imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB );
		return bufferedImage;
	}

	/**
	 * !!!!  Update getRawIntensityFromIntensityUse(...) when update this method
	 *
	 * Convert raw intensity to intensity used for graphing.  Currently using natural log
	 * @param intensity
	 * @return
	 */
	private static double getIntensityUseFromIntensity( double intensity ) {
		double intensityUse = Math.log( intensity );  // If change, update next method  getRawIntensityFromIntensityUse
		return intensityUse;
	}

	/**
	 * !!!!  Update getIntensityUseFromIntensity(...) when update this method
	 *
	 * Convert intensity used for graphing to raw intensity.  Currently using natural log.
	 *
	 * Inverse of method getIntensityUseFromIntensity( intensity )
	 * @param intensity
	 * @return
	 */
	private static double getRawIntensityFromIntensityUse( double intensityUse ) {
		double intensityRaw = Math.exp( intensityUse );  // If change, update previous method getIntensityUseFromIntensity
		return intensityRaw;
	}

	/**
	 * @param scanFileId
	 * @return null if not in db
	 * @throws Exception
	 */
	private MS1_IntensitiesBinnedSummedMapRoot getMS1_IntensitiesBinnedSummedMapRoot( int scanFileId ) throws Exception {

		//  Get from Spectral Storage Service

		String spectralStorageAPIKey = ScanFileDAO.getInstance().getSpectralStorageAPIKeyById( scanFileId );

		if ( spectralStorageAPIKey == null ) {
			log.error( "No spectralStorageAPIKey value in scan file table for scanFileId: " + scanFileId );
			return null;  // EARLY RETURN
		}

		MS1_IntensitiesBinnedSummedMapRoot ms1_IntensitiesBinnedSummedMapRoot_FromSpectralStorage =
				Call_Get_ScanPeakIntensityBinnedOn_RT_MZ_Webservice.getSingletonInstance()
				.getScanPeakIntensityBinnedOn_RT_MZFromSpectralStorageService( spectralStorageAPIKey );

		if ( ms1_IntensitiesBinnedSummedMapRoot_FromSpectralStorage == null ) {

			log.error( "No data in Spectral Storage for ms1_IntensitiesBinnedSummedMapRoot_FromSpectralStorage. scanFileId: " + scanFileId
					+ ", spectralStorageAPIKey: "
					+ spectralStorageAPIKey );

			return null;  // EARLY RETURN
		}

		return ms1_IntensitiesBinnedSummedMapRoot_FromSpectralStorage;
	}
}


text:
package com.eixox.restrictions;

public class MaxNumberExclusiveRestriction implements Restriction {

	private final double _Value;

	public MaxNumberExclusiveRestriction(double value) {
		this._Value = value;
	}

	public MaxNumberExclusiveRestriction(MaxNumberExclusive maxNumberExclusive) {
		this(maxNumberExclusive.value());
	}

	public final double getValue() {
		return this._Value;
	}

	public final boolean validate(Object input) {
		if (input == null || !(input instanceof Number))
			return false;
		else
			return ((Number) input).doubleValue() < _Value;
	}

	public final String getRestrictionMessageFor(Object input) {
		return validate(input) ? null : "Valor acima do máximo (exclusivo) " + _Value;
	}

	public final void assertValid(Object input) throws RestrictionException {
		String msg = getRestrictionMessageFor(input);
		if (msg != null)
			throw new RestrictionException(msg);

	}

}