/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */
// generic user interface
// grouping elements and control base classes

#include "C4Include.h"
#include "gui/C4Gui.h"

#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
#include "gui/C4MouseControl.h"

namespace C4GUI
{

// --------------------------------------------------
// Container

	void Container::Draw(C4TargetFacet &cgo)
	{
		// self visible?
		if (!IsVisible()) return;
		// then draw all visible child elements
		for (Element *pEl = pFirst; pEl; pEl = pEl->pNext)
			if (pEl->fVisible)
			{
				// skip viewport dialogs
				if (!pEl->IsExternalDrawDialog())
				{
					if (pEl->GetDialogWindow())
						pEl->GetDialogWindow()->RequestUpdate();
					else
						pEl->Draw(cgo);
				}
			}
	}

	Container::Container() : Element()
	{
		// zero fields
		pFirst = pLast = nullptr;
	}

	Container::~Container()
	{
		// empty
		Clear();
	}

	void Container::Clear()
	{
		ClearChildren();
	}

	void Container::ClearChildren()
	{
		// delete all items; dtor will update list
		while (pFirst)
		{
			if (pFirst->IsOwnPtrElement())
			{
				// unlink from list
				Element *pANext = pFirst->pNext;
				pFirst->pPrev = pFirst->pNext = nullptr;
				pFirst->pParent = nullptr;
				if ((pFirst = pANext))
					pFirst->pPrev = nullptr;
				else
					pLast = nullptr;
			}
			else
				delete pFirst;
		}
	}

	void Container::RemoveElement(Element *pChild)
	{
		// safety
		if (!pChild) return;
		// inherited
		Element::RemoveElement(pChild);
		// must be from same container
		if (pChild->pParent != this) return;
		// unlink from list
		if (pChild->pPrev) pChild->pPrev->pNext = pChild->pNext; else pFirst = pChild->pNext;
		if (pChild->pNext) pChild->pNext->pPrev = pChild->pPrev; else pLast = pChild->pPrev;
		// unset parent; invalidates pPrev/pNext
		pChild->pParent = nullptr;
		// element has been removed
		AfterElementRemoval();
	}

	void Container::MakeLastElement(Element *pChild)
	{
		// must be from same container
		if (pChild->pParent != this) return;
		// unlink from list
		if (pChild->pPrev) pChild->pPrev->pNext = pChild->pNext; else pFirst = pChild->pNext;
		if (pChild->pNext) pChild->pNext->pPrev = pChild->pPrev; else pLast = pChild->pPrev;
		// readd to front of list
		if (pLast) pLast->pNext = pChild; else pFirst = pChild;
		pChild->pPrev = pLast; pChild->pNext = nullptr; pLast = pChild;
	}

	void Container::AddElement(Element *pChild)
	{
		// safety
		if (!pChild) return;
		// remove from any previous container
		if (pChild->pParent) pChild->pParent->RemoveElement(pChild);
		// add to end of list
		if (pLast) pLast->pNext = pChild; else pFirst = pChild;
		pChild->pPrev = pLast; pChild->pNext = nullptr; pLast = pChild;
		pChild->pParent = this;

		assert(pChild->pNext != pChild);
		assert(pChild->pPrev != pChild);
		assert(pChild->pParent != pChild);
	}

	void Container::ReaddElement(Element *pChild)
	{
		// safety
		if (!pChild || pChild->pParent != this) return;
		// remove from any previous container
		if (pChild->pPrev) pChild->pPrev->pNext = pChild->pNext; else pFirst = pChild->pNext;
		if (pChild->pNext) pChild->pNext->pPrev = pChild->pPrev; else pLast = pChild->pPrev;
		// add to end of list
		if (pLast) pLast->pNext = pChild; else pFirst = pChild;
		pChild->pPrev = pLast; pChild->pNext = nullptr; pLast = pChild;

		assert(pChild->pNext != pChild);
		assert(pChild->pPrev != pChild);
		assert(pChild->pParent != pChild);
	}

	void Container::InsertElement(Element *pChild, Element *pInsertBefore)
	{
		// add?
		if (!pInsertBefore) { AddElement(pChild); return; }
		// safety
		if (!pChild || pInsertBefore->pParent != this) return;
		// remove from any previous container
		if (pChild->pParent) pChild->pParent->RemoveElement(pChild);
		// add before given element
		if ((pChild->pPrev = pInsertBefore->pPrev))
			pInsertBefore->pPrev->pNext = pChild;
		else
			pFirst = pChild;
		pChild->pNext = pInsertBefore; pInsertBefore->pPrev = pChild;
		pChild->pParent = this;

		assert(pChild->pNext != pChild);
		assert(pChild->pPrev != pChild);
		assert(pChild->pParent != pChild);
	}

	Element *Container::GetNextNestedElement(Element *pPrevElement, bool fBackwards)
	{
		if (fBackwards)
		{
			// this is last
			if (pPrevElement == this) return nullptr;
			// no previous given?
			if (!pPrevElement)
				// then use last nested for backwards search
				return GetFirstNestedElement(true);
			// get nested, previous element if present
			if (pPrevElement->pPrev) return pPrevElement->pPrev->GetFirstNestedElement(true);
			// if not, return parent (could be this)
			return pPrevElement->pParent;
		}
		else
		{
			// forward search: first element is this
			if (!pPrevElement) return this;
			// check next nested
			Element *pEl;
			if ((pEl = pPrevElement->GetFirstContained())) return pEl;
			// check next in list, going upwards until this container is reached
			while (pPrevElement && pPrevElement != this)
			{
				if ((pEl = pPrevElement->pNext)) return pEl;
				pPrevElement = pPrevElement->pParent;
			}
			// nothing found
		}
		return nullptr;
	}

	Element *Container::GetFirstNestedElement(bool fBackwards)
	{
		// get first/last in own list
		if (pFirst) return (fBackwards ? pLast : pFirst)->GetFirstNestedElement(fBackwards);
		// no own list: return this one
		return this;
	}

	bool Container::OnHotkey(uint32_t cHotkey)
	{
		if (!IsVisible()) return false;
		// check all nested elements
		for (Element *pEl = pFirst; pEl; pEl=pEl->pNext)
			if (pEl->fVisible)
				if (pEl->OnHotkey(cHotkey)) return true;
		// no match found
		return false;
	}

	Element *Container::GetElementByIndex(int32_t i)
	{
		// get next until end of list or queried index is reached
		// if i is negative or equal or larger than childcount, the loop will never break and nullptr returned
		Element *pEl;
		for (pEl = pFirst; i-- && pEl; pEl=pEl->pNext) {}
		return pEl;
	}

	int32_t Container::GetElementCount()
	{
		int32_t cnt=0;
		for (Element *pEl = pFirst; pEl; pEl=pEl->pNext) ++cnt;
		return cnt;
	}

	bool Container::IsParentOf(Element *pEl)
	{
		// return whether this is the parent container (directly or recursively) of the passed element
		for (Container *pC = pEl->GetParent(); pC; pC = pC->GetParent())
			if (pC == this) return true;
		return false;
	}

	void Container::SetVisibility(bool fToValue)
	{
		// inherited
		Element::SetVisibility(fToValue);
		// remove focus from contained elements
		if (!fToValue)
		{
			Dialog *pDlg = GetDlg();
			if (pDlg)
			{
				Control *pFocus = pDlg->GetFocus();
				if (pFocus)
				{
					if (IsParentOf(pFocus))
					{
						pDlg->SetFocus(nullptr, false);
					}
				}
			}
		}
	}


// --------------------------------------------------
// Window

	void Window::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
	{
		// invisible?
		if (!IsVisible()) return;
		// inherited
		Container::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
		// get client pos
		C4Rect &rcClientRect = GetClientRect(), &rcBounds = GetBounds();
		iX -= rcClientRect.x - rcBounds.x; iY -= rcClientRect.y - rcBounds.y;
		// forward to topmost child element
		for (Element *pChild = pLast; pChild; pChild = pChild->GetPrev())
			if (pChild->fVisible && pChild->GetBounds().Contains(iX, iY))
			{
				// forward
				pChild->MouseInput(rMouse, iButton, iX - pChild->GetBounds().x, iY - pChild->GetBounds().y, dwKeyParam);
				// forward to one control only
				break;
			}
	}

	void Window::Draw(C4TargetFacet &cgo)
	{
		// invisible?
		if (!IsVisible()) return;
		// draw window itself
		DrawElement(cgo);
		// get target area
		C4Rect &rcClientRect = GetClientRect();
		C4Rect &rcClipArea = (IsComponentOutsideClientArea() ? GetBounds() : GetClientRect());
		// clip to window area
		int clx1, cly1, clx2, cly2;
		pDraw->GetPrimaryClipper(clx1, cly1, clx2, cly2);
		float nclx1 = cgo.TargetX+rcClipArea.x, ncly1 = cgo.TargetY+rcClipArea.y, nclx2 = cgo.TargetX+rcClipArea.x+rcClipArea.Wdt-1, ncly2 = cgo.TargetY+rcClipArea.y+rcClipArea.Hgt-1;
		pDraw->ApplyZoom(nclx1, ncly1);
		pDraw->ApplyZoom(nclx2, ncly2);
		pDraw->SubPrimaryClipper(nclx1, ncly1, nclx2, ncly2);
		// update target area
		cgo.TargetX += rcClientRect.x; cgo.TargetY += rcClientRect.y;
		// draw contents
		Container::Draw(cgo);
		// reset target area
		cgo.TargetX -= rcClientRect.x; cgo.TargetY -= rcClientRect.y;
		// reset clipper
		pDraw->SetPrimaryClipper(clx1, cly1, clx2, cly2);
	}

	Window::Window() : Container()
	{
		UpdateOwnPos();
	}

	void Window::UpdateOwnPos()
	{
		Container::UpdateOwnPos();
		// set client rect
		int32_t iMarginL=GetMarginLeft(), iMarginT=GetMarginTop();
		rcClientRect.Set(rcBounds.x + iMarginL, rcBounds.y + iMarginT, std::max<int32_t>(rcBounds.Wdt - iMarginL - GetMarginRight(), 0), std::max<int32_t>(rcBounds.Hgt - iMarginT - GetMarginBottom(), 0));
	}


// --------------------------------------------------
// ScrollBar

	ScrollBar::ScrollBar(C4Rect &rcBounds, ScrollWindow *pWin) : fAutoHide(false), fHorizontal(false), iCBMaxRange(100), pScrollCallback(nullptr), pCustomGfx(nullptr)
	{
		// set bounds
		this->rcBounds = rcBounds;
		// set initial values
		pScrollWindow = pWin;
		fScrolling = false;
		iScrollThumbSize = C4GUI_ScrollThumbHgt; // vertical
		iScrollPos = 0;
		fTopDown = fBottomDown = false;
		// update scroll bar pos
		Update();
	}

	ScrollBar::ScrollBar(C4Rect &rcBounds, bool fHorizontal, BaseParCallbackHandler<int32_t> *pCB, int32_t iCBMaxRange) : fAutoHide(false), fHorizontal(fHorizontal), iCBMaxRange(iCBMaxRange), pScrollWindow(nullptr), pCustomGfx(nullptr)
	{
		// set bounds
		this->rcBounds = rcBounds;
		// set initial values
		if ((pScrollCallback = pCB)) pScrollCallback->Ref();
		fScrolling = true;
		iScrollThumbSize = fHorizontal ? C4GUI_ScrollThumbWdt : C4GUI_ScrollThumbHgt;
		iScrollPos = 0;
		fTopDown = fBottomDown = false;
	}

	ScrollBar::~ScrollBar()
	{
		if (pScrollWindow) { pScrollWindow->pScrollBar = nullptr; }
		if (pScrollCallback) pScrollCallback->DeRef();
	}

	void ScrollBar::Update()
	{
		// check associated control
		if (pScrollWindow)
		{
			int32_t iVisHgt = pScrollWindow->GetBounds().Hgt;
			int32_t iClientHgt = pScrollWindow->GetClientRect().Hgt;
			if ((fScrolling = (iVisHgt < iClientHgt)))
			{
				// scrolling necessary
				// get vertical scroll pos
				int32_t iMaxWinScroll = iClientHgt - iVisHgt;
				int32_t iMaxBarScroll = GetBounds().Hgt - 2*C4GUI_ScrollArrowHgt - iScrollThumbSize;
				int32_t iWinScroll = pScrollWindow->iScrollY;
				// scroll thumb height is currently hardcoded
				// calc scroll pos
				iScrollPos = Clamp<int32_t>(iMaxBarScroll * iWinScroll / iMaxWinScroll, 0, iMaxBarScroll);
			}
		}
		else fScrolling = !!pScrollCallback;
		// reset buttons
		if (!fScrolling)
			fTopDown = fBottomDown = false;
		// set visibility by scroll status
		if (fAutoHide) SetVisibility(fScrolling);
	}

	void ScrollBar::OnPosChanged()
	{
		int32_t iMaxBarScroll = GetMaxScroll();
		if (!iMaxBarScroll) iMaxBarScroll=1;
		// CB - passes scroll pos
		if (pScrollCallback) pScrollCallback->DoCall(Clamp<int32_t>(iScrollPos * (iCBMaxRange-1) / iMaxBarScroll, 0, (iCBMaxRange-1)));
		// safety
		if (!pScrollWindow || !fScrolling) return;
		// get scrolling values
		assert(!fHorizontal); // nyi
		int32_t iVisHgt = pScrollWindow->GetBounds().Hgt;
		int32_t iClientHgt = pScrollWindow->GetClientRect().Hgt;
		int32_t iMaxWinScroll = iClientHgt - iVisHgt;
		int32_t iWinScroll = pScrollWindow->iScrollY;
		// calc new window scrolling
		int32_t iNewWinScroll = Clamp<int32_t>(iMaxWinScroll * iScrollPos / iMaxBarScroll, 0, iMaxWinScroll);
		// apply it, if it is different
		if (iWinScroll != iNewWinScroll)
			pScrollWindow->SetScroll(iNewWinScroll);
	}

	void ScrollBar::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
	{
		// inherited
		Element::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
		// not if scrolling is disabled
		if (!fScrolling) return;
		// reset arrow states
		bool fPrevDown = fTopDown || fBottomDown;
		fTopDown = fBottomDown = false;
		// not if dragging
		if (rMouse.pDragElement) return;
		// left mouse button down?
		if (rMouse.IsLDown())
			// non-scroll-direction area check
			if (fHorizontal ? Inside<int32_t>(iY, 0, C4GUI_ScrollBarHgt) : Inside<int32_t>(iX, 0, C4GUI_ScrollBarWdt))
			{
				// scroll-direction area check: up/left arrow
				if (fHorizontal ? Inside<int32_t>(iX, 0, C4GUI_ScrollArrowWdt-1) : Inside<int32_t>(iY, 0, C4GUI_ScrollArrowHgt-1))
					fTopDown = true;
				// check down arrow
				else if (fHorizontal ? Inside<int32_t>(iX, GetBounds().Wdt-C4GUI_ScrollArrowWdt, GetBounds().Wdt-1)
				         : Inside<int32_t>(iY, GetBounds().Hgt-C4GUI_ScrollArrowHgt, GetBounds().Hgt-1))
					fBottomDown = true;
				else if (HasPin() && (fHorizontal ? Inside<int32_t>(iX, C4GUI_ScrollArrowWdt, GetBounds().Wdt-C4GUI_ScrollArrowWdt-1)
				                      : Inside<int32_t>(iY, C4GUI_ScrollArrowHgt, GetBounds().Hgt-C4GUI_ScrollArrowHgt-1)))
				{
					// move thumb here
					iScrollPos = GetScrollByPos(iX, iY);
					// reflect movement in associated window or do CB
					OnPosChanged();
					// start dragging
					rMouse.pDragElement = this;
					GUISound("UI::Select");
				}
			}
		// sound effekt when buttons are pressed
		if ((fTopDown || fBottomDown) != fPrevDown) GUISound("UI::Tick");
	}

	void ScrollBar::DoDragging(CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam)
	{
		// move thumb
		iScrollPos = GetScrollByPos(iX, iY);
		// reflect movement in associated window
		OnPosChanged();
	}

	void ScrollBar::MouseLeave(CMouse &rMouse)
	{
		// inherited
		Element::MouseLeave(rMouse);
		// reset button states
		fTopDown = fBottomDown = false;
	}

	void ScrollBar::DrawElement(C4TargetFacet &cgo)
	{
		// do scrolling
		// not quite perfect; but there's no OnIdle, and it's be a bit of overkill starting a timer
		if (fTopDown && fScrolling && iScrollPos>0)
			{ --iScrollPos; OnPosChanged(); }
		if (fBottomDown && fScrolling)
		{
			if (iScrollPos < GetMaxScroll()) { ++iScrollPos; OnPosChanged(); }
		}
		// draw bar
		ScrollBarFacets &rUseGfx = pCustomGfx ? *pCustomGfx : ::GraphicsResource.sfctScroll;
		DynBarFacet bar = rUseGfx.barScroll;
		if (fTopDown) bar.fctBegin = rUseGfx.fctScrollDTop;
		if (fBottomDown) bar.fctEnd = rUseGfx.fctScrollDBottom;
		if (fHorizontal)
			DrawHBarByVGfx(cgo, bar);
		else
			DrawVBar(cgo, bar);
		// draw scroll pin
		if (fScrolling && HasPin())
		{
			if (fHorizontal)
				rUseGfx.fctScrollPin.Draw(cgo.Surface, cgo.TargetX+rcBounds.x+C4GUI_ScrollArrowWdt+iScrollPos, cgo.TargetY+rcBounds.y);
			else
				rUseGfx.fctScrollPin.Draw(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y+C4GUI_ScrollArrowHgt+iScrollPos);
		}
	}


// --------------------------------------------------
// ScrollWindow

	ScrollWindow::ScrollWindow(Window *pParentWindow)
			: Window(), pScrollBar(nullptr), iScrollY(0), iClientHeight(0), fHasBar(true), iFrozen(0)
	{
		// place within client rect
		C4Rect rtBounds = pParentWindow->GetClientRect();
		rtBounds.x = rtBounds.y = 0;
		rtBounds.Wdt -= C4GUI_ScrollBarWdt;
		SetBounds(rtBounds);
		// create scroll bar
		rtBounds.x += rtBounds.Wdt; rtBounds.Wdt = C4GUI_ScrollBarWdt;
		pScrollBar = new ScrollBar(rtBounds, this);
		// add self and scroll bar to window
		if (pParentWindow != this)
		{
			pParentWindow->AddElement(this);
			pParentWindow->AddElement(pScrollBar);
		}
	}

	void ScrollWindow::Update()
	{
		// not if window is being refilled
		if (iFrozen) return;
		// do not scroll outside range
		iScrollY = Clamp<int32_t>(iScrollY, 0, std::max<int32_t>(iClientHeight - GetBounds().Hgt, 0));
		// update client rect
		rcClientRect.x = 0;
		rcClientRect.y = -iScrollY;
		rcClientRect.Wdt = rcBounds.Wdt;
		rcClientRect.Hgt = iClientHeight;
		// update scroll bar
		if (pScrollBar) pScrollBar->Update();
	}

	void ScrollWindow::SetScroll(int32_t iToScroll)
	{
		// set values
		rcClientRect.y = -(iScrollY = iToScroll);
	}

	void ScrollWindow::ScrollToBottom()
	{
		int32_t iVisHgt = GetBounds().Hgt;
		int32_t iClientHgt = GetClientRect().Hgt;
		int32_t iMaxScroll = iClientHgt - iVisHgt;
		if (iScrollY < iMaxScroll)
		{
			// scrolling possible: do it
			iScrollY = iMaxScroll;
			// update (self + bar)
			Update();
		}
	}

	void ScrollWindow::ScrollPages(int iPageCount)
	{
		int32_t iVisHgt = GetBounds().Hgt;
		ScrollBy(iPageCount * iVisHgt);
	}

	void ScrollWindow::ScrollBy(int iAmount)
	{
		int32_t iVisHgt = GetBounds().Hgt;
		int32_t iClientHgt = GetClientRect().Hgt;
		int32_t iMaxScroll = iClientHgt - iVisHgt;
		int iNewScrollY = Clamp<int>(iScrollY + iAmount, 0, iMaxScroll);
		if (iScrollY != iNewScrollY)
		{
			// scrolling possible: do it
			iScrollY = iNewScrollY;
			// update (self + bar)
			Update();
		}
	}

	void ScrollWindow::ScrollRangeInView(int32_t iY, int32_t iHgt)
	{
		// safety bounds
		if (iY<0) iY=0;
		int32_t iClientHgt = GetClientRect().Hgt;
		if (iY+iHgt > iClientHgt) { ScrollToBottom(); return; }
		// check top
		if (iScrollY > iY)
		{
			iScrollY = iY;
			Update(); // update (self+bar)
		}
		else
		{
			// check bottom
			int32_t iVisHgt = GetBounds().Hgt;
			// if no height is given, scroll given Y-pos to top
			if (!iHgt) iHgt = iVisHgt;
			if (iScrollY + iVisHgt < iY + iHgt)
			{
				iScrollY = iY + iHgt - iVisHgt;
				Update(); // update (self+bar)
			}
		}
	}

	bool ScrollWindow::IsRangeInView(int32_t iY, int32_t iHgt)
	{
		// returns whether scrolling range is in view
		// check top
		if (iScrollY > iY) return false;
		// check height
		return iScrollY + GetBounds().Hgt >= iY+iHgt;
	}

	void ScrollWindow::UpdateOwnPos()
	{
		if (!GetParent()) { Update(); return; }
		// place within client rect
		C4Rect rtBounds = GetParent()->GetContainedClientRect();
		rtBounds.x = rtBounds.y = 0;
		if (fHasBar) rtBounds.Wdt -= C4GUI_ScrollBarWdt;
		if (GetBounds() != rtBounds)
		{
			SetBounds(rtBounds);
			// scroll bar
			if (fHasBar)
			{
				rtBounds.x += rtBounds.Wdt; rtBounds.Wdt = C4GUI_ScrollBarWdt;
				pScrollBar->SetBounds(rtBounds);
			}
		}
		// standard updates
		Update();
	}

	void ScrollWindow::SetScrollBarEnabled(bool fToVal, bool noAutomaticPositioning)
	{
		if (fHasBar == fToVal) return;
		pScrollBar->SetVisibility(fHasBar = fToVal);
		// in some cases the windows will already care for the correct positioning themselves (see C4ScriptGuiWindow)
		if (!noAutomaticPositioning)
			UpdateOwnPos();
	}

	void ScrollWindow::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
	{
		// process wheel: Scroll
		if (iButton == C4MC_Button_Wheel)
		{
			short iDelta = (short)(dwKeyParam >> 16);
			ScrollBy(-iDelta);
			return;
		}
		// other mouse input: inherited (forward to children)
		Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
	}


// --------------------------------------------------
// GroupBox

	CStdFont *GroupBox::GetTitleFont() const
	{
		// get font; fallback to GUI caption font
		return pFont ? pFont : &(::GraphicsResource.CaptionFont);
	}

	void GroupBox::DrawElement(C4TargetFacet &cgo)
	{
		// draw background
		if (dwBackClr != 0xffffffff)
		{
			pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y, cgo.TargetX+rcBounds.x+rcBounds.Wdt-1, cgo.TargetY+rcBounds.y+rcBounds.Hgt-1, dwBackClr);
		}
		// draw title label
		int32_t iBorderYOff = 0;
		int32_t iTitleGapX = 0;
		int32_t iTitleGapWdt = 0;
		if (HasTitle())
		{
			CStdFont *pTitleFont = GetTitleFont();
			iBorderYOff = pTitleFont->GetLineHeight()/2;
			pTitleFont->GetTextExtent(sTitle.getData(), iTitleGapWdt, iTitleGapX, true);
			iTitleGapX = 7; iTitleGapWdt += 4;
			pDraw->TextOut(sTitle.getData(), *pTitleFont, 1.0f, cgo.Surface, cgo.TargetX+rcBounds.x+iTitleGapX+2, cgo.TargetY+rcBounds.y, dwTitleClr);
		}
		// draw frame
		if (dwFrameClr)
		{
			int32_t x1=cgo.TargetX+rcBounds.x,y1=cgo.TargetY+rcBounds.y+iBorderYOff,x2=x1+rcBounds.Wdt,y2=y1+rcBounds.Hgt-iBorderYOff;
			if (iTitleGapWdt)
			{
				for (int i=0; i<2; ++i)
				{
					pDraw->DrawLineDw(cgo.Surface, (float) x1+i, (float)y1, (float)(x1+i), (float)(y2-1), dwFrameClr); // left
					pDraw->DrawLineDw(cgo.Surface, (float) (x1+2), (float)(y1+i), (float)(x1+iTitleGapX), (float)(y1+i), dwFrameClr); // top - left side
					pDraw->DrawLineDw(cgo.Surface, (float) (x1+iTitleGapX+iTitleGapWdt), (float)(y1+i), (float)(x2-3), (float)(y1+i), dwFrameClr); // top - right side
					pDraw->DrawLineDw(cgo.Surface, (float) (x2-1-i), (float)y1, (float)(x2-1-i), (float)(y2-1), dwFrameClr); // right
					pDraw->DrawLineDw(cgo.Surface, (float) (x1+2), (float)(y2-1-i), (float)(x2-3), (float)(y2-1-i), dwFrameClr); // bottom
				}
			}
			else
			{
				pDraw->DrawFrameDw(cgo.Surface, x1, y1, x2, (y2-1), dwFrameClr);
				pDraw->DrawFrameDw(cgo.Surface, (x1+1), (y1+1), (x2-1), (y2-2), dwFrameClr);
			}
		}
		else
			// default frame color
			// 2do: Make this work with titled group boxes
			Draw3DFrame(cgo);
	}


// --------------------------------------------------
// Control

	Control::Control(const C4Rect &rtBounds) : Window()
	{
		// set bounds
		SetBounds(rtBounds);
		// context menu key binding
		pKeyContext = new C4KeyBinding(C4KeyCodeEx(K_MENU), "GUIContext", KEYSCOPE_Gui,
		                               new ControlKeyCB<Control>(*this, &Control::KeyContext), C4CustomKey::PRIO_Ctrl);
	}

	Control::~Control()
	{
		delete pKeyContext;
	}

	void Control::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
	{
		if (!IsVisible()) return;
		// left down on click=focus-components?
		if (IsFocusOnClick() && IsFocusElement()) if (iButton == C4MC_Button_LeftDown && !HasFocus())
			{
				// then set focus
				Dialog *pParentDlg = GetDlg();
				if (pParentDlg)
				{
					// but do not set focus to this if a child control has it already
					Control *pActiveCtrl = pParentDlg->GetFocus();
					if (!pActiveCtrl || !IsParentOf(pActiveCtrl))
						pParentDlg->SetFocus(this, true);
				}
			}
		// inherited - processing child elements
		Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
	}

	bool Control::HasDrawFocus()
	{
		// has focus at all?
		if (!HasFocus()) return false;
		// is screen ready and not in context?
		if (GetScreen() && GetScreen()->HasContext()) return false;
		// get dlg
		Dialog *pDlg=GetDlg();
		// dlg-less control has focus, OK (shouldn't happen)
		if (!pDlg) return true;
		// check if dlg is active
		return pDlg->IsActive(true);
	}

	void Control::DisableFocus()
	{
		// has it any focus at all?
		if (!HasFocus()) return;
		// then de-focus it
		Dialog *pDlg=GetDlg();
		if (!pDlg) return;
		pDlg->AdvanceFocus(true);
	}

} // end of namespace

