1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // Startup screen for non-parameterized engine start: Player selection dialog
17 // Also contains player creation, editing and crew management
18 
19 #include "C4Include.h"
20 #include "gui/C4StartupPlrSelDlg.h"
21 
22 #include "graphics/C4Draw.h"
23 #include "graphics/C4GraphicsResource.h"
24 #include "gui/C4FileSelDlg.h"
25 #include "gui/C4MouseControl.h"
26 #include "gui/C4StartupMainDlg.h"
27 #include "lib/C4Random.h"
28 #include "lib/StdColors.h"
29 #include "player/C4RankSystem.h"
30 
31 // font clrs
32 const uint32_t ClrPlayerItem   = 0xffffffff;
33 
34 // Arbitrary cut-off value for player color value. This avoids pitch black
35 // colors which look ugly. Note that this limit is only applied in the UI,
36 // it's still possible to edit the Player.txt by hand.
37 const uint32_t PlayerColorValueLowBound = 64;
38 
39 // ----- C4Utilities
40 
TimeString(int iSeconds)41 StdStrBuf TimeString(int iSeconds)
42 {
43 	int iHours = iSeconds / 3600; iSeconds -= 3600*iHours;
44 	int iMinutes = iSeconds / 60; iSeconds -= 60*iMinutes;
45 	return FormatString("%02d:%02d:%02d",iHours,iMinutes,iSeconds);
46 }
47 
DateString(int iTime)48 StdStrBuf DateString(int iTime)
49 {
50 	if (!iTime) return StdStrBuf("", true);
51 	time_t tTime = iTime;
52 	struct tm *pLocalTime;
53 	pLocalTime=localtime(&tTime);
54 	return FormatString(  "%02d.%02d.%d %02d:%02d",
55 	                      pLocalTime->tm_mday,
56 	                      pLocalTime->tm_mon+1,
57 	                      pLocalTime->tm_year+1900,
58 	                      pLocalTime->tm_hour,
59 	                      pLocalTime->tm_min);
60 }
61 
62 // ------------------------------------------------
63 // --- C4StartupPlrSelDlg::ListItem
ListItem(C4StartupPlrSelDlg * pForDlg,C4GUI::ListBox * pForListBox,C4GUI::Element * pInsertBeforeElement,bool fActivated)64 C4StartupPlrSelDlg::ListItem::ListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBeforeElement, bool fActivated)
65 		: Control(C4Rect(0,0,0,0)), pCheck(nullptr), pNameLabel(nullptr), pPlrSelDlg(pForDlg), pIcon(nullptr)
66 {
67 	CStdFont &rUseFont = GraphicsResource.FontRegular;
68 	// calc height
69 	int32_t iHeight = rUseFont.GetLineHeight() + 2 * IconLabelSpacing;
70 	// create subcomponents
71 	pCheck = new C4GUI::CheckBox(C4Rect(0, 0, iHeight, iHeight), nullptr, fActivated);
72 	pCheck->SetOnChecked(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(pForDlg, &C4StartupPlrSelDlg::OnItemCheckChange));
73 	pKeyCheck = new C4KeyBinding(C4KeyCodeEx(K_SPACE), "StartupPlrSelTogglePlayerActive", KEYSCOPE_Gui,
74 	                             new C4GUI::ControlKeyCB<ListItem>(*this, &ListItem::KeyCheck), C4CustomKey::PRIO_Ctrl);
75 	pIcon = new C4GUI::Icon(C4Rect(iHeight + IconLabelSpacing, 0, iHeight, iHeight), C4GUI::Ico_Player);
76 	pNameLabel = new C4GUI::Label("Q", (iHeight + IconLabelSpacing)*2, IconLabelSpacing, ALeft, ClrPlayerItem, &rUseFont, false, false);
77 	pNameLabel->SetAutosize(false);
78 	// calc own bounds - use icon bounds only, because only the height is used when the item is added
79 	SetBounds(pIcon->GetBounds());
80 	// add components
81 	AddElement(pCheck);
82 	AddElement(pIcon); AddElement(pNameLabel);
83 	// add to listbox (will get resized horizontally and moved) - zero indent; no tree structure in this dialog
84 	pForListBox->InsertElement(this, pInsertBeforeElement, 0);
85 	// update name label width to stretch max listbox width
86 	C4Rect rcNameLabelBounds = pNameLabel->GetBounds();
87 	rcNameLabelBounds.Wdt = GetClientRect().Wdt - rcNameLabelBounds.x - IconLabelSpacing;
88 	pNameLabel->SetBounds(rcNameLabelBounds);
89 	// context menu
90 	SetContextHandler(new C4GUI::CBContextHandler<C4StartupPlrSelDlg::ListItem>(this, &C4StartupPlrSelDlg::ListItem::ContextMenu));
91 }
92 
~ListItem()93 C4StartupPlrSelDlg::ListItem::~ListItem()
94 {
95 	delete pKeyCheck;
96 }
97 
GetName() const98 const char *C4StartupPlrSelDlg::ListItem::GetName() const
99 {
100 	// name is stored in label only
101 	return pNameLabel->GetText();
102 }
103 
SetName(const char * szNewName)104 void C4StartupPlrSelDlg::ListItem::SetName(const char *szNewName)
105 {
106 	// update name in label
107 	pNameLabel->SetText(szNewName);
108 	// tooltip by name, so long names can be read via tooltip
109 	SetToolTip(szNewName);
110 }
111 
GrabIcon(C4FacetSurface & rFromFacet)112 void C4StartupPlrSelDlg::ListItem::GrabIcon(C4FacetSurface &rFromFacet)
113 {
114 	// take over icon gfx from facet - deletes them from source facet!
115 	if (rFromFacet.Surface)
116 	{
117 		pIcon->GetMFacet().GrabFrom(rFromFacet);
118 	}
119 	else
120 	{
121 		// reset custom icon
122 		// following update-call will reset to default icon
123 		pIcon->GetMFacet().Clear();
124 	}
125 }
126 
SetIcon(C4GUI::Icons icoNew)127 void C4StartupPlrSelDlg::ListItem::SetIcon(C4GUI::Icons icoNew)
128 {
129 	pIcon->SetIcon(icoNew);
130 }
131 
UpdateOwnPos()132 void C4StartupPlrSelDlg::ListItem::UpdateOwnPos()
133 {
134 	// parent for client rect
135 	typedef C4GUI::Window ParentClass;
136 	ParentClass::UpdateOwnPos();
137 	// reposition items
138 	C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing);
139 	// nothing to reposition for now...
140 }
141 
SetFilename(const StdStrBuf & sNewFN)142 void C4StartupPlrSelDlg::ListItem::SetFilename(const StdStrBuf &sNewFN)
143 {
144 	// just set fn - UpdateCore-call will follow later
145 	Filename.Copy(sNewFN);
146 }
147 
CheckNameHotkey(const char * c)148 bool C4StartupPlrSelDlg::ListItem::CheckNameHotkey(const char * c)
149 {
150 	// return whether this item can be selected by entering given char:
151 	// first char of name must match
152 	// FIXME: Unicode
153 	if (!pNameLabel) return false;
154 	const char *szName = pNameLabel->GetText();
155 	return szName && (toupper(*szName) == toupper(c[0]));
156 }
157 
158 
159 // ------------------------------------------------
160 // --- C4StartupPlrSelDlg::PlayerListItem
PlayerListItem(C4StartupPlrSelDlg * pForDlg,C4GUI::ListBox * pForListBox,C4GUI::Element * pInsertBeforeElement,bool fActivated)161 C4StartupPlrSelDlg::PlayerListItem::PlayerListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBeforeElement, bool fActivated)
162 		: ListItem(pForDlg, pForListBox, pInsertBeforeElement, fActivated), fHasCustomIcon(false)
163 {
164 }
165 
Load(const StdStrBuf & rsFilename)166 void C4StartupPlrSelDlg::PlayerListItem::Load(const StdStrBuf &rsFilename)
167 {
168 	int32_t iHeight = GetBounds().Hgt;
169 	// backup filename
170 	SetFilename(rsFilename);
171 	// load player info
172 	C4Group PlrGroup;
173 	if (!PlrGroup.Open(Config.AtUserDataPath(rsFilename.getData())))
174 		throw LoadError(FormatString("Error loading player file from %s: Error opening group: %s", rsFilename.getData(), PlrGroup.GetError()));
175 	if (!Core.Load(PlrGroup))
176 		throw LoadError(FormatString("Error loading player file from %s: Core data invalid or missing (Group: %s)!", rsFilename.getData(), PlrGroup.GetError()));
177 	// load icon
178 	C4FacetSurface fctIcon;
179 	if (PlrGroup.FindEntry(C4CFN_BigIcon) && fctIcon.Load(PlrGroup, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0))
180 		fHasCustomIcon = true;
181 	else
182 	{
183 		// no custom icon: create default by player color
184 		fctIcon.Create(iHeight,iHeight);
185 		::GraphicsResource.fctPlayerClr.DrawClr(fctIcon, true, Core.PrefColorDw);
186 	}
187 	GrabIcon(fctIcon);
188 	// done loading
189 	if (!PlrGroup.Close())
190 		throw LoadError(FormatString("Error loading player file from %s: Error closing group: %s", rsFilename.getData(), PlrGroup.GetError()));
191 	// default name
192 	if (!*Core.PrefName) SCopy(GetFilenameOnly(rsFilename.getData()), Core.PrefName, sizeof(Core.PrefName)-1);
193 	SetName(Core.PrefName);
194 }
195 
ContextMenu()196 C4GUI::ContextMenu *C4StartupPlrSelDlg::PlayerListItem::ContextMenu()
197 {
198 	// menu operations work on selected item only
199 	pPlrSelDlg->SetSelection(this);
200 	// context menu operations
201 	C4GUI::ContextMenu *pCtx = new C4GUI::ContextMenu();
202 	pCtx->AddItem(LoadResStr("IDS_BTN_PROPERTIES"), LoadResStr("IDS_DLGTIP_PLAYERPROPERTIES"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnPropertyCtx));
203 	pCtx->AddItem(LoadResStr("IDS_BTN_DELETE"), LoadResStr("IDS_DLGTIP_PLAYERDELETE"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnDelCtx));
204 	return pCtx;
205 }
206 
GrabCustomIcon(C4FacetSurface & fctGrabFrom)207 void C4StartupPlrSelDlg::PlayerListItem::GrabCustomIcon(C4FacetSurface &fctGrabFrom)
208 {
209 	// set flag
210 	fHasCustomIcon = !!fctGrabFrom.Surface;
211 	// base class grab
212 	GrabIcon(fctGrabFrom);
213 }
214 
UpdateCore(C4PlayerInfoCore & NewCore)215 void C4StartupPlrSelDlg::PlayerListItem::UpdateCore(C4PlayerInfoCore & NewCore)
216 {
217 	C4Group PlrGroup;
218 	if (!PlrGroup.Open(Config.AtUserDataPath(GetFilename().getData()))
219 	    || !NewCore.Save(PlrGroup)
220 	    || !PlrGroup.Close())
221 	{
222 		GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_MODIFY"), "", C4GUI::Ico_Error);
223 		return;
224 	}
225 	Core = NewCore;
226 	SetName(Core.PrefName);
227 	// re-set non-custom icons
228 	if (!fHasCustomIcon)
229 	{
230 		fHasCustomIcon = false;
231 		int32_t iHeight = GetBounds().Hgt;
232 		C4FacetSurface fctIcon; fctIcon.Create(iHeight,iHeight);
233 		::GraphicsResource.fctPlayerClr.DrawClr(fctIcon, true, Core.PrefColorDw);
234 		GrabIcon(fctIcon);
235 	}
236 	// update in selection
237 	C4StartupPlrSelDlg *pDlg = static_cast<C4StartupPlrSelDlg *>(GetDlg());
238 	if (pDlg && pDlg->GetSelection() == this) pDlg->UpdateSelection();
239 }
240 
SetSelectionInfo(C4GUI::TextWindow * pSelectionInfo)241 void C4StartupPlrSelDlg::PlayerListItem::SetSelectionInfo(C4GUI::TextWindow *pSelectionInfo)
242 {
243 	// write info text for player
244 	pSelectionInfo->ClearText(false);
245 	pSelectionInfo->AddTextLine(FormatString("%s", Core.PrefName).getData(), &C4Startup::Get()->Graphics.BookFontCapt, ClrPlayerItem, false, false);
246 	pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DESC_PLAYER"), (int)Core.TotalScore, (int)Core.Rounds, (int)Core.RoundsWon, (int)Core.RoundsLost, TimeString(Core.TotalPlayingTime).getData(), Core.Comment).getData(), &C4Startup::Get()->Graphics.BookFont, ClrPlayerItem, false, false);
247 	if (Core.LastRound.Title[0])
248 		pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DESC_LASTGAME"),Core.LastRound.Title.getData(),DateString(Core.LastRound.Date).getData(),TimeString(Core.LastRound.Duration).getData(),(int)Core.LastRound.FinalScore).getData(), &C4Startup::Get()->Graphics.BookFont, ClrPlayerItem, false, false);
249 	pSelectionInfo->UpdateHeight();
250 }
251 
GetDelWarning()252 StdStrBuf C4StartupPlrSelDlg::PlayerListItem::GetDelWarning()
253 {
254 	StdStrBuf sWarning;
255 	sWarning.Format(LoadResStr("IDS_MSG_DELETEPLR"), Core.PrefName);
256 	int32_t iPlrTime = Core.TotalPlayingTime;
257 	if (iPlrTime > 60*60*10)
258 		sWarning.Append(FormatString(LoadResStr("IDS_MSG_DELETEPLR_PLAYTIME"),
259 		                             TimeString(iPlrTime).getData()).getData());
260 	return sWarning;
261 }
262 
MoveFilename(const char * szToFilename)263 bool C4StartupPlrSelDlg::PlayerListItem::MoveFilename(const char *szToFilename)
264 {
265 	// anything to do?
266 	if (ItemIdentical(GetFilename().getData(), szToFilename)) return true;
267 	// do it
268 	StdStrBuf PathFrom(Config.General.UserDataPath);
269 	PathFrom.Append(GetFilename());
270 	StdStrBuf PathTo(Config.General.UserDataPath);
271 	PathTo.Append(szToFilename);
272 	if (!MoveItem(PathFrom.getData(), PathTo.getData())) return false;
273 	// reflect change in class
274 	SetFilename(StdStrBuf(szToFilename));
275 	return true;
276 }
277 
278 
279 // ------------------------------------------------
280 // --- C4StartupPlrSelDlg::CrewListItem
281 
CrewListItem(C4StartupPlrSelDlg * pForDlg,C4GUI::ListBox * pForListBox,uint32_t dwPlrClr)282 C4StartupPlrSelDlg::CrewListItem::CrewListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, uint32_t dwPlrClr)
283 		: ListItem(pForDlg, pForListBox, nullptr, false), fLoaded(false), dwPlrClr(dwPlrClr), pParentGrp(nullptr)
284 {
285 	SetIcon(C4GUI::Ico_Wait);
286 }
287 
UpdateClonkEnabled()288 void C4StartupPlrSelDlg::CrewListItem::UpdateClonkEnabled()
289 {
290 	if (!fLoaded) return;
291 	Core.Participation = pCheck->GetChecked();
292 	// immediate save of changes
293 	RewriteCore();
294 }
295 
Load(C4Group & rGrp,const StdStrBuf & rsFilename)296 void C4StartupPlrSelDlg::CrewListItem::Load(C4Group &rGrp, const StdStrBuf &rsFilename)
297 {
298 	// backup filename (doesn't include path)
299 	SetFilename(rsFilename);
300 	// load core
301 	C4Group CrewGroup;
302 	if (!CrewGroup.OpenAsChild(&rGrp, rsFilename.getData()))
303 		throw LoadError(FormatString("Error loading crew file from %s in %s: Error opening group: %s",
304 		                             rsFilename.getData(), rGrp.GetFullName().getData(), CrewGroup.GetError()));
305 	if (!Core.Load(CrewGroup))
306 		throw LoadError(FormatString("Error loading crew file from %s: Core data invalid or missing (Group: %s)!",
307 		                             CrewGroup.GetFullName().getData(), CrewGroup.GetError()));
308 	ListItem::SetName(Core.Name);
309 	pCheck->SetChecked(!!Core.Participation);
310 	// load rank as icon
311 	C4FacetSurface fctIcon;
312 	if (fctIcon.Load(CrewGroup, C4CFN_ClonkRank, C4FCT_Full, C4FCT_Full, false, true))
313 	{
314 		GrabIcon(fctIcon);
315 	}
316 	else
317 	{
318 		// no custom icon: create default by rank system
319 		if (C4RankSystem::DrawRankSymbol(&fctIcon, Core.Rank, &::GraphicsResource.fctRank, ::GraphicsResource.iNumRanks, true))
320 			GrabIcon(fctIcon);
321 	}
322 	// backup group loaded from - assumes it stays valid!
323 	pParentGrp = &rGrp;
324 	// load success!
325 	fLoaded=true;
326 }
327 
ContextMenu()328 C4GUI::ContextMenu *C4StartupPlrSelDlg::CrewListItem::ContextMenu()
329 {
330 	// menu operations work on selected item only
331 	pPlrSelDlg->SetSelection(this);
332 	// context menu operations
333 	C4GUI::ContextMenu *pCtx = new C4GUI::ContextMenu();
334 	pCtx->AddItem(LoadResStr("IDS_BTN_RENAME"), LoadResStr("IDS_DESC_CREWRENAME"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnPropertyCtx));
335 	pCtx->AddItem(LoadResStr("IDS_BTN_DELETE"), LoadResStr("IDS_MSG_DELETECLONK_DESC"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnDelCtx));
336 	pCtx->AddItem(LoadResStr("IDS_MSG_SETDEATHMESSAGE"), LoadResStr("IDS_MSG_SETTHEMESSAGETHATAPPEARWH"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg::CrewListItem>(this, &C4StartupPlrSelDlg::CrewListItem::OnDeathMessageCtx));
337 	return pCtx;
338 }
339 
OnDeathMessageCtx(C4GUI::Element * el)340 void C4StartupPlrSelDlg::CrewListItem::OnDeathMessageCtx(C4GUI::Element *el)
341 {
342 	// Death message dialog
343 	C4GUI::InputDialog *pDlg;
344 	GetScreen()->ShowRemoveDlg(pDlg=new C4GUI::InputDialog(LoadResStr("IDS_MSG_ENTERNEWDEATHMESSAGE"), LoadResStr("IDS_MSG_SETDEATHMESSAGE"), C4GUI::Ico_Ex_Comment, new C4GUI::InputCallback<C4StartupPlrSelDlg::CrewListItem>(this, &C4StartupPlrSelDlg::CrewListItem::OnDeathMessageSet), false));
345 	pDlg->SetMaxText(C4MaxDeathMsg);
346 	pDlg->SetInputText(Core.DeathMessage);
347 }
348 
OnDeathMessageSet(const StdStrBuf & rsNewMessage)349 void C4StartupPlrSelDlg::CrewListItem::OnDeathMessageSet(const StdStrBuf &rsNewMessage)
350 {
351 	// copy msg
352 	if (!rsNewMessage) *Core.DeathMessage='\0'; else SCopy(rsNewMessage.getData(), Core.DeathMessage, C4MaxDeathMsg);
353 	// save
354 	RewriteCore();
355 	// acoustic feedback
356 	C4GUI::GUISound("UI::Confirmed");
357 }
358 
RewriteCore()359 void C4StartupPlrSelDlg::CrewListItem::RewriteCore()
360 {
361 	if (!fLoaded) return;
362 	C4Group CrewGroup;
363 	if (!CrewGroup.OpenAsChild(pParentGrp, GetFilename().getData())
364 	    || !Core.Save(CrewGroup, nullptr)
365 	    || !CrewGroup.Close() || !pParentGrp->Save(true))
366 	{
367 		GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_MODIFY"), "", C4GUI::Ico_Error);
368 		return;
369 	}
370 }
371 
SetName(const char * szNewName)372 bool C4StartupPlrSelDlg::CrewListItem::SetName(const char *szNewName)
373 {
374 	if (!fLoaded) return false;
375 	// validate name
376 	if (!szNewName || !*szNewName) return false;
377 	if (SEqual(szNewName, Core.Name)) return true;
378 	// generate filename from new name
379 	char fn[_MAX_PATH+1];
380 	SCopy(szNewName, fn, _MAX_PATH);
381 	MakeFilenameFromTitle(fn);
382 	if (!*fn) return false;
383 	SAppend(".oci", fn, _MAX_PATH);
384 	// check if a rename is due
385 	if (!ItemIdentical(fn, GetFilename().getData()))
386 	{
387 		// check for duplicate filename
388 		if (pParentGrp->FindEntry(fn))
389 		{
390 			StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_CLONKCOLLISION"), fn);
391 			::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_FAIL_RENAME"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
392 			return false;
393 		}
394 		// OK; then rename
395 		if (!pParentGrp->Rename(GetFilename().getData(), fn) || !pParentGrp->Save(true))
396 		{
397 			StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_RENAMEFILE"), GetFilename().getData(), fn);
398 			::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_FAIL_RENAME"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
399 			return false;
400 		}
401 		const char *szConstFn = fn;
402 		SetFilename(StdStrBuf(szConstFn));
403 	}
404 	// update clonk name and core
405 	ListItem::SetName(szNewName);
406 	SCopy(szNewName, Core.Name, C4MaxName);
407 	RewriteCore();
408 	return true;
409 }
410 
SetSelectionInfo(C4GUI::TextWindow * pSelectionInfo)411 void C4StartupPlrSelDlg::CrewListItem::SetSelectionInfo(C4GUI::TextWindow *pSelectionInfo)
412 {
413 	// write info text for player
414 	pSelectionInfo->ClearText(false);
415 	pSelectionInfo->AddTextLine(FormatString("%s %s", Core.sRankName.getData(), Core.Name).getData(), &C4Startup::Get()->Graphics.BookFontCapt, ClrPlayerItem, false, false);
416 	StdStrBuf sPromo;
417 	int32_t iNextRankExp; StdStrBuf sNextRankName;
418 	if (Core.GetNextRankInfo(::DefaultRanks, &iNextRankExp, &sNextRankName))
419 		sPromo.Format(LoadResStr("IDS_DESC_PROMO"),sNextRankName.getData(),(int) iNextRankExp);
420 	else
421 		sPromo.Copy(LoadResStr("IDS_DESC_NOPROMO"));
422 	pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DESC_OBJECT"),
423 	                            Core.TypeName, Core.Experience, Core.Rounds, Core.DeathCount,
424 	                            sPromo.getData(), TimeString(Core.TotalPlayingTime).getData(), DateString(Core.Birthday).getData()).getData(),
425 	                            &C4Startup::Get()->Graphics.BookFont, ClrPlayerItem, false, false);
426 	pSelectionInfo->UpdateHeight();
427 }
428 
GetDelWarning()429 StdStrBuf C4StartupPlrSelDlg::CrewListItem::GetDelWarning()
430 {
431 	StdStrBuf sWarning;
432 	sWarning.Format(LoadResStr("IDS_MSG_DELETECLONK"),
433 	                Core.sRankName.getData(), Core.Name, GetFilename().getData());
434 	int32_t iPlrTime = Core.TotalPlayingTime;
435 	if (iPlrTime > 60*60*10)
436 		sWarning.Append(static_cast<const StdStrBuf &>(FormatString(LoadResStr("IDS_MSG_DELETECLONK_PLAYTIME"), TimeString(iPlrTime).getData())));
437 	return sWarning;
438 }
439 
CrewRename()440 void C4StartupPlrSelDlg::CrewListItem::CrewRename()
441 {
442 	if (pPlrSelDlg->pRenameEdit) return;
443 	// rename this entry
444 	pPlrSelDlg->pRenameEdit = new C4GUI::CallbackRenameEdit<C4StartupPlrSelDlg::CrewListItem, RenameParams>(pNameLabel, this, RenameParams(), &C4StartupPlrSelDlg::CrewListItem::DoRenaming, &C4StartupPlrSelDlg::CrewListItem::AbortRenaming);
445 }
446 
AbortRenaming(RenameParams par)447 void C4StartupPlrSelDlg::CrewListItem::AbortRenaming(RenameParams par)
448 {
449 	// no renaming
450 	pPlrSelDlg->pRenameEdit = nullptr;
451 }
452 
DoRenaming(RenameParams par,const char * szNewName)453 C4GUI::RenameEdit::RenameResult C4StartupPlrSelDlg::CrewListItem::DoRenaming(RenameParams par, const char *szNewName)
454 {
455 	// accept if name can be set; will fail if name is invalid or already given to another Crew member
456 	if (!SetName(szNewName)) return C4GUI::RenameEdit::RR_Invalid;
457 	pPlrSelDlg->pRenameEdit = nullptr;
458 	// update in selection
459 	C4StartupPlrSelDlg *pDlg = static_cast<C4StartupPlrSelDlg *>(GetDlg());
460 	if (pDlg && pDlg->GetSelection() == this) pDlg->UpdateSelection();
461 	return C4GUI::RenameEdit::RR_Accepted;
462 }
463 
464 
465 
466 // ------------------------------------------------
467 // --- C4StartupPlrSelDlg
468 
C4StartupPlrSelDlg()469 C4StartupPlrSelDlg::C4StartupPlrSelDlg() : C4StartupDlg("W")
470 {
471 	// ctor
472 	UpdateSize(); // for clientrect
473 
474 	// screen calculations
475 	int iButtonHeight = C4GUI_ButtonHgt;
476 	int iButtonXSpacing = (GetClientRect().Wdt > 700) ? GetClientRect().Wdt/58 : 2;
477 	int iButtonCount = 6;
478 	C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
479 	C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(std::max(caMain.GetHeight()/15, iButtonHeight)),0,0);
480 	rcBottomButtons = caButtonArea.GetCentered(caMain.GetWidth(), iButtonHeight);
481 	iBottomButtonWidth = (caButtonArea.GetWidth() - iButtonXSpacing * (iButtonCount-1)) / iButtonCount;
482 	C4Rect rcMain = caMain.GetAll();
483 	C4Rect rcPlrList = C4Rect(rcMain.Wdt/8, rcMain.Hgt/8, rcMain.Wdt*5/16, rcMain.Hgt*6/8);
484 	C4Rect rcInfoWindow = C4Rect(rcMain.Wdt*9/16, rcMain.Hgt/8, rcMain.Wdt*5/16, rcMain.Hgt*6/8);
485 
486 	AddElement(pPlrListBox = new C4GUI::ListBox(rcPlrList));
487 	pPlrListBox->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERFILES"));
488 	pPlrListBox->SetDecoration(true, &C4Startup::Get()->Graphics.sfctBookScroll, true);
489 	pPlrListBox->UpdateElementPositions();
490 	pPlrListBox->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(this, &C4StartupPlrSelDlg::OnSelChange));
491 	pPlrListBox->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(this, &C4StartupPlrSelDlg::OnSelDblClick));
492 	AddElement(pSelectionInfo = new C4GUI::TextWindow(rcInfoWindow,0,0,0,100,4096,"  ",false,nullptr,0,true));
493 	pSelectionInfo->SetDecoration(true, true, &C4Startup::Get()->Graphics.sfctBookScroll, true);
494 	pSelectionInfo->UpdateHeight();
495 
496 	// bottom line buttons - positioning done in UpdateBottomButtons by UpdatePlayerList
497 	C4Rect rcDefault(0,0,10,10);
498 	AddElement(btnBack = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_BACK"), rcDefault, &C4StartupPlrSelDlg::OnBackBtn));
499 	AddElement(btnNew = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_NEW"), rcDefault, &C4StartupPlrSelDlg::OnNewBtn));
500 	btnNew->SetToolTip(LoadResStr("IDS_DLGTIP_NEWPLAYER"));
501 	AddElement(btnActivatePlr = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(nullptr, rcDefault, &C4StartupPlrSelDlg::OnActivateBtn));
502 	AddElement(btnDelete = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_DELETE"), rcDefault, &C4StartupPlrSelDlg::OnDelBtn));
503 	AddElement(btnProperties = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(nullptr, rcDefault, &C4StartupPlrSelDlg::OnPropertyBtn));
504 	AddElement(btnCrew = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_SELECT_CREW"), rcDefault, &C4StartupPlrSelDlg::OnCrewBtn));
505 	btnCrew->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERCREW"));
506 
507 	// refill listboxes
508 	UpdatePlayerList();
509 	// Just safety incase listbox was empty, in which case no selection change callback will have been done:
510 	// Update current listbox selection to that of no selected item
511 	if (!pPlrListBox->GetFirst()) UpdateSelection();
512 
513 	// initial focus on player list
514 	SetFocus(pPlrListBox, false);
515 
516 	// key bindings
517 	C4CustomKey::CodeList keys;
518 	keys.emplace_back(K_BACK);
519 	keys.emplace_back(K_LEFT);
520 	keys.emplace_back(K_ESCAPE);
521 	if (Config.Controls.GamepadGuiControl)
522 	{
523 		ControllerKeys::Cancel(keys);
524 	}
525 	pKeyBack = new C4KeyBinding(keys, "StartupPlrSelBack", KEYSCOPE_Gui,
526 	                            new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyBack), C4CustomKey::PRIO_CtrlOverride);
527 	pKeyProperties = new C4KeyBinding(C4KeyCodeEx(K_F2), "StartupPlrSelProp", KEYSCOPE_Gui,
528 	                                  new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyProperties), C4CustomKey::PRIO_CtrlOverride);
529 	pKeyCrew = new C4KeyBinding(C4KeyCodeEx(K_RIGHT), "StartupPlrSelCrew", KEYSCOPE_Gui,
530 	                            new C4GUI::ControlKeyDlgCB<C4StartupPlrSelDlg>(pPlrListBox, *this, &C4StartupPlrSelDlg::KeyCrew), C4CustomKey::PRIO_CtrlOverride);
531 	pKeyDelete = new C4KeyBinding(C4KeyCodeEx(K_DELETE), "StartupPlrSelDelete", KEYSCOPE_Gui,
532 	                              new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyDelete), C4CustomKey::PRIO_CtrlOverride);
533 	pKeyNew = new C4KeyBinding(C4KeyCodeEx(K_INSERT), "StartupPlrSelNew", KEYSCOPE_Gui,
534 	                           new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyNew), C4CustomKey::PRIO_CtrlOverride);
535 }
536 
~C4StartupPlrSelDlg()537 C4StartupPlrSelDlg::~C4StartupPlrSelDlg()
538 {
539 	delete pKeyDelete;
540 	delete pKeyCrew;
541 	delete pKeyProperties;
542 	delete pKeyBack;
543 	delete pKeyNew;
544 }
545 
AbortRenaming()546 void C4StartupPlrSelDlg::AbortRenaming()
547 {
548 	if (pRenameEdit) pRenameEdit->Abort();
549 }
550 
DrawElement(C4TargetFacet & cgo)551 void C4StartupPlrSelDlg::DrawElement(C4TargetFacet &cgo)
552 {
553 	// draw background
554 	typedef C4GUI::FullscreenDialog Base;
555 	Base::DrawElement(cgo);
556 }
557 
UpdateBottomButtons()558 void C4StartupPlrSelDlg::UpdateBottomButtons()
559 {
560 	// bottom line buttons depend on list mode
561 	C4GUI::ComponentAligner caBottomButtons(rcBottomButtons,0,0);
562 	switch (eMode)
563 	{
564 	case PSDM_Player:
565 	{
566 		// update some buttons for player mode
567 		btnProperties->SetText(LoadResStr("IDS_BTN_PROPERTIES"));
568 		btnProperties->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERPROPERTIES"));
569 		btnNew->SetVisibility(true);
570 		btnCrew->SetVisibility(true);
571 		btnDelete->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERDELETE"));
572 		btnBack->SetToolTip(LoadResStr("IDS_DLGTIP_BACKMAIN"));
573 		btnBack       ->SetBounds(caBottomButtons.GetGridCell(0,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
574 		btnNew        ->SetBounds(caBottomButtons.GetGridCell(1,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
575 		btnActivatePlr->SetBounds(caBottomButtons.GetGridCell(2,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
576 		btnDelete     ->SetBounds(caBottomButtons.GetGridCell(3,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
577 		btnProperties ->SetBounds(caBottomButtons.GetGridCell(4,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
578 		btnCrew       ->SetBounds(caBottomButtons.GetGridCell(5,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
579 	}
580 	break;
581 
582 	case PSDM_Crew:
583 	{
584 		// update some buttons for player mode
585 		btnProperties->SetText(LoadResStr("IDS_BTN_RENAME"));
586 		btnProperties->SetToolTip(LoadResStr("IDS_DESC_CREWRENAME"));
587 		btnNew->SetVisibility(false);
588 		btnCrew->SetVisibility(false);
589 		btnDelete->SetToolTip(LoadResStr("IDS_MSG_DELETECLONK_DESC"));
590 		btnBack->SetToolTip(LoadResStr("IDS_MSG_BACKTOPLAYERDLG"));
591 		btnBack       ->SetBounds(caBottomButtons.GetGridCell(0,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
592 		btnActivatePlr->SetBounds(caBottomButtons.GetGridCell(1,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
593 		btnDelete     ->SetBounds(caBottomButtons.GetGridCell(2,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
594 		btnProperties ->SetBounds(caBottomButtons.GetGridCell(3,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
595 	}
596 	break;
597 	};
598 }
599 
UpdatePlayerList()600 void C4StartupPlrSelDlg::UpdatePlayerList()
601 {
602 	// something has changed!
603 	AbortRenaming();
604 	// refill pPlrListBox with players in player folder or crew
605 	// clear old items
606 	C4GUI::Element *pEl;
607 	while ((pEl = pPlrListBox->GetFirst())) delete pEl;
608 	// update command buttons
609 	UpdateBottomButtons();
610 	// create new
611 	switch (eMode)
612 	{
613 	case PSDM_Player:
614 	{
615 		SetTitle(LoadResStrNoAmp("IDS_DLG_PLAYERSELECTION"));
616 		// player mode: insert all players
617 		const char *szFn;
618 		StdStrBuf sSearchPath(Config.General.UserDataPath);
619 		PlayerListItem *pFirstActivatedPlrItem=nullptr, *pFirstDeactivatedPlrItem=nullptr, *pPlrItem=nullptr;
620 		for (DirectoryIterator i(sSearchPath.getData()); (szFn=*i); i++)
621 		{
622 			szFn = Config.AtRelativePath(szFn);
623 			if (*GetFilename(szFn) == '.') continue; // ignore ".", ".." and private files (".*")
624 			if (!WildcardMatch(C4CFN_PlayerFiles, GetFilename(szFn))) continue;
625 			bool fIsParticipating = !!SIsModule(Config.General.Participants, szFn, nullptr, false);
626 			pPlrItem = new PlayerListItem(this, pPlrListBox, nullptr, fIsParticipating);
627 			try
628 			{
629 				pPlrItem->Load(StdStrBuf(szFn));
630 			}
631 			catch (ListItem::LoadError & e)
632 			{
633 				// invalid player: ignore but log error message
634 				DebugLog(e.getData());
635 				delete pPlrItem;
636 				continue;
637 			}
638 			if (fIsParticipating)
639 			{
640 				if (!pFirstActivatedPlrItem) pFirstActivatedPlrItem = pPlrItem;
641 			}
642 			else if (!pFirstDeactivatedPlrItem) pFirstDeactivatedPlrItem = pPlrItem;
643 		}
644 		// select first element; prefer activated player
645 		if (!(pPlrItem = pFirstActivatedPlrItem))
646 			pPlrItem = pFirstDeactivatedPlrItem;
647 		if (pPlrItem)
648 			pPlrListBox->SelectEntry(pPlrItem, false);
649 		// re-validate Game.PlayerFilename
650 		UpdateActivatedPlayers();
651 		break;
652 	}
653 
654 	case PSDM_Crew:
655 	{
656 		SetTitle(FormatString("%s %s", LoadResStrNoAmp("IDS_CTL_CREW"), CurrPlayer.Core.PrefName).getData());
657 		// crew mode: Insert complete crew of player (2do: sort)
658 		bool fSucc; char szFn[_MAX_PATH+1];
659 		for (fSucc=CurrPlayer.Grp.FindEntry(C4CFN_ObjectInfoFiles, szFn); fSucc; fSucc=CurrPlayer.Grp.FindNextEntry(C4CFN_ObjectInfoFiles, szFn, nullptr, true))
660 		{
661 			CrewListItem *pCrewItem = new CrewListItem(this, pPlrListBox, CurrPlayer.Core.PrefColorDw);
662 			try
663 			{
664 				pCrewItem->Load(CurrPlayer.Grp, StdStrBuf(szFn));
665 			}
666 			catch (ListItem::LoadError & e)
667 			{
668 				// invalid player: ignore but log error message
669 				DebugLog(e.getData());
670 				delete pCrewItem;
671 				continue;
672 			}
673 		}
674 		// resort crew by type and experience
675 		ResortCrew();
676 		pPlrListBox->SelectFirstEntry(false);
677 		break;
678 	}
679 	}
680 }
681 
GetSelection()682 C4StartupPlrSelDlg::ListItem *C4StartupPlrSelDlg::GetSelection()
683 {
684 	// get selected item: There's only instances of PlrListItem in this list
685 	return static_cast<ListItem *>(pPlrListBox->GetSelectedItem());
686 }
687 
SetSelection(ListItem * pNewItem)688 void C4StartupPlrSelDlg::SetSelection(ListItem *pNewItem)
689 {
690 	// update selection in listbox
691 	pPlrListBox->SelectEntry(pNewItem, false);
692 }
693 
UpdateSelection()694 void C4StartupPlrSelDlg::UpdateSelection()
695 {
696 	// something has changed!
697 	AbortRenaming();
698 	// get currently selected player
699 	ListItem *pSel = GetSelection();
700 	// button text 'activate' if current player is deactivated; 'deactivate' otherwise
701 	if (pSel && pSel->IsActivated())
702 	{
703 		btnActivatePlr->SetText(LoadResStr("IDS_BTN_DEACTIVATE"));
704 		btnActivatePlr->SetToolTip(FormatString(LoadResStr("IDS_MSG_NOPARTICIPATE_DESC"), pSel->GetName()).getData());
705 	}
706 	else
707 	{
708 		btnActivatePlr->SetText(LoadResStr("IDS_BTN_ACTIVATE"));
709 		btnActivatePlr->SetToolTip(FormatString(LoadResStr("IDS_MSG_PARTICIPATE_DESC"), pSel ? pSel->GetName() : "").getData());
710 	}
711 	// no item selected?
712 	if (!pSel)
713 	{
714 		pSelectionInfo->ClearText(true);
715 		// 2do: disable buttons
716 		return;
717 	}
718 	// info text for selection
719 	pSel->SetSelectionInfo(pSelectionInfo);
720 }
721 
OnItemCheckChange(C4GUI::Element * pCheckBox)722 void C4StartupPlrSelDlg::OnItemCheckChange(C4GUI::Element *pCheckBox)
723 {
724 	switch (eMode)
725 	{
726 	case PSDM_Player:
727 		// Deselect all other players
728 		for (ListItem* pEl = static_cast<ListItem*>(pPlrListBox->GetFirst()); pEl != nullptr; pEl = pEl->GetNext())
729 			if (pCheckBox && pEl != pCheckBox->GetParent())
730 				pEl->SetActivated(false);
731 		// update Config.General.Participants
732 		UpdateActivatedPlayers();
733 		break;
734 	case PSDM_Crew:
735 		// update affected crew item
736 		if (pCheckBox) static_cast<CrewListItem *>(pCheckBox->GetParent())->UpdateClonkEnabled();
737 		break;
738 	}
739 	// update player selection text
740 	UpdateSelection();
741 }
742 
UpdateActivatedPlayers()743 void C4StartupPlrSelDlg::UpdateActivatedPlayers()
744 {
745 	assert(eMode == PSDM_Player);
746 	// refill Config.General.Participants-list
747 	*Config.General.Participants = '\0';
748 	for (ListItem *pPlrItem = static_cast<ListItem *>(pPlrListBox->GetFirst()); pPlrItem; pPlrItem = pPlrItem->GetNext())
749 		if (pPlrItem->IsActivated())
750 		{
751 			const char *szAddFilename = pPlrItem->GetFilename().getData();
752 			if (std::strlen(Config.General.Participants) + 1 + std::strlen(szAddFilename) < sizeof(Config.General.Participants))
753 				SAddModule(Config.General.Participants, szAddFilename);
754 			else
755 			{
756 				pPlrItem->SetActivated(false);
757 				GetScreen()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLAYERSTOOLONG"), pPlrItem->GetName()).getData(), LoadResStr("IDS_ERR_TITLE"), C4GUI::Ico_Error);
758 			}
759 		}
760 }
761 
OnActivateBtn(C4GUI::Control * btn)762 void C4StartupPlrSelDlg::OnActivateBtn(C4GUI::Control *btn)
763 {
764 	// toggle activation state of current item
765 	// get currently selected player
766 	ListItem *pSel = GetSelection();
767 	if (!pSel) return;
768 	pSel->SetActivated(!pSel->IsActivated());
769 	// update stuff
770 	OnItemCheckChange(pSel->GetCheckBox());
771 }
772 
DoBack()773 void C4StartupPlrSelDlg::DoBack()
774 {
775 	switch (eMode)
776 	{
777 	case PSDM_Player:
778 	{
779 		// back 2 main
780 		C4Startup::Get()->SwitchDialog(C4Startup::SDID_Back);
781 		break;
782 	}
783 
784 	case PSDM_Crew:
785 		// back 2 player list
786 		SetPlayerMode();
787 		break;
788 	}
789 }
790 
OnNewBtn(C4GUI::Control * btn)791 void C4StartupPlrSelDlg::OnNewBtn(C4GUI::Control *btn)
792 {
793 	if (eMode != PSDM_Player) return;
794 	C4GUI::Dialog *pDlg;
795 	GetScreen()->ShowRemoveDlg(pDlg=new C4StartupPlrPropertiesDlg(nullptr, this));
796 	pDlg->SetPos(std::min<int32_t>(GetBounds().Wdt/10, GetBounds().Wdt - pDlg->GetBounds().Wdt), std::min<int32_t>(GetBounds().Hgt/4, GetBounds().Hgt - pDlg->GetBounds().Hgt));
797 }
798 
CheckPlayerName(const StdStrBuf & Playername,StdStrBuf & Filename,const StdStrBuf * pPrevFilename,bool fWarnEmpty)799 bool C4StartupPlrSelDlg::CheckPlayerName(const StdStrBuf &Playername, StdStrBuf &Filename, const StdStrBuf *pPrevFilename, bool fWarnEmpty)
800 {
801 	// must not be empty
802 	if (!Playername.getLength())
803 	{
804 		if (fWarnEmpty) C4GUI::Screen::GetScreenS()->ShowMessage(LoadResStr("IDS_ERR_PLRNAME_EMPTY"), "", C4GUI::Ico_Error);
805 		return false;
806 	}
807 	// generate valid filename
808 	Filename.Copy(Playername);
809 	// Slashes in Filenames are no good
810 	SReplaceChar(Filename.getMData(), '\\', '_');
811 	SReplaceChar(Filename.getMData(), '/', '_');
812 	SReplaceChar(Filename.getMData(), ':', '_');
813 	SReplaceChar(Filename.getMData(), '*', '_');
814 	SReplaceChar(Filename.getMData(), '?', '_');
815 	SReplaceChar(Filename.getMData(), '"', '_');
816 	SReplaceChar(Filename.getMData(), '<', '_');
817 	SReplaceChar(Filename.getMData(), '>', '_');
818 	SReplaceChar(Filename.getMData(), '|', '_');
819 	if (*Filename.getData() == '.') *Filename.getMData() = '_';
820 	Filename.Append(".ocp");
821 	StdStrBuf Path(Config.General.UserDataPath); // start at local path
822 
823 	Path.Append(Filename);
824 	// validity check: Must not exist yet if renamed
825 	if (!pPrevFilename || !ItemIdentical(Path.getData(), Config.AtUserDataPath(pPrevFilename->getData()))) if (ItemExists(Path.getData()))
826 	{
827 		C4GUI::Screen::GetScreenS()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLRNAME_TAKEN"),
828 		    Playername.getData()).getData(), "", C4GUI::Ico_Error);
829 		return false;
830 	}
831 
832 	return true;
833 }
834 
OnCrewBtn(C4GUI::Control * btn)835 void C4StartupPlrSelDlg::OnCrewBtn(C4GUI::Control *btn)
836 {
837 	// display crew for activated player
838 	if (eMode != PSDM_Player) return;
839 	PlayerListItem *pSel = static_cast<PlayerListItem *>(GetSelection());
840 	if (!pSel) return;
841 	SetCrewMode(pSel);
842 }
843 
SetPlayerMode()844 void C4StartupPlrSelDlg::SetPlayerMode()
845 {
846 	// change view to listing players
847 	C4GUI::GUISound("UI::Close");
848 	StdStrBuf LastPlrFilename;
849 	LastPlrFilename.Copy(static_cast<const StdStrBuf &>(CurrPlayer.Grp.GetFullName()));
850 	CurrPlayer.Grp.Close();
851 	eMode = PSDM_Player;
852 	UpdatePlayerList();
853 	SelectItem(LastPlrFilename, false);
854 	UpdateSelection();
855 }
856 
SetCrewMode(PlayerListItem * pSel)857 void C4StartupPlrSelDlg::SetCrewMode(PlayerListItem *pSel)
858 {
859 	// change view to listing crew of a player
860 	CurrPlayer.Core = pSel->GetCore();
861 
862 	StdStrBuf Path(Config.General.UserDataPath); // start at local path
863 //  Path.Append(Config.General.PlayerPath);
864 	Path.Append(pSel->GetFilename());
865 
866 	if (!CurrPlayer.Grp.Open(Path.getData())) return;
867 	if (!CurrPlayer.Grp.FindEntry(C4CFN_ObjectInfoFiles))
868 	{
869 		StdCopyStrBuf strCrew(FormatString("%s %s", LoadResStrNoAmp("IDS_CTL_CREW"), CurrPlayer.Core.PrefName));
870 		// player has no crew!
871 		GetScreen()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLRNOCREW"),
872 		                                      CurrPlayer.Core.PrefName).getData(),
873 		                         strCrew.getData(), C4GUI::Ico_Player);
874 		return;
875 	}
876 	C4GUI::GUISound("UI::Open");
877 	eMode = PSDM_Crew;
878 	UpdatePlayerList();
879 	UpdateSelection();
880 }
881 
OnDelBtn(C4GUI::Control * btn)882 void C4StartupPlrSelDlg::OnDelBtn(C4GUI::Control *btn)
883 {
884 	// something has changed!
885 	AbortRenaming();
886 	// delete selected player
887 	ListItem *pSel = GetSelection();
888 	if (!pSel) return;
889 	StdStrBuf sWarning; sWarning.Take(pSel->GetDelWarning());
890 	GetScreen()->ShowRemoveDlg(new C4GUI::ConfirmationDialog(sWarning.getData(), LoadResStr("IDS_BTN_DELETE"),
891 	                           new C4GUI::CallbackHandlerExPar<C4StartupPlrSelDlg, ListItem *>(this, &C4StartupPlrSelDlg::OnDelBtnConfirm, pSel), C4GUI::MessageDialog::btnYesNo));
892 }
893 
OnDelBtnConfirm(ListItem * pSel)894 void C4StartupPlrSelDlg::OnDelBtnConfirm(ListItem *pSel)
895 {
896 	StdStrBuf Path;
897 
898 	switch (eMode)
899 	{
900 	case PSDM_Player:
901 		Path.Append(Config.General.UserDataPath); // start at local path
902 		Path.Append(pSel->GetFilename());
903 		if (!C4Group_DeleteItem(Path.getData()))
904 		{
905 			StdStrBuf sMsg; sMsg.Copy(LoadResStr("IDS_FAIL_DELETE"));
906 			GetScreen()->ShowMessage(sMsg.getData(), LoadResStr("IDS_DLG_CLEAR"), C4GUI::Ico_Error);
907 		}
908 		break;
909 
910 	case PSDM_Crew:
911 		if (!CurrPlayer.Grp.DeleteEntry(pSel->GetFilename().getData()))
912 		{
913 			StdStrBuf sMsg; sMsg.Copy(LoadResStr("IDS_FAIL_DELETE"));
914 			GetScreen()->ShowMessage(sMsg.getData(), LoadResStr("IDS_DLG_CLEAR"), C4GUI::Ico_Error);
915 		}
916 		break;
917 	}
918 	// update buttons 'n stuff
919 	UpdatePlayerList();
920 }
921 
SelectItem(const StdStrBuf & Filename,bool fActivate)922 void C4StartupPlrSelDlg::SelectItem(const StdStrBuf &Filename, bool fActivate)
923 {
924 	// find item
925 	for (ListItem *pPlrItem = static_cast<ListItem *>(pPlrListBox->GetFirst()); pPlrItem; pPlrItem = pPlrItem->GetNext())
926 		if (ItemIdentical(pPlrItem->GetFilename().getData(), Filename.getData()))
927 		{
928 			// select it
929 			pPlrListBox->SelectEntry(pPlrItem, false);
930 			// activate it
931 			if (fActivate)
932 			{
933 				pPlrItem->SetActivated(true);
934 				// player activation updates
935 				OnItemCheckChange(pPlrItem->GetCheckBox());
936 			}
937 			// max one
938 			return;
939 		}
940 }
941 
OnPropertyBtn(C4GUI::Control * btn)942 void C4StartupPlrSelDlg::OnPropertyBtn(C4GUI::Control *btn)
943 {
944 	// something has changed!
945 	AbortRenaming();
946 	switch (eMode)
947 	{
948 	case PSDM_Player:
949 	{
950 		// show property dialog for selected player
951 		PlayerListItem *pSel = static_cast<PlayerListItem *>(GetSelection());
952 		if (!pSel) return;
953 		C4GUI::Dialog *pDlg;
954 		GetScreen()->ShowRemoveDlg(pDlg=new C4StartupPlrPropertiesDlg(pSel, this));
955 		pDlg->SetPos(std::min<int32_t>(GetBounds().Wdt/10, GetBounds().Wdt - pDlg->GetBounds().Wdt),
956 		             (GetBounds().Hgt - pDlg->GetBounds().Hgt) / 2);
957 	}
958 	break;
959 
960 	case PSDM_Crew:
961 		// rename crew
962 		CrewListItem *pSel = static_cast<CrewListItem *>(GetSelection());
963 		if (!pSel) return;
964 		pSel->CrewRename();
965 		break;
966 	}
967 }
968 
969 
970 /* -- Crew sorting -- */
971 
972 struct C4StartupPlrSelDlg_CrewSortDataEntry
973 {
974 	int32_t iMaxExp;
975 	C4ID idType;
976 
C4StartupPlrSelDlg_CrewSortDataEntryC4StartupPlrSelDlg_CrewSortDataEntry977 	C4StartupPlrSelDlg_CrewSortDataEntry(int32_t iMaxExp, C4ID idType) : iMaxExp(iMaxExp), idType(idType) {}
978 };
979 
980 class C4StartupPlrSelDlg_CrewSortDataMatchType
981 {
982 	C4ID idType;
983 
984 public:
C4StartupPlrSelDlg_CrewSortDataMatchType(C4ID idType)985 	C4StartupPlrSelDlg_CrewSortDataMatchType(C4ID idType) : idType(idType) {}
operator ()(C4StartupPlrSelDlg_CrewSortDataEntry Check)986 	bool operator()(C4StartupPlrSelDlg_CrewSortDataEntry Check) { return Check.idType == idType; }
987 };
988 
989 typedef std::vector<C4StartupPlrSelDlg_CrewSortDataEntry> C4StartupPlrSelDlg_CrewSortData;
990 
CrewSortFunc(const C4GUI::Element * pEl1,const C4GUI::Element * pEl2,void * par)991 int32_t C4StartupPlrSelDlg::CrewSortFunc(const C4GUI::Element *pEl1, const C4GUI::Element *pEl2, void *par)
992 {
993 	const auto *pItem1 = static_cast<const CrewListItem *>(pEl1);
994 	const auto *pItem2 = static_cast<const CrewListItem *>(pEl2);
995 	C4StartupPlrSelDlg_CrewSortData &rSortData = *static_cast<C4StartupPlrSelDlg_CrewSortData *>(par);
996 	C4StartupPlrSelDlg_CrewSortData::iterator i = std::find_if(rSortData.begin(), rSortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pItem1->GetCore().id)),
997 	    j = std::find_if(rSortData.begin(), rSortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pItem2->GetCore().id));
998 	// primary sort: By Clonk type, where high exp Clonk types are sorted atop low exp Clonk types
999 	if (i != j)
1000 	{
1001 		if (i == rSortData.end()) return -1; else if (j == rSortData.end()) return +1; // can't really happen
1002 		return (*i).iMaxExp - (*j).iMaxExp;
1003 	}
1004 	// secondary: By experience
1005 	return pItem1->GetCore().Experience - pItem2->GetCore().Experience;
1006 }
1007 
ResortCrew()1008 void C4StartupPlrSelDlg::ResortCrew()
1009 {
1010 	assert(eMode == PSDM_Crew);
1011 	// create a list of Clonk types and their respective maximum experience
1012 	C4StartupPlrSelDlg_CrewSortData SortData;
1013 	for (CrewListItem *pCrewItem = static_cast<CrewListItem *>(pPlrListBox->GetFirst()); pCrewItem; pCrewItem = pCrewItem->GetNext())
1014 	{
1015 		C4StartupPlrSelDlg_CrewSortData::iterator i = std::find_if(SortData.begin(), SortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pCrewItem->GetCore().id));
1016 		if (i == SortData.end())
1017 			SortData.emplace_back(pCrewItem->GetCore().Experience, pCrewItem->GetCore().id);
1018 		else
1019 			(*i).iMaxExp = std::max<int32_t>((*i).iMaxExp, pCrewItem->GetCore().Experience);
1020 	}
1021 	pPlrListBox->SortElements(&CrewSortFunc, &SortData);
1022 }
1023 
1024 // ------------------------------------------------
1025 // --- Player color HSV chooser
1026 class C4StartupPlrColorPickerDlg : public C4GUI::Dialog
1027 {
1028 public:
1029 	C4StartupPlrColorPickerDlg(C4PlayerInfoCore *plrcore);
1030 
1031 protected:
1032 	// Event handler
1033 	void OnClosed(bool commit) override;
1034 
1035 private:
1036 	class Picker : public C4GUI::Control
1037 	{
1038 	public:
1039 		Picker(const C4Rect &bounds);
1040 
1041 		// Set/retrieve current color value
1042 		void SetColor(uint32_t rgb);
1043 		uint32_t GetColor() const;
1044 
1045 	protected:
1046 		// Event handlers, overridden from C4GUI::Control
1047 		void DrawElement(C4TargetFacet &cgo) override;
1048 		void MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) override;
1049 		void DoDragging(C4GUI::CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam) override;
1050 
1051 	private:
1052 		static const unsigned int HSPickerCursorSize = 5;
1053 		static const unsigned int VPickerCursorSize = 7;
1054 		C4FacetSurface hsFacet, vFacet; // chooser backgrounds
1055 		C4Rect hsPickerRect, vPickerRect;
1056 		C4GUI::Picture *flagPreview, *crewPreview;
1057 		uint32_t hsv; // current color
1058 		enum {
1059 			PS_Idle, // user isn't dragging anything
1060 			PS_IdleDragging, // user started the drag on empty space
1061 			PS_DragHS, // user started the drag over the HS picker
1062 			PS_DragV // user started the drag over the V picker
1063 		} state;
1064 
1065 		bool HandleMouseDown(int32_t x, int32_t y);
1066 		void UpdateVFacet(uint32_t h, uint32_t s);
1067 		void UpdatePreview();
1068 	};
1069 
1070 	C4PlayerInfoCore *plrcore;
1071 	Picker *picker;
1072 
HSV2RGB(uint32_t hsv)1073 	static uint32_t HSV2RGB(uint32_t hsv)
1074 	{
1075 		float h = GetRedValue(hsv) / 255.f * 6.f;
1076 		float s = GetGreenValue(hsv) / 255.f;
1077 		float v = GetBlueValue(hsv) / 255.f;
1078 
1079 		float chroma = s * v;
1080 		float x = chroma * (1.f - std::abs(std::fmod(h, 2.f) - 1.f));
1081 
1082 		float r = 0;
1083 		float g = 0;
1084 		float b = 0;
1085 
1086 		switch (static_cast<int>(h))
1087 		{
1088 		case 0: case 6:
1089 			r = chroma; g = x; break;
1090 		case 1:
1091 			r = x; g = chroma; break;
1092 		case 2:
1093 			g = chroma; b = x; break;
1094 		case 3:
1095 			g = x; b = chroma; break;
1096 		case 4:
1097 			b = chroma; r = x; break;
1098 		case 5:
1099 			b = x; r = chroma; break;
1100 		}
1101 		r += v-chroma;
1102 		g += v-chroma;
1103 		b += v-chroma;
1104 
1105 		return RGBA(r * 255.f, g * 255.f, b * 255.f, hsv >> 24);
1106 	}
RGB2HSV(uint32_t rgb)1107 	static uint32_t RGB2HSV(uint32_t rgb)
1108 	{
1109 		float r = GetRedValue(rgb) / 255.f;
1110 		float g = GetGreenValue(rgb) / 255.f;
1111 		float b = GetBlueValue(rgb) / 255.f;
1112 
1113 		float min = std::min(r, std::min(g, b));
1114 		float max = std::max(r, std::max(g, b));
1115 
1116 		float chroma = max - min;
1117 
1118 		float hue = 0;
1119 		if (r == max)
1120 			hue = std::fmod((g-b) / chroma, 6.f);
1121 		else if (g == max)
1122 			hue = (b-r) / chroma + 2.f;
1123 		else
1124 			hue = (r-g) / chroma + 4.f;
1125 
1126 		float h = hue / 6.f;
1127 		float s = max == 0 ? 0.f : chroma / max;
1128 		float v = max;
1129 
1130 		return RGBA(h * 255.f, s * 255.f, v * 255.f, rgb >> 24);
1131 	}
1132 };
1133 
C4StartupPlrColorPickerDlg(C4PlayerInfoCore * plrcore)1134 C4StartupPlrColorPickerDlg::C4StartupPlrColorPickerDlg(C4PlayerInfoCore *plrcore)
1135 	: Dialog(400, 296 + C4GUI_ButtonAreaHgt, LoadResStr("IDS_DLG_PLAYERCOLORSELECTION"), false), plrcore(plrcore)
1136 {
1137 	C4GUI::ComponentAligner caMain(GetClientRect(), 0, 1, true);
1138 
1139 	picker = new Picker(caMain.GetFromTop(280));
1140 	picker->SetColor(plrcore->PrefColorDw);
1141 	AddElement(picker);
1142 
1143 	// buttons
1144 	C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0, 0);
1145 	caButtonArea = C4GUI::ComponentAligner(caButtonArea.GetCentered(2*128 + 4*8, C4GUI_ButtonAreaHgt), 8, 8);
1146 	C4GUI::Button *cancelButton = new C4GUI::CancelButton(caButtonArea.GetFromRight(128));
1147 	C4GUI::Button *okButton = new C4GUI::OKButton(caButtonArea.GetFromRight(128));
1148 	AddElement(okButton);
1149 	AddElement(cancelButton);
1150 }
1151 
OnClosed(bool commit)1152 void C4StartupPlrColorPickerDlg::OnClosed(bool commit)
1153 {
1154 	// Write chosen color back to player core
1155 	if (commit)
1156 		plrcore->PrefColorDw = picker->GetColor();
1157 }
1158 
Picker(const C4Rect & bounds)1159 C4StartupPlrColorPickerDlg::Picker::Picker(const C4Rect &bounds)
1160 	: Control(bounds), state(PS_Idle)
1161 {
1162 	C4GUI::ComponentAligner caMain(bounds, 8, 8, true);
1163 	caMain.ExpandBottom(-(caMain.GetInnerHeight() - 256));
1164 	hsPickerRect = caMain.GetFromLeft(256);
1165 	vPickerRect = caMain.GetFromLeft(16 + VPickerCursorSize);
1166 	vPickerRect.Hgt = 256 - PlayerColorValueLowBound;
1167 
1168 	C4Facet &flagPreviewPic = ::GraphicsResource.fctFlagClr;
1169 	int preview_width = std::min<int>(flagPreviewPic.Wdt, caMain.GetInnerWidth());
1170 	flagPreview = new C4GUI::Picture(caMain.GetFromTop(flagPreviewPic.GetHeightByWidth(preview_width), preview_width), true);
1171 	flagPreview->SetFacet(flagPreviewPic);
1172 	AddElement(flagPreview);
1173 
1174 	C4Facet &crewPreviewPic = ::GraphicsResource.fctCrewClr;
1175 	preview_width = std::min<int>(crewPreviewPic.Wdt, caMain.GetInnerWidth());
1176 	crewPreview = new C4GUI::Picture(caMain.GetFromTop(crewPreviewPic.GetHeightByWidth(preview_width), preview_width), true);
1177 	crewPreview->SetFacet(crewPreviewPic);
1178 	AddElement(crewPreview);
1179 
1180 	// Pre-draw the H+S chooser background, it never changes anyway
1181 	hsFacet.Create(hsPickerRect.Wdt, hsPickerRect.Hgt);
1182 	hsFacet.Surface->Lock();
1183 	for (int y = 0; y < hsFacet.Hgt; ++y)
1184 		for (int x = 0; x < hsFacet.Wdt; ++x)
1185 			hsFacet.Surface->SetPixDw(x, y, HSV2RGB(C4RGB(x, 255-y, 255)));
1186 	hsFacet.Surface->Unlock();
1187 
1188 	vFacet.Create(vPickerRect.Wdt - VPickerCursorSize, vPickerRect.Hgt);
1189 	UpdateVFacet(255, 255);
1190 }
1191 
UpdateVFacet(uint32_t h,uint32_t s)1192 void C4StartupPlrColorPickerDlg::Picker::UpdateVFacet(uint32_t h, uint32_t s)
1193 {
1194 	// Draw the V chooser background according to current H+S values
1195 	vFacet.Surface->Lock();
1196 	for (int y = 0; y < vPickerRect.Hgt; ++y)
1197 		for (int x = 0; x < vFacet.Wdt; ++x)
1198 			vFacet.Surface->SetPixDw(x, y, HSV2RGB(C4RGB(h, s, 255-y)));
1199 	vFacet.Surface->Unlock();
1200 }
1201 
UpdatePreview()1202 void C4StartupPlrColorPickerDlg::Picker::UpdatePreview()
1203 {
1204 	flagPreview->SetDrawColor(HSV2RGB(hsv));
1205 	crewPreview->SetDrawColor(HSV2RGB(hsv));
1206 }
1207 
SetColor(uint32_t rgb)1208 void C4StartupPlrColorPickerDlg::Picker::SetColor(uint32_t rgb)
1209 {
1210 	hsv = RGB2HSV(rgb);
1211 	UpdateVFacet(GetRedValue(hsv), GetGreenValue(hsv));
1212 	UpdatePreview();
1213 }
1214 
GetColor() const1215 uint32_t C4StartupPlrColorPickerDlg::Picker::GetColor() const
1216 {
1217 	return HSV2RGB(hsv);
1218 }
1219 
DrawElement(C4TargetFacet & cgo)1220 void C4StartupPlrColorPickerDlg::Picker::DrawElement(C4TargetFacet &cgo)
1221 {
1222 	// H+S chooser background
1223 	C4Facet cgoPicker(cgo.Surface, cgo.TargetX + hsPickerRect.x, cgo.TargetY + hsPickerRect.y, hsPickerRect.Wdt, hsPickerRect.Hgt);
1224 	hsFacet.Draw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y);
1225 	// H+S cursor
1226 	cgoPicker.Wdt = cgoPicker.Hgt = HSPickerCursorSize;
1227 	cgoPicker.X += GetRedValue(hsv) - cgoPicker.Wdt / 2;
1228 	cgoPicker.Y += 255 - GetGreenValue(hsv) - cgoPicker.Hgt / 2;
1229 	pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y, cgoPicker.X + cgoPicker.Wdt, cgoPicker.Y + cgoPicker.Hgt, C4RGB(0, 0, 0));
1230 	pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X + cgoPicker.Wdt, cgoPicker.Y, cgoPicker.X, cgoPicker.Y + cgoPicker.Hgt, C4RGB(0, 0, 0));
1231 
1232 	// V chooser background
1233 	cgoPicker.Set(cgo.Surface, cgo.TargetX + vPickerRect.x + VPickerCursorSize, cgo.TargetY + vPickerRect.y, vPickerRect.Wdt - VPickerCursorSize, vPickerRect.Hgt);
1234 	vFacet.Draw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y);
1235 	// V cursor
1236 	cgoPicker.Wdt = cgoPicker.Hgt = VPickerCursorSize;
1237 	cgoPicker.X -= cgoPicker.Wdt / 2 + 1;
1238 	cgoPicker.Y += 255 - GetBlueValue(hsv) - cgoPicker.Hgt / 2;
1239 	for (int i = 0; i < cgoPicker.Hgt / 2 + 1; ++i)
1240 		pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X + i, cgoPicker.Y + i, cgoPicker.X + i, cgoPicker.Y + cgoPicker.Hgt - i, C4RGB(255, 255, 255));
1241 }
1242 
HandleMouseDown(int32_t x,int32_t y)1243 bool C4StartupPlrColorPickerDlg::Picker::HandleMouseDown(int32_t x, int32_t y)
1244 {
1245 	if (state == PS_IdleDragging)
1246 	{
1247 		// User is dragging something that is neither of the pickers. Ignore.
1248 		return false;
1249 	}
1250 	// Check if a drag starts or was originally started over a picker
1251 	else if (state == PS_DragHS || (state == PS_Idle && hsPickerRect.Contains(x, y)))
1252 	{
1253 		int h = Clamp(x - hsPickerRect.x, 0, hsPickerRect.Wdt - 1);
1254 		assert(Inside(h, 0, 255));
1255 		int s = 255 - Clamp(y - hsPickerRect.y, 0, hsPickerRect.Hgt - 1);
1256 		assert(Inside(s, 0, 255));
1257 		hsv = C4RGB(h, s, GetBlueValue(hsv));
1258 		UpdateVFacet(h, s);
1259 		UpdatePreview();
1260 		state = PS_DragHS;
1261 		return true;
1262 	}
1263 	else if (state == PS_DragV || (state == PS_Idle && vPickerRect.Contains(x, y)))
1264 	{
1265 		int v = 255 - Clamp(y - vPickerRect.y, 0, vPickerRect.Hgt - 1);
1266 		assert(Inside(v, 0, 255));
1267 		hsv = (hsv & 0xFFFFFF00) | v;
1268 		UpdatePreview();
1269 		state = PS_DragV;
1270 		return true;
1271 	}
1272 	else
1273 	{
1274 		// Drag started outside of all picker areas; ignore movement until user releases mouse button.
1275 		state = PS_IdleDragging;
1276 		return false;
1277 	}
1278 }
1279 
MouseInput(C4GUI::CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)1280 void C4StartupPlrColorPickerDlg::Picker::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
1281 {
1282 	Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
1283 
1284 	if (!rMouse.IsLDown()) state = PS_Idle;
1285 	if (rMouse.pDragElement) return;
1286 	if (rMouse.IsLDown())
1287 	{
1288 		if (HandleMouseDown(iX, iY))
1289 		{
1290 			rMouse.pDragElement = this;
1291 			C4GUI::GUISound("UI::Select");
1292 		}
1293 		else
1294 		{
1295 			rMouse.pDragElement = nullptr;
1296 		}
1297 	}
1298 }
1299 
DoDragging(C4GUI::CMouse & rMouse,int32_t iX,int32_t iY,DWORD dwKeyParam)1300 void C4StartupPlrColorPickerDlg::Picker::DoDragging(C4GUI::CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam)
1301 {
1302 	HandleMouseDown(iX, iY);
1303 }
1304 
1305 /* ---- Player property dlg ---- */
1306 
C4StartupPlrPropertiesDlg(C4StartupPlrSelDlg::PlayerListItem * pForPlayer,C4StartupPlrSelDlg * pParentDlg)1307 C4StartupPlrPropertiesDlg::C4StartupPlrPropertiesDlg(C4StartupPlrSelDlg::PlayerListItem * pForPlayer, C4StartupPlrSelDlg *pParentDlg)
1308 		: Dialog(C4Startup::Get()->Graphics.fctPlrPropBG.Wdt, C4Startup::Get()->Graphics.fctPlrPropBG.Hgt, "", false), pMainDlg(pParentDlg), pForPlayer(pForPlayer), fClearBigIcon(false)
1309 {
1310 	if (pForPlayer)
1311 	{
1312 		// edit existing player
1313 		C4P = pForPlayer->GetCore();
1314 	}
1315 	else
1316 	{
1317 		// create new player: Use default C4P values, with a few exceptions
1318 		// FIXME: Use Player, not Clonkranks
1319 		C4P.Default(&::DefaultRanks);
1320 		// Set name, color, comment
1321 		SCopy(LoadResStr("IDS_PLR_NEWCOMMENT"), C4P.Comment, C4MaxComment);
1322 		C4P.PrefColor = UnsyncedRandom(8);
1323 		C4P.PrefColorDw = C4P.GetPrefColorValue(C4P.PrefColor);
1324 		C4P.OldPrefControlStyle = 1;
1325 		C4P.OldPrefAutoContextMenu = 1;
1326 		C4P.OldPrefControl = 0;
1327 	}
1328 	const int32_t BetweenElementDist = 2;
1329 	// use black fonts here
1330 	CStdFont *pUseFont = &C4Startup::Get()->Graphics.BookFont;
1331 	CStdFont *pSmallFont = &C4Startup::Get()->Graphics.BookSmallFont;
1332 	// get positions
1333 	UpdateSize();
1334 	C4GUI::ComponentAligner caMain(GetClientRect(), 0, 1, true);
1335 	C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
1336 	// dlg title
1337 	const char *szTitle;
1338 	if (pForPlayer)
1339 	{
1340 		szTitle = LoadResStr("IDS_DLG_PLAYER2");
1341 	}
1342 	else
1343 	{
1344 		szTitle = LoadResStr("IDS_PLR_NEWPLAYER");
1345 	}
1346 	C4GUI::Label *pLbl = new C4GUI::Label(szTitle, caMain.GetFromTop(pUseFont->GetLineHeight()), ALeft, C4StartupFontClr, pUseFont, false);
1347 	AddElement(pLbl);
1348 	caMain.ExpandTop(-BetweenElementDist);
1349 	// place name label
1350 	AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_NAME2"), caMain.GetFromTop(pSmallFont->GetLineHeight()), ALeft, C4StartupFontClr, pSmallFont, false));
1351 	// place name edit
1352 	pNameEdit = new C4GUI::Edit(caMain.GetFromTop(C4GUI::Edit::GetCustomEditHeight(pUseFont)));
1353 	pNameEdit->SetFont(pUseFont);
1354 	pNameEdit->SetColors(C4StartupEditBGColor, C4StartupFontClr, C4StartupEditBorderColor);
1355 	pNameEdit->InsertText(C4P.PrefName, false);
1356 	pNameEdit->SetMaxText(C4MaxName);
1357 	AddElement(pNameEdit);
1358 	SetFocus(pNameEdit, false);
1359 	caMain.ExpandTop(-BetweenElementDist);
1360 
1361 	int32_t iControlPicSize = C4GUI::ArrowButton::GetDefaultHeight(); // GetGridCell(0,3,0,1,-1,-1,false,2)
1362 	int32_t label_hgt = pSmallFont->GetLineHeight();
1363 
1364 	// place color label
1365 	C4GUI::ComponentAligner caColorArea(caMain.GetFromTop(iControlPicSize + BetweenElementDist + label_hgt), 2, 0);
1366 	C4GUI::ComponentAligner caPictureArea(caColorArea.GetFromRight(iControlPicSize, iControlPicSize + BetweenElementDist + label_hgt), 2,0);
1367 	caColorArea.ExpandLeft(2);
1368 	AddElement(new C4GUI::Label(FormatString("%s:", LoadResStr("IDS_CTL_COLOR")).getData(), caColorArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1369 	caColorArea.ExpandTop(-BetweenElementDist);
1370 	// place picture label
1371 	AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_PICTURE"), caPictureArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1372 	caPictureArea.ExpandTop(-BetweenElementDist);
1373 	// place color controls
1374 	C4GUI::Button *pBtn; const char *szTip;
1375 	szTip = LoadResStr("IDS_DLGTIP_PLAYERCOLORS");
1376 	AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caColorArea.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnClrChangeLeft));
1377 	pBtn->SetToolTip(szTip);
1378 	C4Facet &rfctClrPreviewPic = ::GraphicsResource.fctFlagClr;
1379 	pClrPreview = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::IconButton>(C4GUI::Ico_None, caColorArea.GetFromLeft(rfctClrPreviewPic.GetWidthByHeight(caColorArea.GetHeight())), 'C', &C4StartupPlrPropertiesDlg::OnClrChangeCustom);
1380 	pClrPreview->SetFacet(rfctClrPreviewPic);
1381 	AddElement(pClrPreview);
1382 	AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caColorArea.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnClrChangeRight));
1383 	pBtn->SetToolTip(szTip);
1384 	if (!C4P.PrefColorDw) C4P.PrefColorDw=0xff;
1385 	// Place picture controls
1386 	AddElement(pPictureBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::IconButton>(C4GUI::Ico_Player, caPictureArea.GetAll(), 'P' /* 2do */, &C4StartupPlrPropertiesDlg::OnPictureBtn));
1387 	pPictureBtn->SetToolTip(LoadResStr("IDS_DESC_SELECTAPICTUREANDORLOBBYI"));
1388 	UpdateBigIcon();
1389 	UpdatePlayerColor(true);
1390 	caMain.ExpandTop(-BetweenElementDist);
1391 	// place control label
1392 	C4GUI::ComponentAligner caControlArea(caMain.GetFromTop(iControlPicSize + label_hgt + BetweenElementDist), 0,0, false);
1393 	C4GUI::ComponentAligner caSkinArea(caControlArea.GetFromRight(iControlPicSize + label_hgt + BetweenElementDist), 0,0, false);
1394 	AddElement(new C4GUI::Label(FormatString("%s:", LoadResStr("IDS_CTL_CONTROL")).getData(), caControlArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1395 	caControlArea.ExpandTop(-BetweenElementDist);
1396 	// place clonk style label
1397 	AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_CLONKSKIN"), caSkinArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1398 	caSkinArea.ExpandTop(-BetweenElementDist);
1399 	// place control controls
1400 	C4GUI::ComponentAligner caControl(caControlArea.GetFromTop(iControlPicSize), 2,0);
1401 	szTip = LoadResStr("IDS_DLGTIP_PLAYERCONTROL");
1402 	AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caControl.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnCtrlChangeLeft));
1403 	pBtn->SetToolTip(szTip);
1404 	caControl.ExpandBottom(label_hgt); C4Rect ctrl_name_rect = caControl.GetFromBottom(label_hgt);
1405 	C4Facet &rfctCtrlPic = ::GraphicsResource.fctKeyboard; // UpdatePlayerControl() will alternatively set fctGamepad
1406 	AddElement(pCtrlImg = new C4GUI::Picture(caControl.GetFromLeft(rfctCtrlPic.GetWidthByHeight(caControl.GetHeight())), true));
1407 	pCtrlImg->SetToolTip(szTip);
1408 	AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caControl.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnCtrlChangeRight));
1409 	pBtn->SetToolTip(szTip);
1410 	caControl.ExpandLeft(-10);
1411 	C4P.OldPrefControl = Clamp<int32_t>(C4P.OldPrefControl, 0, C4MaxControlSet-1);
1412 	ctrl_name_lbl = new C4GUI::Label("CtrlName", ctrl_name_rect, ALeft, C4StartupFontClr, pSmallFont, false, false, true);
1413 	AddElement(ctrl_name_lbl);
1414 	UpdatePlayerControl();
1415 
1416 	C4GUI::ComponentAligner caSkin(caSkinArea.GetFromTop(iControlPicSize), 2,0);
1417 	szTip = LoadResStr("IDS_DLGTIP_PLAYERCREWSKIN");
1418 	AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caSkin.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnSkinChangeLeft));
1419 	pBtn->SetToolTip(szTip);
1420 	C4Facet rfctSkinPic = ::GraphicsResource.fctClonkSkin.GetPhase(0);
1421 	AddElement(pSkinImg = new C4GUI::Picture(caSkin.GetFromLeft(rfctSkinPic.GetWidthByHeight(caSkin.GetHeight())), true));
1422 	pSkinImg->SetToolTip(szTip);
1423 	pSkinImg->SetFacet(::GraphicsResource.fctClonkSkin.GetPhase(0));
1424 	AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caSkin.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnSkinChangeRight));
1425 	pBtn->SetToolTip(szTip);
1426 	caSkin.ExpandLeft(-10);
1427 	UpdatePlayerSkin();
1428 
1429 	caMain.ExpandTop(-BetweenElementDist);
1430 	// place buttons
1431 	// OK
1432 	C4GUI::Button *pBtnOK = new C4GUI::OKIconButton(C4Rect(147-GetMarginLeft(), 295+35-GetMarginTop(), 54, 33), C4GUI::Ico_None);
1433 	AddElement(pBtnOK);
1434 	// Cancel
1435 	C4GUI::Button *pBtnAbort = new C4GUI::CancelIconButton(C4Rect(317-GetMarginLeft(), 16-GetMarginTop(), 21, 21), C4GUI::Ico_None);
1436 	AddElement(pBtnAbort);
1437 	// when called from player selection screen: input dlg always closed in the end
1438 	// otherwise, modal proc will delete
1439 	if (pMainDlg) SetDelOnClose();
1440 }
1441 
DrawElement(C4TargetFacet & cgo)1442 void C4StartupPlrPropertiesDlg::DrawElement(C4TargetFacet &cgo)
1443 {
1444 	C4Startup::Get()->Graphics.fctPlrPropBG.Draw(cgo.Surface, rcBounds.x+cgo.TargetX, rcBounds.y+cgo.TargetY);
1445 }
1446 
1447 bool IsColorConflict(DWORD dwClr1, DWORD dwClr2);
1448 
UpdatePlayerColor(bool fUpdateSliders)1449 void C4StartupPlrPropertiesDlg::UpdatePlayerColor(bool fUpdateSliders)
1450 {
1451 	C4P.PrefColorDw = C4P.PrefColorDw | 0xFF000000; // Ensure full opacity
1452 	pClrPreview->SetColor(C4P.PrefColorDw);
1453 	pPictureBtn->SetColor(C4P.PrefColorDw);
1454 }
1455 
OnClrChangeLeft(C4GUI::Control * pBtn)1456 void C4StartupPlrPropertiesDlg::OnClrChangeLeft(C4GUI::Control *pBtn)
1457 {
1458 	// previous standard color in list
1459 	C4P.PrefColor = C4P.PrefColor ? C4P.PrefColor - 1 : 11;
1460 	C4P.PrefColorDw = C4PlayerInfoCore::GetPrefColorValue(C4P.PrefColor);
1461 	UpdatePlayerColor(true);
1462 }
OnClrChangeCustom(C4GUI::Control * pBtn)1463 void C4StartupPlrPropertiesDlg::OnClrChangeCustom(C4GUI::Control *pBtn)
1464 {
1465 	GetScreen()->ShowModalDlg(new C4StartupPlrColorPickerDlg(&C4P));
1466 	UpdatePlayerColor(true);
1467 }
1468 
OnClrChangeRight(C4GUI::Control * pBtn)1469 void C4StartupPlrPropertiesDlg::OnClrChangeRight(C4GUI::Control *pBtn)
1470 {
1471 	// next standard color in list
1472 	C4P.PrefColor = (C4P.PrefColor + 1) % 12;
1473 	C4P.PrefColorDw = C4PlayerInfoCore::GetPrefColorValue(C4P.PrefColor);
1474 	UpdatePlayerColor(true);
1475 }
1476 
UpdatePlayerControl()1477 void C4StartupPlrPropertiesDlg::UpdatePlayerControl()
1478 {
1479 	C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1480 	if (!control_set) control_set = Game.PlayerControlUserAssignmentSets.GetDefaultSet();
1481 	// update keyboard image of selected control
1482 	C4Facet fctCtrlPic;
1483 	if (control_set) fctCtrlPic = control_set->GetPicture();
1484 	pCtrlImg->SetFacet(fctCtrlPic);
1485 	if (control_set)
1486 		ctrl_name_lbl->SetText(control_set->GetGUIName());
1487 	else
1488 		ctrl_name_lbl->SetText("???");
1489 }
1490 
OnCtrlChangeLeft(C4GUI::Control * pBtn)1491 void C4StartupPlrPropertiesDlg::OnCtrlChangeLeft(C4GUI::Control *pBtn)
1492 {
1493 	// previous control set in list
1494 	C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1495 	int32_t index = Game.PlayerControlUserAssignmentSets.GetSetIndex(control_set);
1496 	if (index < 0) index = 0; // defined control set not found - probably an old CR player file
1497 	if (!index--) index = Game.PlayerControlUserAssignmentSets.GetSetCount() - 1;
1498 	control_set = Game.PlayerControlUserAssignmentSets.GetSetByIndex(index);
1499 	if (control_set) C4P.PrefControl = control_set->GetName();
1500 	UpdatePlayerControl();
1501 }
1502 
OnCtrlChangeRight(C4GUI::Control * pBtn)1503 void C4StartupPlrPropertiesDlg::OnCtrlChangeRight(C4GUI::Control *pBtn)
1504 {
1505 	// next control set in list
1506 	C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1507 	int32_t index = Game.PlayerControlUserAssignmentSets.GetSetIndex(control_set);
1508 	if (index < 0) index = 0; // defined control set not found - probably an old CR player file
1509 	if (++index >= int32_t(Game.PlayerControlUserAssignmentSets.GetSetCount())) index = 0;
1510 	control_set = Game.PlayerControlUserAssignmentSets.GetSetByIndex(index);
1511 	if (control_set) C4P.PrefControl = control_set->GetName();
1512 	UpdatePlayerControl();
1513 }
1514 
UpdatePlayerSkin()1515 void C4StartupPlrPropertiesDlg::UpdatePlayerSkin()
1516 {
1517 	pSkinImg->SetFacet(::GraphicsResource.fctClonkSkin.GetPhase(C4P.PrefClonkSkin));
1518 }
1519 
OnSkinChangeLeft(C4GUI::Control * pBtn)1520 void C4StartupPlrPropertiesDlg::OnSkinChangeLeft(C4GUI::Control *pBtn)
1521 {
1522 	// previous skin in list
1523 	C4P.PrefClonkSkin = C4P.PrefClonkSkin ? C4P.PrefClonkSkin - 1 : 3;
1524 	UpdatePlayerSkin();
1525 }
OnSkinChangeRight(C4GUI::Control * pBtn)1526 void C4StartupPlrPropertiesDlg::OnSkinChangeRight(C4GUI::Control *pBtn)
1527 {
1528 	// next skin in list
1529 	C4P.PrefClonkSkin = (C4P.PrefClonkSkin + 1) % 4;
1530 	UpdatePlayerSkin();
1531 }
1532 
UserClose(bool fOK)1533 void C4StartupPlrPropertiesDlg::UserClose(bool fOK)
1534 {
1535 	// check name validity
1536 	if (fOK)
1537 	{
1538 		StdStrBuf PlrName(pNameEdit->GetText()), Filename;
1539 		if (!C4StartupPlrSelDlg::CheckPlayerName(PlrName, Filename, pForPlayer ? &pForPlayer->GetFilename() : nullptr, true)) return;
1540 
1541 		// Warn that gamepad controls are still unfinished.
1542 		C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1543 		if (control_set && control_set->HasGamepad())
1544 		{
1545 			GetScreen()->ShowMessageModal(
1546 					LoadResStr("IDS_DLG_GAMEPADEXPERIMENTAL"),
1547 					LoadResStr("IDS_DLG_GAMEPADEXPTITLE"),
1548 					C4GUI::MessageDialog::btnOK,
1549 					C4GUI::Ico_Gamepad
1550 			);
1551 		}
1552 	}
1553 	Close(fOK);
1554 }
1555 
OnClosed(bool fOK)1556 void C4StartupPlrPropertiesDlg::OnClosed(bool fOK)
1557 {
1558 	if (fOK)
1559 	{
1560 		// store selected data if desired
1561 		StdStrBuf PlrName(pNameEdit->GetText()), Filename;
1562 		if (C4StartupPlrSelDlg::CheckPlayerName(PlrName, Filename, pForPlayer ? &pForPlayer->GetFilename() : nullptr, true))
1563 		{
1564 			SCopy(PlrName.getData(), C4P.PrefName, C4MaxName);
1565 			C4Group PlrGroup;
1566 			bool fSucc=false;
1567 			// existant player: update file
1568 			if (pForPlayer)
1569 			{
1570 				if (!pForPlayer->MoveFilename(Filename.getData()))
1571 					GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_RENAME"), "", C4GUI::Ico_Error);
1572 				// update bigicon
1573 				if (fClearBigIcon || fctNewBigIcon.Surface)
1574 				{
1575 					C4Group PlrGroup;
1576 					if (PlrGroup.Open(Config.AtUserDataPath(Filename.getData())))
1577 					{
1578 						if (fClearBigIcon || fctNewBigIcon.Surface) PlrGroup.Delete(C4CFN_BigIcon);
1579 						if (fctNewBigIcon.Surface) fctNewBigIcon.GetFace().SavePNG(PlrGroup, C4CFN_BigIcon);
1580 						if (PlrGroup.Close()) fSucc = true;
1581 						if (fClearBigIcon || fctNewBigIcon.Surface) pForPlayer->GrabCustomIcon(fctNewBigIcon);
1582 					}
1583 				}
1584 				else
1585 				{
1586 					fSucc = true;
1587 				}
1588 				pForPlayer->UpdateCore(C4P);
1589 				// player may have been activated: Make sure any new filename is reflected in participants list
1590 				if (pMainDlg) pMainDlg->UpdateActivatedPlayers();
1591 			}
1592 			else
1593 			{
1594 				// NewPlayer: Open new player group
1595 				if (PlrGroup.Open(Config.AtUserDataPath(Filename.getData()), true))
1596 				{
1597 					// Do not overwrite (should have been caught earlier anyway)
1598 					if (PlrGroup.FindEntry(C4CFN_PlayerInfoCore)) return;
1599 					// Save info core
1600 					C4P.Save(PlrGroup);
1601 					// Add BigIcon
1602 					if (fctNewBigIcon.Surface)
1603 					{
1604 						fctNewBigIcon.GetFace().SavePNG(PlrGroup, C4CFN_BigIcon);
1605 					}
1606 					// Close group
1607 					if (PlrGroup.Close()) fSucc=true;
1608 					// update activate button text
1609 					if (pMainDlg)
1610 					{
1611 						pMainDlg->UpdatePlayerList();
1612 						pMainDlg->SelectItem(Filename, true);
1613 					}
1614 					else
1615 					{
1616 						// no main player selection dialog: This means that this dlg was shown as a creation dialog from the main startup dlg
1617 						// Just set the newly created player as current selection
1618 						SCopy(Config.AtRelativePath(Filename.getData()), Config.General.Participants, sizeof Config.General.Participants);
1619 					}
1620 				}
1621 			}
1622 			if (!fSucc) GetScreen()->ShowErrorMessage(PlrGroup.GetError());
1623 		}
1624 	}
1625 	// Make the dialog go away
1626 	Dialog::OnClosed(fOK);
1627 }
1628 
SetNewPicture(C4Surface & srcSfc,C4FacetSurface * trgFct,int32_t iMaxSize,bool fColorize)1629 bool C4StartupPlrPropertiesDlg::SetNewPicture(C4Surface &srcSfc, C4FacetSurface *trgFct, int32_t iMaxSize, bool fColorize)
1630 {
1631 	if (fColorize)
1632 	{
1633 		C4Surface srcSfcClr;
1634 		if (!srcSfcClr.CreateColorByOwner(&srcSfc)) return false;
1635 		return trgFct->CopyFromSfcMaxSize(srcSfcClr, iMaxSize, C4P.PrefColorDw);
1636 	}
1637 	else
1638 	{
1639 		return trgFct->CopyFromSfcMaxSize(srcSfc, iMaxSize);
1640 	}
1641 }
1642 
SetNewPicture(const char * szFromFilename)1643 void C4StartupPlrPropertiesDlg::SetNewPicture(const char *szFromFilename)
1644 {
1645 	if (!szFromFilename)
1646 	{
1647 		// If szFromFilename==nullptr, clear bigicon
1648 		fClearBigIcon = true;
1649 		fctNewBigIcon.Clear();
1650 	}
1651 	else
1652 	{
1653 		// else set new bigicon by loading and scaling if necessary.
1654 		C4Surface sfcNewPic;
1655 		C4Group SrcGrp;
1656 		StdStrBuf sParentPath;
1657 		GetParentPath(szFromFilename, &sParentPath);
1658 		bool fSucc = false;
1659 		if (SrcGrp.Open(sParentPath.getData()))
1660 		{
1661 			if (sfcNewPic.Load(SrcGrp, GetFilename(szFromFilename), false, false, 0))
1662 			{
1663 				fSucc = true;
1664 				if (!SetNewPicture(sfcNewPic, &fctNewBigIcon, C4MaxBigIconSize, true)) fSucc = false;
1665 			}
1666 		}
1667 		if (!fSucc)
1668 		{
1669 			// error!
1670 			GetScreen()->ShowErrorMessage(FormatString(LoadResStr("IDS_PRC_NOGFXFILE"), szFromFilename, SrcGrp.GetError()).getData());
1671 		}
1672 	}
1673 	// update icon
1674 	UpdateBigIcon();
1675 }
1676 
OnPictureBtn(C4GUI::Control * pBtn)1677 void C4StartupPlrPropertiesDlg::OnPictureBtn(C4GUI::Control *pBtn)
1678 {
1679 	StdStrBuf sNewPic;
1680 	if (C4PortraitSelDlg::SelectPortrait(GetScreen(), &sNewPic))
1681 	{
1682 		SetNewPicture(sNewPic.getData());
1683 	}
1684 }
1685 
UpdateBigIcon()1686 void C4StartupPlrPropertiesDlg::UpdateBigIcon()
1687 {
1688 	// new icon?
1689 	bool fHasIcon = false;
1690 	if (fctNewBigIcon.Surface)
1691 	{
1692 		pPictureBtn->SetFacet(fctNewBigIcon);
1693 		fHasIcon = true;
1694 	}
1695 	// old icon in existing player?
1696 	else if (!fClearBigIcon && pForPlayer)
1697 	{
1698 		C4Group PlrGroup;
1699 		if (PlrGroup.Open(Config.AtUserDataPath(pForPlayer->GetFilename().getData())))
1700 		{
1701 			if (PlrGroup.FindEntry(C4CFN_BigIcon))
1702 			{
1703 				if (fctOldBigIcon.Load(PlrGroup, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0))
1704 				{
1705 					pPictureBtn->SetFacet(fctOldBigIcon);
1706 					fHasIcon = true;
1707 				}
1708 			}
1709 		}
1710 	}
1711 	// no icon: Set default
1712 	if (!fHasIcon)
1713 	{
1714 		pPictureBtn->SetFacet(::GraphicsResource.fctPlayerClr);
1715 	}
1716 }
1717