/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2008-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.
 */
// player listbox used in lobby and game over dlg

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

#include "control/C4GameControl.h"
#include "control/C4PlayerInfo.h"
#include "control/C4RoundResults.h"
#include "control/C4Teams.h"
#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
#include "gui/C4FileSelDlg.h"
#include "gui/C4GameLobby.h"
#include "gui/C4MouseControl.h"
#include "network/C4Network2.h"
#include "network/C4Network2Dialogs.h"

DWORD GenerateRandomPlayerColor(int32_t iTry); // in C4PlayerInfoConflicts.cpp

// ----------- ListItem --------------------------------------------------------------------------------

// helper
C4GameLobby::MainDlg *C4PlayerInfoListBox::ListItem::GetLobby() const
{
	return ::Network.GetLobby();
}

bool C4PlayerInfoListBox::ListItem::CanLocalChooseTeams(int32_t idPlayer) const
{
	// whether the local client can change any teams
	// only if teams are available
	if (!Game.Teams.IsMultiTeams()) return false;
	// only if global change allowed
	C4GameLobby::MainDlg *pLobby = GetLobby();
	if (!pLobby) return false;
	if (pLobby->IsCountdown()) return false;
	// only for unjoined players
	if (idPlayer)
	{
		C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idPlayer);
		if (pInfo && pInfo->HasJoined()) return false;
	}
	// finally, only if team settings permit
	return Game.Teams.CanLocalChooseTeam(idPlayer);
}

void C4PlayerInfoListBox::ListItem::DrawElement(C4TargetFacet &cgo)
{
	if (dwBackground) 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, dwBackground);
	typedef C4GUI::Window BaseClass;
	BaseClass::DrawElement(cgo);
}

// ----------- PlayerListItem -----------------------------------------------------------------------

C4PlayerInfoListBox::PlayerListItem::PlayerListItem(C4PlayerInfoListBox *pForListBox, int32_t idClient,
    int32_t idPlayer, bool fSavegamePlayer, C4GUI::Element *pInsertBeforeElement)
		: ListItem(pForListBox), pScoreLabel(nullptr), pTimeLabel(nullptr), pExtraLabel(nullptr),
		pRankIcon(nullptr), pTeamCombo(nullptr), pTeamPic(nullptr), fIconSet(false), fJoinedInfoSet(false),
		dwJoinClr(0), dwPlrClr(0), idClient(idClient), idPlayer(idPlayer), fFreeSavegamePlayer(fSavegamePlayer)
{
	bool fIsEvaluation = pForListBox->IsEvaluation(), fIsLobby = pForListBox->IsLobby();
	C4PlayerInfo *pInfo = GetPlayerInfo(); assert(pInfo);
	uint32_t dwTextColor = pForListBox->GetTextColor();
	CStdFont *pCustomFont = pForListBox->GetCustomFont();
	uint32_t dwPlayerColor;
	if (fIsEvaluation)
		dwPlayerColor = dwTextColor;
	else
		dwPlayerColor = pInfo->GetLobbyColor() | C4GUI_MessageFontAlpha;
	// league account name? Overwrite the shown name
	StdStrBuf sPlayerName(pInfo->GetLobbyName());
	// calc height
	int32_t iHeight = ::GraphicsResource.TextFont.GetLineHeight() + C4GUI::ComboBox::GetDefaultHeight() + 3 * IconLabelSpacing;
	// create subcomponents
	pIcon = new C4GUI::Icon(C4Rect(0, 0, iHeight, iHeight), C4GUI::Ico_UnknownPlayer);
	if (Game.Parameters.isLeague())
		pRankIcon = new C4GUI::Icon(C4Rect(0, 0, C4GUI::ComboBox::GetDefaultHeight(), C4GUI::ComboBox::GetDefaultHeight()), C4GUI::Ico_None);
	if (Game.Teams.IsMultiTeams() && !(fIsEvaluation && pList->IsTeamFilter()))
	{
		// will be moved when the item is added to the list, and the position moved
		pTeamCombo = new C4GUI::ComboBox(C4Rect(0,0,10,10));
		pTeamCombo->SetComboCB(new C4GUI::ComboBox_FillCallback<PlayerListItem>(this, &PlayerListItem::OnTeamComboFill, &PlayerListItem::OnTeamComboSelChange));
		pTeamCombo->SetSimple(true);
		if (pCustomFont)
		{
			pTeamCombo->SetFont(pCustomFont);
			pTeamCombo->SetColors(dwTextColor, C4GUI_StandardBGColor, 0);
		}
		UpdateTeam();
	}
	// Evaluation
	if (fIsEvaluation)
	{
		// Team image if known and if not placed on top of box anyway
		if (!pList->IsTeamFilter())
		{
			C4Team *pTeam = Game.Teams.GetTeamByID(pInfo->GetTeam());
			if (pTeam && pTeam->GetIconSpec() && *pTeam->GetIconSpec())
			{
				pTeamPic = new C4GUI::Picture(C4Rect(iHeight + IconLabelSpacing, 0, iHeight, iHeight), true);
				Game.DrawTextSpecImage(pTeamPic->GetMFacet(), pTeam->GetIconSpec(), nullptr, pTeam->GetColor());
				pTeamPic->SetDrawColor(pTeam->GetColor());
			}
		}
		// Total playing time (not in team filter because then the second line is taken by the score label)
		if (!pList->IsTeamFilter())
		{
			StdStrBuf sTimeLabelText;
			C4RoundResultsPlayer *pRoundResultsPlr = Game.RoundResults.GetPlayers().GetByID(idPlayer);
			uint32_t iTimeTotal = pRoundResultsPlr ? pRoundResultsPlr->GetTotalPlayingTime() : 0 /* unknown - should not happen */;
			sTimeLabelText.Format(LoadResStr("IDS_CTL_TOTALPLAYINGTIME"), iTimeTotal/3600, (iTimeTotal/60)%60, iTimeTotal%60);
			pTimeLabel = new C4GUI::Label(sTimeLabelText.getData(), 0, 0, ARight, dwTextColor, pForListBox->GetCustomFont(), false, true);
		}
		// Extra info set by script
		C4RoundResultsPlayer *pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(idPlayer);;
		if (pEvaluationPlayer)
		{
			const char *szCustomEval = pEvaluationPlayer->GetCustomEvaluationStrings();
			if (szCustomEval && *szCustomEval)
			{
				pExtraLabel = new C4GUI::Label(szCustomEval, 0,0, ARight); // positioned later
				iHeight += ::GraphicsResource.TextFont.GetLineHeight();
			}
		}
	}
	pNameLabel = new C4GUI::Label(sPlayerName.getData(), (iHeight + IconLabelSpacing) * (1+!!pTeamPic), IconLabelSpacing, ALeft, dwPlayerColor, pCustomFont, !fIsEvaluation, true);
	// calc own bounds - list box needs height only; width and pos will be moved by list
	SetBounds(C4Rect(0,0,10,iHeight));
	// add components
	AddElement(pIcon); AddElement(pNameLabel);
	if (pTeamPic) AddElement(pTeamPic);
	if (pTimeLabel) AddElement(pTimeLabel);
	if (pTeamCombo) AddElement(pTeamCombo);
	if (pRankIcon) AddElement(pRankIcon);
	if (pExtraLabel) AddElement(pExtraLabel);
	// add to listbox (will get resized horizontally and moved)
	pForListBox->InsertElement(this, pInsertBeforeElement, PlayerListBoxIndent);
	// league score update
	UpdateScoreLabel(pInfo);
	// set ID
	if (fFreeSavegamePlayer)
		idListItemID.idType = ListItem::ID::PLI_SAVEGAMEPLR;
	else
		idListItemID.idType = ListItem::ID::PLI_PLAYER;
	idListItemID.id = idPlayer;
	UpdateIcon(pInfo, GetJoinedInfo());
	// context menu for list item
	if (fIsLobby) SetContextHandler(new C4GUI::CBContextHandler<PlayerListItem>(this, &PlayerListItem::OnContext));
	// update collapsed/not collapsed
	fShownCollapsed = false;
	UpdateCollapsed();
}

void C4PlayerInfoListBox::PlayerListItem::UpdateOwnPos()
{
	// parent for client rect
	typedef C4GUI::Window ParentClass;
	ParentClass::UpdateOwnPos();
	C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing);
	// subtract icon(s)
	caBounds.GetFromLeft(pIcon->GetBounds().Wdt);
	if (pTeamPic) caBounds.GetFromLeft(pTeamPic->GetBounds().Wdt - IconLabelSpacing);
	C4Rect rcExtraDataRect;
	// extra data label area
	if (pExtraLabel) rcExtraDataRect = caBounds.GetFromBottom(::GraphicsResource.TextFont.GetLineHeight());
	// second line (team+rank)
	C4GUI::ComponentAligner caTeamArea(caBounds.GetFromBottom(C4GUI::ComboBox::GetDefaultHeight()), 0,0);
	C4Rect rcRankIcon;
	if (pList->IsEvaluation())
	{
		if (pRankIcon)
		{
			rcRankIcon = caBounds.GetFromRight(caBounds.GetInnerHeight());
			if (pExtraLabel) rcExtraDataRect.Wdt = caBounds.GetInnerWidth(); // In evaluation view, rank icon has its own coloumn
		}
	}
	else
	{
		if (pRankIcon) rcRankIcon = caTeamArea.GetFromRight(caTeamArea.GetInnerHeight());
	}
	C4Rect rcTeam = caTeamArea.GetAll();
	// item to positions: team combo box
	if (pTeamCombo)
	{
		pTeamCombo->SetBounds(rcTeam);
	}
	// rank icon
	if (pRankIcon)
	{
		pRankIcon->SetBounds(rcRankIcon);
	}
	// time label
	if (pTimeLabel)
	{
		C4Rect rcUpperBounds = caBounds.GetAll();
		pTimeLabel->SetBounds(rcTeam);
		pTimeLabel->SetX0(rcUpperBounds.x + rcUpperBounds.Wdt);
	}
	// extra label
	if (pExtraLabel) pExtraLabel->SetBounds(rcExtraDataRect);
}

int32_t C4PlayerInfoListBox::PlayerListItem::GetListItemTopSpacing()
{
	int32_t iSpacing = C4GUI_DefaultListSpacing;
	// evaluation: Add some extra spacing between players of different teams
	if (pList->IsEvaluation())
	{
		C4GUI::Element *pPrevItem = GetPrev();
		if (pPrevItem)
		{
			C4PlayerInfoListBox::ListItem *pPrevListItem = static_cast<C4PlayerInfoListBox::ListItem *>(pPrevItem);
			if (pPrevListItem->idListItemID.idType == ListItem::ID::PLI_PLAYER)
			{
				PlayerListItem *pPrevPlayerListItem = static_cast<C4PlayerInfoListBox::PlayerListItem *>(pPrevListItem);
				C4PlayerInfo *pThisInfo = GetPlayerInfo();
				C4PlayerInfo *pPrevInfo = pPrevPlayerListItem->GetPlayerInfo();
				if (pThisInfo && pPrevInfo)
				{
					if (pPrevInfo->GetTeam() != pThisInfo->GetTeam())
					{
						iSpacing += 10;
					}
				}
			}
		}
	}
	return iSpacing;
}

void C4PlayerInfoListBox::PlayerListItem::UpdateIcon(C4PlayerInfo *pInfo, C4PlayerInfo *pJoinedInfo)
{
	// check whether icon is known
	bool fResPresent = false;
	C4Network2Res *pRes = nullptr;
	if (pInfo)
		if ((pRes = pInfo->GetRes()))
			fResPresent = pRes->isComplete();
	C4RoundResultsPlayer *pEvaluationPlayer = nullptr;
	if (pList->IsEvaluation()) pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(idPlayer);
	bool fHasIcon = fResPresent || pEvaluationPlayer || (!::Network.isEnabled() && pInfo);
	// check whether joined info is present
	bool fHasJoinedInfo = !!pJoinedInfo;
	DWORD dwJoinedInfoClr = pJoinedInfo ? pJoinedInfo->GetLobbyColor() : 0;
	DWORD dwPlayerClr = pInfo ? pInfo->GetLobbyColor() : 0;
	// already up-to-date?
	if (fHasIcon == fIconSet && fJoinedInfoSet == fHasJoinedInfo && dwJoinedInfoClr == dwJoinClr && dwPlayerClr == dwPlrClr) return;
	// update then
	// redraw player icon
	if (fHasIcon)
	{
		// custom icon?
		if (pEvaluationPlayer && pEvaluationPlayer->GetBigIcon().Surface)
		{
			pIcon->SetFacet(pEvaluationPlayer->GetBigIcon());
			fIconSet = true;
		}
		else
			fIconSet = pInfo->LoadBigIcon(pIcon->GetMFacet());
		if (!fIconSet)
		{
			// no custom icon: create default by player color
			pIcon->GetMFacet().Create(64,64); // the bigicon is bigger than the normal 40x40 icon
			::GraphicsResource.fctPlayerClr.DrawClr(pIcon->GetMFacet(), true, dwPlayerClr);
		}
		fIconSet = true;
	}
	else
		// no player info known - either res not retrieved yet or script player
		pIcon->SetIcon((pInfo && pInfo->GetType() == C4PT_Script) ? C4GUI::Ico_Host : C4GUI::Ico_UnknownPlayer);
	// join
	if (fHasJoinedInfo)
	{
		// make sure we're not drawing on GraphicsResource
		if (!pIcon->EnsureOwnSurface()) return;
		// draw join info
		C4Facet fctDraw = pIcon->GetFacet();
		int32_t iSizeMax = std::max<int32_t>(fctDraw.Wdt, fctDraw.Hgt);
		int32_t iCrewClrHgt = iSizeMax/2;
		fctDraw.Hgt -= iCrewClrHgt; fctDraw.Y += iCrewClrHgt;
		fctDraw.Wdt = iSizeMax/2;
		fctDraw.X = 2;
		// shadow
		DWORD dwPrevMod; bool fPrevMod = pDraw->GetBlitModulation(dwPrevMod);
		pDraw->ActivateBlitModulation(1);
		::GraphicsResource.fctCrewClr.DrawClr(fctDraw, true, dwJoinedInfoClr);
		if (fPrevMod) pDraw->ActivateBlitModulation(dwPrevMod); else pDraw->DeactivateBlitModulation();
		fctDraw.X = 0;
		// gfx
		::GraphicsResource.fctCrewClr.DrawClr(fctDraw, true, dwJoinedInfoClr);
	}
	fJoinedInfoSet = fHasJoinedInfo;
	dwJoinClr = dwJoinedInfoClr;
	dwPlrClr = dwPlayerClr;
}

void C4PlayerInfoListBox::PlayerListItem::UpdateTeam()
{
	if (!pTeamCombo) return; // unassigned for no teams
	const char *szTeamName = ""; bool fReadOnly = true;
	fReadOnly = !CanLocalChooseTeam();
	int32_t idTeam; C4Team *pTeam;
	C4PlayerInfo *pInfo = GetPlayerInfo();
	if (!Game.Teams.CanLocalSeeTeam())
		szTeamName = LoadResStr("IDS_MSG_RNDTEAM");
	else if (pInfo)
		if ((idTeam = pInfo->GetTeam()))
			if ((pTeam = Game.Teams.GetTeamByID(idTeam)))
				szTeamName = pTeam->GetName();
	pTeamCombo->SetText(szTeamName);
	pTeamCombo->SetReadOnly(fReadOnly);
}

void C4PlayerInfoListBox::PlayerListItem::UpdateScoreLabel(C4PlayerInfo *pInfo)
{
	assert(pInfo);
	C4RoundResultsPlayer *pRoundResultsPlr = nullptr;
	if (pList->IsEvaluation()) pRoundResultsPlr = Game.RoundResults.GetPlayers().GetByID(idPlayer);

	if (pInfo->getLeagueScore() || pInfo->IsLeagueProjectedGainValid() || pRoundResultsPlr)
	{
		int32_t iScoreRightPos = ((pRankIcon && pList->IsEvaluation()) ? pRankIcon->GetBounds().x : GetBounds().Wdt) - IconLabelSpacing;
		int32_t iScoreYPos = IconLabelSpacing;
		// if evaluation and team lists, move score label into second line - TODO: some hack only, still needs to be done right
		C4RoundResultsPlayer *pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(pInfo->GetID());;
		bool fPlayerHasEvaluationData=false;
		if (pEvaluationPlayer)
		{
			const char *szCustomEval = pEvaluationPlayer->GetCustomEvaluationStrings();
			if (szCustomEval && *szCustomEval)
				fPlayerHasEvaluationData=true;
		}
		if (pList->IsEvaluation() && pList->IsTeamFilter())
			iScoreYPos = GetBounds().Hgt - (C4GUI::ComboBox::GetDefaultHeight()*(1+(int32_t)fPlayerHasEvaluationData)) - IconLabelSpacing;

		// score label visible
		if (!pScoreLabel)
		{
			AddElement(pScoreLabel = new C4GUI::Label("", iScoreRightPos, iScoreYPos, ARight, pList->GetTextColor(), pList->GetCustomFont(), false));
			if (pList->IsEvaluation())
				pScoreLabel->SetToolTip(LoadResStr("IDS_DESC_OLDANDNEWSCORE"));
			else
				pScoreLabel->SetToolTip(LoadResStr("IDS_DESC_LEAGUESCOREANDPROJECTEDGA"));
		}
		StdStrBuf sText;
		// Evaluation (GameOver)
		if (pList->IsEvaluation())
		{
			if (pInfo->getLeagueScore() || pInfo->IsLeagueProjectedGainValid() || (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid()))
			{
				if (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid())
				{
					// Show old league score, gain, and new league score
					// Normally, the league server should make sure that [Old score] + [Gain] == [New score]
					int32_t iOldScore = pInfo->getLeagueScore(), iScoreGain = pRoundResultsPlr->GetLeagueScoreGain(), iNewScore = pRoundResultsPlr->GetLeagueScoreNew();
					int32_t iDiscrepancy = iNewScore - (iOldScore + iScoreGain);
					if (!iDiscrepancy)
					{
						sText.Format("{{Ico:League}}<c afafaf>%d (%+d)</c> %d %s", (int)iOldScore, (int)iScoreGain, (int)iNewScore, LoadResStr("IDS_TEXT_SCORE"));
					}
					else
					{
						// If there's a discrepancy, there must have been some kind of admin intervention during the game - display it in red!
						sText.Format("{{Ico:League}}<c afafaf>%d (%+d)</c><c ff0000>(%+d)</c> %d %s", (int)iOldScore, (int)iScoreGain, (int)iDiscrepancy, (int)iNewScore, LoadResStr("IDS_TEXT_SCORE"));
					}
				}
				// Show old league score only
				else
				{
					sText.Format("{{Ico:League}}<c afafaf>(%d)</c> %s", (int)pInfo->getLeagueScore(), LoadResStr("IDS_TEXT_SCORE"));
				}
			}
			else if (pRoundResultsPlr && pRoundResultsPlr->IsScoreNewValid() && !Game.RoundResults.SettlementScoreIsHidden())
			{
				// new score known
				sText.Format("{{Ico:Settlement}}<c afafaf>%d (%+d)</c> %d %s", (int)pRoundResultsPlr->GetScoreOld(), (int)(pRoundResultsPlr->GetScoreNew()-pRoundResultsPlr->GetScoreOld()), (int)pRoundResultsPlr->GetScoreNew(), LoadResStr("IDS_TEXT_SCORE"));
			}
			else if (pRoundResultsPlr && !pRoundResultsPlr->IsScoreNewValid() && !Game.RoundResults.SettlementScoreIsHidden())
			{
				// only old score known (e.g., player disconnected)
				sText.Format("{{Ico:Settlement}}<c afafaf>(%d)</c> %s", (int)pRoundResultsPlr->GetScoreOld(), LoadResStr("IDS_TEXT_SCORE"));
			}
			else
			{
				// nothing known. Shouldn't really happen.
				sText.Ref("");
			}
		}
		// Pre-evaluation (Lobby)
		else
		{
			// Show current league score and projected gain
			// Don't show if team invisible, so random surprise teams don't get spoiled
			if (pInfo->IsLeagueProjectedGainValid() && Game.Teams.IsTeamVisible())
				sText.Format("%d (%+d)", (int)pInfo->getLeagueScore(), (int)pInfo->GetLeagueProjectedGain());
			// Show current league score only
			else
				sText.Format("%d", (int)pInfo->getLeagueScore());
		}
		pScoreLabel->SetX0(iScoreRightPos);
		pScoreLabel->SetText(sText.getData(), false);
	}
	else if (pScoreLabel)
	{
		// score label invisible
		delete pScoreLabel;
		pScoreLabel = nullptr;
	}
	if (pRankIcon)
	{
		int32_t iSym = 0;
		if (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid())
			iSym = pRoundResultsPlr->GetLeagueRankSymbolNew();
		if (!iSym)
			iSym = pInfo->getLeagueRankSymbol();
		if (iSym && !fShownCollapsed)
		{
			C4GUI::Icons eRankIcon = (C4GUI::Icons) (C4GUI::Ico_Rank1 + Clamp<int32_t>(iSym-1, 0, C4GUI::Ico_Rank9-C4GUI::Ico_Rank1));
			pRankIcon->SetVisibility(true);
			pRankIcon->SetIcon(eRankIcon);
		}
		else
		{
			pRankIcon->SetVisibility(false);
		}
	}
}

void C4PlayerInfoListBox::PlayerListItem::UpdateCollapsed()
{
	bool fShouldBeCollapsed = pList->IsPlayerItemCollapsed(this);
	if (fShouldBeCollapsed == fShownCollapsed) return;
	// so update collapsed state
	int32_t iHeight; int32_t iNameLblX0;
	if ((fShownCollapsed = fShouldBeCollapsed))
	{
		// calc height
		iHeight = ::GraphicsResource.TextFont.GetLineHeight() + 2 * IconLabelSpacing;
		// teamcombo not visible if collapsed
		if (pTeamCombo) pTeamCombo->SetVisibility(false);
	}
	else
	{
		// calc height
		iHeight = ::GraphicsResource.TextFont.GetLineHeight() + C4GUI::ComboBox::GetDefaultHeight() + 3 * IconLabelSpacing;
		// teamcombo visible if not collapsed
		if (pTeamCombo) pTeamCombo->SetVisibility(true);
	}
	// update subcomponents
	iNameLblX0 = iHeight + IconLabelSpacing;
	pIcon->GetBounds() = C4Rect(0, 0, iHeight, iHeight);
	pIcon->UpdateOwnPos();
	pNameLabel->SetX0(iNameLblX0);
	// calc own bounds - use icon bounds only, because only the height is used when the item is added
	SetBounds(pIcon->GetBounds());
	// update positions
	pList->UpdateElementPosition(this, PlayerListBoxIndent);
}


C4GUI::ContextMenu *C4PlayerInfoListBox::PlayerListItem::OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
{
	C4PlayerInfo *pInfo = GetPlayerInfo();
	assert(pInfo);
	// no context menu for evaluation
	if (!GetLobby()) return nullptr;
	// create context menu
	C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
	// if this is a free player, add an option to take it over
	if (fFreeSavegamePlayer)
	{
		if (pInfo->GetType() != C4PT_Script)
		{
			StdCopyStrBuf strTakeOver(LoadResStr("IDS_MSG_TAKEOVERPLR"));
			pMenu->AddItem(strTakeOver.getData(), LoadResStr("IDS_MSG_TAKEOVERPLR_DESC"), C4GUI::Ico_Player, nullptr,
			               new C4GUI::CBContextHandler<PlayerListItem>(this, &PlayerListItem::OnContextTakeOver));
		}
	}
	else
	{
		// owned players or host can manipulate players
		if (::Network.isHost() || IsLocalClientPlayer())
		{
			// player removal (except for joined script players)
			if (pInfo->GetType() != C4PT_Script || !pInfo->GetAssociatedSavegamePlayerID())
			{
				StdCopyStrBuf strRemove(LoadResStr("IDS_MSG_REMOVEPLR"));
				pMenu->AddItem(strRemove.getData(), LoadResStr("IDS_MSG_REMOVEPLR_DESC"), C4GUI::Ico_Close,
				               new C4GUI::CBMenuHandler<PlayerListItem>(this, &PlayerListItem::OnCtxRemove), nullptr);
			}
			// color was changed: Add option to assign a new color
			C4PlayerInfo *pInfo = GetPlayerInfo();
			assert (pInfo);
			if (pInfo && pInfo->HasAutoGeneratedColor() && (!Game.Teams.IsTeamColors() || !pInfo->GetTeam()))
			{
				StdCopyStrBuf strNewColor(LoadResStr("IDS_MSG_NEWPLRCOLOR"));
				pMenu->AddItem(strNewColor.getData(), LoadResStr("IDS_MSG_NEWPLRCOLOR_DESC"), C4GUI::Ico_Player,
				               new C4GUI::CBMenuHandler<PlayerListItem>(this, &PlayerListItem::OnCtxNewColor), nullptr);
			}
		}
	}
	// open it
	return pMenu;
}

C4GUI::ContextMenu *C4PlayerInfoListBox::PlayerListItem::OnContextTakeOver(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
{
	// create context menu
	C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
	// add options for all own, unassigned players
	C4ClientPlayerInfos *pkInfo = ::Network.Players.GetLocalPlayerInfoPacket();
	if (pkInfo)
	{
		int32_t i=0; C4PlayerInfo *pInfo;
		while ((pInfo = pkInfo->GetPlayerInfo(i++)))
			if (!pInfo->HasJoinIssued())
				if (!pInfo->GetAssociatedSavegamePlayerID())
				{
					pMenu->AddItem(FormatString(LoadResStr("IDS_MSG_USINGPLR"), pInfo->GetName()).getData(),
					               LoadResStr("IDS_MSG_USINGPLR_DESC"), C4GUI::Ico_Player,
					               new C4GUI::CBMenuHandlerEx<PlayerListItem, int32_t>(this, &PlayerListItem::OnCtxTakeOver, pInfo->GetID()));
				}
	}
	// add option to use a new one... TODO
	// add option to take over from savegame player TODO
	// open it
	return pMenu;
}

void C4PlayerInfoListBox::PlayerListItem::OnCtxTakeOver(C4GUI::Element *pListItem, const int32_t &idPlayer)
{
	// use player idPlayer to take over this one
	// this must be processed as a request by the host
	// some safety first...
	C4ClientPlayerInfos *pLocalInfo = ::Network.Players.GetLocalPlayerInfoPacket();
	if (!fFreeSavegamePlayer || !idPlayer || !pLocalInfo) return;
	C4ClientPlayerInfos LocalInfoRequest(*pLocalInfo);
	C4PlayerInfo *pGrabbingInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
	if (!pGrabbingInfo) return;
	// now adjust info packet
	pGrabbingInfo->SetAssociatedSavegamePlayer(this->idPlayer);
	// and request this update (host processes it directly)
	::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
}

void C4PlayerInfoListBox::PlayerListItem::OnCtxRemove(C4GUI::Element *pListItem)
{
	// only host or own player
	if (!::Network.isEnabled() || (!::Network.isHost() && !IsLocalClientPlayer())) return;
	// remove the player
	// this must be processed as a request by the host
	// now change it in its own request packet
	C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
	if (!pChangeInfo || !idPlayer) return;
	C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
	if (!LocalInfoRequest.GetPlayerInfoByID(idPlayer)) return;
	LocalInfoRequest.RemoveInfo(idPlayer);
	// and request this update (host processes it directly)
	::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
}

void C4PlayerInfoListBox::PlayerListItem::OnCtxNewColor(C4GUI::Element *pListItem)
{
	// only host or own player
	if (!::Network.isEnabled() || (!::Network.isHost() && !IsLocalClientPlayer())) return;
	// just send a request to reclaim the original color to the host
	// the host will deny this and decide on a new color
	C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
	if (!pChangeInfo || !idPlayer) return;
	C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
	C4PlayerInfo *pPlrInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
	if (!pPlrInfo) return;
	pPlrInfo->SetColor(pPlrInfo->GetOriginalColor());
	// and request this update (host processes it directly)
	::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
}

void C4PlayerInfoListBox::PlayerListItem::OnTeamComboFill(C4GUI::ComboBox_FillCB *pFiller)
{
	// add all possible teams
	C4Team *pTeam; int32_t i=0;
	while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
		if (!pTeam->IsFull() || GetPlayerInfo()->GetTeam() == pTeam->GetID())
			pFiller->AddEntry(pTeam->GetName(), pTeam->GetID());
}

bool C4PlayerInfoListBox::PlayerListItem::OnTeamComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection)
{
	// always return true to mark combo sel as processed, so the GUI won't change the team text
	// get new team id by name
	C4Team *pNewTeam = Game.Teams.GetTeamByID(idNewSelection);
	// some safety first...
	if (!CanLocalChooseTeam() || !pNewTeam) return true;
	C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
	if (!pChangeInfo || !idPlayer) return true;
	// this must be processed as a request by the host
	// now change it in its own request packet
	C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
	C4PlayerInfo *pChangedInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
	if (!pChangedInfo) return true;
	pChangedInfo->SetTeam(pNewTeam->GetID());
	// and request this update (host processes it directly)
	::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
	// next update will change the combo box text
	return true;
}

void C4PlayerInfoListBox::PlayerListItem::Update()
{
	UpdateCollapsed();
	UpdateIcon(GetPlayerInfo(), GetJoinedInfo());
	UpdateTeam();
	C4PlayerInfo *pNfo = GetPlayerInfo();
	if (pNfo)
	{
		UpdateScoreLabel(pNfo);
		// update name + color
		StdStrBuf sShowName(pNfo->GetLobbyName());
		if (pList->IsEvaluation())
		{
			bool fShowWinners = (pList->GetMode() != PILBM_EvaluationNoWinners);
			bool fHasWon = fShowWinners && pNfo->HasTeamWon();
			// Append "winner" or "loser" to player name
			if (fShowWinners)
			{
				sShowName.Take(FormatString("%s (%s)", sShowName.getData(), LoadResStr(fHasWon ? "IDS_CTL_WON" : "IDS_CTL_LOST")));
			}
			// evaluation: Golden color+background for winners; gray for losers or no winner show
			if (fHasWon)
			{
				pNameLabel->SetColor(C4GUI_WinningTextColor, false);
				dwBackground = C4GUI_WinningBackgroundColor;
			}
			else
			{
				pNameLabel->SetColor(C4GUI_LosingTextColor, false);
				dwBackground = C4GUI_LosingBackgroundColor;
			}
		}
		else
		{
			// lobby: Label color by player color
			pNameLabel->SetColor(pNfo->GetLobbyColor());
		}
		pNameLabel->SetText(sShowName.getData(), false);
	}
}

C4PlayerInfo *C4PlayerInfoListBox::PlayerListItem::GetPlayerInfo() const
{
	return fFreeSavegamePlayer ? Game.RestorePlayerInfos.GetPlayerInfoByID(idPlayer) : Game.PlayerInfos.GetPlayerInfoByID(idPlayer);
}

C4PlayerInfo *C4PlayerInfoListBox::PlayerListItem::GetJoinedInfo() const
{
	// safety
	C4PlayerInfo *pInfo = GetPlayerInfo();
	if (!pInfo) return nullptr;
	// is it a joined savegame player?
	if (fFreeSavegamePlayer)
		// then this is the joined player
		return pInfo;
	// otherwise, does it have a savegame association?
	int32_t idSavegameInfo;
	if ((idSavegameInfo = pInfo->GetAssociatedSavegamePlayerID()))
		// then return the respective info from savegame recreation list
		return Game.RestorePlayerInfos.GetPlayerInfoByID(idSavegameInfo);
	// not joined
	return nullptr;
}

bool C4PlayerInfoListBox::PlayerListItem::CanLocalChooseTeam() const
{
	// never on savegame players
	if (fFreeSavegamePlayer || GetJoinedInfo()) return false;
	// only host or own player
	if (!::Network.isHost() && !IsLocalClientPlayer()) return false;
	// finally, only if team settings permit
	return CanLocalChooseTeams(idPlayer);
}

bool C4PlayerInfoListBox::PlayerListItem::IsLocalClientPlayer() const
{
	// check whether client is local
	// if no client can be found, assume network disconnect and everythign local then
	C4Network2Client *pClient = GetNetClient();
	return !pClient || pClient->isLocal();
}

C4Network2Client *C4PlayerInfoListBox::PlayerListItem::GetNetClient() const
{
	return ::Network.Clients.GetClientByID(idClient);
}


// ----------- ClientListItem -----------------------------------------------------------------

C4PlayerInfoListBox::ClientListItem::ClientListItem(C4PlayerInfoListBox *pForListBox, const C4ClientCore &rClientInfo, ListItem *pInsertBefore) // ctor
		: ListItem(pForListBox), idClient(rClientInfo.getID()), dwClientClr(0xffffff), tLastSoundTime(0)
{
	// set current active-flag (not really needed until player info is retrieved)
	fIsShownActive = rClientInfo.isActivated();
	// set ID
	idListItemID.idType = ListItem::ID::PLI_CLIENT;
	idListItemID.id = idClient;
	// get height
	int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
	// create subcomponents
	pStatusIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), GetCurrentStatusIcon());
	pNameLabel = new C4GUI::Label(rClientInfo.getName(), iIconSize + IconLabelSpacing,0, ALeft, dwClientClr | C4GUI_MessageFontAlpha, nullptr, true, false);
	pPingLabel = nullptr;
	C4GUI::CallbackButton<ClientListItem, C4GUI::IconButton> *btnAddPlayer = nullptr;
	if (IsLocalClientPlayer())
	{
		// this computer: add player button
		btnAddPlayer = new C4GUI::CallbackButton<ClientListItem, C4GUI::IconButton>(C4GUI::Ico_AddPlr, C4Rect(0, 0, iIconSize, iIconSize), 'P' /* 2do TODO */, &ClientListItem::OnBtnAddPlr, this);
	}
	// calc own bounds
	C4Rect rcOwnBounds = pNameLabel->GetBounds();
	rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
	rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
	SetBounds(rcOwnBounds);
	// add components
	AddElement(pStatusIcon); AddElement(pNameLabel);
	if (btnAddPlayer) AddElement(btnAddPlayer);
	// tooltip (same for all components for now. separate tooltip for status icon later?)
	SetToolTip(FormatString("Client %s (%s)", rClientInfo.getName(), rClientInfo.getNick()).getData());
	// insert into listbox at correct order
	// (will eventually get resized horizontally and moved)
	pForListBox->InsertElement(this, pInsertBefore);
	// after move: update add player button pos
	if (btnAddPlayer)
	{
		int32_t iHgt = GetClientRect().Hgt;
		btnAddPlayer->GetBounds() = GetToprightCornerRect(iHgt,iHgt,2,0);
	}
	// context menu for list item
	SetContextHandler(new C4GUI::CBContextHandler<ClientListItem>(this, &ClientListItem::OnContext));
	// update (also sets color)
	Update();
}

void C4PlayerInfoListBox::ClientListItem::SetPing(int32_t iToPing)
{
	// no ping?
	if (iToPing == -1)
	{
		// remove any ping label
		if (pPingLabel) { delete pPingLabel; pPingLabel = nullptr; }
		return;
	}
	// get ping as text
	StdStrBuf ping;
	ping.Format("%d ms", iToPing);
	// create ping label if necessary
	if (!pPingLabel)
	{
		pPingLabel = new C4GUI::Label(ping.getData(), GetBounds().Wdt, 0, ARight, C4GUI_MessageFontClr);
		pPingLabel->SetToolTip(LoadResStr("IDS_DLGTIP_PING"));
		AddElement(pPingLabel);
	}
	else
		// or just set updated text
		pPingLabel->SetText(ping.getData());
}

void C4PlayerInfoListBox::ClientListItem::UpdateInfo()
{
	// update color (always, because it can change silently)
	SetColor(::Network.Players.GetClientChatColor(idClient, true));
	// update activation status
	fIsShownActive = GetClient() && GetClient()->isActivated();
	// update status icon
	SetStatus(GetCurrentStatusIcon());
}

C4Client *C4PlayerInfoListBox::ClientListItem::GetClient() const
{
	// search (let's hope it exists)
	return Game.Clients.getClientByID(idClient);
}

bool C4PlayerInfoListBox::ClientListItem::IsLocalClientPlayer() const
{
	// check whether client is local
	// if no client can be found, something is wrong - assume network disconnect and everything local then
	C4Network2Client *pClient = GetNetClient();
	assert(pClient);
	return !pClient || pClient->isLocal();
}

C4Network2Client *C4PlayerInfoListBox::ClientListItem::GetNetClient() const
{
	return ::Network.Clients.GetClientByID(idClient);
}

bool C4PlayerInfoListBox::ClientListItem::IsLocal() const
{
	// it's local if client ID matches local ID
	return idClient == Game.Clients.getLocalID();
}

C4GUI::Icons C4PlayerInfoListBox::ClientListItem::GetCurrentStatusIcon()
{
	if (GetClient()->IsIgnored()) return C4GUI::Ico_Ignored;

	// sound icon?
	if (tLastSoundTime)
	{
		time_t dt = time(nullptr) - tLastSoundTime;
		if (dt >= SoundIconShowTime)
		{
			// stop showing sound icon
			tLastSoundTime = 0;
		}
		else
		{
			// time not up yet: show sound icon
			return C4GUI::Ico_Sound;
		}
	}
	// info present?
	C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(idClient);
	if (!pInfoPacket || !GetClient())
		// unknown status
		return C4GUI::Ico_UnknownClient;
	// host?
	if (GetClient()->isHost()) return C4GUI::Ico_Host;
	// active client?
	if (GetClient()->isActivated())
	{
		if (GetClient()->isLobbyReady())
		{
			return C4GUI::Ico_Ready;
		}
		else
		{
			return C4GUI::Ico_Client;
		}
	}
	// observer
	return C4GUI::Ico_ObserverClient;
}

void C4PlayerInfoListBox::ClientListItem::UpdatePing()
{
	// safety for removed clients
	if (!GetClient()) return;
	// default value indicating no ping
	int32_t iPing = -1;
	C4Network2Client *pClient = GetNetClient();
	C4Network2IOConnection *pConn;
	// must be a remote client
	if (pClient && !pClient->isLocal())
	{
		// must have a connection
		if ((pConn = pClient->getMsgConn()))
			// get ping of that connection
			iPing = pConn->getLag();
		// check data connection if msg conn gave no value
		// what's the meaning of those two connections anyway? o_O
		if (iPing<=0)
			if ((pConn = pClient->getDataConn()))
				iPing = pConn->getLag();
	}
	// set that ping in label
	SetPing(iPing);
}

void C4PlayerInfoListBox::ClientListItem::SetSoundIcon()
{
	// remember time for reset
	tLastSoundTime = time(nullptr);
	// force icon
	SetStatus(GetCurrentStatusIcon());
}

C4GUI::ContextMenu *C4PlayerInfoListBox::ClientListItem::OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
{
	// safety
	if (!::Network.isEnabled()) return nullptr;
	// get associated client
	C4Client *pClient = GetClient();
	// create context menu
	C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
	// host options
	if (::Network.isHost() && GetNetClient())
	{
		StdCopyStrBuf strKickDesc(LoadResStr("IDS_NET_KICKCLIENT_DESC"));
		pMenu->AddItem(LoadResStr("IDS_NET_KICKCLIENT"), strKickDesc.getData(), C4GUI::Ico_None,
		               new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxKick));
		StdCopyStrBuf strActivateDesc(LoadResStr("IDS_NET_ACTIVATECLIENT_DESC"));
		pMenu->AddItem(LoadResStr(pClient->isActivated() ? "IDS_NET_DEACTIVATECLIENT" : "IDS_NET_ACTIVATECLIENT"),
		               strActivateDesc.getData(), C4GUI::Ico_None,
		               new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxActivate));
	}
	// info
	StdCopyStrBuf strClientInfoDesc(LoadResStr("IDS_NET_CLIENTINFO_DESC"));
	pMenu->AddItem(LoadResStr("IDS_NET_CLIENTINFO"), strClientInfoDesc.getData(), C4GUI::Ico_None,
	               new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxInfo));
	//Ignore button
	if(!pClient->isLocal())
	{
		StdCopyStrBuf strNewColor(LoadResStr(pClient->IsIgnored() ? "IDS_NET_CLIENT_UNIGNORE" : "IDS_NET_CLIENT_IGNORE"));
		pMenu->AddItem(strNewColor.getData(), FormatString(LoadResStr("IDS_NET_CLIENT_IGNORE_DESC"), pClient->getName()).getData(),
			C4GUI::Ico_None, new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxIgnore), nullptr);
	}
	
	// open it
	return pMenu;
}

void C4PlayerInfoListBox::ClientListItem::OnCtxIgnore(C4GUI::Element *pListItem)
{
	GetClient()->ToggleIgnore();
}

void C4PlayerInfoListBox::ClientListItem::OnCtxKick(C4GUI::Element *pListItem)
{
	// host only
	if (!::Network.isEnabled() || !::Network.isHost()) return;
	// add control
	Game.Clients.CtrlRemove(GetClient(), LoadResStr("IDS_MSG_KICKFROMLOBBY"));
}

void C4PlayerInfoListBox::ClientListItem::OnCtxActivate(C4GUI::Element *pListItem)
{
	// host only
	C4Client *pClient = GetClient();
	if (!::Network.isEnabled() || !::Network.isHost() || !pClient) return;
	// add control
	::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(idClient, CUT_Activate, !pClient->isActivated()), CDT_Sync);
}

void C4PlayerInfoListBox::ClientListItem::OnCtxInfo(C4GUI::Element *pListItem)
{
	// show client info dialog
	::pGUI->ShowRemoveDlg(new C4Network2ClientDlg(idClient));
}

void C4PlayerInfoListBox::ClientListItem::OnBtnAddPlr(C4GUI::Control *btn)
{
	// show player add dialog
	GetScreen()->ShowRemoveDlg(new C4PlayerSelDlg(new C4FileSel_CBEx<C4GameLobby::MainDlg>(GetLobby(), &C4GameLobby::MainDlg::OnClientAddPlayer, idClient)));
}


// ----------- TeamListItem ---------------------------------------------

C4PlayerInfoListBox::TeamListItem::TeamListItem(C4PlayerInfoListBox *pForListBox, int32_t idTeam, ListItem *pInsertBefore)
		: ListItem(pForListBox), idTeam(idTeam)
{
	bool fEvaluation = pList->IsEvaluation();
	// get team data
	const char *szTeamName;
	C4Team *pTeam = nullptr;
	if (idTeam == TEAMID_Unknown)
		szTeamName = LoadResStr("IDS_MSG_RNDTEAM");
	else
	{
		pTeam = Game.Teams.GetTeamByID(idTeam); assert(pTeam);
		if (pTeam) szTeamName = pTeam->GetName(); else szTeamName = "INTERNAL TEAM ERROR";
	}
	// set ID
	idListItemID.idType = ListItem::ID::PLI_TEAM;
	idListItemID.id = idTeam;
	// get height
	int32_t iIconSize; CStdFont *pFont;
	if (!fEvaluation)
	{
		pFont = &::GraphicsResource.TextFont;
		iIconSize = pFont->GetLineHeight();
	}
	else
	{
		pFont = &::GraphicsResource.TitleFont;
		iIconSize = C4SymbolSize; // C4PictureSize doesn't fit...
	}
	// create subcomponents
	pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Team);
	pNameLabel = new C4GUI::Label(szTeamName, iIconSize + IconLabelSpacing, (iIconSize - pFont->GetLineHeight())/2, ALeft, pList->GetTextColor(), pFont, false);
	if (fEvaluation && pTeam && pTeam->GetIconSpec() && *pTeam->GetIconSpec())
	{
		C4FacetSurface fctSymbol;
		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
		Game.DrawTextSpecImage(fctSymbol, pTeam->GetIconSpec(), nullptr, pTeam->GetColor());
		pIcon->GetMFacet().GrabFrom(fctSymbol);
	}
	// calc own bounds
	C4Rect rcOwnBounds = pNameLabel->GetBounds();
	rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
	rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
	SetBounds(rcOwnBounds);
	// add components
	AddElement(pIcon); AddElement(pNameLabel);
	// tooltip
	SetToolTip(FormatString(LoadResStr("IDS_DESC_TEAM"), szTeamName).getData());
	// insert into listbox at correct order
	// (will eventually get resized horizontally and moved)
	pForListBox->InsertElement(this, pInsertBefore);
}

void C4PlayerInfoListBox::TeamListItem::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
{
	// double click on team to enter it with all local players
	if (iButton == C4MC_Button_LeftDouble)
	{
		MoveLocalPlayersIntoTeam();
	}
	else
		ListItem::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
}

void C4PlayerInfoListBox::TeamListItem::UpdateOwnPos()
{
	// parent for client rect
	typedef C4GUI::Window ParentClass;
	ParentClass::UpdateOwnPos();
	// evaluation: Center team label
	if (pList->IsEvaluation())
	{
		int32_t iTotalWdt = pIcon->GetBounds().Wdt + IconLabelSpacing + ::GraphicsResource.TitleFont.GetTextWidth(pNameLabel->GetText());
		C4GUI::ComponentAligner caAll(GetContainedClientRect(), 0,0);
		C4GUI::ComponentAligner caBounds(caAll.GetCentered(iTotalWdt, caAll.GetInnerHeight()), 0,0);
		pIcon->SetBounds(caBounds.GetFromLeft(pIcon->GetBounds().Wdt, pIcon->GetBounds().Hgt));
		pNameLabel->SetBounds(caBounds.GetCentered(caBounds.GetInnerWidth(), ::GraphicsResource.TitleFont.GetLineHeight()));
	}
}

void C4PlayerInfoListBox::TeamListItem::MoveLocalPlayersIntoTeam()
{
	// check if changing teams is allowed
	if (!CanLocalChooseTeams()) return;
	// safety: Clicked team must exist
	if (!Game.Teams.GetTeamByID(idTeam)) return;
	// get local client to change teams of
	bool fAnyChange = false;
	C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(Game.Clients.getLocalID());
	if (!pChangeInfo) return;
	// this must be processed as a request by the host
	// now change it in its own request packet
	C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
	C4PlayerInfo *pInfo; int32_t i=0;
	while ((pInfo = LocalInfoRequest.GetPlayerInfo(i++)))
		if (pInfo->GetTeam() != idTeam)
			if (pInfo->GetType() == C4PT_User)
			{
				pInfo->SetTeam(idTeam);
				fAnyChange = true;
			}
	if (!fAnyChange) return;
	// and request this update (host processes it directly)
	::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
	// next update will move the player labels
}

void C4PlayerInfoListBox::TeamListItem::Update()
{
	// evaluation: update color by team winning status
	if (pList->IsEvaluation())
	{
		C4Team *pTeam = Game.Teams.GetTeamByID(idTeam);
		if (pTeam && pTeam->HasWon())
		{
			pNameLabel->SetColor(C4GUI_WinningTextColor, false);
		}
		else
		{
			pNameLabel->SetColor(C4GUI_LosingTextColor, false);
		}
	}
}



// ----------- FreeSavegamePlayersListItem ---------------------------------------------

C4PlayerInfoListBox::FreeSavegamePlayersListItem::FreeSavegamePlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
		: ListItem(pForListBox)
{
	// set ID
	idListItemID.idType = ListItem::ID::PLI_SAVEGAMEPLR;
	idListItemID.id = 0;
	// get height
	int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
	// create subcomponents
	pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_SavegamePlayer);
	pNameLabel = new C4GUI::Label(LoadResStr("IDS_MSG_FREESAVEGAMEPLRS"), iIconSize + IconLabelSpacing,0, ALeft);
	// calc own bounds
	C4Rect rcOwnBounds = pNameLabel->GetBounds();
	rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
	rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
	SetBounds(rcOwnBounds);
	// add components
	AddElement(pIcon); AddElement(pNameLabel);
	// tooltip
	SetToolTip(LoadResStr("IDS_DESC_UNASSOCIATEDSAVEGAMEPLAYE"));
	// insert into listbox at correct order
	// (will eventually get resized horizontally and moved)
	pForListBox->InsertElement(this, pInsertBefore);
	// initial update
	Update();
}

void C4PlayerInfoListBox::FreeSavegamePlayersListItem::Update()
{
	// 2do: none-label
}


// ----------- ScriptPlayersListItem ---------------------------------------------

C4PlayerInfoListBox::ScriptPlayersListItem::ScriptPlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
		: ListItem(pForListBox)
{
	// set ID
	idListItemID.idType = ListItem::ID::PLI_SCRIPTPLR;
	idListItemID.id = 0;
	// get height
	int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
	// create subcomponents
	pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Record);
	pNameLabel = new C4GUI::Label(LoadResStr("IDS_CTL_SCRIPTPLAYERS"), iIconSize + IconLabelSpacing,0, ALeft);
	btnAddPlayer = nullptr;
	if (::Control.isCtrlHost())
	{
		btnAddPlayer = new C4GUI::CallbackButton<ScriptPlayersListItem, C4GUI::IconButton>(C4GUI::Ico_AddPlr, C4Rect(0, 0, iIconSize, iIconSize), 'A' /* 2do TODO */, &ScriptPlayersListItem::OnBtnAddPlr, this);
	}
	// calc own bounds
	C4Rect rcOwnBounds = pNameLabel->GetBounds();
	rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
	rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
	SetBounds(rcOwnBounds);
	// add components
	AddElement(pIcon); AddElement(pNameLabel);
	if (btnAddPlayer) AddElement(btnAddPlayer);
	// tooltip
	SetToolTip(LoadResStr("IDS_DESC_PLAYERSCONTROLLEDBYCOMPUT"));
	// insert into listbox at correct order
	// (will eventually get resized horizontally and moved)
	pForListBox->InsertElement(this, pInsertBefore);
	// after move: update add player button pos
	if (btnAddPlayer)
	{
		int32_t iHgt = GetClientRect().Hgt;
		btnAddPlayer->GetBounds() = GetToprightCornerRect(iHgt,iHgt,2,0);
	}
	// initial update
	Update();
}

void C4PlayerInfoListBox::ScriptPlayersListItem::Update()
{
	// player join button: Visible if there's still some room for script players
	if (btnAddPlayer)
	{
		bool fCanJoinScriptPlayers = (Game.Teams.GetMaxScriptPlayers() - Game.PlayerInfos.GetActiveScriptPlayerCount(true, true) > 0);
		btnAddPlayer->SetVisibility(fCanJoinScriptPlayers);
	}
}

void C4PlayerInfoListBox::ScriptPlayersListItem::OnBtnAddPlr(C4GUI::Control *btn)
{
	// safety
	int32_t iCurrScriptPlrCount = Game.PlayerInfos.GetActiveScriptPlayerCount(true, true);
	bool fCanJoinScriptPlayers = (Game.Teams.GetMaxScriptPlayers() - iCurrScriptPlrCount > 0);
	if (!fCanJoinScriptPlayers) return;
	if (!::Control.isCtrlHost()) return;
	// request a script player join
	C4PlayerInfo *pScriptPlrInfo = new C4PlayerInfo();
	pScriptPlrInfo->SetAsScriptPlayer(Game.Teams.GetScriptPlayerName().getData(), GenerateRandomPlayerColor(iCurrScriptPlrCount), 0, C4ID::None);
	C4ClientPlayerInfos JoinPkt(nullptr, true, pScriptPlrInfo);
	// add to queue!
	Game.PlayerInfos.DoPlayerInfoUpdate(&JoinPkt);
}


// ----------- ReplayPlayersListItem ---------------------------------------------

C4PlayerInfoListBox::ReplayPlayersListItem::ReplayPlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
		: ListItem(pForListBox)
{
	// set ID
	idListItemID.idType = ListItem::ID::PLI_REPLAY;
	idListItemID.id = 0;
	// get height
	int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
	// create subcomponents
	pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Record);
	pNameLabel = new C4GUI::Label(LoadResStr("IDS_MSG_REPLAYPLRS"), iIconSize + IconLabelSpacing,0, ALeft);
	// calc own bounds
	C4Rect rcOwnBounds = pNameLabel->GetBounds();
	rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
	rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
	SetBounds(rcOwnBounds);
	// add components
	AddElement(pIcon); AddElement(pNameLabel);
	// tooltip
	SetToolTip(LoadResStr("IDS_MSG_REPLAYPLRS_DESC"));
	// insert into listbox at correct order
	// (will eventually get resized horizontally and moved)
	pForListBox->InsertElement(this, pInsertBefore);
}



// ------------------- C4PlayerInfoListBox ------------------------

C4PlayerInfoListBox::C4PlayerInfoListBox(const C4Rect &rcBounds, Mode eMode, int32_t iTeamFilter)
		: C4GUI::ListBox(rcBounds), eMode(eMode), iMaxUncollapsedPlayers(10), fIsCollapsed(false), iTeamFilter(iTeamFilter), dwTextColor(C4GUI_MessageFontClr), pCustomFont(nullptr)
{
	// update if client listbox selection changes
	SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4PlayerInfoListBox>(this, &C4PlayerInfoListBox::OnPlrListSelChange));
	// initial update
	Update();
}

void C4PlayerInfoListBox::SetClientSoundIcon(int32_t iForClientID)
{
	// get client element
	ListItem *pItem = GetPlayerListItem(ListItem::ID::PLI_CLIENT, iForClientID);
	if (pItem)
	{
		ClientListItem *pClientItem = static_cast<ClientListItem *>(pItem);
		pClientItem->SetSoundIcon();
	}
}

C4PlayerInfoListBox::ListItem *C4PlayerInfoListBox::GetPlayerListItem(ListItem::ID::IDType eType, int32_t id)
{
	ListItem::ID idSearch(eType, id);
	// search through listbox
	for (C4GUI::Element *pEItem = GetFirst(); pEItem; pEItem = pEItem->GetNext())
	{
		// only playerlistitems in this box
		ListItem *pItem = static_cast<ListItem *>(pEItem);
		if (pItem->idListItemID == idSearch) return pItem;
	}
	// nothing found
	return nullptr;
}

bool C4PlayerInfoListBox::PlrListItemUpdate(ListItem::ID::IDType eType, int32_t id, class ListItem **pEnsurePos)
{
	assert(pEnsurePos);
	// search item
	ListItem *pItem = GetPlayerListItem(eType, id);
	if (!pItem) return false;
	// ensure its position is correct
	if (pItem != *pEnsurePos)
	{
		RemoveElement(pItem);
		InsertElement(pItem, *pEnsurePos);
	}
	else
	{
		// pos correct; advance past it
		*pEnsurePos = static_cast<ListItem *>(pItem->GetNext());
	}
	// update item
	pItem->Update();
	// done, success
	return true;
}

// static safety var to prevent recusive updates
static bool fPlayerListUpdating=false;

void C4PlayerInfoListBox::Update()
{
	if (fPlayerListUpdating) return;
	fPlayerListUpdating = true;

	// synchronize current list with what it should be
	// call update on all other list items
	ListItem *pCurrInList = static_cast<ListItem *>(GetFirst()); // list item being compared with the searched item

	// free savegame players first
	UpdateSavegamePlayers(&pCurrInList);

	// next comes the regular players, sorted either by clients or teams, or special sort for evaluation mode
	switch (eMode)
	{
	case PILBM_LobbyTeamSort:
		// sort by team
		if (Game.Teams.CanLocalSeeTeam())
			UpdatePlayersByTeam(&pCurrInList);
		else
			UpdatePlayersByRandomTeam(&pCurrInList);
		break;

	case PILBM_LobbyClientSort:
		// sort by client
		// replay players first?
		if (Game.C4S.Head.Replay) UpdateReplayPlayers(&pCurrInList);
		// script controlled players from the main list
		UpdateScriptPlayers(&pCurrInList);
		// regular players
		UpdatePlayersByClient(&pCurrInList);
		break;

	case PILBM_Evaluation:
	case PILBM_EvaluationNoWinners:
		UpdatePlayersByEvaluation(&pCurrInList, eMode == PILBM_Evaluation);
		break;
	}

	// finally: remove any remaining list items at the end
	while (pCurrInList)
	{
		ListItem *pDel = pCurrInList;
		pCurrInList = static_cast<ListItem *>(pCurrInList->GetNext());
		delete pDel;
	}

	// update done
	fPlayerListUpdating = false;

	// check whether view needs to be collapsed
	if (!fIsCollapsed && IsScrollingActive() && !IsEvaluation())
	{
		// then collapse it, and update window
		iMaxUncollapsedPlayers = Game.PlayerInfos.GetPlayerCount()-1;
		fIsCollapsed = true;
		Update(); // recursive call!
	}
	else if (fIsCollapsed && Game.PlayerInfos.GetPlayerCount() <= iMaxUncollapsedPlayers)
	{
		// player count dropped below collapse-limit: uncollapse
		// note that this may again cause a collapse after that update, if scrolling was still necessary
		// however, it will then not recurse any further, because iMaxUncollapsedPlayers will have been updated
		fIsCollapsed = false;
		Update();
	}
}

void C4PlayerInfoListBox::UpdateSavegamePlayers(ListItem **ppCurrInList)
{
	// add unassociated savegame players (script players excluded)
	if (Game.RestorePlayerInfos.GetActivePlayerCount(true) - Game.RestorePlayerInfos.GetActiveScriptPlayerCount(true, true))
	{
		// caption
		if (!PlrListItemUpdate(ListItem::ID::PLI_SAVEGAMEPLR, 0, ppCurrInList))
			new FreeSavegamePlayersListItem(this, *ppCurrInList);
		// the players
		bool fAnyPlayers = false;
		C4PlayerInfo *pInfo; int32_t iInfoID=0;
		while ((pInfo = Game.RestorePlayerInfos.GetNextPlayerInfoByID(iInfoID)))
		{
			iInfoID = pInfo->GetID();
			// skip assigned
			if (Game.PlayerInfos.GetPlayerInfoBySavegameID(iInfoID)) continue;
			// skip script controlled - those are put into the script controlled player list
			if (pInfo->GetType() == C4PT_Script) continue;
			// players are in the list
			fAnyPlayers = true;
			// show them
			if (!PlrListItemUpdate(ListItem::ID::PLI_SAVEGAMEPLR, iInfoID, ppCurrInList))
				new PlayerListItem(this, -1, iInfoID, true, *ppCurrInList);
		}
		// 2do: none-label
		(void) fAnyPlayers;
	}

}

void C4PlayerInfoListBox::UpdateReplayPlayers(ListItem **ppCurrInList)
{
	// header
	if (!PlrListItemUpdate(ListItem::ID::PLI_REPLAY, 0, ppCurrInList))
		new ReplayPlayersListItem(this, *ppCurrInList);
	// players
	C4PlayerInfo *pInfo; int32_t iInfoID=0;
	while ((pInfo = Game.PlayerInfos.GetNextPlayerInfoByID(iInfoID)))
	{
		if (pInfo->IsInvisible()) continue;
		iInfoID = pInfo->GetID();
		// show them
		if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, iInfoID, ppCurrInList))
			new PlayerListItem(this, -1, iInfoID, false, *ppCurrInList);
	}
	// 2do: none-label
}

void C4PlayerInfoListBox::UpdateScriptPlayers(ListItem **ppCurrInList)
{
	// script controlled players from the main list
	// processing the restore list would be redundant, because all script players should have been taken over by a new script player join automatically
	// also show the label if script players can be joined
	if (Game.Teams.GetMaxScriptPlayers() || Game.PlayerInfos.GetActiveScriptPlayerCount(true, false))
	{
		// header
		if (!PlrListItemUpdate(ListItem::ID::PLI_SCRIPTPLR, 0, ppCurrInList))
			new ScriptPlayersListItem(this, *ppCurrInList);
		// players
		C4PlayerInfo *pInfo; int32_t iClientIdx=0; C4ClientPlayerInfos *pInfos;
		while ((pInfos = Game.PlayerInfos.GetIndexedInfo(iClientIdx++)))
		{
			int32_t iInfoIdx=0;
			while ((pInfo = pInfos->GetPlayerInfo(iInfoIdx++)))
			{
				if (pInfo->GetType() != C4PT_Script) continue;
				if (pInfo->IsRemoved()) continue;
				if (pInfo->IsInvisible()) continue;
				// show them
				int32_t iInfoID = pInfo->GetID();
				if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, iInfoID, ppCurrInList))
					new PlayerListItem(this, pInfos->GetClientID(), iInfoID, false, *ppCurrInList);
			}
		}
	}
}

void C4PlayerInfoListBox::UpdatePlayersByTeam(ListItem **ppCurrInList)
{
	// sort by team
	C4Team *pTeam; int32_t i=0;
	while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
	{
		// no empty teams that are not used
		if (Game.Teams.IsAutoGenerateTeams() && !pTeam->GetPlayerCount()) continue;
		// the team label
		if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, pTeam->GetID(), ppCurrInList))
			new TeamListItem(this, pTeam->GetID(), *ppCurrInList);
		// players for this team
		int32_t idPlr, j=0; int32_t idClient; C4Client *pClient; C4PlayerInfo *pPlrInfo;
		while ((idPlr = pTeam->GetIndexedPlayer(j++)))
			if ((pPlrInfo = Game.PlayerInfos.GetPlayerInfoByID(idPlr, &idClient)))
				if (!pPlrInfo->IsInvisible())
					if ((pClient=Game.Clients.getClientByID(idClient)) && pClient->isActivated())
						if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, idPlr, ppCurrInList))
							new PlayerListItem(this, idClient, idPlr, false, *ppCurrInList);
	}
}

void C4PlayerInfoListBox::UpdatePlayersByRandomTeam(ListItem **ppCurrInList)
{
	// team sort but teams set to random and invisible: Show all players within one "Random Team"-label
	bool fTeamLabelPut = false;
	C4Client *pClient = nullptr;
	while ((pClient = Game.Clients.getClient(pClient)))
	{
		// player infos for this client - not for deactivated, and never in replays
		if (Game.C4S.Head.Replay || !pClient->isActivated()) continue;
		C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(pClient->getID());
		if (pInfoPacket)
		{
			C4PlayerInfo *pPlrInfo; int32_t i=0;
			while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
			{
				if (pPlrInfo->IsInvisible()) continue;
				if (!fTeamLabelPut)
				{
					if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, TEAMID_Unknown, ppCurrInList))
						new TeamListItem(this, TEAMID_Unknown, *ppCurrInList);
					fTeamLabelPut = true;
				}
				if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
					new PlayerListItem(this, pClient->getID(), pPlrInfo->GetID(), false, *ppCurrInList);
			}
		}
	}
}

void C4PlayerInfoListBox::UpdatePlayersByClient(ListItem **ppCurrInList)
{
	// regular players
	C4Client *pClient = nullptr;
	while ((pClient = Game.Clients.getClient(pClient)))
	{
		// the client label
		if (!PlrListItemUpdate(ListItem::ID::PLI_CLIENT, pClient->getID(), ppCurrInList))
			new ClientListItem(this, pClient->getCore(), *ppCurrInList);
		// player infos for this client - not for observers, and never in replays
		// could also check for activated here. However, non-observers will usually be activated later and thus be using their players
		if (Game.C4S.Head.Replay || pClient->isObserver()) continue;
		C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(pClient->getID());
		if (pInfoPacket)
		{
			C4PlayerInfo *pPlrInfo; int32_t i=0;
			while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
			{
				if (pPlrInfo->GetType() == C4PT_Script) continue;
				if (pPlrInfo->IsRemoved()) continue;
				if (pPlrInfo->IsInvisible()) continue;
				if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
					new PlayerListItem(this, pClient->getID(), pPlrInfo->GetID(), false, *ppCurrInList);
			}
		}
	}
}

void C4PlayerInfoListBox::UpdatePlayersByEvaluation(ListItem **ppCurrInList, bool fShowWinners)
{
	// if a team filter is provided, add team label first
	if (iTeamFilter)
		if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, iTeamFilter, ppCurrInList))
			new TeamListItem(this, iTeamFilter, *ppCurrInList);
	// Add by teams: In show-winner-mode winning teams first
	// Otherwise, just add all
	AddMode pShowWinnersAddModes[] = { AM_Winners, AM_Losers };
	AddMode pHideWinnersAddModes[] = { AM_All };
	AddMode *pAddModes; int32_t iAddModeCount;
	if (fShowWinners)
	{
		pAddModes = pShowWinnersAddModes; iAddModeCount = 2;
	}
	else
	{
		pAddModes = pHideWinnersAddModes; iAddModeCount = 1;
	}
	for (int32_t iAddMode = 0; iAddMode < iAddModeCount; ++iAddMode)
	{
		AddMode eAddMode = pAddModes[iAddMode];
		if (iTeamFilter)
		{
			// Team filter mode: Add only players of specified team
			UpdatePlayersByEvaluation(ppCurrInList, Game.Teams.GetTeamByID(iTeamFilter), eAddMode);
		}
		else
		{
			// Normal mode: Add all teams of winning status
			C4Team *pTeam; int32_t i=0;
			while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
			{
				UpdatePlayersByEvaluation(ppCurrInList, pTeam, eAddMode);
			}
			// Add teamless players of winning status
			UpdatePlayersByEvaluation(ppCurrInList, nullptr, eAddMode);
		}
	}
}

void C4PlayerInfoListBox::UpdatePlayersByEvaluation(ListItem **ppCurrInList, C4Team *pTeam, C4PlayerInfoListBox::AddMode eWinMode)
{
	// check winning status of team first
	if (pTeam && eWinMode != AM_All) if (pTeam->HasWon() != (eWinMode == AM_Winners)) return;
	// now add all matching players
	int32_t iTeamID = pTeam ? pTeam->GetID() : 0;
	C4ClientPlayerInfos *pInfoPacket; int32_t iClient=0;
	while ((pInfoPacket = Game.PlayerInfos.GetIndexedInfo(iClient++)))
	{
		C4PlayerInfo *pPlrInfo; int32_t i=0;
		while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
		{
			if (!pPlrInfo->HasJoined()) continue;
			if (pPlrInfo->GetTeam() != iTeamID) continue;
			if (pPlrInfo->IsInvisible()) continue;
			if (!pTeam && eWinMode != AM_All && pPlrInfo->HasWon() != (eWinMode == AM_Winners)) continue;
			if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
				new PlayerListItem(this, pInfoPacket->GetClientID(), pPlrInfo->GetID(), false, *ppCurrInList);
		}
	}
}

bool C4PlayerInfoListBox::IsPlayerItemCollapsed(PlayerListItem *pItem)
{
	// never if view is not collapsed
	if (!fIsCollapsed) return false;
	// collapsed if not selected
	return GetSelectedItem() != pItem;
}

void C4PlayerInfoListBox::SetMode(Mode eNewMode)
{
	if (eMode != eNewMode)
	{
		eMode = eNewMode;
		Update();
	}
}

void C4PlayerInfoListBox::SetCustomFont(CStdFont *pNewFont, uint32_t dwTextColor)
{
	pCustomFont = pNewFont;
	this->dwTextColor = dwTextColor;
	// update done later by caller anyway
}
